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 std::fmt;
19
use std::fmt::Formatter;
20

            
21
use crate::reader::Pos;
22
use crate::types::{SourceString, ToSource};
23

            
24
use super::json::JsonValue;
25

            
26
#[derive(Clone, Debug, PartialEq, Eq)]
27
pub struct KeyValue {
28
    pub line_terminators: Vec<LineTerminator>,
29
    pub space0: Whitespace,
30
    pub key: Template,
31
    pub space1: Whitespace,
32
    pub space2: Whitespace,
33
    pub value: Template,
34
    pub line_terminator0: LineTerminator,
35
}
36

            
37
#[derive(Clone, Debug, PartialEq, Eq)]
38
pub struct MultilineString {
39
    pub space: Whitespace,
40
    pub newline: Whitespace,
41
    pub kind: MultilineStringKind,
42
}
43

            
44
impl fmt::Display for MultilineString {
45
70
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
46
70
        match &self.kind {
47
15
            MultilineStringKind::Text(value)
48
15
            | MultilineStringKind::Json(value)
49
40
            | MultilineStringKind::Xml(value) => write!(f, "{value}"),
50
15
            MultilineStringKind::Raw(value) => write!(f, "{value}"),
51
15
            MultilineStringKind::GraphQl(value) => write!(f, "{value}"),
52
        }
53
    }
54
}
55

            
56
impl ToSource for MultilineString {
57
445
    fn to_source(&self) -> SourceString {
58
445
        let mut source = SourceString::new();
59
445
        source.push_str("```");
60
445
        source.push_str(self.lang());
61
445
        source.push_str(self.space.as_str());
62
445
        source.push_str(self.newline.as_str());
63
445
        source.push_str(self.kind.to_source().as_str());
64
445
        source.push_str("```");
65
445
        source
66
    }
67
}
68

            
69
impl MultilineString {
70
445
    pub fn lang(&self) -> &'static str {
71
445
        match self.kind {
72
135
            MultilineStringKind::Text(_) => "",
73
80
            MultilineStringKind::Json(_) => "json",
74
60
            MultilineStringKind::Xml(_) => "xml",
75
75
            MultilineStringKind::Raw(_) => "raw",
76
95
            MultilineStringKind::GraphQl(_) => "graphql",
77
        }
78
    }
79

            
80
245
    pub fn value(&self) -> Template {
81
245
        match &self.kind {
82
145
            MultilineStringKind::Text(text)
83
25
            | MultilineStringKind::Json(text)
84
195
            | MultilineStringKind::Xml(text) => text.clone(),
85
25
            MultilineStringKind::Raw(text) => text.clone(),
86
25
            MultilineStringKind::GraphQl(text) => text.value.clone(),
87
        }
88
    }
89
}
90

            
91
#[allow(clippy::large_enum_variant)]
92
#[derive(Clone, Debug, PartialEq, Eq)]
93
pub enum MultilineStringKind {
94
    Text(Template),
95
    Json(Template),
96
    Xml(Template),
97
    Raw(Template),
98
    GraphQl(GraphQl),
99
}
100

            
101
impl ToSource for MultilineStringKind {
102
445
    fn to_source(&self) -> SourceString {
103
445
        match self {
104
135
            MultilineStringKind::Text(value)
105
80
            | MultilineStringKind::Json(value)
106
275
            | MultilineStringKind::Xml(value) => value.to_source(),
107
75
            MultilineStringKind::Raw(value) => value.to_source(),
108
95
            MultilineStringKind::GraphQl(value) => value.to_source(),
109
        }
110
    }
111
}
112

            
113
#[derive(Clone, Debug, PartialEq, Eq)]
114
pub struct GraphQl {
115
    pub value: Template,
116
    pub variables: Option<GraphQlVariables>,
117
}
118

            
119
impl fmt::Display for GraphQl {
120
15
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
121
15
        write!(f, "{}", self.value)?;
122
15
        if let Some(vars) = &self.variables {
123
5
            write!(f, "{}", vars.to_source())?;
124
        }
125
15
        Ok(())
126
    }
