1
/*
2
 * Hurl (https://hurl.dev)
3
 * Copyright (C) 2024 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::{
19
    BooleanOption, CookieAttribute, CookieAttributeName, CookiePath, CountOption, DurationOption,
20
    Expr, ExprKind, Float, Function, Hex, Method, MultilineString, MultilineStringAttribute,
21
    MultilineStringKind, NaturalOption, Number, Placeholder, PredicateFuncValue, Regex, Status,
22
    StatusValue, Template, TemplateElement, Variable, VariableDefinition, VariableValue, Version,
23
    VersionValue,
24
};
25
use core::fmt;
26

            
27
impl fmt::Display for Method {
28
2215
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29
2215
        write!(f, "{}", self.0)
30
    }
31
}
32

            
33
impl fmt::Display for Version {
34
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35
        write!(f, "{}", self.value)
36
    }
37
}
38

            
39
impl fmt::Display for VersionValue {
40
12630
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41
12630
        let s = match self {
42
25
            VersionValue::Version1 => "HTTP/1.0",
43
65
            VersionValue::Version11 => "HTTP/1.1",
44
50
            VersionValue::Version2 => "HTTP/2",
45
20
            VersionValue::Version3 => "HTTP/3",
46
12440
            VersionValue::VersionAny => "HTTP",
47
30
            VersionValue::VersionAnyLegacy => "HTTP/*",
48
        };
49
12630
        write!(f, "{s}")
50
    }
51
}
52

            
53
impl fmt::Display for Status {
54
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55
        write!(f, "{}", self.value)
56
    }
57
}
58

            
59
impl fmt::Display for StatusValue {
60
1325
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61
1325
        match self {
62
25
            StatusValue::Any => write!(f, "*"),
63
1300
            StatusValue::Specific(v) => write!(f, "{v}"),
64
        }
65
    }
66
}
67

            
68
impl fmt::Display for Template {
69
3675
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
70
3675
        let mut buffer = String::new();
71
3720
        for element in self.elements.iter() {
72
3720
            buffer.push_str(element.to_string().as_str());
73
        }
74
3675
        write!(f, "{buffer}")
75
    }
76
}
77

            
78
impl fmt::Display for TemplateElement {
79
3720
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
80
3720
        let s = match self {
81
3545
            TemplateElement::String { value, .. } => value.clone(),
82
175
            TemplateElement::Placeholder(value) => format!("{{{{{value}}}}}"),
83
        };
84
3720
        write!(f, "{s}")
85
    }
86
}
87

            
88
impl fmt::Display for Number {
89
1270
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
90
1270
        match self {
91
400
            Number::Float(value) => write!(f, "{}", value),
92
865
            Number::Integer(value) => write!(f, "{}", value),
93
5
            Number::BigInteger(value) => write!(f, "{}", value),
94
        }
95
    }
96
}
97

            
98
impl fmt::Display for Float {
99
400
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
100
400
        write!(f, "{}", self.encoded)
101
    }
102
}
103

            
104
impl fmt::Display for Placeholder {
105
770
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
106
770
        write!(f, "{}", self.expr)
107
    }
108
}
109

            
110
impl fmt::Display for Expr {
111
770
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
112
770
        write!(f, "{}", self.kind)
113
    }
114
}
115

            
116
impl fmt::Display for ExprKind {
117
770
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
118
770
        match self {
119
750
            ExprKind::Variable(variable) => write!(f, "{}", variable),
120
20
            ExprKind::Function(function) => write!(f, "{}", function),
121
        }
122
    }
123
}
124

            
125
impl fmt::Display for Variable {
126
750
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
127
750
        write!(f, "{}", self.name)
128
    }
129
}
130

            
131
impl fmt::Display for Function {
132
20
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
133
20
        match self {
134
10
            Function::NewDate => write!(f, "newDate"),
135
10
            Function::NewUuid => write!(f, "newUuid"),
136
        }
137
    }
138
}
139

            
140
impl fmt::Display for CookiePath {
141
10
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
142
10
        let mut buf = self.name.to_string();
143
10
        if let Some(attribute) = &self.attribute {
144
5
            let s = format!("[{attribute}]");
145
5
            buf.push_str(s.as_str());
146
        }
147
10
        write!(f, "{buf}")
148
    }
149
}
150

            
151
impl fmt::Display for CookieAttribute {
152
5
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
153
5
        let s = match self.name {
154
            CookieAttributeName::MaxAge(_) => "Max-Age",
155
            CookieAttributeName::Value(_) => "Value",
156
5
            CookieAttributeName::Expires(_) => "Expires",
157
            CookieAttributeName::Domain(_) => "Domain",
158
            CookieAttributeName::Path(_) => "Path",
159
            CookieAttributeName::Secure(_) => "Secure",
160
            CookieAttributeName::HttpOnly(_) => "HttpOnly",
161
            CookieAttributeName::SameSite(_) => "SameSite",
162
        };
163
5
        write!(f, "{s}")
164
    }
165
}
166

            
167
impl fmt::Display for Hex {
168
35
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
169
35
        write!(
170
35
            f,
171
35
            "hex,{}{}{};",
172
35
            self.space0.value, self.encoded, self.space1.value
173
35
        )
174
    }
175
}
176

            
177
impl fmt::Display for Regex {
178
10
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
179
10
        write!(f, "{}", self.inner)
180
    }
181
}
182

            
183
impl fmt::Display for MultilineString {
184
95
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
185
95
        let body = match &self.kind {
186
35
            MultilineStringKind::Text(text)
187
20
            | MultilineStringKind::Json(text)
188
75
            | MultilineStringKind::Xml(text) => text.value.to_string(),
189
20
            MultilineStringKind::GraphQl(graphql) => {
190
20
                let var = match &graphql.variables {
191
20
                    None => String::new(),
192
                    Some(var) => {
193
                        format!(
194
                            "variables{}{}{}",
195
                            var.space.value, var.value, var.whitespace.value
196
                        )
197
                    }
198
                };
199
20
                format!("{}{}", graphql.value, var)
200
            }
201
        };
202
95
        write!(f, "{body}")
203
    }
204
}
205

            
206
impl fmt::Display for MultilineStringAttribute {
207
10
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
208
10
        match self {
209
10
            MultilineStringAttribute::Escape => write!(f, "escape"),
210
            MultilineStringAttribute::NoVariable => write!(f, "novariable"),
211
        }
212
    }
213
}
214

            
215
impl fmt::Display for BooleanOption {
216
760
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
217
760
        match self {
218
755
            BooleanOption::Literal(v) => write!(f, "{}", v),
219
5
            BooleanOption::Placeholder(v) => write!(f, "{}", v),
220
        }
221
    }
222
}
223

            
224
impl fmt::Display for NaturalOption {
225
5
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
226
5
        match self {
227
5
            NaturalOption::Literal(v) => write!(f, "{}", v),
228
            NaturalOption::Placeholder(v) => write!(f, "{}", v),
229
        }
230
    }
231
}
232

            
233
impl fmt::Display for CountOption {
234
160
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
235
160
        match self {
236
150
            CountOption::Literal(v) => write!(f, "{}", v),
237
10
            CountOption::Placeholder(v) => write!(f, "{}", v),
238
        }
239
    }
240
}
241

            
242
impl fmt::Display for DurationOption {
243
70
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
244
70
        match self {
245
65
            DurationOption::Literal(v) => write!(f, "{}", v),
246
5
            DurationOption::Placeholder(v) => write!(f, "{}", v),
247
        }
248
    }
249
}
250

            
251
impl fmt::Display for VariableDefinition {
252
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
253
        write!(f, "{}={}", self.name, self.value)
254
    }
255
}
256

            
257
impl fmt::Display for VariableValue {
258
390
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
259
390
        let s = match self {
260
5
            VariableValue::Null => "null".to_string(),
261
10
            VariableValue::Bool(value) => value.to_string(),
262
135
            VariableValue::Number(n) => n.to_string(),
263
240
            VariableValue::String(s) => s.to_string(),
264
        };
265
390
        write!(f, "{}", s)
266
    }
267
}
268

            
269
impl PredicateFuncValue {
270
3295
    pub fn name(&self) -> String {
271
3295
        match self {
272
2080
            PredicateFuncValue::Equal { operator, .. } => {
273
2080
                if *operator {
274
2080
                    "==".to_string()
275
                } else {
276
                    "equals".to_string()
277
                }
278
            }
279
70
            PredicateFuncValue::NotEqual { operator, .. } => {
280
70
                if *operator {
281
70
                    "!=".to_string()
282
                } else {
283
                    "notEquals".to_string()
284
                }
285
            }
286
85
            PredicateFuncValue::GreaterThan { operator, .. } => {
287
85
                if *operator {
288
85
                    ">".to_string()
289
                } else {
290
                    "greaterThan".to_string()
291
                }
292
            }
293
20
            PredicateFuncValue::GreaterThanOrEqual { operator, .. } => {
294
20
                if *operator {
295
20
                    ">=".to_string()
296
                } else {
297
                    "greaterThanOrEquals".to_string()
298
                }
299
            }
300
70
            PredicateFuncValue::LessThan { operator, .. } => {
301
70
                if *operator {
302
70
                    "<".to_string()
303
                } else {
304
                    "lessThan".to_string()
305
                }
306
            }
307
30
            PredicateFuncValue::LessThanOrEqual { operator, .. } => {
308
30
                if *operator {
309
30
                    "<=".to_string()
310
                } else {
311
                    "lessThanOrEquals".to_string()
312
                }
313
            }
314
135
            PredicateFuncValue::StartWith { .. } => "startsWith".to_string(),
315
45
            PredicateFuncValue::EndWith { .. } => "endsWith".to_string(),
316
75
            PredicateFuncValue::Contain { .. } => "contains".to_string(),
317
85
            PredicateFuncValue::Include { .. } => "includes".to_string(),
318
95
            PredicateFuncValue::Match { .. } => "matches".to_string(),
319
20
            PredicateFuncValue::IsInteger => "isInteger".to_string(),
320
25
            PredicateFuncValue::IsFloat => "isFloat".to_string(),
321
20
            PredicateFuncValue::IsBoolean => "isBoolean".to_string(),
322
20
            PredicateFuncValue::IsString => "isString".to_string(),
323
45
            PredicateFuncValue::IsCollection => "isCollection".to_string(),
324
35
            PredicateFuncValue::IsDate => "isDate".to_string(),
325
45
            PredicateFuncValue::IsIsoDate => "isIsoDate".to_string(),
326
215
            PredicateFuncValue::Exist => "exists".to_string(),
327
55
            PredicateFuncValue::IsEmpty => "isEmpty".to_string(),
328
25
            PredicateFuncValue::IsNumber => "isNumber".to_string(),
329
        }
330
    }
331
}
332

            
333
#[cfg(test)]
334
mod tests {
335
    use super::*;
336
    use crate::ast::{CookieAttributeName, SourceInfo, Whitespace};
337
    use crate::reader::Pos;
338

            
339
    fn whitespace() -> Whitespace {
340
        Whitespace {
341
            value: String::new(),
342
            source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
343
        }
344
    }
345

            
346
    fn variable_placeholder() -> Placeholder {
347
        Placeholder {
348
            space0: whitespace(),
349
            expr: Expr {
350
                kind: ExprKind::Variable(Variable {
351
                    name: "name".to_string(),
352
                    source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
353
                }),
354
                source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
355
            },
356
            space1: whitespace(),
357
        }
358
    }
359

            
360
    fn hello_template() -> Template {
361
        Template {
362
            delimiter: None,
363
            elements: vec![
364
                TemplateElement::String {
365
                    value: "Hello ".to_string(),
366
                    encoded: "Hello ".to_string(),
367
                },
368
                TemplateElement::Placeholder(variable_placeholder()),
369
                TemplateElement::String {
370
                    value: "!".to_string(),
371
                    encoded: "!".to_string(),
372
                },
373
            ],
374
            source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
375
        }
376
    }
377

            
378
    #[test]
379
    fn test_float() {
380
        assert_eq!(
381
            Float {
382
                value: 1.0,
383
                encoded: "1.0".to_string()
384
            }
385
            .to_string(),
386
            "1.0"
387
        );
388
        assert_eq!(
389
            Float {
390
                value: 1.01,
391
                encoded: "1.01".to_string()
392
            }
393
            .to_string(),
394
            "1.01"
395
        );
396
        assert_eq!(
397
            Float {
398
                value: 1.01,
399
                encoded: "1.010".to_string()
400
            }
401
            .to_string(),
402
            "1.010"
403
        );
404
        assert_eq!(
405
            Float {
406
                value: -1.333,
407
                encoded: "-1.333".to_string()
408
            }
409
            .to_string(),
410
            "-1.333"
411
        );
412
    }
413

            
414
    #[test]
415
    fn test_template() {
416
        assert_eq!(hello_template().to_string(), "Hello {{name}}!");
417
    }
418

            
419
    #[test]
420
    fn test_cookie_path() {
421
        assert_eq!(
422
            CookiePath {
423
                name: Template {
424
                    delimiter: None,
425
                    elements: vec![TemplateElement::String {
426
                        value: "LSID".to_string(),
427
                        encoded: "unused".to_string(),
428
                    }],
429
                    source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
430
                },
431
                attribute: Some(CookieAttribute {
432
                    space0: whitespace(),
433
                    name: CookieAttributeName::MaxAge("Max-Age".to_string()),
434
                    space1: whitespace(),
435
                }),
436
            }
437
            .to_string(),
438
            "LSID[Max-Age]".to_string()
439
        );
440
    }
441
}