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 crate::ast::{
19
    Assert, Capture, Cookie, FilenameParam, FilenameValue, MultipartParam, Section, SectionValue,
20
    SourceInfo, Whitespace,
21
};
22
use crate::combinator::{optional, recover, zero_or_more};
23
use crate::parser::filter::filters;
24
use crate::parser::predicate::predicate;
25
use crate::parser::primitives::{
26
    key_value, line_terminator, literal, one_or_more_spaces, optional_line_terminators,
27
    try_literal, zero_or_more_spaces,
28
};
29
use crate::parser::query::query;
30
use crate::parser::string::unquoted_template;
31
use crate::parser::{ParseError, ParseErrorKind, ParseResult, filename, key_string, option};
32
use crate::reader::{Pos, Reader};
33

            
34
16470
pub fn request_sections(reader: &mut Reader) -> ParseResult<Vec<Section>> {
35
16470
    let sections = zero_or_more(request_section, reader)?;
36
16415
    Ok(sections)
37
}
38

            
39
14900
pub fn response_sections(reader: &mut Reader) -> ParseResult<Vec<Section>> {
40
14900
    let sections = zero_or_more(response_section, reader)?;
41
14840
    Ok(sections)
42
}
43

            
44
19860
fn request_section(reader: &mut Reader) -> ParseResult<Section> {
45
19860
    let line_terminators = optional_line_terminators(reader)?;
46
19860
    let space0 = zero_or_more_spaces(reader)?;
47
19860
    let start = reader.cursor();
48
19860
    let name = section_name(reader)?;
49
3670
    let source_info = SourceInfo::new(start.pos, reader.cursor().pos);
50

            
51
3670
    let line_terminator0 = line_terminator(reader)?;
52
3670
    let value = match name.as_str() {
53
3670
        "Query" => section_value_query_params(reader, true)?,
54
3450
        "QueryStringParams" => section_value_query_params(reader, false)?,
55
3260
        "BasicAuth" => section_value_basic_auth(reader)?,
56
3170
        "Form" => section_value_form_params(reader, true)?,
57
3105
        "FormParams" => section_value_form_params(reader, false)?,
58
3015
        "Multipart" => section_value_multipart_form_data(reader, true)?,
59
2980
        "MultipartFormData" => section_value_multipart_form_data(reader, false)?,
60
2855
        "Cookies" => section_value_cookies(reader)?,
61
2505
        "Options" => section_value_options(reader)?,
62
        _ => {
63
10
            let kind = ParseErrorKind::RequestSectionName { name: name.clone() };
64
10
            let pos = Pos::new(start.pos.line, start.pos.column + 1);
65
10
            return Err(ParseError::new(pos, false, kind));
66
        }
67
    };
68

            
69
3615
    Ok(Section {
70
3615
        line_terminators,
71
3615
        space0,
72
3615
        line_terminator0,
73
3615
        value,
74
3615
        source_info,
75
3615
    })
76
}
77

            
78
21770
fn response_section(reader: &mut Reader) -> ParseResult<Section> {
79
21770
    let line_terminators = optional_line_terminators(reader)?;
80
21770
    let space0 = zero_or_more_spaces(reader)?;
81
21770
    let start = reader.cursor();
82
21770
    let name = section_name(reader)?;
83
9250
    let end = reader.cursor();
84
9250
    let source_info = SourceInfo::new(start.pos, end.pos);
85

            
86
9250
    let line_terminator0 = line_terminator(reader)?;
87
9250
    let value = match name.as_str() {
88
9250
        "Captures" => section_value_captures(reader)?,
89
8620
        "Asserts" => section_value_asserts(reader)?,
90
        _ => {
91
            let kind = ParseErrorKind::ResponseSectionName { name: name.clone() };
92
            let pos = Pos::new(start.pos.line, start.pos.column + 1);
93
            return Err(ParseError::new(pos, false, kind));
94
        }
95
    };
96

            
97
9190
    Ok(Section {
98
9190
        line_terminators,
99
9190
        space0,
100
9190
        line_terminator0,
101
9190
        value,
102
9190
        source_info,
103
9190
    })
104
}
105

            
106
41630
fn section_name(reader: &mut Reader) -> ParseResult<String> {
107
41630
    let pos = reader.cursor().pos;
108
41630
    try_literal("[", reader)?;
109
109704
    let name = reader.read_while(|c| c.is_alphanumeric());
110
12995
    if name.is_empty() {
111
        // Could be the empty json array for the body
112
70
        let kind = ParseErrorKind::Expecting {
113
70
            value: "a valid section name".to_string(),
114
70
        };
115
70
        return Err(ParseError::new(pos, true, kind));
116
    }
117
12925
    try_literal("]", reader)?;
118
12920
    Ok(name)
119
}
120

            
121
410
fn section_value_query_params(reader: &mut Reader, short: bool) -> ParseResult<SectionValue> {
122
410
    let items = zero_or_more(key_value, reader)?;
123
410
    Ok(SectionValue::QueryParams(items, short))
124
}
125

            
126
90
fn section_value_basic_auth(reader: &mut Reader) -> ParseResult<SectionValue> {
127
90
    let v = optional(key_value, reader)?;
128
90
    Ok(SectionValue::BasicAuth(v))
129
}
130

            
131
155
fn section_value_form_params(reader: &mut Reader, short: bool) -> ParseResult<SectionValue> {
132
155
    let items = zero_or_more(key_value, reader)?;
133
155
    Ok(SectionValue::FormParams(items, short))
134
}
135

            
136
160
fn section_value_multipart_form_data(
137
160
    reader: &mut Reader,
138
160
    short: bool,
139
160
) -> ParseResult<SectionValue> {
140
160
    let items = zero_or_more(multipart_param, reader)?;
141
155
    Ok(SectionValue::MultipartFormData(items, short))
142
}
143

            
144
350
fn section_value_cookies(reader: &mut Reader) -> ParseResult<SectionValue> {
145
350
    let items = zero_or_more(cookie, reader)?;
146
350
    Ok(SectionValue::Cookies(items))
147
}
148

            
149
630
fn section_value_captures(reader: &mut Reader) -> ParseResult<SectionValue> {
150
630
    let items = zero_or_more(capture, reader)?;
151
630
    Ok(SectionValue::Captures(items))
152
}
153

            
154
8620
fn section_value_asserts(reader: &mut Reader) -> ParseResult<SectionValue> {
155
8620
    let asserts = zero_or_more(assert, reader)?;
156
8560
    Ok(SectionValue::Asserts(asserts))
157
}
158

            
159
2495
fn section_value_options(reader: &mut Reader) -> ParseResult<SectionValue> {
160
2495
    let options = zero_or_more(option::parse, reader)?;
161
2455
    Ok(SectionValue::Options(options))
162
}
163

            
164
695
fn cookie(reader: &mut Reader) -> ParseResult<Cookie> {
165
    // let start = reader.state.clone();
166
695
    let line_terminators = optional_line_terminators(reader)?;
167
695
    let space0 = zero_or_more_spaces(reader)?;
168
695
    let name = recover(key_string::parse, reader)?;
169
625
    let space1 = zero_or_more_spaces(reader)?;
170
750
    recover(|p1| literal(":", p1), reader)?;
171
350
    let space2 = zero_or_more_spaces(reader)?;
172
350
    let value = unquoted_template(reader)?;
173
350
    let line_terminator0 = line_terminator(reader)?;
174
350
    Ok(Cookie {
175
350
        line_terminators,
176
350
        space0,
177
350
        name,
178
350
        space1,
179
350
        space2,
180
350
        value,
181
350
        line_terminator0,
182
350
    })
183
}
184

            
185
495
fn multipart_param(reader: &mut Reader) -> ParseResult<MultipartParam> {
186
495
    let save = reader.cursor();
187
495
    match file_param(reader) {
188
210
        Ok(f) => Ok(MultipartParam::FilenameParam(f)),
189
285
        Err(e) => {
190
285
            if e.recoverable {
191
280
                reader.seek(save);
192
280
                let param = key_value(reader)?;
193
125
                Ok(MultipartParam::Param(param))
194
            } else {
195
5
                Err(e)
196
            }
197
        }
198
    }
199
}
200

            
201
495
fn file_param(reader: &mut Reader) -> ParseResult<FilenameParam> {
202
495
    let line_terminators = optional_line_terminators(reader)?;
203
495
    let space0 = zero_or_more_spaces(reader)?;
204
495
    let key = recover(key_string::parse, reader)?;
205
425
    let space1 = zero_or_more_spaces(reader)?;
206
510
    recover(|reader1| literal(":", reader1), reader)?;
207
340
    let space2 = zero_or_more_spaces(reader)?;
208
340
    let value = file_value(reader)?;
209
210
    let line_terminator0 = line_terminator(reader)?;
210
210
    Ok(FilenameParam {
211
210
        line_terminators,
212
210
        space0,
213
210
        key,
214
210
        space1,
215
210
        space2,
216
210
        value,
217
210
        line_terminator0,
218
210
    })
219
}
220

            
221
340
fn file_value(reader: &mut Reader) -> ParseResult<FilenameValue> {
222
340
    try_literal("file,", reader)?;
223
215
    let space0 = zero_or_more_spaces(reader)?;
224
215
    let f = filename::parse(reader)?;
225
215
    let space1 = zero_or_more_spaces(reader)?;
226
215
    literal(";", reader)?;
227
215
    let save = reader.cursor();
228
215
    let (space2, content_type) = match line_terminator(reader) {
229
        Ok(_) => {
230
140
            reader.seek(save);
231
140
            let space2 = Whitespace {
232
140
                value: String::new(),
233
140
                source_info: SourceInfo {
234
140
                    start: save.pos,
235
140
                    end: save.pos,
236
140
                },
237
140
            };
238
140
            (space2, None)
239
        }
240
        Err(_) => {
241
75
            reader.seek(save);
242
75
            let space2 = zero_or_more_spaces(reader)?;
243
75
            let start = reader.cursor();
244
75
            let Ok(content_type) = unquoted_template(reader) else {
245
5
                return Err(ParseError::new(
246
5
                    start.pos,
247
5
                    false,
248
5
                    ParseErrorKind::FileContentType,
249
5
                ));
250
            };
251
70
            (space2, Some(content_type))
252
        }
253
    };
254

            
255
210
    Ok(FilenameValue {
256
210
        space0,
257
210
        filename: f,
258
210
        space1,
259
210
        space2,
260
210
        content_type,
261
210
    })
262
}
263

            
264
2845
fn capture(reader: &mut Reader) -> ParseResult<Capture> {
265
2845
    let line_terminators = optional_line_terminators(reader)?;
266
2845
    let space0 = zero_or_more_spaces(reader)?;
267
2845
    let name = recover(key_string::parse, reader)?;
268
2435
    let space1 = zero_or_more_spaces(reader)?;
269
2922
    recover(|p1| literal(":", p1), reader)?;
270
2260
    let space2 = zero_or_more_spaces(reader)?;
271
2260
    let q = query(reader)?;
272
2260
    let filters = filters(reader)?;
273
2260
    let (redacted, space3) = if let Some(ws) = optional(redacted, reader)? {
274
100
        (true, ws)
275
    } else {
276
        // No `redact` keywork, space3 is an empty whitespace and the optional remaining whitespace
277
        // will be consumed by the line terminator.
278
2160
        let pos = reader.cursor().pos;
279
2160
        let value = String::new();
280
2160
        let source_info = SourceInfo::new(pos, pos);
281
2160
        (false, Whitespace { value, source_info })
282
    };
283

            
284
2260
    let line_terminator0 = line_terminator(reader)?;
285
2260
    Ok(Capture {
286
2260
        line_terminators,
287
2260
        space0,
288
2260
        name,
289
2260
        space1,
290
2260
        space2,
291
2260
        query: q,
292
2260
        filters,
293
2260
        space3,
294
2260
        redacted,
295
2260
        line_terminator0,
296
2260
    })
297
}
298

            
299
2260
fn redacted(reader: &mut Reader) -> ParseResult<Whitespace> {
300
2260
    let space = zero_or_more_spaces(reader)?;
301
2260
    try_literal("redact", reader)?;
302
100
    Ok(space)
303
}
304

            
305
31945
fn assert(reader: &mut Reader) -> ParseResult<Assert> {
306
31945
    let line_terminators = optional_line_terminators(reader)?;
307
31945
    let space0 = zero_or_more_spaces(reader)?;
308
31945
    let query0 = query(reader)?;
309
24095
    let filters = filters(reader)?;
310
24090
    let space1 = one_or_more_spaces(reader)?;
311
24090
    let predicate0 = predicate(reader)?;
312

            
313
24060
    let line_terminator0 = line_terminator(reader)?;
314
24060
    Ok(Assert {
315
24060
        line_terminators,
316
24060
        space0,
317
24060
        query: query0,
318
24060
        filters,
319
24060
        space1,
320
24060
        predicate: predicate0,
321
24060
        line_terminator0,
322
24060
    })
323
}
324

            
325
#[cfg(test)]
326
mod tests {
327
    use super::*;
328
    use crate::ast::{
329
        I64, KeyValue, LineTerminator, Number, Predicate, PredicateFunc, PredicateFuncValue,
330
        PredicateValue, Query, QueryValue, Template, TemplateElement,
331
    };
332
    use crate::reader::CharPos;
333
    use crate::types::ToSource;
334

            
335
    #[test]
336
    fn test_section_name() {
337
        let mut reader = Reader::new("[SectionA]");
338
        assert_eq!(section_name(&mut reader).unwrap(), String::from("SectionA"));
339

            
340
        let mut reader = Reader::new("[]");
341
        assert!(section_name(&mut reader).err().unwrap().recoverable);
342
    }
343

            
344
    #[test]
345
    fn test_asserts_section() {
346
        let mut reader = Reader::new("[Asserts]\nheader \"Location\" == \"https://google.fr\"\n");
347

            
348
        assert_eq!(
349
            response_section(&mut reader).unwrap(),
350
            Section {
351
                line_terminators: vec![],
352
                space0: Whitespace {
353
                    value: String::new(),
354
                    source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 1)),
355
                },
356
                line_terminator0: LineTerminator {
357
                    space0: Whitespace {
358
                        value: String::new(),
359
                        source_info: SourceInfo::new(Pos::new(1, 10), Pos::new(1, 10)),
360
                    },
361
                    comment: None,
362
                    newline: Whitespace {
363
                        value: String::from("\n"),
364
                        source_info: SourceInfo::new(Pos::new(1, 10), Pos::new(2, 1)),
365
                    },
366
                },
367
                value: SectionValue::Asserts(vec![Assert {
368
                    line_terminators: vec![],
369
                    space0: Whitespace {
370
                        value: String::new(),
371
                        source_info: SourceInfo::new(Pos::new(2, 1), Pos::new(2, 1)),
372
                    },
373
                    query: Query {
374
                        source_info: SourceInfo::new(Pos::new(2, 1), Pos::new(2, 18)),
375
                        value: QueryValue::Header {
376
                            space0: Whitespace {
377
                                value: String::from(" "),
378
                                source_info: SourceInfo::new(Pos::new(2, 7), Pos::new(2, 8)),
379
                            },
380
                            name: Template::new(
381
                                Some('"'),
382
                                vec![TemplateElement::String {
383
                                    value: "Location".to_string(),
384
                                    source: "Location".to_source(),
385
                                }],
386
                                SourceInfo::new(Pos::new(2, 8), Pos::new(2, 18)),
387
                            ),
388
                        },
389
                    },
390
                    filters: vec![],
391
                    space1: Whitespace {
392
                        value: String::from(" "),
393
                        source_info: SourceInfo::new(Pos::new(2, 18), Pos::new(2, 19)),
394
                    },
395
                    predicate: Predicate {
396
                        not: false,
397
                        space0: Whitespace {
398
                            value: String::new(),
399
                            source_info: SourceInfo::new(Pos::new(2, 19), Pos::new(2, 19)),
400
                        },
401
                        predicate_func: PredicateFunc {
402
                            source_info: SourceInfo::new(Pos::new(2, 19), Pos::new(2, 41)),
403
                            value: PredicateFuncValue::Equal {
404
                                space0: Whitespace {
405
                                    value: String::from(" "),
406
                                    source_info: SourceInfo::new(Pos::new(2, 21), Pos::new(2, 22)),
407
                                },
408
                                value: PredicateValue::String(Template::new(
409
                                    Some('"'),
410
                                    vec![TemplateElement::String {
411
                                        value: "https://google.fr".to_string(),
412
                                        source: "https://google.fr".to_source(),
413
                                    }],
414
                                    SourceInfo::new(Pos::new(2, 22), Pos::new(2, 41))
415
                                )),
416
                            },
417
                        },
418
                    },
419
                    line_terminator0: LineTerminator {
420
                        space0: Whitespace {
421
                            value: String::new(),
422
                            source_info: SourceInfo::new(Pos::new(2, 41), Pos::new(2, 41)),
423
                        },
424
                        comment: None,
425
                        newline: Whitespace {
426
                            value: String::from("\n"),
427
                            source_info: SourceInfo::new(Pos::new(2, 41), Pos::new(3, 1)),
428
                        },
429
                    },
430
                }]),
431
                source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 10)),
