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
    Bytes, KeyValue, LineTerminator, SourceInfo, Template, Whitespace, I64,
23
};
24
use crate::ast::section::{
25
    Assert, Capture, Cookie, MultipartParam, RegexValue, Section, SectionValue,
26
};
27
use crate::ast::Placeholder;
28
use crate::typing::{SourceString, ToSource};
29

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

            
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
15190
    pub fn source_info(&self) -> SourceInfo {
46
15190
        self.request.space0.source_info
47
    }
48

            
49
    /// Returns true if the request or the response uses multilines string attributes
50
12250
    pub fn use_multiline_string_body_with_attributes(&self) -> bool {
51
        if let Some(Body {
52
225
            value: Bytes::MultilineString(multiline),
53
            ..
54
680
        }) = &self.request.body
55
        {
56
225
            if multiline.has_attributes() {
57
15
                return true;
58
            }
59
        }
60
12235
        if let Some(response) = &self.response {
61
            if let Some(Body {
62
175
                value: Bytes::MultilineString(multiline),
63
                ..
64
2000
            }) = &response.body
65
            {
66
175
                if multiline.has_attributes() {
67
10
                    return true;
68
                }
69
            }
70
        }
71
12225
        false
72
    }
73
}
74

            
75
#[derive(Clone, Debug, PartialEq, Eq)]
76
pub struct Request {
77
    pub line_terminators: Vec<LineTerminator>,
78
    pub space0: Whitespace,
79
    pub method: Method,
80
    pub space1: Whitespace,
81
    pub url: Template,
82
    pub line_terminator0: LineTerminator,
83
    pub headers: Vec<KeyValue>,
84
    pub sections: Vec<Section>,
85
    pub body: Option<Body>,
86
    pub source_info: SourceInfo,
87
}
88

            
89
impl Request {
90
    /// Returns the query strings params for this request.
91
    ///
92
    /// See <https://developer.mozilla.org/en-US/docs/Web/API/URL/searchParams>.
93
12540
    pub fn querystring_params(&self) -> &[KeyValue] {
94
14635
        for section in &self.sections {
95
2360
            if let SectionValue::QueryParams(params, _) = &section.value {
96
265
                return params;
97
            }
98
        }
99
12275
        &[]
100
    }
101

            
102
    /// Returns the form params for this request.
103
    ///
104
    /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST#url-encoded_form_submission>.
105
12500
    pub fn form_params(&self) -> &[KeyValue] {
106
14770
        for section in &self.sections {
107
2360
            if let SectionValue::FormParams(params, _) = &section.value {
108
90
                return params;
109
            }
110
        }
111
12410
        &[]
112
    }
113

            
114
    /// Returns the multipart form data for this request.
115
    ///
116
    /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST#multipart_form_submission>.
117
12420
    pub fn multipart_form_data(&self) -> &[MultipartParam] {
118
14685
        for section in &self.sections {
119
2380
            if let SectionValue::MultipartFormData(params, _) = &section.value {
120
115
                return params;
121
            }
122
        }
123
12305
        &[]
124
    }
125

            
126
    /// Returns the list of cookies on this request.
127
    ///
128
    /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cookie>.
129
12500
    pub fn cookies(&self) -> &[Cookie] {
130
14705
        for section in &self.sections {
131
2415
            if let SectionValue::Cookies(cookies) = &section.value {
132
210
                return cookies;
133
            }
134
        }
135
12290
        &[]
136
    }
137

            
138
    /// Returns the basic authentication on this request.
139
    ///
140
    /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication>.
141
12205
    pub fn basic_auth(&self) -> Option<&KeyValue> {
142
14455
        for section in &self.sections {
143
2300
            if let SectionValue::BasicAuth(kv) = &section.value {
144
50
                return kv.as_ref();
145
            }
146
        }
147
12155
        None
148
    }
149

            
150
    /// Returns the options specific for this request.
151
26360
    pub fn options(&self) -> &[EntryOption] {
152
27245
        for section in &self.sections {
153
5915
            if let SectionValue::Options(options) = &section.value {
154
5030
                return options;
155
            }
156
        }
157
21330
        &[]
158
    }
159
}
160

            
161
#[derive(Clone, Debug, PartialEq, Eq)]
162
pub struct Response {
163
    pub line_terminators: Vec<LineTerminator>,
164
    pub version: Version,
165
    pub space0: Whitespace,
166
    pub status: Status,
167
    pub space1: Whitespace,
168
    pub line_terminator0: LineTerminator,
169
    pub headers: Vec<KeyValue>,
170
    pub sections: Vec<Section>,
171
    pub body: Option<Body>,
172
    pub source_info: SourceInfo,
173
}
174

            
175
impl Response {
176
    /// Returns the captures list of this spec response.
177
24090
    pub fn captures(&self) -> &[Capture] {
178
24090
        for section in self.sections.iter() {
179
15565
            if let SectionValue::Captures(captures) = &section.value {
180
890
                return captures;
181
            }
182
        }
183
23200
        &[]
184
    }
185

            
186
    /// Returns the asserts list of this spec response.
187
34945
    pub fn asserts(&self) -> &[Assert] {
188
34945
        for section in self.sections.iter() {
189
23600
            if let SectionValue::Asserts(asserts) = &section.value {
190
22430
                return asserts;
191
            }
192
        }
193
12515
        &[]
194
    }
195
}
196

            
197
#[derive(Clone, Debug, PartialEq, Eq)]
198
pub struct Method(String);
199

            
200
impl Method {
201
    /// Creates a new AST element method/
202
15375
    pub fn new(method: &str) -> Method {
203
15375
        Method(method.to_string())
204
    }
205
}
206

            
207
impl fmt::Display for Method {
208
14260
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
209
14260
        write!(f, "{}", self.0)
210
    }
