1
/*
2
 * Hurl (https://hurl.dev)
3
 * Copyright (C) 2026 Orange
4
 *
5
 * Licensed under the Apache License, Version 2.0 (the "License");
6
 * you may not use this file except in compliance with the License.
7
 * You may obtain a copy of the License at
8
 *
9
 *          http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 *
17
 */
18
use base64::Engine;
19
use base64::engine::general_purpose;
20
use hurl_core::ast::{
21
    Assert, Base64, Body, BooleanOption, Bytes, Capture, CertificateAttributeName, Comment, Cookie,
22
    CountOption, Duration, DurationOption, Entry, EntryOption, File, FilenameParam, Filter,
23
    FilterValue, Hex, HurlFile, JsonListElement, JsonValue, KeyValue, MultilineString,
24
    MultilineStringKind, MultipartParam, NaturalOption, OptionKind, Placeholder, Predicate,
25
    PredicateFuncValue, PredicateValue, Query, QueryValue, Regex, RegexValue, Request, Response,
26
    StatusValue, VersionValue,
27
};
28
use hurl_core::types::{Count, ToSource};
29

            
30
use crate::format::serialize_json::JValue;
31

            
32
57
pub fn format(hurl_file: &HurlFile) -> String {
33
57
    hurl_file.to_json().format()
34
}
35

            
36
pub trait ToJson {
37
    fn to_json(&self) -> JValue;
38
}
39

            
40
impl ToJson for HurlFile {
41
57
    fn to_json(&self) -> JValue {
42
57
        JValue::Object(vec![(
43
57
            "entries".to_string(),
44
211
            JValue::List(self.entries.iter().map(|e| e.to_json()).collect()),
45
        )])
46
    }
47
}
48

            
49
impl ToJson for Entry {
50
192
    fn to_json(&self) -> JValue {
51
192
        let mut attributes = vec![("request".to_string(), self.request.to_json())];
52
192
        if let Some(response) = &self.response {
53
87
            attributes.push(("response".to_string(), response.to_json()));
54
        }
55
192
        JValue::Object(attributes)
56
    }
57
}
58

            
59
impl ToJson for Request {
60
192
    fn to_json(&self) -> JValue {
61
192
        let mut attributes = vec![
62
192
            (
63
192
                "method".to_string(),
64
192
                JValue::String(self.method.to_string()),
65
192
            ),
66
192
            ("url".to_string(), JValue::String(self.url.to_string())),
67
        ];
68
192
        add_headers(&mut attributes, &self.headers);
69

            
70
192
        if !self.querystring_params().is_empty() {
71
6
            let params = self
72
6
                .querystring_params()
73
6
                .iter()
74
17
                .map(|p| p.to_json())
75
6
                .collect();
76
6
            attributes.push(("query_string_params".to_string(), JValue::List(params)));
77
        }
78
192
        if !self.form_params().is_empty() {
79
13
            let params = self.form_params().iter().map(|p| p.to_json()).collect();
80
3
            attributes.push(("form_params".to_string(), JValue::List(params)));
81
        }
82
192
        if !self.multipart_form_data().is_empty() {
83
3
            let params = self
84
3
                .multipart_form_data()
85
3
                .iter()
86
10
                .map(|p| p.to_json())
87
3
                .collect();
88
3
            attributes.push(("multipart_form_data".to_string(), JValue::List(params)));
89
        }
90
192
        if !self.cookies().is_empty() {
91
7
            let cookies = self.cookies().iter().map(|c| c.to_json()).collect();
92
3
            attributes.push(("cookies".to_string(), JValue::List(cookies)));
93
        }
94
192
        if !self.options().is_empty() {
95
325
            let options = self.options().iter().map(|c| c.to_json()).collect();
96
12
            attributes.push(("options".to_string(), JValue::List(options)));
97
        }
98
192
        if let Some(body) = &self.body {
99
48
            attributes.push(("body".to_string(), body.to_json()));
100
        }
101

            
102
        // Request comments (can be used to check custom commands)
103
192
        let comments: Vec<_> = self
104
192
            .line_terminators
105
192
            .iter()
106
233
            .filter_map(|l| l.comment.as_ref())
107
192
            .collect();
108
192
        if !comments.is_empty() {
109
26
            let comments = comments.iter().map(|c| c.to_json()).collect();
110
15
            attributes.push(("comments".to_string(), JValue::List(comments)));
111
        }
112

            
113
192
        JValue::Object(attributes)
114
    }
115
}
116

            
117
impl ToJson for Response {
118
    /// Transforms this response to a JSON object.
119
87
    fn to_json(&self) -> JValue {
120
87
        let mut attributes = vec![];
121
87
        if let Some(v) = get_json_version(&self.version.value) {
122
12
            attributes.push(("version".to_string(), JValue::String(v)));
123
        }
124
87
        if let StatusValue::Specific(n) = self.status.value {
125
87
            attributes.push(("status".to_string(), JValue::Number(n.to_string())));
126
        }
127
87
        add_headers(&mut attributes, &self.headers);
128
87
        if !self.captures().is_empty() {
129
21
            let captures = self.captures().iter().map(|c| c.to_json()).collect();
130
9
            attributes.push(("captures".to_string(), JValue::List(captures)));
131
        }
132
87
        if !self.asserts().is_empty() {
133
387
            let asserts = self.asserts().iter().map(|a| a.to_json()).collect();
134
36
            attributes.push(("asserts".to_string(), JValue::List(asserts)));
135
        }
136
87
        if let Some(body) = &self.body {
137
24
            attributes.push(("body".to_string(), body.to_json()));
138
        }
139
87
        JValue::Object(attributes)
140
    }
141
}
142

            
143
279
fn add_headers(attributes: &mut Vec<(String, JValue)>, headers: &[KeyValue]) {
144
279
    if !headers.is_empty() {
145
62
        let headers = JValue::List(headers.iter().map(|h| h.to_json()).collect());
146
24
        attributes.push(("headers".to_string(), headers));
147
    }
148
}
149

            
150
impl ToJson for Body {
151
72
    fn to_json(&self) -> JValue {
152
72
        self.value.to_json()
153
    }
154
}
155

            
156
impl ToJson for Bytes {
157
72
    fn to_json(&self) -> JValue {
158
72
        match self {
159
6
            Bytes::Base64(value) => value.to_json(),
160
3
            Bytes::Hex(value) => value.to_json(),
161
3
            Bytes::File(value) => value.to_json(),
162
6
            Bytes::Json(value) => JValue::Object(vec![
163
6
                ("type".to_string(), JValue::String("json".to_string())),
164
6
                ("value".to_string(), value.to_json()),
165
6
            ]),
166
3
            Bytes::Xml(value) => JValue::Object(vec![
167
3
                ("type".to_string(), JValue::String("xml".to_string())),
168
3
                ("value".to_string(), JValue::String(value.clone())),
169
3
            ]),
170
9
            Bytes::OnelineString(value) => JValue::Object(vec![
171
9
                ("type".to_string(), JValue::String("text".to_string())),
172
9
                ("value".to_string(), JValue::String(value.to_string())),
173
9
            ]),
174
42
            Bytes::MultilineString(multi) => {
175
                // TODO: check these values. Maybe we want to have the same
176
                // export when using:
177
                //
178
                // ~~~
179
                // GET https://foo.com
180
                // ```base64
181
                // SGVsbG8gd29ybGQ=
182
                // ```
183
                //
184
                // or
185
                //
186
                // ~~~
187
                // GET https://foo.com
188
                // base64,SGVsbG8gd29ybGQ=;
189
                // ~~~
190
42
                let lang = match multi {
191
                    MultilineString {
192
                        kind: MultilineStringKind::Text(_),
193
                        ..
194
9
                    } => "text",
195
                    MultilineString {
196
                        kind: MultilineStringKind::Raw(_),
197
                        ..
198
9
                    } => "raw",
199
                    MultilineString {
200
                        kind: MultilineStringKind::Json(_),
201
                        ..
202
9
                    } => "json",
203
                    MultilineString {
204
                        kind: MultilineStringKind::Xml(_),
205
                        ..
206
6
                    } => "xml",
207
                    MultilineString {
208
                        kind: MultilineStringKind::GraphQl(_),
209
                        ..
210
9
                    } => "graphql",
211
                };
212
42
                JValue::Object(vec![
213
42
                    ("type".to_string(), JValue::String(lang.to_string())),
214
42
                    ("value".to_string(), JValue::String(multi.to_string())),
215
42
                ])
216
            }
217
        }
218
    }
219
}
220

            
221
impl ToJson for Base64 {
222
6
    fn to_json(&self) -> JValue {
223
6
        let value = general_purpose::STANDARD.encode(&self.value);
224
6
        JValue::Object(vec![
225
6
            ("encoding".to_string(), JValue::String("base64".to_string())),
226
6
            ("value".to_string(), JValue::String(value)),
227
6
        ])
228
    }
229
}
230

            
231
impl ToJson for Hex {
232
3
    fn to_json(&self) -> JValue {
233
3
        let value = general_purpose::STANDARD.encode(&self.value);
234
3
        JValue::Object(vec![
235
3
            ("encoding".to_string(), JValue::String("base64".to_string())),
236
3
            ("value".to_string(), JValue::String(value)),
237
3
        ])
238
    }
239
}
240

            
241
impl ToJson for File {
242
6
    fn to_json(&self) -> JValue {
243
6
        JValue::Object(vec![
244
6
            ("type".to_string(), JValue::String("file".to_string())),
245
6
            (
246
6
                "filename".to_string(),
247
6
                JValue::String(self.filename.to_string()),
248
6
            ),
249
6
        ])
250
    }
251
}
252

            
253
87
fn get_json_version(version_value: &VersionValue) -> Option<String> {
254
87
    match version_value {
255
3
        VersionValue::Version1 => Some("HTTP/1.0".to_string()),
256
3
        VersionValue::Version11 => Some("HTTP/1.1".to_string()),
257
3
        VersionValue::Version2 => Some("HTTP/2".to_string()),
258
3
        VersionValue::Version3 => Some("HTTP/3".to_string()),
259
75
        VersionValue::VersionAny => None,
260
    }
261
}
262

            
263
impl ToJson for KeyValue {
264
84
    fn to_json(&self) -> JValue {
265
84
        let attributes = vec![
266
84
            ("name".to_string(), JValue::String(self.key.to_string())),
267
84
            ("value".to_string(), JValue::String(self.value.to_string())),
268
        ];
269
84
        JValue::Object(attributes)
270
    }
271
}
272

            
273
impl ToJson for MultipartParam {
274
9
    fn to_json(&self) -> JValue {
275
9
        match self {
276
3
            MultipartParam::Param(param) => param.to_json(),
277
6
            MultipartParam::FilenameParam(param) => param.to_json(),
278
        }
279
    }
280
}
281

            
282
impl ToJson for FilenameParam {
283
6
    fn to_json(&self) -> JValue {
284
6
        let mut attributes = vec![
285
6
            ("name".to_string(), JValue::String(self.key.to_string())),
286
6
            (
287
6
                "filename".to_string(),
288
6
                JValue::String(self.value.filename.to_string()),
289
6
            ),
290
        ];
291
6
        if let Some(content_type) = &self.value.content_type {
292
3
            attributes.push((
293
3
                "content_type".to_string(),
294
3
                JValue::String(content_type.to_string()),
295
3
            ));
296
        }
297
6
        JValue::Object(attributes)
298
    }
299
}
300

            
301
impl ToJson for Cookie {
302
6
    fn to_json(&self) -> JValue {
303
6
        let attributes = vec![
304
6
            ("name".to_string(), JValue::String(self.name.to_string())),
305
6
            ("value".to_string(), JValue::String(self.value.to_string())),
306
        ];
307
6
        JValue::Object(attributes)
308
    }
309
}
310

            
311
impl ToJson for EntryOption {
312
321
    fn to_json(&self) -> JValue {
313
321
        let value = match &self.kind {
314
6
            OptionKind::AwsSigV4(value) => JValue::String(value.to_string()),
315
6
            OptionKind::CaCertificate(filename) => JValue::String(filename.to_string()),
316
9
            OptionKind::ClientCert(filename) => JValue::String(filename.to_string()),
317
6
            OptionKind::ClientKey(filename) => JValue::String(filename.to_string()),
318
6
            OptionKind::Compressed(value) => value.to_json(),
319
6
            OptionKind::ConnectTo(value) => JValue::String(value.to_string()),
320
6
            OptionKind::ConnectTimeout(value) => value.to_json(),
321
18
            OptionKind::Delay(value) => value.to_json(),
322
6
            OptionKind::Digest(value) => value.to_json(),
323
6
            OptionKind::FailWithBody(value) => value.to_json(),
324
6
            OptionKind::FollowLocation(value) => value.to_json(),
325
6
            OptionKind::FollowLocationTrusted(value) => value.to_json(),
326
6
            OptionKind::Header(value) => JValue::String(value.to_string()),
327
6
            OptionKind::Http10(value) => value.to_json(),
328
6
            OptionKind::Http11(value) => value.to_json(),
329
6
            OptionKind::Http2(value) => value.to_json(),
330
3
            OptionKind::Http2PriorKnowledge(value) => value.to_json(),
331
6
            OptionKind::Http3(value) => value.to_json(),
332
6
            OptionKind::Insecure(value) => value.to_json(),
333
6
            OptionKind::IpV4(value) => value.to_json(),
334
6
            OptionKind::IpV6(value) => value.to_json(),
335
6
            OptionKind::LimitRate(value) => value.to_json(),
336
6
            OptionKind::MaxRedirect(value) => value.to_json(),
337
6
            OptionKind::MaxTime(value) => value.to_json(),
338
6
            OptionKind::Negotiate(value) => value.to_json(),
339
6
            OptionKind::NetRc(value) => value.to_json(),
340
6
            OptionKind::NetRcFile(filename) => JValue::String(filename.to_string()),
341
6
            OptionKind::NetRcOptional(value) => value.to_json(),
342
6
            OptionKind::NoHeader(value) => JValue::String(value.to_string()),
343
6
            OptionKind::Ntlm(value) => value.to_json(),
344
6
            OptionKind::Output(filename) => JValue::String(filename.to_string()),
345
6
            OptionKind::PathAsIs(value) => value.to_json(),
346
6
            OptionKind::PinnedPublicKey(value) => JValue::String(value.to_string()),
347
6
            OptionKind::Proxy(value) => JValue::String(value.to_string()),
348
9
            OptionKind::Repeat(value) => value.to_json(),
349
6
            OptionKind::Resolve(value) => JValue::String(value.to_string()),
350
12
            OptionKind::Retry(value) => value.to_json(),
351
12
            OptionKind::RetryInterval(value) => value.to_json(),
352
6
            OptionKind::Skip(value) => value.to_json(),
353
6
            OptionKind::UnixSocket(value) => JValue::String(value.to_string()),
354
6
            OptionKind::User(value) => JValue::String(value.to_string()),
355
27
            OptionKind::Variable(value) => {
356
27
                JValue::String(format!("{}={}", value.name, value.value.to_source()))
357
            }
358
9
            OptionKind::Verbose(value) => value.to_json(),
359
6
            OptionKind::Verbosity(value) => JValue::String(value.to_string()),
360
6
            OptionKind::VeryVerbose(value) => value.to_json(),
361
        };
362

            
363
        // If the value contains the unit such as `{ "value": 10, "unit": "second" }`
364
        // The JSON for this option should still have one level
365
        // for example: { "name": "delay", "value": 10, "unit", "second" }
366
321
        let attributes = if let JValue::Object(mut attributes) = value {
367
24
            attributes.push((
368
24
                "name".to_string(),
369
24
                JValue::String(self.kind.identifier().to_string()),
370
24
            ));
371
24
            attributes
372
        } else {
373
297
            vec![
374
297
                (
375
297
                    "name".to_string(),
376
297
                    JValue::String(self.kind.identifier().to_string()),
377
297
                ),
378
297
                ("value".to_string(), value),
379
            ]
380
        };
381
321
        JValue::Object(attributes)
382
    }
383
}
384

            
385
impl ToJson for BooleanOption {
386
126
    fn to_json(&self) -> JValue {
387
126
        match self {
388
66
            BooleanOption::Literal(value) => JValue::Boolean(*value),
389
60
            BooleanOption::Placeholder(placeholder) => placeholder.to_json(),
390
        }
391
    }
392
}
393

            
394
impl ToJson for CountOption {
395
27
    fn to_json(&self) -> JValue {
396
27
        match self {
397
18
            CountOption::Literal(value) => value.to_json(),
398
9
            CountOption::Placeholder(placeholder) => placeholder.to_json(),
399
        }
400
    }
401
}
402

            
403
impl ToJson for Count {
404
18
    fn to_json(&self) -> JValue {
405
18
        match self {
406
12
            Count::Finite(n) => JValue::Number(n.to_string()),
407
6
            Count::Infinite => JValue::Number("-1".to_string()),
408
        }
409
    }
410
}
411

            
412
impl ToJson for DurationOption {
413
42
    fn to_json(&self) -> JValue {
414
42
        match self {
415
30
            DurationOption::Literal(value) => value.to_json(),
416
12
            DurationOption::Placeholder(placeholder) => placeholder.to_json(),
417
        }
418
    }
419
}
420

            
421
impl ToJson for Duration {
422
30
    fn to_json(&self) -> JValue {
423
30
        if let Some(unit) = self.unit {
424
24
            let mut attributes =
425
24
                vec![("value".to_string(), JValue::Number(self.value.to_string()))];
426
24
            attributes.push(("unit".to_string(), JValue::String(unit.to_string())));
427
24
            JValue::Object(attributes)
428
        } else {
429
6
            JValue::Number(self.value.to_string())
430
        }
431
    }
432
}
433

            
434
impl ToJson for Capture {
435
18
    fn to_json(&self) -> JValue {
436
18
        let mut attributes = vec![
437
18
            ("name".to_string(), JValue::String(self.name.to_string())),
438
18
            ("query".to_string(), self.query.to_json()),
439
        ];
440
18
        if !self.filters.is_empty() {
441
4
            let filters = JValue::List(self.filters.iter().map(|(_, f)| f.to_json()).collect());
442
3
            attributes.push(("filters".to_string(), filters));
443
        }
444
18
        if self.redacted {
445
6
            attributes.push(("redact".to_string(), JValue::Boolean(true)));
446
        }
447
18
        JValue::Object(attributes)
448
    }
449
}
450

            
451
impl ToJson for Assert {
452
375
    fn to_json(&self) -> JValue {
453
375
        let mut attributes = vec![("query".to_string(), self.query.to_json())];
454
375
        if !self.filters.is_empty() {
455
183
            let filters = JValue::List(self.filters.iter().map(|(_, f)| f.to_json()).collect());
456
117
            attributes.push(("filters".to_string(), filters));
457
        }
458
375
        attributes.push(("predicate".to_string(), self.predicate.to_json()));
459
375
        JValue::Object(attributes)
460
    }
461
}
462

            
463
impl ToJson for Query {
464
393
    fn to_json(&self) -> JValue {
465
393
        let attributes = query_value_attributes(&self.value);
466
393
        JValue::Object(attributes)
467
    }
468
}
469

            
470
393
fn query_value_attributes(query_value: &QueryValue) -> Vec<(String, JValue)> {
471
393
    let mut attributes = vec![];
472
393
    let att_type = JValue::String(query_value.identifier().to_string());
473
393
    attributes.push(("type".to_string(), att_type));
474

            
475
393
    match query_value {
476
243
        QueryValue::Jsonpath { expr, .. } => {
477
243
            attributes.push(("expr".to_string(), JValue::String(expr.to_string())));
478
        }
479
6
        QueryValue::Header { name, .. } => {
480
6
            attributes.push(("name".to_string(), JValue::String(name.to_string())));
481
        }
482
9
        QueryValue::Cookie { expr, .. } => {
483
9
            attributes.push(("expr".to_string(), JValue::String(expr.to_string())));
484
        }
485
3
        QueryValue::Xpath { expr, .. } => {
486
3
            attributes.push(("expr".to_string(), JValue::String(expr.to_string())));
487
        }
488
3
        QueryValue::Regex { value, .. } => {
489
3
            attributes.push(("expr".to_string(), value.to_json()));
490
        }
491
9
        QueryValue::Variable { name, .. } => {
492
9
            attributes.push(("name".to_string(), JValue::String(name.to_string())));
493
        }
494
        QueryValue::Certificate {
495
33
            attribute_name: field,
496
            ..
497
33
        } => {
498
33
            attributes.push(("expr".to_string(), field.to_json()));
499
        }
500
87
        _ => {}
501
    };
502
393
    attributes
503
}
504

            
505
impl ToJson for RegexValue {
506
9
    fn to_json(&self) -> JValue {
507
9
        match self {
508
3
            RegexValue::Template(template) => JValue::String(template.to_string()),
509
6
            RegexValue::Regex(regex) => regex.to_json(),
510
        }
511
    }
512
}
513

            
514
impl ToJson for Regex {
515
6
    fn to_json(&self) -> JValue {
516
6
        let attributes = vec![
517
6
            ("type".to_string(), JValue::String("regex".to_string())),
518
6
            ("value".to_string(), JValue::String(self.to_string())),
519
        ];
520
6
        JValue::Object(attributes)
521
    }
522
}
523

            
524
impl ToJson for CertificateAttributeName {
525
33
    fn to_json(&self) -> JValue {
526
33
        JValue::String(self.identifier().to_string())
527
    }
528
}
529

            
530
impl ToJson for Predicate {
531
375
    fn to_json(&self) -> JValue {
532
375
        let mut attributes = vec![];
533
375
        if self.not {
534
3
            attributes.push(("not".to_string(), JValue::Boolean(true)));
535
        }
536
375
        let identifier = self.predicate_func.value.identifier();
537
375
        attributes.push(("type".to_string(), JValue::String(identifier.to_string())));
538

            
539
375
        match &self.predicate_func.value {
540
252
            PredicateFuncValue::Equal { value, .. } => add_predicate_value(&mut attributes, value),
541
9
            PredicateFuncValue::NotEqual { value, .. } => {
542
9
                add_predicate_value(&mut attributes, value);
543
            }
544
9
            PredicateFuncValue::GreaterThan { value, .. } => {
545
9
                add_predicate_value(&mut attributes, value);
546
            }
547
3
            PredicateFuncValue::GreaterThanOrEqual { value, .. } => {
548
3
                add_predicate_value(&mut attributes, value);
549
            }
550
9
            PredicateFuncValue::LessThan { value, .. } => {
551
9
                add_predicate_value(&mut attributes, value);
552
            }
553
3
            PredicateFuncValue::LessThanOrEqual { value, .. } => {
554
3
                add_predicate_value(&mut attributes, value);
555
            }
556
9
            PredicateFuncValue::StartWith { value, .. } => {
557
9
                add_predicate_value(&mut attributes, value);
558
            }
559
6
            PredicateFuncValue::EndWith { value, .. } => {
560
6
                add_predicate_value(&mut attributes, value);
561
            }
562
9
            PredicateFuncValue::Contain { value, .. } => {
563
9
                add_predicate_value(&mut attributes, value);
564
            }
565
3
            PredicateFuncValue::Include { value, .. } => {
566
3
                add_predicate_value(&mut attributes, value);
567
            }
568
9
            PredicateFuncValue::Match { value, .. } => {
569
9
                add_predicate_value(&mut attributes, value);
570
            }
571
            PredicateFuncValue::Exist
572
            | PredicateFuncValue::IsBoolean
573
            | PredicateFuncValue::IsCollection
574
            | PredicateFuncValue::IsDate
575
            | PredicateFuncValue::IsEmpty
576
            | PredicateFuncValue::IsFloat
577
            | PredicateFuncValue::IsInteger
578
            | PredicateFuncValue::IsIpv4
579
            | PredicateFuncValue::IsIpv6
580
            | PredicateFuncValue::IsIsoDate
581
            | PredicateFuncValue::IsList
582
            | PredicateFuncValue::IsNumber
583
            | PredicateFuncValue::IsObject
584
            | PredicateFuncValue::IsString
585
54
            | PredicateFuncValue::IsUuid => {}
586
        }
587
375
        JValue::Object(attributes)
588
    }
589
}
590

            
591
321
fn add_predicate_value(attributes: &mut Vec<(String, JValue)>, predicate_value: &PredicateValue) {
592
321
    let (value, encoding) = json_predicate_value(predicate_value);
593
321
    attributes.push(("value".to_string(), value));
594
321
    if let Some(encoding) = encoding {
595
36
        attributes.push(("encoding".to_string(), JValue::String(encoding)));
596
    }
597
}
598

            
599
321
fn json_predicate_value(predicate_value: &PredicateValue) -> (JValue, Option<String>) {
600
321
    match predicate_value {
601
150
        PredicateValue::String(value) => (JValue::String(value.to_string()), None),
602
18
        PredicateValue::MultilineString(value) => (JValue::String(value.value().to_string()), None),
603
3
        PredicateValue::Bool(value) => (JValue::Boolean(*value), None),
604
3
        PredicateValue::Null => (JValue::Null, None),
605
105
        PredicateValue::Number(value) => (JValue::Number(value.to_string()), None),
606
3
        PredicateValue::File(value) => (value.to_json(), None),
607
27
        PredicateValue::Hex(value) => {
608
27
            let base64_string = general_purpose::STANDARD.encode(value.value.clone());
609
27
            (JValue::String(base64_string), Some("base64".to_string()))
610
        }
611
3
        PredicateValue::Base64(value) => {
612
3
            let base64_string = general_purpose::STANDARD.encode(value.value.clone());
613
3
            (JValue::String(base64_string), Some("base64".to_string()))
614
        }
615
3
        PredicateValue::Placeholder(value) => (JValue::String(value.to_string()), None),
616
6
        PredicateValue::Regex(value) => {
617
6
            (JValue::String(value.to_string()), Some("regex".to_string()))
618
        }
619
    }
620
}
621

            
622
impl ToJson for JsonValue {
623
102
    fn to_json(&self) -> JValue {
624
102
        match self {
625
3
            JsonValue::Null => JValue::Null,
626
45
            JsonValue::Number(s) => JValue::Number(s.to_string()),
627
18
            JsonValue::String(s) => JValue::String(s.to_string()),
628
3
            JsonValue::Boolean(v) => JValue::Boolean(*v),
629
15
            JsonValue::List { elements, .. } => {
630
56
                JValue::List(elements.iter().map(|e| e.to_json()).collect())
631
            }
632
15
            JsonValue::Object { elements, .. } => JValue::Object(
633
15
                elements
634
15
                    .iter()
635
50
                    .map(|elem| (elem.name.to_string(), elem.value.to_json()))
636
15
                    .collect(),
637
            ),
638
3
            JsonValue::Placeholder(exp) => JValue::String(format!("{{{{{exp}}}}}")),
639
        }
640
    }
641
}
642

            
643
impl ToJson for JsonListElement {
644
51
    fn to_json(&self) -> JValue {
645
51
        self.value.to_json()
646
    }
647
}
648

            
649
impl ToJson for Filter {
650
147
    fn to_json(&self) -> JValue {
651
147
        self.value.to_json()
652
    }
653
}
654

            
655
impl ToJson for FilterValue {
656
147
    fn to_json(&self) -> JValue {
657
147
        let mut attributes = vec![];
658
147
        let att_name = "type".to_string();
659
147
        let att_value = JValue::String(self.identifier().to_string());
660
147
        attributes.push((att_name, att_value));
661

            
662
147
        match self {
663
3
            FilterValue::Decode { encoding, .. } => {
664
3
                attributes.push(("encoding".to_string(), JValue::String(encoding.to_string())));
665
            }
666
6
            FilterValue::Format { fmt, .. } => {
667
6
                attributes.push(("fmt".to_string(), JValue::String(fmt.to_string())));
668
            }
669
9
            FilterValue::DateFormat { fmt, .. } => {
670
9
                attributes.push(("fmt".to_string(), JValue::String(fmt.to_string())));
671
            }
672
9
            FilterValue::JsonPath { expr, .. } => {
673
9
                attributes.push(("expr".to_string(), JValue::String(expr.to_string())));
674
            }
675
3
            FilterValue::Nth { n, .. } => {
676
3
                attributes.push(("n".to_string(), JValue::Number(n.to_string())));
677
            }
678
3
            FilterValue::Regex { value, .. } => {
679
3
                attributes.push(("expr".to_string(), value.to_json()));
680
            }
681
            FilterValue::Replace {
682
15
                old_value,
683
15
                new_value,
684
                ..
685
15
            } => {
686
15
                attributes.push((
687
15
                    "old_value".to_string(),
688
15
                    JValue::String(old_value.to_string()),
689
15
                ));
690
15
                attributes.push((
691
15
                    "new_value".to_string(),
692
15
                    JValue::String(new_value.to_string()),
693
15
                ));
694
            }
695
            FilterValue::ReplaceRegex {
696
3
                pattern, new_value, ..
697
3
            } => {
698
3
                attributes.push(("pattern".to_string(), pattern.to_json()));
699
3
                attributes.push((
700
3
                    "new_value".to_string(),
701
3
                    JValue::String(new_value.to_string()),
702
3
                ));
703
            }
704
3
            FilterValue::Split { sep, .. } => {
705
3
                attributes.push(("sep".to_string(), JValue::String(sep.to_string())));
706
            }
707
3
            FilterValue::ToDate { fmt, .. } => {
708
3
                attributes.push(("fmt".to_string(), JValue::String(fmt.to_string())));
709
            }
710
3
            FilterValue::UrlQueryParam { param, .. } => {
711
3
                attributes.push(("param".to_string(), JValue::String(param.to_string())));
712
            }
713
3
            FilterValue::XPath { expr, .. } => {
714
3
                attributes.push(("expr".to_string(), JValue::String(expr.to_string())));
715
            }
716
84
            _ => {}
717
        }
718
147
        JValue::Object(attributes)
719
    }
720
}
721

            
722
impl ToJson for Placeholder {
723
84
    fn to_json(&self) -> JValue {
724
84
        JValue::String(format!("{{{{{self}}}}}"))
725
    }
726
}
727

            
728
impl ToJson for Comment {
729
21
    fn to_json(&self) -> JValue {
730
21
        JValue::String(self.value.to_string())
731
    }
732
}
733

            
734
impl ToJson for NaturalOption {
735
6
    fn to_json(&self) -> JValue {
736
6
        match self {
737
3
            NaturalOption::Literal(value) => JValue::Number(value.to_string()),
738
3
            NaturalOption::Placeholder(placeholder) => placeholder.to_json(),
739
        }
740
    }
741
}
742
#[cfg(test)]
743
pub mod tests {
744
    use hurl_core::ast::{
745
        I64, LineTerminator, Method, Number, PredicateFunc, SourceInfo, Status, Template,
746
        TemplateElement, Version, Whitespace,
747
    };
748
    use hurl_core::reader::Pos;
749
    use hurl_core::types::ToSource;
750

            
751
    use super::*;
752

            
753
    fn whitespace() -> Whitespace {
754
        Whitespace {
755
            value: String::new(),
756
            source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
757
        }
758
    }
759

            
760
    fn line_terminator() -> LineTerminator {
761
        LineTerminator {
762
            space0: whitespace(),
763
            comment: None,
764
            newline: whitespace(),
765
        }
766
    }
767

            
768
    #[test]
769
    pub fn test_request() {
770
        assert_eq!(
771
            Request {
772
                line_terminators: vec![],
773
                space0: whitespace(),
774
                method: Method::new("GET"),
775
                space1: whitespace(),
776
                url: Template::new(
777
                    None,
778
                    vec![TemplateElement::String {
779
                        value: "http://example.com".to_string(),
780
                        source: "not_used".to_source(),
781
                    }],
782
                    SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
783
                ),
784
                line_terminator0: line_terminator(),
785
                headers: vec![KeyValue {
786
                    line_terminators: vec![],
787
                    space0: whitespace(),
788
                    key: Template::new(
789
                        None,
790
                        vec![TemplateElement::String {
791
                            value: "Foo".to_string(),
792
                            source: "unused".to_source(),
793
                        }],
794
                        SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0))
795
                    ),
796
                    space1: whitespace(),
797
                    space2: whitespace(),
798
                    value: Template::new(
799
                        None,
800
                        vec![TemplateElement::String {
801
                            value: "Bar".to_string(),
802
                            source: "unused".to_source(),
803
                        }],
804
                        SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0))
805
                    ),
806
                    line_terminator0: line_terminator(),
807
                }],