432
            }
433
        );
434
    }
435

            
436
    #[test]
437
    fn test_asserts_section_error() {
438
        let mut reader = Reader::new("x[Assertsx]\nheader Location == \"https://google.fr\"\n");
439
        let error = response_section(&mut reader).err().unwrap();
440
        assert_eq!(error.pos, Pos { line: 1, column: 1 });
441
        assert_eq!(
442
            error.kind,
443
            ParseErrorKind::Expecting {
444
                value: String::from("[")
445
            }
446
        );
447
        assert!(error.recoverable);
448

            
449
        let mut reader = Reader::new("[Assertsx]\nheader Location == \"https://google.fr\"\n");
450
        let error = response_section(&mut reader).err().unwrap();
451
        assert_eq!(error.pos, Pos { line: 1, column: 2 });
452
        assert_eq!(
453
            error.kind,
454
            ParseErrorKind::ResponseSectionName {
455
                name: String::from("Assertsx")
456
            }
457
        );
458
        assert!(!error.recoverable);
459
    }
460

            
461
    #[test]
462
    fn test_cookie() {
463
        let mut reader = Reader::new("Foo: Bar");
464
        let c = cookie(&mut reader).unwrap();
465
        assert_eq!(c.name.to_string(), String::from("Foo"));
466
        assert_eq!(
467
            c.value,
468
            Template::new(
469
                None,
470
                vec![TemplateElement::String {
471
                    value: "Bar".to_string(),
472
                    source: "Bar".to_source(),
473
                }],
474
                SourceInfo::new(Pos::new(1, 6), Pos::new(1, 9))
475
            )
476
        );
477
    }
