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
38
pub fn format(hurl_file: &HurlFile) -> String {
33
38
    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
38
    fn to_json(&self) -> JValue {
42
38
        JValue::Object(vec![(
43
38
            "entries".to_string(),
44
128
            JValue::List(self.entries.iter().map(|e| e.to_json()).collect()),
45
        )])
46
    }
47
}
48

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

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

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

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

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

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

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

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

            
156
impl ToJson for Bytes {
157
48
    fn to_json(&self) -> JValue {
158
48
        match self {
159
4
            Bytes::Base64(value) => value.to_json(),
160
2
            Bytes::Hex(value) => value.to_json(),
161
2
            Bytes::File(value) => value.to_json(),
162
4
            Bytes::Json(value) => JValue::Object(vec![
163
4
                ("type".to_string(), JValue::String("json".to_string())),
164
4
                ("value".to_string(), value.to_json()),
165
4
            ]),
166
2
            Bytes::Xml(value) => JValue::Object(vec![
167
2
                ("type".to_string(), JValue::String("xml".to_string())),
168
2
                ("value".to_string(), JValue::String(value.clone())),
169
2
            ]),
170
6
            Bytes::OnelineString(value) => JValue::Object(vec![
171
6
                ("type".to_string(), JValue::String("text".to_string())),
172
6
                ("value".to_string(), JValue::String(value.to_string())),
173
6
            ]),
174
28
            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
28
                let lang = match multi {
191
                    MultilineString {
192
                        kind: MultilineStringKind::Text(_),
193
                        ..
194
6
                    } => "text",
195
                    MultilineString {
196
                        kind: MultilineStringKind::Raw(_),
197
                        ..
198
6
                    } => "raw",
199
                    MultilineString {
200
                        kind: MultilineStringKind::Json(_),
201
                        ..
202
6
                    } => "json",
203
                    MultilineString {
204
                        kind: MultilineStringKind::Xml(_),
205
                        ..
206
4
                    } => "xml",
207
                    MultilineString {
208
                        kind: MultilineStringKind::GraphQl(_),
209
                        ..
210
6
                    } => "graphql",
211
                };
212
28
                JValue::Object(vec![
213
28
                    ("type".to_string(), JValue::String(lang.to_string())),
214
28
                    ("value".to_string(), JValue::String(multi.to_string())),
215
28
                ])
216
            }
217
        }
218
    }
219
}
220

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

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

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

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

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

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

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

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

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

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

            
387
impl ToJson for BooleanOption {
388
86
    fn to_json(&self) -> JValue {
389
86
        match self {
390
46
            BooleanOption::Literal(value) => JValue::Boolean(*value),
391
40
            BooleanOption::Placeholder(placeholder) => placeholder.to_json(),
392
        }
393
    }
394
}
395

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

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

            
414
impl ToJson for DurationOption {
415
28
    fn to_json(&self) -> JValue {
416
28
        match self {
417
20
            DurationOption::Literal(value) => value.to_json(),
418
8
            DurationOption::Placeholder(placeholder) => placeholder.to_json(),
419
        }
420
    }
421
}
422

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

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

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

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

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

            
477
262
    match query_value {
478
162
        QueryValue::Jsonpath { expr, .. } => {
479
162
            attributes.push(("expr".to_string(), JValue::String(expr.to_string())));
480
        }
481
4
        QueryValue::Header { name, .. } => {
482
4
            attributes.push(("name".to_string(), JValue::String(name.to_string())));
483
        }
484
6
        QueryValue::Cookie { expr, .. } => {
485
6
            attributes.push(("expr".to_string(), JValue::String(expr.to_string())));
486
        }
487
2
        QueryValue::Xpath { expr, .. } => {
488
2
            attributes.push(("expr".to_string(), JValue::String(expr.to_string())));
489
        }
490
2
        QueryValue::Regex { value, .. } => {
491
2
            attributes.push(("expr".to_string(), value.to_json()));
492
        }
493
6
        QueryValue::Variable { name, .. } => {
494
6
            attributes.push(("name".to_string(), JValue::String(name.to_string())));
495
        }
496
        QueryValue::Certificate {
497
22
            attribute_name: field,
498
            ..
499
22
        } => {
500
22
            attributes.push(("expr".to_string(), field.to_json()));
501
        }
502
58
        _ => {}
503
    };
504
262
    attributes
505
}
506

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

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

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

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

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

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

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

            
624
impl ToJson for JsonValue {
625
68
    fn to_json(&self) -> JValue {
626
68
        match self {
627
2
            JsonValue::Null => JValue::Null,
628
30
            JsonValue::Number(s) => JValue::Number(s.to_string()),
629
12
            JsonValue::String(s) => JValue::String(s.to_string()),
630
2
            JsonValue::Boolean(v) => JValue::Boolean(*v),
631
10
            JsonValue::List { elements, .. } => {
632
34
                JValue::List(elements.iter().map(|e| e.to_json()).collect())
633
            }
634
10
            JsonValue::Object { elements, .. } => JValue::Object(
635
10
                elements
636
10
                    .iter()
637
30
                    .map(|elem| (elem.name.to_string(), elem.value.to_json()))
638
10
                    .collect(),
639
            ),
640
2
            JsonValue::Placeholder(exp) => JValue::String(format!("{{{{{exp}}}}}")),
641
        }
642
    }
643
}
644

            
645
impl ToJson for JsonListElement {
646
34
    fn to_json(&self) -> JValue {
647
34
        self.value.to_json()
648
    }
649
}
650

            
651
impl ToJson for Filter {
652
98
    fn to_json(&self) -> JValue {
653
98
        self.value.to_json()
654
    }
655
}
656

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

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

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

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

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

            
753
    use super::*;
754

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

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

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

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

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

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

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

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

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

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

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

            
999
    #[test]
    pub fn test_assert() {
        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()))
                    ])
                )
            ]),
        );
    }
}