127
}
128

            
129
impl ToSource for GraphQl {
130
95
    fn to_source(&self) -> SourceString {
131
95
        let mut source = SourceString::new();
132
95
        source.push_str(self.value.to_source().as_str());
133
95
        if let Some(vars) = &self.variables {
134
25
            source.push_str(vars.to_source().as_str());
135
        }
136
95
        source
137
    }
138
}
139

            
140
#[derive(Clone, Debug, PartialEq, Eq)]
141
pub struct GraphQlVariables {
142
    pub space: Whitespace,
143
    pub value: JsonValue,
144
    pub whitespace: Whitespace,
145
}
146

            
147
impl ToSource for GraphQlVariables {
148
30
    fn to_source(&self) -> SourceString {
149
30
        let mut source = "variables".to_source();
150
30
        source.push_str(self.space.as_str());
151
30
        source.push_str(self.value.to_source().as_str());
152
30
        source.push_str(self.whitespace.as_str());
153
30
        source
154
    }
155
}
156

            
157
#[derive(Clone, Debug, PartialEq, Eq)]
158
pub struct Base64 {
159
    pub space0: Whitespace,
160
    pub value: Vec<u8>,
161
    pub source: SourceString,
162
    pub space1: Whitespace,
163
}
164

            
165
#[derive(Clone, Debug, PartialEq, Eq)]
166
pub struct File {
167
    pub space0: Whitespace,
168
    pub filename: Template,
169
    pub space1: Whitespace,
170
}
171

            
172
#[derive(Clone, Debug, PartialEq, Eq)]
173
pub struct Template {
174
    pub delimiter: Option<char>,
175
    pub elements: Vec<TemplateElement>,
176
    pub source_info: SourceInfo,
177
}
178

            
179
impl fmt::Display for Template {
180
2560
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
181
2560
        let mut buffer = String::new();
182
2650
        for element in self.elements.iter() {
183
2650
            buffer.push_str(element.to_string().as_str());
184
        }
185
2560
        write!(f, "{buffer}")
186
    }
187
}
188

            
189
impl ToSource for Template {
190
15220
    fn to_source(&self) -> SourceString {
191
15220
        let mut s = SourceString::new();
192
15220
        if let Some(d) = self.delimiter {
193
7970
            s.push(d);
194
        }
195
18389
        let elements: Vec<SourceString> = self.elements.iter().map(|e| e.to_source()).collect();
196
15220
        s.push_str(elements.join("").as_str());
197
15220
        if let Some(d) = self.delimiter {
198
7970
            s.push(d);
199
        }
200
15220
        s
201
    }
202
}
203

            
204
impl Template {
205
    /// Creates a new template.
206
82130
    pub fn new(
207
82130
        delimiter: Option<char>,
208
82130
        elements: Vec<TemplateElement>,
209
82130
        source_info: SourceInfo,
210
82130
    ) -> Template {
211
82130
        Template {
212
82130
            delimiter,
213
82130
            elements,
214
82130
            source_info,
215
        }
216
    }
217

            
218
    /// Returns true if this template is empty.
219
210
    pub fn is_empty(&self) -> bool {
220
210
        self.elements.is_empty()
221
    }
222
}
223

            
224
#[derive(Clone, Debug, PartialEq, Eq)]
225
pub enum TemplateElement {
226
    String { value: String, source: SourceString },
227
    Placeholder(Placeholder),
228
}
229

            
230
impl fmt::Display for TemplateElement {
231
2650
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
232
2650
        let s = match self {
233
2455
            TemplateElement::String { value, .. } => value.clone(),
234
            // TODO: see why we can't need to us `{{` and `}}` in a to_string method
235
195
            TemplateElement::Placeholder(value) => format!("{{{{{value}}}}}"),
236
        };
237
2650
        write!(f, "{s}")
238
    }