478

            
479
    #[test]
480
    fn test_cookie_error() {
481
        let mut reader = Reader::new("Foo: {{Bar");
482
        let error = cookie(&mut reader).err().unwrap();
483
        assert_eq!(
484
            error.pos,
485
            Pos {
486
                line: 1,
487
                column: 11,
488
            }
489
        );
490
        assert!(!error.recoverable);
491
        assert_eq!(
492
            error.kind,
493
            ParseErrorKind::Expecting {
494
                value: "}}".to_string()
495
            }
496
        );
497
    }
498

            
499
    #[test]
500
    fn test_file_value() {
501
        let mut reader = Reader::new("file,hello.txt;");
502
        assert_eq!(
503
            file_value(&mut reader).unwrap(),
504
            FilenameValue {
505
                space0: Whitespace {
506
                    value: String::new(),
507
                    source_info: SourceInfo::new(Pos::new(1, 6), Pos::new(1, 6)),
508
                },
509
                filename: Template::new(
510
                    None,
511
                    vec![TemplateElement::String {
512
                        value: "hello.txt".to_string(),
513
                        source: "hello.txt".to_source(),
514
                    }],
515
                    SourceInfo::new(Pos::new(1, 6), Pos::new(1, 15)),
516
                ),
517
                space1: Whitespace {
518
                    value: String::new(),
519
                    source_info: SourceInfo::new(Pos::new(1, 15), Pos::new(1, 15)),
520
                },
521
                space2: Whitespace {
522
                    value: String::new(),
523
                    source_info: SourceInfo::new(Pos::new(1, 16), Pos::new(1, 16)),
524
                },
525
                content_type: None,
526
            }
527
        );
528
        let mut reader = Reader::new("file,hello.txt; text/html");
529
        assert_eq!(
530
            file_value(&mut reader).unwrap(),
531
            FilenameValue {
532
                space0: Whitespace {
533
                    value: String::new(),
534
                    source_info: SourceInfo::new(Pos::new(1, 6), Pos::new(1, 6)),
535
                },
536
                filename: Template::new(
537
                    None,
538
                    vec![TemplateElement::String {
539
                        value: "hello.txt".to_string(),
540
                        source: "hello.txt".to_source(),
541
                    }],
542
                    SourceInfo::new(Pos::new(1, 6), Pos::new(1, 15)),
543
                ),
544
                space1: Whitespace {
545
                    value: String::new(),
546
                    source_info: SourceInfo::new(Pos::new(1, 15), Pos::new(1, 15)),
547
                },
548
                space2: Whitespace {
549
                    value: " ".to_string(),
550
                    source_info: SourceInfo::new(Pos::new(1, 16), Pos::new(1, 17)),
551
                },
552
                content_type: Some(Template::new(
553
                    None,
554
                    vec![TemplateElement::String {
555
                        value: "text/html".to_string(),
556
                        source: "text/html".to_source(),
557
                    }],
558
                    SourceInfo::new(Pos::new(1, 17), Pos::new(1, 26)),
559
                )),
560
            }
561
        );
562
    }