211
}
212

            
213
impl ToSource for Method {
214
920
    fn to_source(&self) -> SourceString {
215
920
        self.0.to_source()
216
    }
217
}
218

            
219
#[derive(Clone, Debug, PartialEq, Eq)]
220
pub struct Version {
221
    pub value: VersionValue,
222
    pub source_info: SourceInfo,
223
}
224

            
225
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
226
pub enum VersionValue {
227
    Version1,
228
    Version11,
229
    Version2,
230
    Version3,
231
    VersionAny,
232
}
233

            
234
impl fmt::Display for VersionValue {
235
13570
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
236
13570
        let s = match self {
237
45
            VersionValue::Version1 => "HTTP/1.0",
238
55
            VersionValue::Version11 => "HTTP/1.1",
239
60
            VersionValue::Version2 => "HTTP/2",
240
25
            VersionValue::Version3 => "HTTP/3",
241
13385
            VersionValue::VersionAny => "HTTP",
242
        };
243
13570
        write!(f, "{s}")
244
    }
245
}
246

            
247
impl ToSource for VersionValue {
248
190
    fn to_source(&self) -> SourceString {
249
190
        self.to_string().to_source()
250
    }
251
}
252

            
253
#[derive(Clone, Debug, PartialEq, Eq)]
254
pub struct Status {
255
    pub value: StatusValue,
256
    pub source_info: SourceInfo,
257
}
258

            
259
#[derive(Clone, Debug, PartialEq, Eq)]
260
pub enum StatusValue {
261
    Any,
262
    Specific(u64),
263
}
264

            
265
impl fmt::Display for StatusValue {
266
1805
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
267
1805
        match self {
268
40
            StatusValue::Any => write!(f, "*"),
269
1765
            StatusValue::Specific(v) => write!(f, "{v}"),
270
        }
271
    }
