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::option::EntryOption;
21
use crate::ast::primitive::{
22
    Base64, File, Hex, KeyValue, LineTerminator, MultilineString, Number, Placeholder, Regex,
23
    SourceInfo, Template, Whitespace,
24
};
25
use crate::ast::Filter;
26

            
27
#[derive(Clone, Debug, PartialEq, Eq)]
28
pub struct Section {
29
    pub line_terminators: Vec<LineTerminator>,
30
    pub space0: Whitespace,
31
    pub line_terminator0: LineTerminator,
32
    pub value: SectionValue,
33
    pub source_info: SourceInfo,
34
}
35

            
36
impl Section {
37
    /// Returns the Hurl string identifier of this section.
38
6280
    pub fn identifier(&self) -> &'static str {
39
6280
        match self.value {
40
715
            SectionValue::Asserts(_) => "Asserts",
41
260
            SectionValue::QueryParams(_, true) => "Query",
42
495
            SectionValue::QueryParams(_, false) => "QueryStringParams",
43
170
            SectionValue::BasicAuth(_) => "BasicAuth",
44
75
            SectionValue::FormParams(_, true) => "Form",
45
175
            SectionValue::FormParams(_, false) => "FormParams",
46
190
            SectionValue::Cookies(_) => "Cookies",
47
125
            SectionValue::Captures(_) => "Captures",
48
75
            SectionValue::MultipartFormData(_, true) => "Multipart",
49
240
            SectionValue::MultipartFormData(_, false) => "MultipartFormData",
50
3760
            SectionValue::Options(_) => "Options",
51
        }
52
    }
