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::reader::{Pos, Reader};
24

            
25
use super::error::{ParseError, ParseErrorKind};
26
use super::filter::filters;
27
use super::predicate::predicate;
28
use super::primitives::{
29
    key_value, line_terminator, literal, one_or_more_spaces, optional_line_terminators,
30
    try_literal, zero_or_more_spaces,
31
};
32
use super::query::query;
33
use super::string::unquoted_template;
34
use super::{ParseResult, filename, key_string, option};
35

            
36
14000
pub fn request_sections(reader: &mut Reader) -> ParseResult<Vec<Section>> {
37
14000
    let sections = zero_or_more(request_section, reader)?;
38
13956
    Ok(sections)
39
}
40

            
41
12720
pub fn response_sections(reader: &mut Reader) -> ParseResult<Vec<Section>> {
42
12720
    let sections = zero_or_more(response_section, reader)?;
43
12672
    Ok(sections)
44
}
45

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

            
53
3208
    let line_terminator0 = line_terminator(reader)?;
54
3208
    let value = match name.as_str() {
55
3208
        "Query" => section_value_query_params(reader, true)?,
56
3028
        "QueryStringParams" => section_value_query_params(reader, false)?,
57
2876
        "BasicAuth" => section_value_basic_auth(reader)?,
58
2796
        "Form" => section_value_form_params(reader, true)?,
59
2740
        "FormParams" => section_value_form_params(reader, false)?,
60
2668
        "Multipart" => section_value_multipart_form_data(reader, true)?,
61
2636
        "MultipartFormData" => section_value_multipart_form_data(reader, false)?,
62
2532
        "Cookies" => section_value_cookies(reader)?,
63
2220
        "Options" => section_value_options(reader)?,
64
        _ => {
65
8
            let kind = ParseErrorKind::RequestSectionName { name: name.clone() };
66
8
            let pos = Pos::new(start.pos.line, start.pos.column + 1);
67
8
            return Err(ParseError::new(pos, false, kind));
68
        }
69
    };
70

            
71
3164
    Ok(Section {
72
3164
        line_terminators,
73
3164
        space0,
74
3164
        line_terminator0,
75
3164
        value,
76
3164
        source_info,
77
3164
    })
78
}
79

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

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

            
99
7684
    Ok(Section {
100
7684
        line_terminators,
101
7684
        space0,
102
7684
        line_terminator0,
103
7684
        value,
104
7684
        source_info,
105
7684
    })
106
}
107

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

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

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

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

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

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

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

            
156
7212
fn section_value_asserts(reader: &mut Reader) -> ParseResult<SectionValue> {
157
7212
    let asserts = zero_or_more(assert, reader)?;
158
7164
    Ok(SectionValue::Asserts(asserts))
159
}
160

            
161
2212
fn section_value_options(reader: &mut Reader) -> ParseResult<SectionValue> {
162
2212
    let options = zero_or_more(option::parse, reader)?;
163
2180
    Ok(SectionValue::Options(options))
164
}
165

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

            
187
412
fn multipart_param(reader: &mut Reader) -> ParseResult<MultipartParam> {
188
412
    let save = reader.cursor();
189
412
    match file_param(reader) {
190
176
        Ok(f) => Ok(MultipartParam::FilenameParam(f)),
191
236
        Err(e) => {
192
236
            if e.recoverable {
193
232
                reader.seek(save);
194
232
                let param = key_value(reader)?;
195
100
                Ok(MultipartParam::Param(param))
196
            } else {
197
4
                Err(e)
198
            }
199
        }
200
    }
201
}
202

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

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

            
257
176
    Ok(FilenameValue {
258
176
        space0,
259
176
        filename: f,
260
176
        space1,
261
176
        space2,
262
176
        content_type,
263
176
    })
264
}
265

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

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

            
301
1824
fn redacted(reader: &mut Reader) -> ParseResult<Whitespace> {
302
1824
    let space = zero_or_more_spaces(reader)?;
303
1824
    try_literal("redact", reader)?;
304
80
    Ok(space)
305
}
306

            
307
27580
fn assert(reader: &mut Reader) -> ParseResult<Assert> {
308
27580
    let line_terminators = optional_line_terminators(reader)?;
309
27580
    let space0 = zero_or_more_spaces(reader)?;
310
27580
    let query0 = query(reader)?;
311
21072
    let filters = filters(reader)?;
312
21068
    let space1 = one_or_more_spaces(reader)?;
313
21068
    let predicate0 = predicate(reader)?;
314

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

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

            
337
    use super::*;
338

            
339
    #[test]
340
    fn test_section_name() {
341
        let mut reader = Reader::new("[SectionA]");
342
        assert_eq!(section_name(&mut reader).unwrap(), String::from("SectionA"));
343

            
344
        let mut reader = Reader::new("[]");
345
        assert!(section_name(&mut reader).err().unwrap().recoverable);
346
    }
347

            
348
    #[test]
349
    fn test_asserts_section() {
350
        let mut reader = Reader::new("[Asserts]\nheader \"Location\" == \"https://google.fr\"\n");
351

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

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

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

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

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

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

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

            
576
        let mut reader = Reader::new("file,------; text/plain; charset=us-ascii");
577
        let file_value = sections::file_value(&mut reader).unwrap();
578
        let content_type = file_value.content_type.unwrap();
579
        assert_eq!(
580
            content_type.to_string(),
581
            "text/plain; charset=us-ascii".to_string()
582
        );
583
        assert_eq!(reader.cursor().index, CharPos(41));
584

            
585
        let mut reader = Reader::new("file,******; text/html # comment");
586
        let file_value = sections::file_value(&mut reader).unwrap();
587
        let content_type = file_value.content_type.unwrap();
588
        assert_eq!(content_type.to_string(), "text/html".to_string());
589
        assert_eq!(reader.cursor().index, CharPos(22));
590

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

            
602
        let mut reader = Reader::new("file,{{some_file}}; {{some_content_type}} # comment");
603
        let file_value = sections::file_value(&mut reader).unwrap();
604
        let content_type = file_value.content_type.unwrap();
605
        assert_eq!(
606
            content_type.to_string(),
607
            "{{some_content_type}}".to_string()
608
        );
609
        assert_eq!(reader.cursor().index, CharPos(41));
610
    }
611

            
612
    #[test]
613
    fn test_capture() {
614
        let mut reader = Reader::new("url: header \"Location\"");
615
        let capture0 = capture(&mut reader).unwrap();
616

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

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

            
760
    #[test]
761
    fn test_capture_with_filter() {
762
        let mut reader = Reader::new("token: header \"Location\" regex \"token=(.*)\"");
763
        let capture0 = capture(&mut reader).unwrap();
764

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

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

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

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

            
834
    #[test]
835
    fn test_assert() {
836
        let mut reader = Reader::new("header \"Location\" == \"https://google.fr\"");
837
        let assert0 = assert(&mut reader).unwrap();
838

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

            
861
    #[test]
862
    fn test_assert_jsonpath() {
863
        let mut reader = Reader::new("jsonpath \"$.errors\" == 5");
864

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

            
890
    #[test]
891
    fn test_basicauth_section() {
892
        let mut reader = Reader::new("[BasicAuth]\nuser:password\n\nHTTP 200\n");
893

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

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