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 crate::ast::{
19
    Assert, Capture, Cookie, FileParam, FileValue, 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::{filename, key_string, option, ParseError, ParseErrorKind, ParseResult};
32
use crate::reader::{Pos, Reader};
33

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

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

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

            
51
2430
    let line_terminator0 = line_terminator(reader)?;
52
2430
    let value = match name.as_str() {
53
2430
        "Query" => section_value_query_params(reader, true)?,
54
2315
        "QueryStringParams" => section_value_query_params(reader, false)?,
55
2090
        "BasicAuth" => section_value_basic_auth(reader)?,
56
2015
        "Form" => section_value_form_params(reader, true)?,
57
1980
        "FormParams" => section_value_form_params(reader, false)?,
58
1905
        "Multipart" => section_value_multipart_form_data(reader, true)?,
59
1870
        "MultipartFormData" => section_value_multipart_form_data(reader, false)?,
60
1760
        "Cookies" => section_value_cookies(reader)?,
61
1675
        "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
2380
    Ok(Section {
70
2380
        line_terminators,
71
2380
        space0,
72
2380
        line_terminator0,
73
2380
        value,
74
2380
        source_info,
75
2380
    })
76
}
77

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

            
86
8455
    let line_terminator0 = line_terminator(reader)?;
87
8455
    let value = match name.as_str() {
88
8455
        "Captures" => section_value_captures(reader)?,
89
7995
        "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
8400
    Ok(Section {
98
8400
        line_terminators,
99
8400
        space0,
100
8400
        line_terminator0,
101
8400
        value,
102
8400
        source_info,
103
8400
    })
104
}
105

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

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

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

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

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

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

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

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

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

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

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

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

            
221
325
fn file_value(reader: &mut Reader) -> ParseResult<FileValue> {
222
325
    try_literal("file,", reader)?;
223
205
    let space0 = zero_or_more_spaces(reader)?;
224
205
    let f = filename::parse(reader)?;
225
205
    let space1 = zero_or_more_spaces(reader)?;
226
205
    literal(";", reader)?;
227
205
    let save = reader.cursor();
228
205
    let (space2, content_type) = match line_terminator(reader) {
229
        Ok(_) => {
230
135
            reader.seek(save);
231
135
            let space2 = Whitespace {
232
135
                value: String::new(),
233
135
                source_info: SourceInfo {
234
135
                    start: save.pos,
235
135
                    end: save.pos,
236
135
                },
237
135
            };
238
135
            (space2, None)
239
        }
240
        Err(_) => {
241
70
            reader.seek(save);
242
70
            let space2 = zero_or_more_spaces(reader)?;
243
70
            let content_type = file_content_type(reader)?;
244
65
            (space2, Some(content_type))
245
        }
246
    };
247

            
248
200
    Ok(FileValue {
249
200
        space0,
250
200
        filename: f,
251
200
        space1,
252
200
        space2,
253
200
        content_type,
254
200
    })
255
}
256

            
257
70
fn file_content_type(reader: &mut Reader) -> ParseResult<String> {
258
70
    let start = reader.cursor();
259
70
    let mut buf = String::new();
260
70
    let mut spaces = String::new();
261
70
    let mut save = reader.cursor();
262
755
    while let Some(c) = reader.read() {
263
755
        if c.is_alphanumeric() || c == '/' || c == ';' || c == '=' || c == '-' {
264
685
            buf.push_str(spaces.as_str());
265
685
            spaces = String::new();
266
685
            buf.push(c);
267
685
            save = reader.cursor();
268
685
        } else if c == ' ' {
269
            spaces.push(' ');
270
        } else {
271
70
            break;
272
        }
273
    }
274

            
275
70
    reader.seek(save);
276
70
    if buf.is_empty() {
277
5
        return Err(ParseError::new(
278
5
            start.pos,
279
5
            false,
280
5
            ParseErrorKind::FileContentType,
281
5
        ));
282
    }
283
65
    Ok(buf)
284
}
285

            
286
2335
fn capture(reader: &mut Reader) -> ParseResult<Capture> {
287
2335
    let line_terminators = optional_line_terminators(reader)?;
288
2335
    let space0 = zero_or_more_spaces(reader)?;
289
2335
    let name = recover(key_string::parse, reader)?;
290
2015
    let space1 = zero_or_more_spaces(reader)?;
291
2418
    recover(|p1| literal(":", p1), reader)?;
292
1895
    let space2 = zero_or_more_spaces(reader)?;
293
1895
    let q = query(reader)?;
294
1895
    let filters = filters(reader)?;
295
1895
    let space3 = zero_or_more_spaces(reader)?;
296
1895
    let redact = try_literal("redact", reader).is_ok();
297
1895
    let line_terminator0 = line_terminator(reader)?;
298
1895
    Ok(Capture {
299
1895
        line_terminators,
300
1895
        space0,
301
1895
        name,
302
1895
        space1,
303
1895
        space2,
304
1895
        query: q,
305
1895
        filters,
306
1895
        space3,
307
1895
        redact,
308
1895
        line_terminator0,
309
1895
    })
310
}
311

            
312
25620
fn assert(reader: &mut Reader) -> ParseResult<Assert> {
313
25620
    let line_terminators = optional_line_terminators(reader)?;
314
25620
    let space0 = zero_or_more_spaces(reader)?;
315
25620
    let query0 = query(reader)?;
316
18260
    let filters = filters(reader)?;
317
18260
    let space1 = one_or_more_spaces(reader)?;
318
18260
    let predicate0 = predicate(reader)?;
319

            
320
18230
    let line_terminator0 = line_terminator(reader)?;
321
18230
    Ok(Assert {
322
18230
        line_terminators,
323
18230
        space0,
324
18230
        query: query0,
325
18230
        filters,
326
18230
        space1,
327
18230
        predicate: predicate0,
328
18230
        line_terminator0,
329
18230
    })
330
}
331

            
332
#[cfg(test)]
333
mod tests {
334
    use super::*;
335
    use crate::ast::{
336
        KeyValue, LineTerminator, Number, Predicate, PredicateFunc, PredicateFuncValue,
337
        PredicateValue, Query, QueryValue, Template, TemplateElement, I64,
338
    };
339

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

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

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

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

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

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

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

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

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

            
562
    #[test]
563
    fn test_file_content_type() {
564
        let mut reader = Reader::new("text/html");
565
        assert_eq!(
566
            file_content_type(&mut reader).unwrap(),
567
            "text/html".to_string()
568
        );
569
        assert_eq!(reader.cursor().index, 9);
570

            
571
        let mut reader = Reader::new("text/plain; charset=us-ascii");
572
        assert_eq!(
573
            file_content_type(&mut reader).unwrap(),
574
            "text/plain; charset=us-ascii".to_string()
575
        );
576
        assert_eq!(reader.cursor().index, 28);
577

            
578
        let mut reader = Reader::new("text/html # comment");
579
        assert_eq!(
580
            file_content_type(&mut reader).unwrap(),
581
            "text/html".to_string()
582
        );
583
        assert_eq!(reader.cursor().index, 9);
584
    }
585

            
586
    #[test]
587
    fn test_capture() {
588
        let mut reader = Reader::new("url: header \"Location\"");
589
        let capture0 = capture(&mut reader).unwrap();
590

            
591
        assert_eq!(
592
            capture0.name,
593
            Template {
594
                delimiter: None,
595
                elements: vec![TemplateElement::String {
596
                    value: "url".to_string(),
597
                    encoded: "url".to_string(),
598
                }],
599
                source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 4)),
600
            },
601
        );
602
        assert_eq!(
603
            capture0.query,
604
            Query {
605
                source_info: SourceInfo::new(Pos::new(1, 6), Pos::new(1, 23)),
606
                value: QueryValue::Header {
607
                    space0: Whitespace {
608
                        value: String::from(" "),
609
                        source_info: SourceInfo::new(Pos::new(1, 12), Pos::new(1, 13)),
610
                    },
611
                    name: Template {
612
                        delimiter: Some('"'),
613
                        elements: vec![TemplateElement::String {
614
                            value: "Location".to_string(),
615
                            encoded: "Location".to_string(),
616
                        }],
617
                        source_info: SourceInfo::new(Pos::new(1, 13), Pos::new(1, 23)),
618
                    },
619
                },
620
            }
621
        );
622

            
623
        let mut reader = Reader::new("url: header \"Token\" redact");
624
        let capture0 = capture(&mut reader).unwrap();
625
        assert!(capture0.redact);
626
    }