563

            
564
    #[test]
565
    fn test_file_content_type() {
566
        let mut reader = Reader::new("file,hello.txt; text/html");
567
        let file_value = file_value(&mut reader).unwrap();
568
        let content_type = file_value.content_type.unwrap();
569
        assert_eq!(content_type.to_string(), "text/html".to_string());
570
        assert_eq!(reader.cursor().index, CharPos(25));
571

            
572
        let mut reader = Reader::new("file,------; text/plain; charset=us-ascii");
573
        let file_value = crate::parser::sections::file_value(&mut reader).unwrap();
574
        let content_type = file_value.content_type.unwrap();
575
        assert_eq!(
576
            content_type.to_string(),
577
            "text/plain; charset=us-ascii".to_string()
578
        );
579
        assert_eq!(reader.cursor().index, CharPos(41));
580

            
581
        let mut reader = Reader::new("file,******; text/html # comment");
582
        let file_value = crate::parser::sections::file_value(&mut reader).unwrap();
583
        let content_type = file_value.content_type.unwrap();
584
        assert_eq!(content_type.to_string(), "text/html".to_string());
585
        assert_eq!(reader.cursor().index, CharPos(22));
586

            
587
        let mut reader = Reader::new(
588
            "file,{{some_file}}; application/vnd.openxmlformats-officedocument.wordprocessingml.document # comment",
589
        );
590
        let file_value = crate::parser::sections::file_value(&mut reader).unwrap();
591
        let content_type = file_value.content_type.unwrap();
592
        assert_eq!(
593
            content_type.to_string(),
594
            "application/vnd.openxmlformats-officedocument.wordprocessingml.document".to_string()
595
        );
596
        assert_eq!(reader.cursor().index, CharPos(91));
597

            
598
        let mut reader = Reader::new("file,{{some_file}}; {{some_content_type}} # comment");
599
        let file_value = crate::parser::sections::file_value(&mut reader).unwrap();
600
        let content_type = file_value.content_type.unwrap();
601
        assert_eq!(
602
            content_type.to_string(),
603
            "{{some_content_type}}".to_string()
604
        );
605
        assert_eq!(reader.cursor().index, CharPos(41));
606
    }
