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, 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::{filename, key_string, option, ParseError, ParseErrorKind, ParseResult};
32
use crate::reader::{Pos, Reader};
33

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

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

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

            
51
3055
    let line_terminator0 = line_terminator(reader)?;
52
3055
    let value = match name.as_str() {
53
3055
        "Query" => section_value_query_params(reader, true)?,
54
2895
        "QueryStringParams" => section_value_query_params(reader, false)?,
55
2700
        "BasicAuth" => section_value_basic_auth(reader)?,
56
2620
        "Form" => section_value_form_params(reader, true)?,
57
2585
        "FormParams" => section_value_form_params(reader, false)?,
58
2500
        "Multipart" => section_value_multipart_form_data(reader, true)?,
59
2465
        "MultipartFormData" => section_value_multipart_form_data(reader, false)?,
60
2345
        "Cookies" => section_value_cookies(reader)?,
61
2060
        "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
3005
    Ok(Section {
70
3005
        line_terminators,
71
3005
        space0,
72
3005
        line_terminator0,
73
3005
        value,
74
3005
        source_info,
75
3005
    })
76
}
77

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

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

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

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

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

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

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

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

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

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

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

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

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

            
201
490
fn file_param(reader: &mut Reader) -> ParseResult<FilenameParam> {
202
490
    let line_terminators = optional_line_terminators(reader)?;
203
490
    let space0 = zero_or_more_spaces(reader)?;
204
490
    let key = recover(key_string::parse, reader)?;
205
420
    let space1 = zero_or_more_spaces(reader)?;
206
504
    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
2490
fn capture(reader: &mut Reader) -> ParseResult<Capture> {
265
2490
    let line_terminators = optional_line_terminators(reader)?;
266
2490
    let space0 = zero_or_more_spaces(reader)?;
267
2490
    let name = recover(key_string::parse, reader)?;
268
2135
    let space1 = zero_or_more_spaces(reader)?;
269
2562
    recover(|p1| literal(":", p1), reader)?;
270
2015
    let space2 = zero_or_more_spaces(reader)?;
271
2015
    let q = query(reader)?;
272
2015
    let filters = filters(reader)?;
273
2015
    let (redacted, space3) = if let Some(ws) = optional(redacted, reader)? {
274
75
        (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
1940
        let pos = reader.cursor().pos;
279
1940
        let value = String::new();
280
1940
        let source_info = SourceInfo::new(pos, pos);
281
1940
        (false, Whitespace { value, source_info })
282
    };
283

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

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

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

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

            
325
#[cfg(test)]
326
mod tests {
327
    use super::*;
328
    use crate::ast::{
329
        KeyValue, LineTerminator, Number, Predicate, PredicateFunc, PredicateFuncValue,
330
        PredicateValue, Query, QueryValue, Template, TemplateElement, I64,
331
    };
332
    use crate::reader::CharPos;
333
    use crate::typing::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("file,{{some_file}}; application/vnd.openxmlformats-officedocument.wordprocessingml.document # comment");
588
        let file_value = crate::parser::sections::file_value(&mut reader).unwrap();
589
        let content_type = file_value.content_type.unwrap();
590
        assert_eq!(
591
            content_type.to_string(),
592
            "application/vnd.openxmlformats-officedocument.wordprocessingml.document".to_string()
593
        );
594
        assert_eq!(reader.cursor().index, CharPos(91));
595

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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