627

            
628
    #[test]
629
    fn test_capture_with_filter() {
630
        let mut reader = Reader::new("token: header \"Location\" regex \"token=(.*)\"");
631
        let capture0 = capture(&mut reader).unwrap();
632

            
633
        assert_eq!(
634
            capture0.query,
635
            Query {
636
                source_info: SourceInfo::new(Pos::new(1, 8), Pos::new(1, 25)),
637
                value: QueryValue::Header {
638
                    space0: Whitespace {
639
                        value: String::from(" "),
640
                        source_info: SourceInfo::new(Pos::new(1, 14), Pos::new(1, 15)),
641
                    },
642
                    name: Template {
643
                        delimiter: Some('"'),
644
                        elements: vec![TemplateElement::String {
645
                            value: "Location".to_string(),
646
                            encoded: "Location".to_string(),
647
                        }],
648
                        source_info: SourceInfo::new(Pos::new(1, 15), Pos::new(1, 25)),
649
                    },
650
                },
651
            }
652
        );
653
        assert_eq!(reader.cursor().index, 43);
654
    }
655

            
656
    #[test]
657
    fn test_capture_with_filter_error() {
658
        let mut reader = Reader::new("token: header \"Location\" regex ");
659
        let error = capture(&mut reader).err().unwrap();
660
        assert_eq!(
661
            error.pos,
662
            Pos {
663
                line: 1,
664
                column: 32,
665
            }
666
        );
667
        assert_eq!(
668
            error.kind,
669
            ParseErrorKind::Expecting {
670
                value: "\" or /".to_string()
671
            }
672
        );
673
        assert!(!error.recoverable);
674

            
675
        let mut reader = Reader::new("token: header \"Location\" xxx");
676
        let error = capture(&mut reader).err().unwrap();
677
        assert_eq!(
678
            error.pos,
679
            Pos {
680
                line: 1,
681
                column: 26,
682
            }
683
        );
684
        assert_eq!(
685
            error.kind,
686
            ParseErrorKind::Expecting {
687
                value: "line_terminator".to_string()
688
            }
689
        );
690
        assert!(!error.recoverable);
691
    }
