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

            
49
    /// Returns true if the request or the response uses multilines string attributes
50
12190
    pub fn use_multiline_string_body_with_attributes(&self) -> bool {
51
        if let Some(Body {
52
225
            value: Bytes::MultilineString(multiline),
53
            ..
54
675
        }) = &self.request.body
55
        {
56
225
            if multiline.has_attributes() {
57
15
                return true;
58
            }
59
        }
60
12175
        if let Some(response) = &self.response {
61
            if let Some(Body {
62
170
                value: Bytes::MultilineString(multiline),
63
                ..
64
2020
            }) = &response.body
65
            {
66
170
                if multiline.has_attributes() {
67
5
                    return true;
68
                }
69
            }
70
        }
71
12170
        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
12435
    pub fn querystring_params(&self) -> &[KeyValue] {
94
14470
        for section in &self.sections {
95
2260
            if let SectionValue::QueryParams(params, _) = &section.value {
96
225
                return params;
97
            }
98
        }
99
12210
        &[]
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
12395
    pub fn form_params(&self) -> &[KeyValue] {
106
14565
        for section in &self.sections {
107
2255
            if let SectionValue::FormParams(params, _) = &section.value {
108
85
                return params;
109
            }
110
        }
111
12310
        &[]
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
12315
    pub fn multipart_form_data(&self) -> &[MultipartParam] {
118
14475
        for section in &self.sections {
119
2270
            if let SectionValue::MultipartFormData(params, _) = &section.value {
120
110
                return params;
121
            }
122
        }
123
12205
        &[]
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
12395
    pub fn cookies(&self) -> &[Cookie] {
130
14495
        for section in &self.sections {
131
2305
            if let SectionValue::Cookies(cookies) = &section.value {
132
205
                return cookies;
133
            }
134
        }
135
12190
        &[]
136
    }
137

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

            
150
    /// Returns the options specific for this request.
151
26065
    pub fn options(&self) -> &[EntryOption] {
152
26900
        for section in &self.sections {
153
5700
            if let SectionValue::Options(options) = &section.value {
154
4865
                return options;
155
            }
156
        }
157
21200
        &[]
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
12350
    pub fn captures(&self) -> &[Capture] {
178
12350
        for section in self.sections.iter() {
179
7840
            if let SectionValue::Captures(captures) = &section.value {
180
415
                return captures;
181
            }
182
        }
183
11935
        &[]
184
    }
185

            
186
    /// Returns the asserts list of this spec response.
187
23135
    pub fn asserts(&self) -> &[Assert] {
188
23135
        for section in self.sections.iter() {
189
15575
            if let SectionValue::Asserts(asserts) = &section.value {
190
14905
                return asserts;
191
            }
192
        }
193
8230
        &[]
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
15240
    pub fn new(method: &str) -> Method {
203
15240
        Method(method.to_string())
204
    }
205
}
206

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

            
213
impl ToSource for Method {
214
880
    fn to_source(&self) -> SourceString {
215
880
        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
13420
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
236
13420
        let s = match self {
237
30
            VersionValue::Version1 => "HTTP/1.0",
238
70
            VersionValue::Version11 => "HTTP/1.1",
239
60
            VersionValue::Version2 => "HTTP/2",
240
25
            VersionValue::Version3 => "HTTP/3",
241
13235
            VersionValue::VersionAny => "HTTP",
242
        };
243
13420
        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
1775
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
267
1775
        match self {
268
40
            StatusValue::Any => write!(f, "*"),
269
1735
            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
845
pub fn is_variable_reserved(name: &str) -> bool {
291
845
    ["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
    HtmlEscape,
319
    HtmlUnescape,
320
    JsonPath {
321
        space0: Whitespace,
322
        expr: Template,
323
    },
324
    Last,
325
    Location,
326
    Nth {
327
        space0: Whitespace,
328
        n: IntegerValue,
329
    },
330
    Regex {
331
        space0: Whitespace,
332
        value: RegexValue,
333
    },
334
    Replace {
335
        space0: Whitespace,
336
        old_value: Template,
337
        space1: Whitespace,
338
        new_value: Template,
339
    },
340
    ReplaceRegex {
341
        space0: Whitespace,
342
        pattern: RegexValue,
343
        space1: Whitespace,
344
        new_value: Template,
345
    },
346
    Split {
347
        space0: Whitespace,
348
        sep: Template,
349
    },
350
    ToDate {
351
        space0: Whitespace,
352
        fmt: Template,
353
    },
354
    ToFloat,
355
    ToHex,
356
    ToInt,
357
    ToString,
358
    UrlDecode,
359
    UrlEncode,
360
    UrlQueryParam {
361
        space0: Whitespace,
362
        param: Template,
363
    },
364
    XPath {
365
        space0: Whitespace,
366
        expr: Template,
367
    },
368
}
369

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

            
407
#[derive(Clone, Debug, PartialEq, Eq)]
408
pub enum IntegerValue {
409
    Literal(I64),
410
    Placeholder(Placeholder),
411
}
412

            
413
impl fmt::Display for IntegerValue {
414
5
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
415
5
        match self {
416
5
            IntegerValue::Literal(v) => write!(f, "{v}"),
417
            IntegerValue::Placeholder(v) => write!(f, "{v}"),
418
        }
419
    }
420
}