53
}
54

            
55
#[derive(Clone, Debug, PartialEq, Eq)]
56
#[allow(clippy::large_enum_variant)]
57
pub enum SectionValue {
58
    QueryParams(Vec<KeyValue>, bool), // boolean param indicates if we use the short syntax
59
    BasicAuth(Option<KeyValue>),      // boolean param indicates if we use the short syntax
60
    FormParams(Vec<KeyValue>, bool),
61
    MultipartFormData(Vec<MultipartParam>, bool), // boolean param indicates if we use the short syntax
62
    Cookies(Vec<Cookie>),
63
    Captures(Vec<Capture>),
64
    Asserts(Vec<Assert>),
65
    Options(Vec<EntryOption>),
66
}
67

            
68
#[derive(Clone, Debug, PartialEq, Eq)]
69
pub struct Cookie {
70
    pub line_terminators: Vec<LineTerminator>,
71
    pub space0: Whitespace,
72
    pub name: Template,
73
    pub space1: Whitespace,
74
    pub space2: Whitespace,
75
    pub value: Template,
76
    pub line_terminator0: LineTerminator,
77
}
78

            
79
#[allow(clippy::large_enum_variant)]
80
#[derive(Clone, Debug, PartialEq, Eq)]
81
pub enum MultipartParam {
82
    Param(KeyValue),
83
    FileParam(FileParam),
84
}
85

            
86
#[derive(Clone, Debug, PartialEq, Eq)]
87
pub struct FileParam {
88
    pub line_terminators: Vec<LineTerminator>,
89
    pub space0: Whitespace,
90
    pub key: Template,
91
    pub space1: Whitespace,
92
    pub space2: Whitespace,
93
    pub value: FileValue,
94
    pub line_terminator0: LineTerminator,
95
}
96

            
97
#[derive(Clone, Debug, PartialEq, Eq)]
98
pub struct FileValue {
99
    pub space0: Whitespace,
100
    pub filename: Template,
101
    pub space1: Whitespace,
102
    pub space2: Whitespace,
103
    pub content_type: Option<Template>,
104
}
105

            
106
#[derive(Clone, Debug, PartialEq, Eq)]
107
pub struct Capture {
108
    pub line_terminators: Vec<LineTerminator>,
109
    pub space0: Whitespace,
110
    pub name: Template,
111
    pub space1: Whitespace,
112
    pub space2: Whitespace,
113
    pub query: Query,
114
    pub filters: Vec<(Whitespace, Filter)>,
115
    pub space3: Whitespace,
116
    pub redact: bool,
117
    pub line_terminator0: LineTerminator,
118
}
119

            
120
#[derive(Clone, Debug, PartialEq, Eq)]
121
pub struct Assert {
122
    pub line_terminators: Vec<LineTerminator>,
123
    pub space0: Whitespace,
124
    pub query: Query,
125
    pub filters: Vec<(Whitespace, Filter)>,
126
    pub space1: Whitespace,
127
    pub predicate: Predicate,
128
    pub line_terminator0: LineTerminator,
129
}
130

            
131
#[derive(Clone, Debug, PartialEq, Eq)]
132
pub struct Query {
133
    pub source_info: SourceInfo,
134
    pub value: QueryValue,
135
}
136

            
137
#[derive(Clone, Debug, PartialEq, Eq)]
138
#[allow(clippy::large_enum_variant)]
139
pub enum QueryValue {
140
    Status,
141
    Version,
142
    Url,
143
    Header {
144
        space0: Whitespace,
145
        name: Template,
146
    },
147
    Cookie {
148
        space0: Whitespace,
149
        expr: CookiePath,
150
    },
151
    Body,
152
    Xpath {
153
        space0: Whitespace,
154
        expr: Template,
155
    },
156
    Jsonpath {
157
        space0: Whitespace,
158
        expr: Template,
159
    },
160
    Regex {
161
        space0: Whitespace,
162
        value: RegexValue,
163
    },
164
    Variable {
165
        space0: Whitespace,
166
        name: Template,
167
    },
168
    Duration,
169
    Bytes,
170
    Sha256,
171
    Md5,
172
    Certificate {
173
        space0: Whitespace,
174
        attribute_name: CertificateAttributeName,
175
    },
176
    Ip,
177
    Redirects,
178
}
179

            
180
impl QueryValue {
181
    /// Returns the Hurl string identifier of this query type.
182
4525
    pub fn identifier(&self) -> &'static str {
183
4525
        match self {
184
40
            QueryValue::Status => "status",
185
30
            QueryValue::Version => "version",
186
60
            QueryValue::Url => "url",
187
340
            QueryValue::Header { .. } => "header",
188
95
            QueryValue::Cookie { .. } => "cookie",
189
365
            QueryValue::Body => "body",
190
155
            QueryValue::Xpath { .. } => "xpath",
191
2520
            QueryValue::Jsonpath { .. } => "jsonpath",
192
40
            QueryValue::Regex { .. } => "regex",
193
255
            QueryValue::Variable { .. } => "variable",
194
25
            QueryValue::Duration => "duration",
195
305
            QueryValue::Bytes => "bytes",
196
60
            QueryValue::Sha256 => "sha256",
197
55
            QueryValue::Md5 => "md5",
198
150
            QueryValue::Certificate { .. } => "certificate",
199
30
            QueryValue::Ip => "ip",
200
            QueryValue::Redirects => "redirects",
201
        }
202
    }
203
}
204

            
205
#[derive(Clone, Debug, PartialEq, Eq)]
206
pub enum RegexValue {
207
    Template(Template),
208
    Regex(Regex),
209
}
210

            
211
#[derive(Clone, Debug, PartialEq, Eq)]
212
pub struct CookiePath {
213
    pub name: Template,
214
    pub attribute: Option<CookieAttribute>,
215
}
216

            
217
impl fmt::Display for CookiePath {
218
10
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
219
10
        let mut buf = self.name.to_string();
220
10
        if let Some(attribute) = &self.attribute {
221
5
            let s = format!("[{}]", attribute.identifier());
222
5
            buf.push_str(s.as_str());
223
        }
224
10
        write!(f, "{buf}")
225
    }
