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
685
    fn to_source(&self) -> SourceString {
55
685
        match self {
56
5
            JsonValue::Placeholder(expr) => format!("{{{{{expr}}}}}").to_source(),
57
225
            JsonValue::Number(s) => s.to_source(),
58
200
            JsonValue::String(template) => template.to_source(),
59
10
            JsonValue::Boolean(value) => {
60
10
                if *value {
61
                    "true".to_source()
62
                } else {
63
10
                    "false".to_source()
64
                }
65
            }
66
85
            JsonValue::List { space0, elements } => {
67
85
                let elements = elements
68
85
                    .iter()
69
262
                    .map(|e| e.to_source())
70
85
                    .collect::<Vec<SourceString>>();
71
85
                format!("[{}{}]", space0, elements.join(",")).to_source()
72
            }
73
150
            JsonValue::Object { space0, elements } => {
74
150
                let elements = elements
75
150
                    .iter()
76
400
                    .map(|e| e.to_source())
77
150
                    .collect::<Vec<SourceString>>();
78
150
                format!("{{{}{}}}", space0, elements.join(",")).to_source()
79
            }
80
10
            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
245
    fn to_source(&self) -> SourceString {
94
245
        let mut s = SourceString::new();
95
245
        s.push_str(self.space0.as_str());
96
245
        s.push_str(self.value.to_source().as_str());
97
245
        s.push_str(self.space1.as_str());
98
245
        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
370
    fn to_source(&self) -> SourceString {
114
370
        let mut s = SourceString::new();
115
370
        s.push_str(self.space0.as_str());
116
370
        s.push_str(self.name.to_source().as_str());
117
370
        s.push_str(self.space1.as_str());
118
370
        s.push(':');
119
370
        s.push_str(self.space2.as_str());
120
370
        s.push_str(self.value.to_source().as_str());
121
370
        s.push_str(self.space3.as_str());
122
370
        s
123
    }
124
}