1
/*
2
 * Hurl (https://hurl.dev)
3
 * Copyright (C) 2025 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 crate::ast::primitive::Placeholder;
19
use crate::ast::Template;
20
use crate::typing::{SourceString, ToSource};
21

            
22
/// This the AST for the JSON used within Hurl (for instance in [implicit JSON body request](https://hurl.dev/docs/request.html#json-body)).
23
///
24
/// # Example
25
///
26
/// ```hurl
27
/// POST https://example.org/api/cats
28
/// {
29
///     "id": 42,
30
///     "lives": {{lives_count}},
31
///     "name": "{{name}}"
32
/// }
33
/// ```
34
///
35
/// It is a superset of the standard JSON spec. Strings have been replaced by Hurl [`Placeholder`].
36
#[derive(Clone, Debug, PartialEq, Eq)]
37
pub enum JsonValue {
38
    Placeholder(Placeholder),
39
    Number(String),
40
    String(Template),
41
    Boolean(bool),
42
    List {
43
        space0: String,
44
        elements: Vec<JsonListElement>,
45
    },
46
    Object {
47
        space0: String,
48
        elements: Vec<JsonObjectElement>,
49
    },
50
    Null,
51
}
52

            
53
impl ToSource for JsonValue {
54
885
    fn to_source(&self) -> SourceString {
55
885
        match self {
56
10
            JsonValue::Placeholder(expr) => format!("{{{{{expr}}}}}").to_source(),
57
300
            JsonValue::Number(s) => s.to_source(),
58
245
            JsonValue::String(template) => template.to_source(),
59
15
            JsonValue::Boolean(value) => {
60
15
                if *value {
61
                    "true".to_source()
62
                } else {
63
15
                    "false".to_source()
64
                }
65
            }
66
110
            JsonValue::List { space0, elements } => {
67
110
                let elements = elements
68
110
                    .iter()
69
352
                    .map(|e| e.to_source())
70
110
                    .collect::<Vec<SourceString>>();
71
110
                format!("[{}{}]", space0, elements.join(",")).to_source()
72
            }
73
190
            JsonValue::Object { space0, elements } => {
74
190
                let elements = elements
75
190
                    .iter()
76
498
                    .map(|e| e.to_source())
77
190
                    .collect::<Vec<SourceString>>();
78
190
                format!("{{{}{}}}", space0, elements.join(",")).to_source()
79
            }
80
15
            JsonValue::Null => "null".to_source(),
81
        }
82
    }
83
}
84

            
85
#[derive(Clone, Debug, PartialEq, Eq)]
86
pub struct JsonListElement {
87
    pub space0: String,
88
    pub value: JsonValue,
89
    pub space1: String,
90
}
91

            
92
impl ToSource for JsonListElement {
93
330
    fn to_source(&self) -> SourceString {
94
330
        let mut s = SourceString::new();
95
330
        s.push_str(self.space0.as_str());
96
330
        s.push_str(self.value.to_source().as_str());
97
330
        s.push_str(self.space1.as_str());
98
330
        s
99
    }
100
}
101

            
102
#[derive(Clone, Debug, PartialEq, Eq)]
103
pub struct JsonObjectElement {
104
    pub space0: String,
105
    pub name: Template,
106
    pub space1: String,
107
    pub space2: String,
108
    pub value: JsonValue,
109
    pub space3: String,
110
}
111

            
112
impl ToSource for JsonObjectElement {
113
460
    fn to_source(&self) -> SourceString {
114
460
        let mut s = SourceString::new();
115
460
        s.push_str(self.space0.as_str());
116
460
        s.push_str(self.name.to_source().as_str());
117
460
        s.push_str(self.space1.as_str());
118
460
        s.push(':');
119
460
        s.push_str(self.space2.as_str());
120
460
        s.push_str(self.value.to_source().as_str());
121
460
        s.push_str(self.space3.as_str());
122
460
        s
123
    }
124
}