226
}
227

            
228
#[derive(Clone, Debug, PartialEq, Eq)]
229
pub struct CookieAttribute {
230
    pub space0: Whitespace,
231
    pub name: CookieAttributeName,
232
    pub space1: Whitespace,
233
}
234

            
235
impl CookieAttribute {
236
5
    fn identifier(&self) -> &'static str {
237
5
        match self.name {
238
            CookieAttributeName::MaxAge(_) => "Max-Age",
239
            CookieAttributeName::Value(_) => "Value",
240
5
            CookieAttributeName::Expires(_) => "Expires",
241
            CookieAttributeName::Domain(_) => "Domain",
242
            CookieAttributeName::Path(_) => "Path",
243
            CookieAttributeName::Secure(_) => "Secure",
244
            CookieAttributeName::HttpOnly(_) => "HttpOnly",
245
            CookieAttributeName::SameSite(_) => "SameSite",
246
        }
247
    }
248
}
249

            
250
#[derive(Clone, Debug, PartialEq, Eq)]
251
pub enum CookieAttributeName {
252
    Value(String),
253
    Expires(String),
254
    MaxAge(String),
255
    Domain(String),
256
    Path(String),
257
    Secure(String),
258
    HttpOnly(String),
259
    SameSite(String),
260
}
261

            
262
impl CookieAttributeName {
263
65
    pub fn value(&self) -> String {
264
65
        match self {
265
            CookieAttributeName::Value(value)
266
15
            | CookieAttributeName::Expires(value)
267
5
            | CookieAttributeName::MaxAge(value)
268
5
            | CookieAttributeName::Domain(value)
269
5
            | CookieAttributeName::Path(value)
270
30
            | CookieAttributeName::Secure(value)
271
5
            | CookieAttributeName::HttpOnly(value)
272
65
            | CookieAttributeName::SameSite(value) => value.to_string(),
273
        }
274
    }
275
}
276

            
277
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
278
pub enum CertificateAttributeName {
279
    Subject,
280
    Issuer,
281
    StartDate,
282
    ExpireDate,
283
    SerialNumber,
284
}
285

            
286
impl CertificateAttributeName {
287
    /// Returns the Hurl string identifier of this certificate attribute name.
288
150
    pub fn identifier(&self) -> &'static str {
289
150
        match self {
290
15
            CertificateAttributeName::Subject => "Subject",
291
15
            CertificateAttributeName::Issuer => "Issuer",
292
45
            CertificateAttributeName::StartDate => "Start-Date",
293
60
            CertificateAttributeName::ExpireDate => "Expire-Date",
294
15
            CertificateAttributeName::SerialNumber => "Serial-Number",
295
        }
296
    }