692

            
693
    #[test]
694
    fn test_assert() {
695
        let mut reader = Reader::new("header \"Location\" == \"https://google.fr\"");
696
        let assert0 = assert(&mut reader).unwrap();
697

            
698
        assert_eq!(
699
            assert0.query,
700
            Query {
701
                source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 18)),
702
                value: QueryValue::Header {
703
                    space0: Whitespace {
704
                        value: String::from(" "),
705
                        source_info: SourceInfo::new(Pos::new(1, 7), Pos::new(1, 8)),
706
                    },
707
                    name: Template {
708
                        delimiter: Some('"'),
709
                        elements: vec![TemplateElement::String {
710
                            value: "Location".to_string(),
711
                            encoded: "Location".to_string(),
712
                        }],
713
                        source_info: SourceInfo::new(Pos::new(1, 8), Pos::new(1, 18)),
714
                    },
715
                },
716
            }
717
        );
718
    }
719

            
720
    #[test]
721
    fn test_assert_jsonpath() {
722
        let mut reader = Reader::new("jsonpath \"$.errors\" == 5");
723

            
724
        assert_eq!(
725
            assert(&mut reader).unwrap().predicate,
726
            Predicate {
727
                not: false,
728
                space0: Whitespace {
729
                    value: String::new(),
730
                    source_info: SourceInfo::new(Pos::new(1, 21), Pos::new(1, 21)),
731
                },
732
                predicate_func: PredicateFunc {
733
                    source_info: SourceInfo::new(Pos::new(1, 21), Pos::new(1, 25)),
734
                    value: PredicateFuncValue::Equal {
735
                        space0: Whitespace {
736
                            value: String::from(" "),
737
                            source_info: SourceInfo::new(Pos::new(1, 23), Pos::new(1, 24)),
738
                        },
739
                        value: PredicateValue::Number(Number::Integer(I64::new(
740
                            5,
741
                            "5".to_string()
742
                        ))),
743
                    },
744
                },
745
            }
746
        );
747
    }