808
                sections: vec![],
809
                body: None,
810
                source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
811
            }
812
            .to_json(),
813
            JValue::Object(vec![
814
                ("method".to_string(), JValue::String("GET".to_string())),
815
                (
816
                    "url".to_string(),
817
                    JValue::String("http://example.com".to_string())
818
                ),
819
                (
820
                    "headers".to_string(),
821
                    JValue::List(vec![JValue::Object(vec![
822
                        ("name".to_string(), JValue::String("Foo".to_string())),
823
                        ("value".to_string(), JValue::String("Bar".to_string()))
824
                    ])])
825
                )
826
            ])
827
        );
828
    }
829

            
830
    #[test]
831
    pub fn test_response() {
832
        assert_eq!(
833
            Response {
834
                line_terminators: vec![],
835
                version: Version {
836
                    value: VersionValue::Version11,
837
                    source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
838
                },
839
                space0: whitespace(),
840
                status: Status {
841
                    value: StatusValue::Specific(200),
842
                    source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
843
                },
844
                space1: whitespace(),
845
                line_terminator0: line_terminator(),
846
                headers: vec![],
847
                sections: vec![],
848
                body: None,
849
                source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
850
            }
851
            .to_json(),
852
            JValue::Object(vec![
853
                (
854
                    "version".to_string(),
855
                    JValue::String("HTTP/1.1".to_string())
856
                ),
857
                ("status".to_string(), JValue::Number("200".to_string()))
858
            ])
859
        );
860
        assert_eq!(
861
            Response {
862
                line_terminators: vec![],
863
                version: Version {
864
                    value: VersionValue::VersionAny,
865
                    source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
866
                },
867
                space0: whitespace(),
868
                status: Status {
869
                    value: StatusValue::Any,
870
                    source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
871
                },
872
                space1: whitespace(),
873
                line_terminator0: line_terminator(),
874
                headers: vec![],
875
                sections: vec![],
876
                body: None,
877
                source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
878
            }
879
            .to_json(),
880
            JValue::Object(vec![])
881
        );
882
    }
