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, U64,
23
};
24
use crate::ast::section::{
25
    Assert, Capture, Cookie, MultipartParam, RegexValue, Section, SectionValue,
26
};
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
#[derive(Clone, Debug, PartialEq, Eq)]
36
pub struct Entry {
37
    pub request: Request,
38
    pub response: Option<Response>,
39
}
40

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
211
#[derive(Clone, Debug, PartialEq, Eq)]
212
pub struct Version {
213
    pub value: VersionValue,
214
    pub source_info: SourceInfo,
215
}
216

            
217
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
218
pub enum VersionValue {
219
    Version1,
220
    Version11,
221
    Version2,
222
    Version3,
223
    VersionAny,
224
}
225

            
226
impl fmt::Display for VersionValue {
227
12930
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
228
12930
        let s = match self {
229
25
            VersionValue::Version1 => "HTTP/1.0",
230
65
            VersionValue::Version11 => "HTTP/1.1",
231
55
            VersionValue::Version2 => "HTTP/2",
232
20
            VersionValue::Version3 => "HTTP/3",
233
12765
            VersionValue::VersionAny => "HTTP",
234
        };
235
12930
        write!(f, "{s}")
236
    }
237
}
238

            
239
#[derive(Clone, Debug, PartialEq, Eq)]
240
pub struct Status {
241
    pub value: StatusValue,
242
    pub source_info: SourceInfo,
243
}
244

            
245
#[derive(Clone, Debug, PartialEq, Eq)]
246
pub enum StatusValue {
247
    Any,
248
    Specific(u64),
249
}
250

            
251
impl fmt::Display for StatusValue {
252
1375
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
253
1375
        match self {
254
30
            StatusValue::Any => write!(f, "*"),
255
1345
            StatusValue::Specific(v) => write!(f, "{v}"),
256
        }
257
    }
258
}
259

            
260
#[derive(Clone, Debug, PartialEq, Eq)]
261
pub struct Body {
262
    pub line_terminators: Vec<LineTerminator>,
263
    pub space0: Whitespace,
264
    pub value: Bytes,
265
    pub line_terminator0: LineTerminator,
266
}
267

            
268
/// Check that variable name is not reserved
269
/// (would conflicts with an existing function)
270
740
pub fn is_variable_reserved(name: &str) -> bool {
271
740
    ["getEnv", "newDate", "newUuid"].contains(&name)
272
}
273

            
274
#[derive(Clone, Debug, PartialEq, Eq)]
275
pub struct Filter {
276
    pub source_info: SourceInfo,
277
    pub value: FilterValue,
278
}
279

            
280
#[derive(Clone, Debug, PartialEq, Eq)]
281
pub enum FilterValue {
282
    Base64Decode,
283
    Base64Encode,
284
    Base64UrlSafeDecode,
285
    Base64UrlSafeEncode,
286
    Count,
287
    DaysAfterNow,
288
    DaysBeforeNow,
289
    Decode {
290
        space0: Whitespace,
291
        encoding: Template,
292
    },
293
    Format {
294
        space0: Whitespace,
295
        fmt: Template,
296
    },
297
    HtmlEscape,
298
    HtmlUnescape,
299
    JsonPath {
300
        space0: Whitespace,
301
        expr: Template,
302
    },
303
    Nth {
304
        space0: Whitespace,
305
        n: U64,
306
    },
307
    Regex {
308
        space0: Whitespace,
309
        value: RegexValue,
310
    },
311
    Replace {
312
        space0: Whitespace,
313
        old_value: RegexValue,
314
        space1: Whitespace,
315
        new_value: Template,
316
    },
317
    Split {
318
        space0: Whitespace,
319
        sep: Template,
320
    },
321
    ToDate {
322
        space0: Whitespace,
323
        fmt: Template,
324
    },
325
    ToFloat,
326
    ToInt,
327
    ToString,
328
    UrlDecode,
329
    UrlEncode,
330
    UrlQueryParam {
331
        space0: Whitespace,
332
        param: Template,
333
    },
334
    XPath {
335
        space0: Whitespace,
336
        expr: Template,
337
    },
338
}
339

            
340
impl FilterValue {
341
    /// Returns the Hurl identifier for this filter type.
342
1305
    pub fn identifier(&self) -> &'static str {
343
1305
        match self {
344
40
            FilterValue::Base64Decode => "base64Decode",
345
30
            FilterValue::Base64Encode => "base64Encode",
346
45
            FilterValue::Base64UrlSafeDecode => "base64UrlSafeDecode",
347
30
            FilterValue::Base64UrlSafeEncode => "base64UrlSafeEncode",
348
270
            FilterValue::Count => "count",
349
20
            FilterValue::DaysAfterNow => "daysAfterNow",
350
40
            FilterValue::DaysBeforeNow => "daysBeforeNow",
351
65
            FilterValue::Decode { .. } => "decode",
352
65
            FilterValue::Format { .. } => "format",
353
30
            FilterValue::HtmlEscape => "htmlEscape",
354
40
            FilterValue::HtmlUnescape => "htmlUnescape",
355
30
            FilterValue::JsonPath { .. } => "jsonpath",
356
125
            FilterValue::Nth { .. } => "nth",
357
50
            FilterValue::Regex { .. } => "regex",
358
85
            FilterValue::Replace { .. } => "replace",
359
25
            FilterValue::Split { .. } => "split",
360
80
            FilterValue::ToDate { .. } => "toDate",
361
40
            FilterValue::ToFloat => "toFloat",
362
55
            FilterValue::ToInt => "toInt",
363
20
            FilterValue::ToString => "toString",
364
30
            FilterValue::UrlDecode => "urlDecode",
365
30
            FilterValue::UrlEncode => "urlEncode",
366
30
            FilterValue::UrlQueryParam { .. } => "urlQueryParam",
367
30
            FilterValue::XPath { .. } => "xpath",
368
        }
369
    }
370
}