607

            
608
    #[test]
609
    fn test_capture() {
610
        let mut reader = Reader::new("url: header \"Location\"");
611
        let capture0 = capture(&mut reader).unwrap();
612

            
613
        assert_eq!(
614
            capture0,
615
            Capture {
616
                line_terminators: vec![],
617
                space0: Whitespace {
618
                    value: String::new(),
619
                    source_info: SourceInfo {
620
                        start: Pos::new(1, 1),
621
                        end: Pos::new(1, 1),
622
                    },
623
                },
624
                name: Template::new(
625
                    None,
626
                    vec![TemplateElement::String {
627
                        value: "url".to_string(),
628
                        source: "url".to_source(),
629
                    }],
630
                    SourceInfo::new(Pos::new(1, 1), Pos::new(1, 4))
631
                ),
632
                space1: Whitespace {
633
                    value: String::new(),
634
                    source_info: SourceInfo {
635
                        start: Pos::new(1, 4),
636
                        end: Pos::new(1, 4),
637
                    }
638
                },
639
                space2: Whitespace {
640
                    value: " ".to_string(),
641
                    source_info: SourceInfo {
642
                        start: Pos::new(1, 5),
643
                        end: Pos::new(1, 6),
644
                    }
645
                },
646
                query: Query {
647
                    source_info: SourceInfo::new(Pos::new(1, 6), Pos::new(1, 23)),
648
                    value: QueryValue::Header {
649
                        space0: Whitespace {
650
                            value: String::from(" "),
651
                            source_info: SourceInfo::new(Pos::new(1, 12), Pos::new(1, 13)),
652
                        },
653
                        name: Template::new(
654
                            Some('"'),
655
                            vec![TemplateElement::String {
656
                                value: "Location".to_string(),
657
                                source: "Location".to_source(),
658
                            }],
659
                            SourceInfo::new(Pos::new(1, 13), Pos::new(1, 23))
660
                        )
661
                    }
662
                },
663
                filters: vec![],
664
                space3: Whitespace {
665
                    value: String::new(),
666
                    source_info: SourceInfo::new(Pos::new(1, 23), Pos::new(1, 23)),
667
                },
668
                redacted: false,
669
                line_terminator0: LineTerminator {
670
                    space0: Whitespace {
671
                        value: String::new(),
672
                        source_info: SourceInfo::new(Pos::new(1, 23), Pos::new(1, 23)),
673
                    },
674
                    comment: None,
675
                    newline: Whitespace {
676
                        value: String::new(),
677
                        source_info: SourceInfo::new(Pos::new(1, 23), Pos::new(1, 23)),
678
                    },
679
                },
680
            }
681
        );
682

            
683
        let mut reader = Reader::new("url: header \"Token\"    redact");
684
        let capture0 = capture(&mut reader).unwrap();
685
        assert_eq!(
686
            capture0,
687
            Capture {
688
                line_terminators: vec![],
689
                space0: Whitespace {
690
                    value: String::new(),
691
                    source_info: SourceInfo {
692
                        start: Pos::new(1, 1),
693
                        end: Pos::new(1, 1),
694
                    },
695
                },
696
                name: Template::new(
697
                    None,
698
                    vec![TemplateElement::String {
699
                        value: "url".to_string(),
700
                        source: "url".to_source(),
701
                    }],
702
                    SourceInfo::new(Pos::new(1, 1), Pos::new(1, 4))
703
                ),
704
                space1: Whitespace {
705
                    value: String::new(),
706
                    source_info: SourceInfo {
707
                        start: Pos::new(1, 4),
708
                        end: Pos::new(1, 4),
709
                    }
710
                },
711
                space2: Whitespace {
712
                    value: " ".to_string(),
713
                    source_info: SourceInfo {
714
                        start: Pos::new(1, 5),
715
                        end: Pos::new(1, 6),
716
                    }
717
                },
718
                query: Query {
719
                    source_info: SourceInfo::new(Pos::new(1, 6), Pos::new(1, 20)),
720
                    value: QueryValue::Header {
721
                        space0: Whitespace {
722
                            value: String::from(" "),
723
                            source_info: SourceInfo::new(Pos::new(1, 12), Pos::new(1, 13)),
724
                        },
725
                        name: Template::new(
726
                            Some('"'),
727
                            vec![TemplateElement::String {
728
                                value: "Token".to_string(),
729
                                source: "Token".to_source(),
730
                            }],
731
                            SourceInfo::new(Pos::new(1, 13), Pos::new(1, 20))
732
                        )
733
                    }
734
                },
735
                filters: vec![],
736
                space3: Whitespace {
737
                    value: "    ".to_string(),
738
                    source_info: SourceInfo::new(Pos::new(1, 20), Pos::new(1, 24)),
739
                },
740
                redacted: true,
741
                line_terminator0: LineTerminator {
742
                    space0: Whitespace {
743
                        value: String::new(),
744
                        source_info: SourceInfo::new(Pos::new(1, 30), Pos::new(1, 30)),
745
                    },
746
                    comment: None,
747
                    newline: Whitespace {
748
                        value: String::new(),
749
                        source_info: SourceInfo::new(Pos::new(1, 30), Pos::new(1, 30)),
750
                    },
751
                },
752
            }
753
        );
754
    }