239
}
240

            
241
impl ToSource for TemplateElement {
242
15345
    fn to_source(&self) -> SourceString {
243
15345
        match self {
244
14710
            TemplateElement::String { source, .. } => source.clone(),
245
635
            TemplateElement::Placeholder(value) => value.to_source(),
246
        }
247
    }
248
}
249

            
250
#[derive(Clone, Debug, PartialEq, Eq)]
251
pub struct Comment {
252
    pub value: String,
253
    pub source_info: SourceInfo,
254
}
255

            
256
impl ToSource for Comment {
257
2300
    fn to_source(&self) -> SourceString {
258
2300
        format!("#{}", self.value).to_source()
259
    }
260
}
261

            
262
#[derive(Clone, Debug, PartialEq, Eq)]
263
pub struct Whitespace {
264
    pub value: String,
265
    pub source_info: SourceInfo,
266
}
267

            
268
impl fmt::Display for Whitespace {
269
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
270
        write!(f, "{}", self.value)
271
    }
272
}
273

            
274
impl Whitespace {
275
78340
    pub fn as_str(&self) -> &str {
276
78340
        &self.value
277
    }
278
}
279

            
280
#[derive(Clone, Debug, PartialEq, Eq)]
281
pub enum Number {
282
    Float(Float),
283
    Integer(I64),
284
    BigInteger(String),
285
}
286

            
287
impl fmt::Display for Number {
288
330
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
289
330
        match self {
290
90
            Number::Float(value) => write!(f, "{value}"),
291
240
            Number::Integer(value) => write!(f, "{value}"),
292
            Number::BigInteger(value) => write!(f, "{value}"),
293
        }
294
    }
295
}
296

            
297
impl ToSource for Number {
298
1415
    fn to_source(&self) -> SourceString {
299
1415
        match self {
300
420
            Number::Float(value) => value.to_source(),
301
990
            Number::Integer(value) => value.to_source(),
302
5
            Number::BigInteger(value) => value.to_source(),
303
        }
304
    }
305
}
306

            
307
// keep Number terminology for both Integer and Decimal Numbers
308
// different representation for the same float value
309
// 1.01 and 1.010
310

            
311
#[derive(Clone, Debug)]
312
pub struct Float {
313
    value: f64,
314
    source: SourceString,
315
}
316

            
317
impl Float {
318
970
    pub fn new(value: f64, source: SourceString) -> Float {
319
970
        Float { value, source }
320
    }
321

            
322
415
    pub fn as_f64(&self) -> f64 {
323
415
        self.value
324
    }
325
}
326

            
327
impl fmt::Display for Float {
328
90
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
329
90
        write!(f, "{}", self.value)
330
    }
331
}
332

            
333
impl ToSource for Float {
334
420
    fn to_source(&self) -> SourceString {
335
420
        self.source.clone()
336
    }
337
}
338

            
339
#[derive(Clone, Debug, PartialEq, Eq)]
340
pub struct U64 {
341
    value: u64,
342
    source: SourceString,
343
}
344

            
345
impl U64 {
346
570
    pub fn new(value: u64, source: SourceString) -> U64 {
347
570
        U64 { value, source }
348
    }
349

            
350
335
    pub fn as_u64(&self) -> u64 {
351
335
        self.value
352
    }
353
}
354

            
355
impl fmt::Display for U64 {
356
345
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
357
345
        write!(f, "{}", self.value)
358
    }
359
}
360

            
361
impl ToSource for U64 {
362
175
    fn to_source(&self) -> SourceString {
363
175
        self.source.clone()
364
    }
365
}
366

            
367
#[derive(Clone, Debug, PartialEq, Eq)]
368
pub struct I64 {
369
    value: i64,
370
    source: SourceString,
371
}
372

            
373
impl I64 {
374
5580
    pub fn new(value: i64, source: SourceString) -> I64 {
375
5580
        I64 { value, source }
376
    }
377

            
378
4610
    pub fn as_i64(&self) -> i64 {
379
4610
        self.value
380
    }
381
}
382

            
383
impl fmt::Display for I64 {
384
245
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
385
245
        write!(f, "{}", self.value)
386
    }