748

            
749
    #[test]
750
    fn test_basicauth_section() {
751
        let mut reader = Reader::new("[BasicAuth]\nuser:password\n\nHTTP 200\n");
752

            
753
        assert_eq!(
754
            request_section(&mut reader).unwrap(),
755
            Section {
756
                line_terminators: vec![],
757
                space0: Whitespace {
758
                    value: String::new(),
759
                    source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 1)),
760
                },
761
                line_terminator0: LineTerminator {
762
                    space0: Whitespace {
763
                        value: String::new(),
764
                        source_info: SourceInfo::new(Pos::new(1, 12), Pos::new(1, 12)),
765
                    },
766
                    comment: None,
767
                    newline: Whitespace {
768
                        value: String::from("\n"),
769
                        source_info: SourceInfo::new(Pos::new(1, 12), Pos::new(2, 1)),
770
                    },
771
                },
772
                value: SectionValue::BasicAuth(Some(KeyValue {
773
                    line_terminators: vec![],
774
                    space0: Whitespace {
775
                        value: String::new(),
776
                        source_info: SourceInfo::new(Pos::new(2, 1), Pos::new(2, 1))
777
                    },
778
                    key: Template {
779
                        delimiter: None,
780
                        elements: vec![TemplateElement::String {
781
                            value: "user".to_string(),
782
                            encoded: "user".to_string()
783
                        }],
784
                        source_info: SourceInfo::new(Pos::new(2, 1), Pos::new(2, 5)),
785
                    },
786
                    space1: Whitespace {
787
                        value: String::new(),
788
                        source_info: SourceInfo::new(Pos::new(2, 5), Pos::new(2, 5))
789
                    },
790
                    space2: Whitespace {
791
                        value: String::new(),
792
                        source_info: SourceInfo::new(Pos::new(2, 6), Pos::new(2, 6))
793
                    },
794
                    value: Template {
795
                        delimiter: None,
796
                        elements: vec![TemplateElement::String {
797
                            value: "password".to_string(),
798
                            encoded: "password".to_string()
799
                        }],
800
                        source_info: SourceInfo::new(Pos::new(2, 6), Pos::new(2, 14)),
801
                    },
802
                    line_terminator0: LineTerminator {
803
                        space0: Whitespace {
804
                            value: String::new(),
805
                            source_info: SourceInfo::new(Pos::new(2, 14), Pos::new(2, 14))
806
                        },
807
                        comment: None,
808
                        newline: Whitespace {
809
                            value: "\n".to_string(),
810
                            source_info: SourceInfo::new(Pos::new(2, 14), Pos::new(3, 1))
811
                        },
812
                    },
813
                })),
814
                source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 12)),
815
            }
816
        );
817
        assert_eq!(reader.cursor().pos, Pos { line: 3, column: 1 });
818

            
819
        let mut reader = Reader::new("[BasicAuth]\nHTTP 200\n");
820
        assert_eq!(
821
            request_section(&mut reader).unwrap(),
822
            Section {
823
                line_terminators: vec![],
824
                space0: Whitespace {
825
                    value: String::new(),
826
                    source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 1)),
827
                },
828
                line_terminator0: LineTerminator {
829
                    space0: Whitespace {
830
                        value: String::new(),
831
                        source_info: SourceInfo::new(Pos::new(1, 12), Pos::new(1, 12)),
832
                    },
833
                    comment: None,
834
                    newline: Whitespace {
835
                        value: String::from("\n"),
836
                        source_info: SourceInfo::new(Pos::new(1, 12), Pos::new(2, 1)),
837
                    },
838
                },
839
                value: SectionValue::BasicAuth(None),
840
                source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 12)),
841
            }
842
        );
843
        assert_eq!(reader.cursor().pos, Pos { line: 2, column: 1 });
844
    }
845
}