755

            
756
    #[test]
757
    fn test_capture_with_filter() {
758
        let mut reader = Reader::new("token: header \"Location\" regex \"token=(.*)\"");
759
        let capture0 = capture(&mut reader).unwrap();
760

            
761
        assert_eq!(
762
            capture0.query,
763
            Query {
764
                source_info: SourceInfo::new(Pos::new(1, 8), Pos::new(1, 25)),
765
                value: QueryValue::Header {
766
                    space0: Whitespace {
767
                        value: String::from(" "),
768
                        source_info: SourceInfo::new(Pos::new(1, 14), Pos::new(1, 15)),
769
                    },
770
                    name: Template::new(
771
                        Some('"'),
772
                        vec![TemplateElement::String {
773
                            value: "Location".to_string(),
774
                            source: "Location".to_source(),
775
                        }],
776
                        SourceInfo::new(Pos::new(1, 15), Pos::new(1, 25))
777
                    )
778
                }
779
            }
780
        );
781
        assert_eq!(reader.cursor().index, CharPos(43));
782
    }
783

            
784
    #[test]
785
    fn test_capture_with_filter_error() {
786
        let mut reader = Reader::new("token: header \"Location\" regex ");
787
        let error = capture(&mut reader).err().unwrap();
788
        assert_eq!(
789
            error.pos,
790
            Pos {
791
                line: 1,
792
                column: 32,
793
            }
794
        );
795
        assert_eq!(
796
            error.kind,
797
            ParseErrorKind::Expecting {
798
                value: "\" or /".to_string()
799
            }
800
        );
801
        assert!(!error.recoverable);
802

            
803
        let mut reader = Reader::new("token: header \"Location\" xxx");
804
        let error = capture(&mut reader).err().unwrap();
805
        assert_eq!(
806
            error.pos,
807
            Pos {
808
                line: 1,
809
                column: 26,
810
            }
811
        );
812
        assert_eq!(
813
            error.kind,
814
            ParseErrorKind::Expecting {
815
                value: "line_terminator".to_string()
816
            }
817
        );
818
        assert!(!error.recoverable);
819
    }