387
}
388

            
389
impl ToSource for I64 {
390
995
    fn to_source(&self) -> SourceString {
391
995
        self.source.clone()
392
    }
393
}
394

            
395
impl PartialEq for Float {
396
    fn eq(&self, other: &Self) -> bool {
397
        self.source == other.source
398
    }
399
}
400

            
401
impl Eq for Float {}
402

            
403
#[derive(Clone, Debug, PartialEq, Eq)]
404
pub struct LineTerminator {
405
    pub space0: Whitespace,
406
    pub comment: Option<Comment>,
407
    pub newline: Whitespace,
408
}
409

            
410
#[allow(clippy::large_enum_variant)]
411
#[derive(Clone, Debug, PartialEq, Eq)]
412
pub enum Bytes {
413
    Json(JsonValue),
414
    Xml(String),
415
    MultilineString(MultilineString),
416
    OnelineString(Template),
417
    Base64(Base64),
418
    File(File),
419
    Hex(Hex),
420
}
421

            
422
#[derive(Clone, Debug, PartialEq, Eq)]
423
pub struct Hex {
424
    pub space0: Whitespace,
425
    pub value: Vec<u8>,
426
    pub source: SourceString,
427
    pub space1: Whitespace,
428
}
429

            
430
impl fmt::Display for Hex {
431
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
432
        write!(f, "hex,{}{}{};", self.space0, self.source, self.space1)
433
    }
434
}
435

            
436
/// Literal Regex.
437
#[derive(Clone, Debug)]
438
pub struct Regex {
439
    pub inner: regex::Regex,
440
    pub source: SourceString,
441
}
442

            
443
impl ToSource for Regex {
444
195
    fn to_source(&self) -> SourceString {
445
195
        self.source.clone()
446
    }
447
}
448

            
449
impl fmt::Display for Regex {
450
20
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
451
20
        write!(f, "{}", self.inner)
452
    }
453
}
454

            
455
impl PartialEq for Regex {
456
    fn eq(&self, other: &Self) -> bool {
457
        self.inner.to_string() == other.inner.to_string()
458
    }
459
}
460

            
461
impl Eq for Regex {}
462

            
463
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
464
pub struct SourceInfo {
465
    pub start: Pos,
466
    pub end: Pos,
467
}
468

            
469
impl SourceInfo {
470
1353270
    pub fn new(start: Pos, end: Pos) -> SourceInfo {
471
1353270
        SourceInfo { start, end }
472
    }
473
}
474

            
475
#[derive(Clone, Debug, PartialEq, Eq)]
476
pub struct Placeholder {
477
    pub space0: Whitespace,
478
    pub expr: Expr,
479
    pub space1: Whitespace,
480
}
481

            
482
impl fmt::Display for Placeholder {
483
375
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
484
375
        write!(f, "{}", self.expr)
485
    }
486
}
487

            
488
impl ToSource for Placeholder {
489
1200
    fn to_source(&self) -> SourceString {
490
1200
        let mut source = SourceString::new();
491
1200
        source.push_str("{{");
492
1200
        source.push_str(self.space0.as_str());
493
1200
        source.push_str(self.expr.to_source().as_str());
494
1200
        source.push_str(self.space1.as_str());
495
1200
        source.push_str("}}");
496
1200
        source
497
    }
498
}
499

            
500
#[derive(Clone, Debug, PartialEq, Eq)]
501
pub struct Expr {
502
    pub source_info: SourceInfo,
503
    pub kind: ExprKind,
504
}
505

            
506
impl fmt::Display for Expr {
507
375
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
508
375
        write!(f, "{}", self.kind)
509
    }
510
}
511

            
512
impl ToSource for Expr {
513
1200
    fn to_source(&self) -> SourceString {
514
1200
        self.kind.to_string().to_source()
515
    }
516
}
517

            
518
#[derive(Clone, Debug, PartialEq, Eq)]
519
pub enum ExprKind {
520
    Variable(Variable),
521
    Function(Function),
522
}
523

            
524
impl fmt::Display for ExprKind {
525
1575
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
526
1575
        match self {
527
1535
            ExprKind::Variable(variable) => write!(f, "{variable}"),
528
40
            ExprKind::Function(function) => write!(f, "{function}"),
529
        }
530
    }