297
}
298

            
299
#[derive(Clone, Debug, PartialEq, Eq)]
300
pub struct Predicate {
301
    pub not: bool,
302
    pub space0: Whitespace,
303
    pub predicate_func: PredicateFunc,
304
}
305

            
306
#[derive(Clone, Debug, PartialEq, Eq)]
307
pub struct Not {
308
    pub value: bool,
309
    pub space0: Whitespace,
310
}
311

            
312
#[derive(Clone, Debug, PartialEq, Eq)]
313
pub struct PredicateFunc {
314
    pub source_info: SourceInfo,
315
    pub value: PredicateFuncValue,
316
}
317

            
318
#[derive(Clone, Debug, PartialEq, Eq)]
319
#[allow(clippy::large_enum_variant)]
320
pub enum PredicateValue {
321
    Base64(Base64),
322
    Bool(bool),
323
    File(File),
324
    Hex(Hex),
325
    MultilineString(MultilineString),
326
    Null,
327
    Number(Number),
328
    Placeholder(Placeholder),
329
    Regex(Regex),
330
    String(Template),
331
}
332

            
333
#[derive(Clone, Debug, PartialEq, Eq)]
334
#[allow(clippy::large_enum_variant)]
335
pub enum PredicateFuncValue {
336
    Equal {
337
        space0: Whitespace,
338
        value: PredicateValue,
339
    },
340
    NotEqual {
341
        space0: Whitespace,
342
        value: PredicateValue,
343
    },
344
    GreaterThan {
345
        space0: Whitespace,
346
        value: PredicateValue,
347
    },
348
    GreaterThanOrEqual {
349
        space0: Whitespace,
350
        value: PredicateValue,
351
    },
352
    LessThan {
353
        space0: Whitespace,
354
        value: PredicateValue,
355
    },
356
    LessThanOrEqual {
357
        space0: Whitespace,
358
        value: PredicateValue,
359
    },
360
    StartWith {
361
        space0: Whitespace,
362
        value: PredicateValue,
363
    },
364
    EndWith {
365
        space0: Whitespace,
366
        value: PredicateValue,
367
    },
368
    Contain {
369
        space0: Whitespace,
370
        value: PredicateValue,
371
    },
372
    Include {
373
        space0: Whitespace,
374
        value: PredicateValue,
375
    },
376
    Match {
377
        space0: Whitespace,
378
        value: PredicateValue,
379
    },
380
    IsInteger,
381
    IsFloat,
382
    IsBoolean,
383
    IsString,
384
    IsCollection,
385
    IsDate,
386
    IsIsoDate,
387
    Exist,
388
    IsEmpty,
389
    IsNumber,
390
    IsIpv4,
391
    IsIpv6,
392
}
393

            
394
impl PredicateFuncValue {
395
    /// Returns the Hurl string identifier of this predicate.
396
4195
    pub fn identifier(&self) -> &'static str {
397
4195
        match self {
398
2660
            PredicateFuncValue::Equal { .. } => "==",
399
90
            PredicateFuncValue::NotEqual { .. } => "!=",
400
105
            PredicateFuncValue::GreaterThan { .. } => ">",
401
30
            PredicateFuncValue::GreaterThanOrEqual { .. } => ">=",
402
90
            PredicateFuncValue::LessThan { .. } => "<",
403
40
            PredicateFuncValue::LessThanOrEqual { .. } => "<=",
404
165
            PredicateFuncValue::StartWith { .. } => "startsWith",
405
60
            PredicateFuncValue::EndWith { .. } => "endsWith",
406
165
            PredicateFuncValue::Contain { .. } => "contains",
407
15
            PredicateFuncValue::Include { .. } => "includes",
408
115
            PredicateFuncValue::Match { .. } => "matches",
409
25
            PredicateFuncValue::IsInteger => "isInteger",
410
30
            PredicateFuncValue::IsFloat => "isFloat",
411
25
            PredicateFuncValue::IsBoolean => "isBoolean",
412
25
            PredicateFuncValue::IsString => "isString",
413
50
            PredicateFuncValue::IsCollection => "isCollection",
414
50
            PredicateFuncValue::IsDate => "isDate",
415
50
            PredicateFuncValue::IsIsoDate => "isIsoDate",
416
225
            PredicateFuncValue::Exist => "exists",
417
60
            PredicateFuncValue::IsEmpty => "isEmpty",
418
30
            PredicateFuncValue::IsNumber => "isNumber",
419
45
            PredicateFuncValue::IsIpv4 => "isIpv4",
420
45
            PredicateFuncValue::IsIpv6 => "isIpv6",
421
        }
422
    }
423
}
424

            
425
#[cfg(test)]
426
mod tests {
427
    use super::*;
428
    use crate::ast::primitive::{SourceInfo, Template, TemplateElement};
429
    use crate::reader::Pos;
430
    use crate::typing::ToSource;
431

            
432
    fn whitespace() -> Whitespace {
433
        Whitespace {
434
            value: String::new(),
435
            source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
436
        }
437
    }
438

            
439
    #[test]
440
    fn test_cookie_path() {
441
        assert_eq!(
442
            CookiePath {
443
                name: Template {
444
                    delimiter: None,
445
                    elements: vec![TemplateElement::String {
446
                        value: "LSID".to_string(),
447
                        source: "unused".to_source(),
448
                    }],
449
                    source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
450
                },
451
                attribute: Some(CookieAttribute {
452
                    space0: whitespace(),
453
                    name: CookieAttributeName::MaxAge("Max-Age".to_string()),
454
                    space1: whitespace(),
455
                }),
456
            }
457
            .to_string(),
458
            "LSID[Max-Age]".to_string()
459
        );
460
    }
461
}