820

            
821
    #[test]
822
    fn test_capture_with_comment() {
823
        let mut reader = Reader::new("name: jsonpath \"$.name\"          # name");
824
        let capture0 = capture(&mut reader).unwrap();
825
        assert!(capture0.filters.is_empty());
826
        assert!(capture0.space3.value.is_empty());
827
        assert_eq!(capture0.line_terminator0.space0.as_str(), "          ");
828
    }
829

            
830
    #[test]
831
    fn test_assert() {
832
        let mut reader = Reader::new("header \"Location\" == \"https://google.fr\"");
833
        let assert0 = assert(&mut reader).unwrap();
834

            
835
        assert_eq!(
836
            assert0.query,
837
            Query {
838
                source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 18)),
839
                value: QueryValue::Header {
840
                    space0: Whitespace {
841
                        value: String::from(" "),
842
                        source_info: SourceInfo::new(Pos::new(1, 7), Pos::new(1, 8)),
843
                    },
844
                    name: Template::new(
845
                        Some('"'),
846
                        vec![TemplateElement::String {
847
                            value: "Location".to_string(),
848
                            source: "Location".to_source(),
849
                        }],
850
                        SourceInfo::new(Pos::new(1, 8), Pos::new(1, 18)),
851
                    )
852
                }
853
            }
854
        );
855
    }
856

            
857
    #[test]