272
}
273

            
274
impl ToSource for StatusValue {
275
190
    fn to_source(&self) -> SourceString {
276
190
        self.to_string().to_source()
277
    }
278
}
279

            
280
#[derive(Clone, Debug, PartialEq, Eq)]
281
pub struct Body {
282
    pub line_terminators: Vec<LineTerminator>,
283
    pub space0: Whitespace,
284
    pub value: Bytes,
285
    pub line_terminator0: LineTerminator,
286
}
287

            
288
/// Check that variable name is not reserved
289
/// (would conflicts with an existing function)
290
860
pub fn is_variable_reserved(name: &str) -> bool {
291
860
    ["getEnv", "newDate", "newUuid"].contains(&name)
292
}
293

            
294
#[derive(Clone, Debug, PartialEq, Eq)]
295
pub struct Filter {
296
    pub source_info: SourceInfo,
297
    pub value: FilterValue,
298
}
299

            
300
#[derive(Clone, Debug, PartialEq, Eq)]
301
pub enum FilterValue {
302
    Base64Decode,
303
    Base64Encode,
304
    Base64UrlSafeDecode,
305
    Base64UrlSafeEncode,
306
    Count,
307
    DaysAfterNow,
308
    DaysBeforeNow,
309
    Decode {
310
        space0: Whitespace,
311
        encoding: Template,
312
    },
313
    First,
314
    Format {
315
        space0: Whitespace,
316
        fmt: Template,
317
    },
318
    DateFormat {
319
        space0: Whitespace,
320
        fmt: Template,
321
    },
322
    HtmlEscape,
323
    HtmlUnescape,
324
    JsonPath {
325
        space0: Whitespace,
326
        expr: Template,
327
    },
328
    Last,
329
    Location,
330
    Nth {
331
        space0: Whitespace,
332
        n: IntegerValue,
333
    },
334
    Regex {
335
        space0: Whitespace,
336
        value: RegexValue,
337
    },
338
    Replace {
339
        space0: Whitespace,
340
        old_value: Template,
341
        space1: Whitespace,
342
        new_value: Template,
343
    },
344
    ReplaceRegex {
345
        space0: Whitespace,
346
        pattern: RegexValue,
347
        space1: Whitespace,
348
        new_value: Template,
349
    },
350
    Split {
351
        space0: Whitespace,
352
        sep: Template,
353
    },
354
    ToDate {
355
        space0: Whitespace,
356
        fmt: Template,
357
    },
358
    ToFloat,
359
    ToHex,
360
    ToInt,
361
    ToString,
362
    UrlDecode,
363
    UrlEncode,
364
    UrlQueryParam {
365
        space0: Whitespace,
366
        param: Template,
367
    },
368
    XPath {
369
        space0: Whitespace,
370
        expr: Template,
371
    },
372
}
373

            
374
impl FilterValue {
375
    /// Returns the Hurl identifier for this filter type.
376
2100
    pub fn identifier(&self) -> &'static str {
377
2100
        match self {
378
50
            FilterValue::Base64Decode => "base64Decode",
379
35
            FilterValue::Base64Encode => "base64Encode",
380
50
            FilterValue::Base64UrlSafeDecode => "base64UrlSafeDecode",
381
35
            FilterValue::Base64UrlSafeEncode => "base64UrlSafeEncode",
382
385
            FilterValue::Count => "count",
383
25
            FilterValue::DaysAfterNow => "daysAfterNow",
384
50
            FilterValue::DaysBeforeNow => "daysBeforeNow",
385
75
            FilterValue::Decode { .. } => "decode",
386
40
            FilterValue::First => "first",
387
50
            FilterValue::Format { .. } => "format",
388
85
            FilterValue::DateFormat { .. } => "dateFormat",
389
35
            FilterValue::HtmlEscape => "htmlEscape",
390
45
            FilterValue::HtmlUnescape => "htmlUnescape",
391
80
            FilterValue::JsonPath { .. } => "jsonpath",
392
40
            FilterValue::Last => "last",
393
80
            FilterValue::Location => "location",
394
230
            FilterValue::Nth { .. } => "nth",
395
60
            FilterValue::Regex { .. } => "regex",
396
115
            FilterValue::Replace { .. } => "replace",
397
45
            FilterValue::ReplaceRegex { .. } => "replaceRegex",
398
40
            FilterValue::Split { .. } => "split",
399
105
            FilterValue::ToDate { .. } => "toDate",
400
60
            FilterValue::ToFloat => "toFloat",
401
40
            FilterValue::ToHex => "toHex",
402
60
            FilterValue::ToInt => "toInt",
403
30
            FilterValue::ToString => "toString",
404
35
            FilterValue::UrlDecode => "urlDecode",
405
35
            FilterValue::UrlEncode => "urlEncode",
406
45
            FilterValue::UrlQueryParam { .. } => "urlQueryParam",
407
40
            FilterValue::XPath { .. } => "xpath",
408
        }
409
    }
410
}
411

            
412
#[derive(Clone, Debug, PartialEq, Eq)]
413
pub enum IntegerValue {
414
    Literal(I64),
415
    Placeholder(Placeholder),
416
}
417

            
418
impl fmt::Display for IntegerValue {
419
5
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
420
5
        match self {
421
5
            IntegerValue::Literal(v) => write!(f, "{v}"),
422
            IntegerValue::Placeholder(v) => write!(f, "{v}"),
423
        }
424
    }
425
}