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

            
20
use crate::ast::primitive::Placeholder;
21
use crate::ast::Template;
22
use crate::typing::ToSource;
23

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

            
55
#[derive(Clone, Debug, PartialEq, Eq)]
56
pub struct JsonListElement {
57
    pub space0: String,
58
    pub value: JsonValue,
59
    pub space1: String,
60
}
61

            
62
#[derive(Clone, Debug, PartialEq, Eq)]
63
pub struct JsonObjectElement {
64
    pub space0: String,
65
    pub name: Template,
66
    pub space1: String,
67
    pub space2: String,
68
    pub value: JsonValue,
69
    pub space3: String,
70
}
71

            
72
impl fmt::Display for JsonValue {
73
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
74
        let s = match self {
75
            JsonValue::Placeholder(expr) => format!("{{{{{expr}}}}}"),
76
            JsonValue::Number(s) => s.to_string(),
77
            JsonValue::String(template) => format!("\"{template}\""),
78
            JsonValue::Boolean(value) => {
79
                if *value {
80
                    "true".to_string()
81
                } else {
82
                    "false".to_string()
83
                }
84
            }
85
            JsonValue::List { space0, elements } => {
86
                let elements = elements
87
                    .iter()
88
                    .map(|e| e.to_string())
89
                    .collect::<Vec<String>>();
90
                format!("[{}{}]", space0, elements.join(","))
91
            }
92
            JsonValue::Object { space0, elements } => {
93
                let elements = elements
94
                    .iter()
95
                    .map(|e| e.to_string())
96
                    .collect::<Vec<String>>();
97
                format!("{{{}{}}}", space0, elements.join(","))
98
            }
99
            JsonValue::Null => "null".to_string(),
100
        };
101
        write!(f, "{s}")
102
    }
103
}
104

            
105
impl fmt::Display for JsonListElement {
106
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
107
        let mut s = String::new();
108
        s.push_str(self.space0.as_str());
109
        s.push_str(self.value.to_string().as_str());
110
        s.push_str(self.space1.as_str());
111
        write!(f, "{s}")
112
    }
113
}
114

            
115
impl fmt::Display for JsonObjectElement {
116
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
117
        let mut s = String::new();
118
        s.push_str(self.space0.as_str());
119
        s.push('"');
120
        s.push_str(self.name.to_string().as_str());
121
        s.push('"');
122
        s.push_str(self.space1.as_str());
123
        s.push(':');
124
        s.push_str(self.space2.as_str());
125
        s.push_str(self.value.to_string().as_str());
126
        s.push_str(self.space3.as_str());
127
        write!(f, "{s}")
128
    }
129
}
130

            
131
impl JsonValue {
132
680
    pub fn encoded(&self) -> String {
133
680
        match self {
134
5
            JsonValue::Placeholder(expr) => format!("{{{{{expr}}}}}"),
135
225
            JsonValue::Number(s) => s.to_string(),
136
195
            JsonValue::String(template) => template.to_source().to_string(),
137
10
            JsonValue::Boolean(value) => {
138
10
                if *value {
139
                    "true".to_string()
140
                } else {
141
10
                    "false".to_string()
142
                }
143
            }
144
85
            JsonValue::List { space0, elements } => {
145
85
                let elements = elements
146
85
                    .iter()
147
262
                    .map(|e| e.encoded())
148
85
                    .collect::<Vec<String>>();
149
85
                format!("[{}{}]", space0, elements.join(","))
150
            }
151
150
            JsonValue::Object { space0, elements } => {
152
150
                let elements = elements
153
150
                    .iter()
154
395
                    .map(|e| e.encoded())
155
150
                    .collect::<Vec<String>>();
156
150
                format!("{{{}{}}}", space0, elements.join(","))
157
            }
158
10
            JsonValue::Null => "null".to_string(),
159
        }
160
    }
161
}
162

            
163
impl JsonListElement {
164
245
    fn encoded(&self) -> String {
165
245
        let mut s = String::new();
166
245
        s.push_str(self.space0.as_str());
167
245
        s.push_str(self.value.encoded().as_str());
168
245
        s.push_str(self.space1.as_str());
169
245
        s
170
    }
171
}
172

            
173
impl JsonObjectElement {
174
365
    fn encoded(&self) -> String {
175
365
        let mut s = String::new();
176
365
        s.push_str(self.space0.as_str());
177
365
        s.push_str(self.name.to_source().as_str());
178
365
        s.push_str(self.space1.as_str());
179
365
        s.push(':');
180
365
        s.push_str(self.space2.as_str());
181
365
        s.push_str(self.value.encoded().as_str());
182
365
        s.push_str(self.space3.as_str());
183
365
        s
184
    }
185
}