883

            
884
    fn header_query() -> Query {
885
        Query {
886
            source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
887
            value: QueryValue::Header {
888
                space0: whitespace(),
889
                name: Template::new(
890
                    None,
891
                    vec![TemplateElement::String {
892
                        value: "Content-Length".to_string(),
893
                        source: "Content-Length".to_source(),
894
                    }],
895
                    SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
896
                ),
897
            },
898
        }
899
    }
900

            
901
    fn header_capture() -> Capture {
902
        Capture {
903
            line_terminators: vec![],
904
            space0: whitespace(),
905
            name: Template::new(
906
                None,
907
                vec![TemplateElement::String {
908
                    value: "size".to_string(),
909
                    source: "unused".to_source(),
910
                }],
911
                SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
912
            ),
913
            space1: whitespace(),
914
            space2: whitespace(),
915
            query: header_query(),
916
            filters: vec![],
917
            space3: whitespace(),
918
            redacted: false,
919
            line_terminator0: line_terminator(),
920
        }
921
    }
922

            
923
    fn header_assert() -> Assert {
924
        Assert {
925
            line_terminators: vec![],
926
            space0: whitespace(),
927
            query: header_query(),
928
            filters: vec![],
929
            space1: whitespace(),
930
            predicate: equal_int_predicate(10),
931
            line_terminator0: line_terminator(),
932
        }
933
    }
