1
/*
2
 * Hurl (https://hurl.dev)
3
 * Copyright (C) 2026 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::types::{SourceString, ToSource};
21

            
22
use super::option::EntryOption;
23
use super::primitive::{
24
    Bytes, KeyValue, LineTerminator, Placeholder, SourceInfo, Template, Whitespace, I64,
25
};
26
use super::section::{Assert, Capture, Cookie, MultipartParam, RegexValue, Section, SectionValue};
27

            
28
/// Represents Hurl AST root node.
29
#[derive(Clone, Debug, PartialEq, Eq)]
30
pub struct HurlFile {
31
    pub entries: Vec<Entry>,
32
    pub line_terminators: Vec<LineTerminator>,
33
}
34

            
35
/// Represents an entry; a request AST specification to be run and an optional response AST
36
/// specification to be checked.
37
#[derive(Clone, Debug, PartialEq, Eq)]
38
pub struct Entry {
39
    pub request: Request,
40
    pub response: Option<Response>,
41
}
42

            
43
impl Entry {
44
    /// Returns the source information for this entry.
45
15830
    pub fn source_info(&self) -> SourceInfo {
46
15830
        self.request.space0.source_info
47
    }
48
}
49

            
50
#[derive(Clone, Debug, PartialEq, Eq)]
51
pub struct Request {
52
    pub line_terminators: Vec<LineTerminator>,
53
    pub space0: Whitespace,
54
    pub method: Method,
55
    pub space1: Whitespace,
56
    pub url: Template,
57
    pub line_terminator0: LineTerminator,
58
    pub headers: Vec<KeyValue>,
59
    pub sections: Vec<Section>,
60
    pub body: Option<Body>,
61
    pub source_info: SourceInfo,
62
}
63

            
64
impl Request {
65
    /// Returns the query strings params for this request.
66
    ///
67
    /// See <https://developer.mozilla.org/en-US/docs/Web/API/URL/searchParams>.
68
13160
    pub fn querystring_params(&self) -> &[KeyValue] {
69
13160
        for section in &self.sections {
70
2670
            if let SectionValue::QueryParams(params, _) = &section.value {
71
275
                return params;
72
            }
73
        }
74
12885
        &[]
75
    }
76

            
77
    /// Returns the form params for this request.
78
    ///
79
    /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST#url-encoded_form_submission>.
80
13120
    pub fn form_params(&self) -> &[KeyValue] {
81
13120
        for section in &self.sections {
82
2670
            if let SectionValue::FormParams(params, _) = &section.value {
83
110
                return params;
84
            }
85
        }
86
13010
        &[]
87
    }
88

            
89
    /// Returns the multipart form data for this request.
90
    ///
91
    /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST#multipart_form_submission>.
92
13040
    pub fn multipart_form_data(&self) -> &[MultipartParam] {
93
13040
        for section in &self.sections {
94
2690
            if let SectionValue::MultipartFormData(params, _) = &section.value {
95
115
                return params;
96
            }
97
        }
98
12925
        &[]
99
    }
100

            
101
    /// Returns the list of cookies on this request.
102
    ///
103
    /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cookie>.
104
13120
    pub fn cookies(&self) -> &[Cookie] {
105
13120
        for section in &self.sections {
106
2725
            if let SectionValue::Cookies(cookies) = &section.value {
107
225
                return cookies;
108
            }
109
        }
110
12895
        &[]
111
    }
112

            
113
    /// Returns the basic authentication on this request.
114
    ///
115
    /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication>.
116
12830
    pub fn basic_auth(&self) -> Option<&KeyValue> {
117
12830
        for section in &self.sections {
118
2610
            if let SectionValue::BasicAuth(kv) = &section.value {
119
50
                return kv.as_ref();
120
            }
121
        }
122
12780
        None
123
    }
124

            
125
    /// Returns the options specific for this request.
126
27820
    pub fn options(&self) -> &[EntryOption] {
127
27820
        for section in &self.sections {
128
6770
            if let SectionValue::Options(options) = &section.value {
129
5825
                return options;
130
            }
131
        }
132
21995
        &[]
133
    }
134
}
135

            
136
#[derive(Clone, Debug, PartialEq, Eq)]
137
pub struct Response {
138
    pub line_terminators: Vec<LineTerminator>,
139
    pub version: Version,
140
    pub space0: Whitespace,
141
    pub status: Status,
142
    pub space1: Whitespace,
143
    pub line_terminator0: LineTerminator,
144
    pub headers: Vec<KeyValue>,
145
    pub sections: Vec<Section>,
146
    pub body: Option<Body>,
147
    pub source_info: SourceInfo,
148
}
149

            
150
impl Response {
151
    /// Returns the captures list of this spec response.
152
37495
    pub fn captures(&self) -> &[Capture] {
153
37495
        for section in self.sections.iter() {
154
23640
            if let SectionValue::Captures(captures) = &section.value {
155
1455
                return captures;
156
            }
157
        }
158
36040
        &[]
159
    }
160

            
161
    /// Returns the asserts list of this spec response.
162
48765
    pub fn asserts(&self) -> &[Assert] {
163
48765
        for section in self.sections.iter() {
164
32035
            if let SectionValue::Asserts(asserts) = &section.value {
165
30285
                return asserts;
166
            }
167
        }
168
18480
        &[]
169
    }
170
}
171

            
172
#[derive(Clone, Debug, PartialEq, Eq)]
173
pub struct Method(String);
174

            
175
impl Method {
176
    /// Creates a new AST element method/
177
15995
    pub fn new(method: &str) -> Method {
178
15995
        Method(method.to_string())
179
    }
180
}
181

            
182
impl fmt::Display for Method {
183
14860
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
184
14860
        write!(f, "{}", self.0)
185
    }
186
}
187

            
188
impl ToSource for Method {
189
910
    fn to_source(&self) -> SourceString {
190
910
        self.0.to_source()
191
    }
192
}
193

            
194
#[derive(Clone, Debug, PartialEq, Eq)]
195
pub struct Version {
196
    pub value: VersionValue,
197
    pub source_info: SourceInfo,
198
}
199

            
200
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
201
pub enum VersionValue {
202
    Version1,
203
    Version11,
204
    Version2,
205
    Version3,
206
    VersionAny,
207
}
208

            
209
impl fmt::Display for VersionValue {
210
14150
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
211
14150
        let s = match self {
212
45
            VersionValue::Version1 => "HTTP/1.0",
213
55
            VersionValue::Version11 => "HTTP/1.1",
214
60
            VersionValue::Version2 => "HTTP/2",
215
25
            VersionValue::Version3 => "HTTP/3",
216
13965
            VersionValue::VersionAny => "HTTP",
217
        };
218
14150
        write!(f, "{s}")
219
    }
220
}
221

            
222
impl ToSource for VersionValue {
223
190
    fn to_source(&self) -> SourceString {
224
190
        self.to_string().to_source()
225
    }
226
}
227

            
228
#[derive(Clone, Debug, PartialEq, Eq)]
229
pub struct Status {
230
    pub value: StatusValue,
231
    pub source_info: SourceInfo,
232
}
233

            
234
#[derive(Clone, Debug, PartialEq, Eq)]
235
pub enum StatusValue {
236
    Any,
237
    Specific(u64),
238
}
239

            
240
impl fmt::Display for StatusValue {
241
1825
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
242
1825
        match self {
243
40
            StatusValue::Any => write!(f, "*"),
244
1785
            StatusValue::Specific(v) => write!(f, "{v}"),
245
        }
246
    }
247
}
248

            
249
impl ToSource for StatusValue {
250
190
    fn to_source(&self) -> SourceString {
251
190
        self.to_string().to_source()
252
    }
253
}
254

            
255
#[derive(Clone, Debug, PartialEq, Eq)]
256
pub struct Body {
257
    pub line_terminators: Vec<LineTerminator>,
258
    pub space0: Whitespace,
259
    pub value: Bytes,
260
    pub line_terminator0: LineTerminator,
261
}
262

            
263
/// Check that variable name is not reserved
264
/// (would conflicts with an existing function)
265
885
pub fn is_variable_reserved(name: &str) -> bool {
266
885
    ["getEnv", "newDate", "newUuid"].contains(&name)
267
}
268

            
269
#[derive(Clone, Debug, PartialEq, Eq)]
270
pub struct Filter {
271
    pub source_info: SourceInfo,
272
    pub value: FilterValue,
273
}
274

            
275
#[derive(Clone, Debug, PartialEq, Eq)]
276
pub enum FilterValue {
277
    Base64Decode,
278
    Base64Encode,
279
    Base64UrlSafeDecode,
280
    Base64UrlSafeEncode,
281
    CharsetDecode {
282
        space0: Whitespace,
283
        encoding: Template,
284
    },
285
    Count,
286
    DaysAfterNow,
287
    DaysBeforeNow,
288
    Decode {
289
        space0: Whitespace,
290
        encoding: Template,
291
    },
292
    First,
293
    Format {
294
        space0: Whitespace,
295
        fmt: Template,
296
    },
297
    DateFormat {
298
        space0: Whitespace,
299
        fmt: Template,
300
    },
301
    HtmlEscape,
302
    HtmlUnescape,
303
    JsonPath {
304
        space0: Whitespace,
305
        expr: Template,
306
    },
307
    Last,
308
    Location,
309
    Nth {
310
        space0: Whitespace,
311
        n: IntegerValue,
312
    },
313
    Regex {
314
        space0: Whitespace,
315
        value: RegexValue,
316
    },
317
    Replace {
318
        space0: Whitespace,
319
        old_value: Template,
320
        space1: Whitespace,
321
        new_value: Template,
322
    },
323
    ReplaceRegex {
324
        space0: Whitespace,
325
        pattern: RegexValue,
326
        space1: Whitespace,
327
        new_value: Template,
328
    },
329
    Split {
330
        space0: Whitespace,
331
        sep: Template,
332
    },
333
    ToDate {
334
        space0: Whitespace,
335
        fmt: Template,
336
    },
337
    ToFloat,
338
    ToHex,
339
    ToInt,
340
    ToString,
341
    UrlDecode,
342
    UrlEncode,
343
    UrlQueryParam {
344
        space0: Whitespace,
345
        param: Template,
346
    },
347
    Utf8Decode,
348
    Utf8Encode,
349
    XPath {
350
        space0: Whitespace,
351
        expr: Template,
352
    },
353
}
354

            
355
impl FilterValue {
356
    /// Returns the Hurl identifier for this filter type.
357
2260
    pub fn identifier(&self) -> &'static str {
358
2260
        match self {
359
75
            FilterValue::Base64Decode => "base64Decode",
360
35
            FilterValue::Base64Encode => "base64Encode",
361
50
            FilterValue::Base64UrlSafeDecode => "base64UrlSafeDecode",
362
35
            FilterValue::Base64UrlSafeEncode => "base64UrlSafeEncode",
363
75
            FilterValue::CharsetDecode { .. } => "charsetDecode",
364
450
            FilterValue::Count => "count",
365
25
            FilterValue::DaysAfterNow => "daysAfterNow",
366
50
            FilterValue::DaysBeforeNow => "daysBeforeNow",
367
20
            FilterValue::Decode { .. } => "decode",
368
40
            FilterValue::First => "first",
369
50
            FilterValue::Format { .. } => "format",
370
100
            FilterValue::DateFormat { .. } => "dateFormat",
371
35
            FilterValue::HtmlEscape => "htmlEscape",
372
45
            FilterValue::HtmlUnescape => "htmlUnescape",
373
80
            FilterValue::JsonPath { .. } => "jsonpath",
374
40
            FilterValue::Last => "last",
375
80
            FilterValue::Location => "location",
376
175
            FilterValue::Nth { .. } => "nth",
377
60
            FilterValue::Regex { .. } => "regex",
378
115
            FilterValue::Replace { .. } => "replace",
379
45
            FilterValue::ReplaceRegex { .. } => "replaceRegex",
380
40
            FilterValue::Split { .. } => "split",
381
120
            FilterValue::ToDate { .. } => "toDate",
382
60
            FilterValue::ToFloat => "toFloat",
383
65
            FilterValue::ToHex => "toHex",
384
60
            FilterValue::ToInt => "toInt",
385
30
            FilterValue::ToString => "toString",
386
35
            FilterValue::UrlDecode => "urlDecode",
387
35
            FilterValue::UrlEncode => "urlEncode",
388
45
            FilterValue::UrlQueryParam { .. } => "urlQueryParam",
389
25
            FilterValue::Utf8Decode => "utf8Decode",
390
25
            FilterValue::Utf8Encode => "utf8Encode",
391
40
            FilterValue::XPath { .. } => "xpath",
392
        }
393
    }
394
}
395

            
396
#[derive(Clone, Debug, PartialEq, Eq)]
397
pub enum IntegerValue {
398
    Literal(I64),
399
    Placeholder(Placeholder),
400
}
401

            
402
impl fmt::Display for IntegerValue {
403
5
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
404
5
        match self {
405
5
            IntegerValue::Literal(v) => write!(f, "{v}"),
406
            IntegerValue::Placeholder(v) => write!(f, "{v}"),
407
        }
408
    }
409
}