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
15430
    pub fn source_info(&self) -> SourceInfo {
46
15430
        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
12760
    pub fn querystring_params(&self) -> &[KeyValue] {
69
14935
        for section in &self.sections {
70
2450
            if let SectionValue::QueryParams(params, _) = &section.value {
71
275
                return params;
72
            }
73
        }
74
12485
        &[]
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
12720
    pub fn form_params(&self) -> &[KeyValue] {
81
15060
        for section in &self.sections {
82
2450
            if let SectionValue::FormParams(params, _) = &section.value {
83
110
                return params;
84
            }
85
        }
86
12610
        &[]
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
12640
    pub fn multipart_form_data(&self) -> &[MultipartParam] {
93
14995
        for section in &self.sections {
94
2470
            if let SectionValue::MultipartFormData(params, _) = &section.value {
95
115
                return params;
96
            }
97
        }
98
12525
        &[]
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
12720
    pub fn cookies(&self) -> &[Cookie] {
105
15000
        for section in &self.sections {
106
2505
            if let SectionValue::Cookies(cookies) = &section.value {
107
225
                return cookies;
108
            }
109
        }
110
12495
        &[]
111
    }
112

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

            
125
    /// Returns the options specific for this request.
126
26800
    pub fn options(&self) -> &[EntryOption] {
127
27745
        for section in &self.sections {
128
6110
            if let SectionValue::Options(options) = &section.value {
129
5165
                return options;
130
            }
131
        }
132
21635
        &[]
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
24575
    pub fn captures(&self) -> &[Capture] {
153
24575
        for section in self.sections.iter() {
154
15805
            if let SectionValue::Captures(captures) = &section.value {
155
945
                return captures;
156
            }
157
        }
158
23630
        &[]
159
    }
160

            
161
    /// Returns the asserts list of this spec response.
162
35545
    pub fn asserts(&self) -> &[Assert] {
163
35545
        for section in self.sections.iter() {
164
23930
            if let SectionValue::Asserts(asserts) = &section.value {
165
22700
                return asserts;
166
            }
167
        }
168
12845
        &[]
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
15580
    pub fn new(method: &str) -> Method {
178
15580
        Method(method.to_string())
179
    }
180
}
181

            
182
impl fmt::Display for Method {
183
14460
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
184
14460
        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
13790
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
211
13790
        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
13605
            VersionValue::VersionAny => "HTTP",
217
        };
218
13790
        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
    Count,
282
    DaysAfterNow,
283
    DaysBeforeNow,
284
    Decode {
285
        space0: Whitespace,
286
        encoding: Template,
287
    },
288
    First,
289
    Format {
290
        space0: Whitespace,
291
        fmt: Template,
292
    },
293
    DateFormat {
294
        space0: Whitespace,
295
        fmt: Template,
296
    },
297
    HtmlEscape,
298
    HtmlUnescape,
299
    JsonPath {
300
        space0: Whitespace,
301
        expr: Template,
302
    },
303
    Last,
304
    Location,
305
    Nth {
306
        space0: Whitespace,
307
        n: IntegerValue,
308
    },
309
    Regex {
310
        space0: Whitespace,
311
        value: RegexValue,
312
    },
313
    Replace {
314
        space0: Whitespace,
315
        old_value: Template,
316
        space1: Whitespace,
317
        new_value: Template,
318
    },
319
    ReplaceRegex {
320
        space0: Whitespace,
321
        pattern: RegexValue,
322
        space1: Whitespace,
323
        new_value: Template,
324
    },
325
    Split {
326
        space0: Whitespace,
327
        sep: Template,
328
    },
329
    ToDate {
330
        space0: Whitespace,
331
        fmt: Template,
332
    },
333
    ToFloat,
334
    ToHex,
335
    ToInt,
336
    ToString,
337
    UrlDecode,
338
    UrlEncode,
339
    UrlQueryParam {
340
        space0: Whitespace,
341
        param: Template,
342
    },
343
    Utf8Decode,
344
    Utf8Encode,
345
    XPath {
346
        space0: Whitespace,
347
        expr: Template,
348
    },
349
}
350

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

            
391
#[derive(Clone, Debug, PartialEq, Eq)]
392
pub enum IntegerValue {
393
    Literal(I64),
394
    Placeholder(Placeholder),
395
}
396

            
397
impl fmt::Display for IntegerValue {
398
5
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
399
5
        match self {
400
5
            IntegerValue::Literal(v) => write!(f, "{v}"),
401
            IntegerValue::Placeholder(v) => write!(f, "{v}"),
402
        }
403
    }
404
}