934

            
935
    fn equal_int_predicate(value: i64) -> Predicate {
936
        Predicate {
937
            not: false,
938
            space0: whitespace(),
939
            predicate_func: PredicateFunc {
940
                source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
941
                value: PredicateFuncValue::Equal {
942
                    space0: whitespace(),
943
                    value: PredicateValue::Number(Number::Integer(I64::new(
944
                        value,
945
                        value.to_string().to_source(),
946
                    ))),
947
                },
948
            },
949
        }
950
    }
951

            
952
    #[test]
953
    pub fn test_query() {
954
        assert_eq!(
955
            header_query().to_json(),
956
            JValue::Object(vec![
957
                ("type".to_string(), JValue::String("header".to_string())),
958
                (
959
                    "name".to_string(),
960
                    JValue::String("Content-Length".to_string())
961
                ),
962
            ])
963
        );
964
    }
965

            
966
    #[test]
967
    pub fn test_capture() {
968
        assert_eq!(
969
            header_capture().to_json(),
970
            JValue::Object(vec![
971
                ("name".to_string(), JValue::String("size".to_string())),
972
                (
973
                    "query".to_string(),
974
                    JValue::Object(vec![
975
                        ("type".to_string(), JValue::String("header".to_string())),
976
                        (
977
                            "name".to_string(),
978
                            JValue::String("Content-Length".to_string())
979
                        ),
980
                    ])
981
                ),
982
            ])
983
        );
984
    }
985

            
986
    #[test]
987
    pub fn test_predicate() {
988
        assert_eq!(
989
            equal_int_predicate(10).to_json(),
990
            JValue::Object(vec![
991
                ("type".to_string(), JValue::String("==".to_string())),
992
                ("value".to_string(), JValue::Number("10".to_string()))
993
            ]),
994
        );
995
    }
996

            
997
    #[test]
998
    pub fn test_assert() {
999
        assert_eq!(
            header_assert().to_json(),
            JValue::Object(vec![
                (
                    "query".to_string(),
                    JValue::Object(vec![
                        ("type".to_string(), JValue::String("header".to_string())),
                        (
                            "name".to_string(),
                            JValue::String("Content-Length".to_string())
                        ),
                    ])
                ),
                (
                    "predicate".to_string(),
                    JValue::Object(vec![
                        ("type".to_string(), JValue::String("==".to_string())),
                        ("value".to_string(), JValue::Number("10".to_string()))
                    ])
                )
            ]),
        );
    }
}