531
}
532

            
533
#[derive(Clone, Debug, PartialEq, Eq)]
534
pub struct Variable {
535
    pub name: String,
536
    pub source_info: SourceInfo,
537
}
538

            
539
impl fmt::Display for Variable {
540
1535
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
541
1535
        write!(f, "{}", self.name)
542
    }
543
}
544

            
545
#[derive(Clone, Debug, PartialEq, Eq)]
546
pub enum Function {
547
    NewDate,
548
    NewUuid,
549
}
550

            
551
impl fmt::Display for Function {
552
40
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
553
40
        match self {
554
20
            Function::NewDate => write!(f, "newDate"),
555
20
            Function::NewUuid => write!(f, "newUuid"),
556
        }
557
    }
558
}
559

            
560
#[cfg(test)]
561
mod tests {
562
    use super::*;
563
    use crate::ast::json::{JsonListElement, JsonObjectElement, JsonValue};
564
    use crate::types::ToSource;
565

            
566
    #[test]
567
    fn test_float() {
568
        assert_eq!(
569
            Float {
570
                value: 1.0,
571
                source: "1.0".to_source()
572
            }
573
            .to_source()
574
            .as_str(),
575
            "1.0"
576
        );
577
        assert_eq!(
578
            Float {
579
                value: 1.0,
580
                source: "1.0".to_source()
581
            }
582
            .to_string(),
583
            "1"
584
        );
585

            
586
        assert_eq!(
587
            Float {
588
                value: 1.01,
589
                source: "1.01".to_source()
590
            }
591
            .to_source()
592
            .as_str(),
593
            "1.01"
594
        );
595
        assert_eq!(
596
            Float {
597
                value: 1.01,
598
                source: "1.01".to_source()
599
            }
600
            .to_string(),
601
            "1.01"
602
        );
603

            
604
        assert_eq!(
605
            Float {
606
                value: 1.01,
607
                source: "1.010".to_source()
608
            }
609
            .to_source()
610
            .as_str(),
611
            "1.010"
612
        );
613
        assert_eq!(
614
            Float {
615
                value: 1.01,
616
                source: "1.010".to_source()
617
            }
618
            .to_string(),
619
            "1.01"
620
        );
621

            
622
        assert_eq!(
623
            Float {
624
                value: -1.333,
625
                source: "-1.333".to_source()
626
            }
627
            .to_source()
628
            .as_str(),
629
            "-1.333"
630
        );
631
        assert_eq!(
632
            Float {
633
                value: -1.333,
634
                source: "-1.333".to_source()
635
            }
636
            .to_string(),
637
            "-1.333"
638
        );
639
    }
640

            
641
    fn whitespace() -> Whitespace {
642
        Whitespace {
643
            value: String::new(),
644
            source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
645
        }
646
    }
647

            
648
    fn variable_placeholder() -> Placeholder {
649
        Placeholder {
650
            space0: whitespace(),
651
            expr: Expr {
652
                kind: ExprKind::Variable(Variable {
653
                    name: "name".to_string(),
654
                    source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
655
                }),
656
                source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
657
            },
658
            space1: whitespace(),
659
        }
660
    }
661

            
662
    fn hello_template() -> Template {
663
        Template::new(
664
            None,
665
            vec![
666
                TemplateElement::String {
667
                    value: "Hello ".to_string(),
668
                    source: "Hello ".to_source(),
669
                },
670
                TemplateElement::Placeholder(variable_placeholder()),
671
                TemplateElement::String {
672
                    value: "!".to_string(),
673
                    source: "!".to_source(),
674
                },
675
            ],
676
            SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
677
        )
678
    }
679

            
680
    #[test]
681
    fn test_template() {
682
        assert_eq!(hello_template().to_string(), "Hello {{name}}!");
683
    }
684

            
685
    #[test]
686
    fn test_template_to_source() {
687
        assert_eq!(
688
            "{{x}}",
689
            JsonValue::Placeholder(Placeholder {
690
                space0: Whitespace {
691
                    value: String::new(),
692
                    source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
693
                },
694
                expr: Expr {
695
                    kind: ExprKind::Variable(Variable {
696
                        name: "x".to_string(),
697
                        source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
698
                    }),
699
                    source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 1)),
700
                },
701
                space1: Whitespace {
702
                    value: String::new(),
703
                    source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
704
                },
705
            })
706
            .to_source()
707
            .as_str()
708
        );