858
    fn test_assert_jsonpath() {
859
        let mut reader = Reader::new("jsonpath \"$.errors\" == 5");
860

            
861
        assert_eq!(
862
            assert(&mut reader).unwrap().predicate,
863
            Predicate {
864
                not: false,
865
                space0: Whitespace {
866
                    value: String::new(),
867
                    source_info: SourceInfo::new(Pos::new(1, 21), Pos::new(1, 21)),
868
                },
869
                predicate_func: PredicateFunc {
870
                    source_info: SourceInfo::new(Pos::new(1, 21), Pos::new(1, 25)),
871
                    value: PredicateFuncValue::Equal {
872
                        space0: Whitespace {
873
                            value: String::from(" "),
874
                            source_info: SourceInfo::new(Pos::new(1, 23), Pos::new(1, 24)),
875
                        },
876
                        value: PredicateValue::Number(Number::Integer(I64::new(
877
                            5,
878
                            "5".to_source()
879
                        ))),
880
                    },
881
                },
882
            }
883
        );
884
    }
885

            
886
    #[test]
887
    fn test_basicauth_section() {
888
        let mut reader = Reader::new("[BasicAuth]\nuser:password\n\nHTTP 200\n");
889

            
890
        assert_eq!(
891
            request_section(&mut reader).unwrap(),
892
            Section {
893
                line_terminators: vec![],
894
                space0: Whitespace {
895
                    value: String::new(),
896
                    source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 1)),
897
                },
898
                line_terminator0: LineTerminator {
899
                    space0: Whitespace {
900
                        value: String::new(),
901
                        source_info: SourceInfo::new(Pos::new(1, 12), Pos::new(1, 12)),
902
                    },
903
                    comment: None,
904
                    newline: Whitespace {
905
                        value: String::from("\n"),
906
                        source_info: SourceInfo::new(Pos::new(1, 12), Pos::new(2, 1)),
907
                    },
908
                },
909
                value: SectionValue::BasicAuth(Some(KeyValue {
910
                    line_terminators: vec![],
911
                    space0: Whitespace {
912
                        value: String::new(),
913
                        source_info: SourceInfo::new(Pos::new(2, 1), Pos::new(2, 1))
914
                    },
915
                    key: Template::new(
916
                        None,
917
                        vec![TemplateElement::String {
918
                            value: "user".to_string(),
919
                            source: "user".to_source()
920
                        }],
921
                        SourceInfo::new(Pos::new(2, 1), Pos::new(2, 5)),
922
                    ),
923
                    space1: Whitespace {
924
                        value: String::new(),
925
                        source_info: SourceInfo::new(Pos::new(2, 5), Pos::new(2, 5))
926
                    },
927
                    space2: Whitespace {
928
                        value: String::new(),
929
                        source_info: SourceInfo::new(Pos::new(2, 6), Pos::new(2, 6))
930
                    },
931
                    value: Template::new(
932
                        None,
933
                        vec![TemplateElement::String {
934
                            value: "password".to_string(),
935
                            source: "password".to_source()
936
                        }],
937
                        SourceInfo::new(Pos::new(2, 6), Pos::new(2, 14)),
938
                    ),
939
                    line_terminator0: LineTerminator {
940
                        space0: Whitespace {
941
                            value: String::new(),
942
                            source_info: SourceInfo::new(Pos::new(2, 14), Pos::new(2, 14))
943
                        },
944
                        comment: None,
945
                        newline: Whitespace {
946
                            value: "\n".to_string(),
947
                            source_info: SourceInfo::new(Pos::new(2, 14), Pos::new(3, 1))
948
                        },
949
                    },
950
                })),
951
                source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 12)),
952
            }
953
        );
954
        assert_eq!(reader.cursor().pos, Pos { line: 3, column: 1 });
955

            
956
        let mut reader = Reader::new("[BasicAuth]\nHTTP 200\n");
957
        assert_eq!(
958
            request_section(&mut reader).unwrap(),
959
            Section {
960
                line_terminators: vec![],
961
                space0: Whitespace {
962
                    value: String::new(),
963
                    source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 1)),
964
                },
965
                line_terminator0: LineTerminator {
966
                    space0: Whitespace {
967
                        value: String::new(),
968
                        source_info: SourceInfo::new(Pos::new(1, 12), Pos::new(1, 12)),
969
                    },
970
                    comment: None,
971
                    newline: Whitespace {
972
                        value: String::from("\n"),
973
                        source_info: SourceInfo::new(Pos::new(1, 12), Pos::new(2, 1)),
974
                    },
975
                },
976
                value: SectionValue::BasicAuth(None),
977
                source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 12)),
978
            }
979
        );
980
        assert_eq!(reader.cursor().pos, Pos { line: 2, column: 1 });
981
    }
982
}