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::typing::{SourceString, ToSource};
28

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

            
36
#[derive(Clone, Debug, PartialEq, Eq)]
37
pub struct Entry {
38
    pub request: Request,
39
    pub response: Option<Response>,
40
}
41

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

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

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

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

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

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

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

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

            
149
    /// Returns the options specific for this request.
150
25690
    pub fn options(&self) -> &[EntryOption] {
151
26485
        for section in &self.sections {
152
5285
            if let SectionValue::Options(options) = &section.value {
153
4490
                return options;
154
            }
155
        }
156
21200
        &[]
157
    }
158
}
159

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

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

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

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

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

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

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

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

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

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

            
246
#[derive(Clone, Debug, PartialEq, Eq)]
247
pub struct Status {
248
    pub value: StatusValue,
249
    pub source_info: SourceInfo,
250
}
251

            
252
#[derive(Clone, Debug, PartialEq, Eq)]
253
pub enum StatusValue {
254
    Any,
255
    Specific(u64),
256
}
257

            
258
impl fmt::Display for StatusValue {
259
1580
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
260
1580
        match self {
261
35
            StatusValue::Any => write!(f, "*"),
262
1545
            StatusValue::Specific(v) => write!(f, "{v}"),
263
        }
264
    }
265
}
266

            
267
#[derive(Clone, Debug, PartialEq, Eq)]
268
pub struct Body {
269
    pub line_terminators: Vec<LineTerminator>,
270
    pub space0: Whitespace,
271
    pub value: Bytes,
272
    pub line_terminator0: LineTerminator,
273
}
274

            
275
/// Check that variable name is not reserved
276
/// (would conflicts with an existing function)
277
740
pub fn is_variable_reserved(name: &str) -> bool {
278
740
    ["getEnv", "newDate", "newUuid"].contains(&name)
279
}
280

            
281
#[derive(Clone, Debug, PartialEq, Eq)]
282
pub struct Filter {
283
    pub source_info: SourceInfo,
284
    pub value: FilterValue,
285
}
286

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

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