709
        assert_eq!("1", JsonValue::Number("1".to_string()).to_source().as_str());
710
        assert_eq!(
711
            "\"hello\"",
712
            JsonValue::String(Template::new(
713
                Some('"'),
714
                vec![TemplateElement::String {
715
                    value: "hello".to_string(),
716
                    source: "hello".to_source(),
717
                }],
718
                SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0))
719
            ))
720
            .to_source()
721
            .as_str()
722
        );
723
        assert_eq!("true", JsonValue::Boolean(true).to_source().as_str());
724
        assert_eq!(
725
            "[]",
726
            JsonValue::List {
727
                space0: String::new(),
728
                elements: vec![],
729
            }
730
            .to_source()
731
            .as_str()
732
        );
733
        assert_eq!(
734
            "[1, 2, 3]",
735
            JsonValue::List {
736
                space0: String::new(),
737
                elements: vec![
738
                    JsonListElement {
739
                        space0: String::new(),
740
                        value: JsonValue::Number("1".to_string()),
741
                        space1: String::new(),
742
                    },
743
                    JsonListElement {
744
                        space0: " ".to_string(),
745
                        value: JsonValue::Number("2".to_string()),
746
                        space1: String::new(),
747
                    },
748
                    JsonListElement {
749
                        space0: " ".to_string(),
750
                        value: JsonValue::Number("3".to_string()),
751
                        space1: String::new(),
752
                    }
753
                ],
754
            }
755
            .to_source()
756
            .as_str()
757
        );
758
        assert_eq!(
759
            "{}",
760
            JsonValue::Object {
761
                space0: String::new(),
762
                elements: vec![],
763
            }
764
            .to_source()
765
            .as_str()
766
        );
767
        assert_eq!(
768
            "{ \"id\": 123 }",
769
            JsonValue::Object {
770
                space0: String::new(),
771
                elements: vec![JsonObjectElement {
772
                    space0: " ".to_string(),
773
                    name: Template::new(
774
                        Some('"'),
775
                        vec![TemplateElement::String {
776
                            value: "id".to_string(),
777
                            source: "id".to_source(),
778
                        }],
779
                        SourceInfo::new(Pos::new(1, 1), Pos::new(1, 1))
780
                    ),
781
                    space1: String::new(),
782
                    space2: " ".to_string(),
783
                    value: JsonValue::Number("123".to_string()),
784
                    space3: " ".to_string(),
785
                }],
786
            }
787
            .to_source()
788
            .as_str()
789
        );
790
        assert_eq!("null", JsonValue::Null.to_source().as_str());
791

            
792
        assert_eq!(
793
            "{{name}}",
794
            TemplateElement::Placeholder(Placeholder {
795
                space0: Whitespace {
796
                    value: String::new(),
797
                    source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 1)),
798
                },
799
                expr: Expr {
800
                    kind: ExprKind::Variable(Variable {
801
                        name: "name".to_string(),
802
                        source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 1)),
803
                    }),
804
                    source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 1)),
805
                },
806
                space1: Whitespace {
807
                    value: String::new(),
808
                    source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 1)),
809
                },
810
            })
811
            .to_source()
812
            .as_str(),
813
        );
814

            
815
        assert_eq!(
816
            "{{name}}",
817
            Template::new(
818
                None,
819
                vec![TemplateElement::Placeholder(Placeholder {
820
                    space0: Whitespace {
821
                        value: String::new(),
822
                        source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 1)),
823
                    },
824
                    expr: Expr {
825
                        kind: ExprKind::Variable(Variable {
826
                            name: "name".to_string(),
827
                            source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 1)),
828
                        }),
829
                        source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 1)),
830
                    },
831
                    space1: Whitespace {
832
                        value: String::new(),
833
                        source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 1)),
834
                    },
835
                })],
836
                SourceInfo::new(Pos::new(1, 1), Pos::new(1, 1))
837
            )
838
            .to_source()
839
            .as_str(),
840
        );
841
    }
842
}