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 super::placeholder;
19
use crate::ast::{
20
    is_variable_reserved, BooleanOption, CountOption, DurationOption, EntryOption, NaturalOption,
21
    OptionKind, SourceInfo, VariableDefinition, VariableValue,
22
};
23
use crate::combinator::{choice, non_recover};
24
use crate::parser::duration::duration;
25
use crate::parser::number::{integer, natural, number};
26
use crate::parser::primitives::{
27
    boolean, line_terminator, literal, null, optional_line_terminators, try_literal,
28
    zero_or_more_spaces,
29
};
30
use crate::parser::string::{quoted_template, unquoted_template};
31
use crate::parser::{filename, filename_password, ParseError, ParseErrorKind, ParseResult};
32
use crate::reader::Reader;
33
use crate::typing::Count;
34

            
35
/// Parse an option in an `[Options]` section.
36
5175
pub fn parse(reader: &mut Reader) -> ParseResult<EntryOption> {
37
5175
    let line_terminators = optional_line_terminators(reader)?;
38
5175
    let space0 = zero_or_more_spaces(reader)?;
39
5175
    let start = reader.cursor();
40
5175
    // We accept '_' even if there is no option name with this character. We do this to be able to
41
5175
    // enter the parsing of the option name and to have better error description (ex: 'max-redirs'
42
5175
    // vs 'max_redirs').
43
5175
    let option =
44
39985
        reader.read_while(|c| c.is_ascii_alphanumeric() || c == '-' || c == '.' || c == '_');
45
5175
    let space1 = zero_or_more_spaces(reader)?;
46
5175
    try_literal(":", reader)?;
47
3540
    let space2 = zero_or_more_spaces(reader)?;
48
3540
    let kind = match option.as_str() {
49
3540
        "aws-sigv4" => option_aws_sigv4(reader)?,
50
3500
        "cacert" => option_cacert(reader)?,
51
3440
        "cert" => option_cert(reader)?,
52
3375
        "compressed" => option_compressed(reader)?,
53
2965
        "connect-to" => option_connect_to(reader)?,
54
2900
        "connect-timeout" => option_connect_timeout(reader)?,
55
2870
        "delay" => option_delay(reader)?,
56
2775
        "insecure" => option_insecure(reader)?,
57
2680
        "header" => option_header(reader)?,
58
2625
        "http1.0" => option_http_10(reader)?,
59
2505
        "http1.1" => option_http_11(reader)?,
60
2415
        "http2" => option_http_2(reader)?,
61
2365
        "http3" => option_http_3(reader)?,
62
2335
        "ipv4" => option_ipv4(reader)?,
63
2305
        "ipv6" => option_ipv6(reader)?,
64
2275
        "key" => option_key(reader)?,
65
2225
        "limit-rate" => option_limit_rate(reader)?,
66
2190
        "location" => option_follow_location(reader)?,
67
1790
        "location-trusted" => option_follow_location_trusted(reader)?,
68
1740
        "max-redirs" => option_max_redirect(reader)?,
69
1635
        "netrc" => option_netrc(reader)?,
70
1605
        "netrc-file" => option_netrc_file(reader)?,
71
1570
        "netrc-optional" => option_netrc_optional(reader)?,
72
1540
        "output" => option_output(reader)?,
73
1410
        "path-as-is" => option_path_as_is(reader)?,
74
1375
        "proxy" => option_proxy(reader)?,
75
1305
        "repeat" => option_repeat(reader)?,
76
1225
        "resolve" => option_resolve(reader)?,
77
1175
        "retry" => option_retry(reader)?,
78
1025
        "retry-interval" => option_retry_interval(reader)?,
79
915
        "skip" => option_skip(reader)?,
80
875
        "unix-socket" => option_unix_socket(reader)?,
81
845
        "user" => option_user(reader)?,
82
745
        "variable" => option_variable(reader)?,
83
170
        "verbose" => option_verbose(reader)?,
84
60
        "very-verbose" => option_very_verbose(reader)?,
85
        _ => {
86
10
            return Err(ParseError::new(
87
10
                start.pos,
88
10
                false,
89
10
                ParseErrorKind::InvalidOption(option.to_string()),
90
10
            ))
91
        }
92
    };
93

            
94
3505
    let line_terminator0 = line_terminator(reader)?;
95
3505
    Ok(EntryOption {
96
3505
        line_terminators,
97
3505
        space0,
98
3505
        space1,
99
3505
        space2,
100
3505
        kind,
101
3505
        line_terminator0,
102
3505
    })
103
}
104

            
105
40
fn option_aws_sigv4(reader: &mut Reader) -> ParseResult<OptionKind> {
106
40
    let value = unquoted_template(reader)?;
107
40
    Ok(OptionKind::AwsSigV4(value))
108
}
109

            
110
60
fn option_cacert(reader: &mut Reader) -> ParseResult<OptionKind> {
111
60
    let value = filename::parse(reader)?;
112
55
    Ok(OptionKind::CaCertificate(value))
113
}
114

            
115
65
fn option_cert(reader: &mut Reader) -> ParseResult<OptionKind> {
116
65
    let value = filename_password::parse(reader)?;
117
65
    Ok(OptionKind::ClientCert(value))
118
}
119

            
120
410
fn option_compressed(reader: &mut Reader) -> ParseResult<OptionKind> {
121
410
    let value = non_recover(boolean_option, reader)?;
122
410
    Ok(OptionKind::Compressed(value))
123
}
124

            
125
65
fn option_connect_to(reader: &mut Reader) -> ParseResult<OptionKind> {
126
65
    let value = unquoted_template(reader)?;
127
65
    Ok(OptionKind::ConnectTo(value))
128
}
129

            
130
30
fn option_connect_timeout(reader: &mut Reader) -> ParseResult<OptionKind> {
131
30
    let value = duration_option(reader)?;
132
30
    Ok(OptionKind::ConnectTimeout(value))
133
}
134

            
135
95
fn option_delay(reader: &mut Reader) -> ParseResult<OptionKind> {
136
95
    let value = duration_option(reader)?;
137
90
    Ok(OptionKind::Delay(value))
138
}
139

            
140
400
fn option_follow_location(reader: &mut Reader) -> ParseResult<OptionKind> {
141
400
    let value = non_recover(boolean_option, reader)?;
142
400
    Ok(OptionKind::FollowLocation(value))
143
}
144

            
145
50
fn option_follow_location_trusted(reader: &mut Reader) -> ParseResult<OptionKind> {
146
50
    let value = non_recover(boolean_option, reader)?;
147
50
    Ok(OptionKind::FollowLocationTrusted(value))
148
}
149

            
150
55
fn option_header(reader: &mut Reader) -> ParseResult<OptionKind> {
151
55
    let value = unquoted_template(reader)?;
152
55
    Ok(OptionKind::Header(value))
153
}
154

            
155
120
fn option_http_10(reader: &mut Reader) -> ParseResult<OptionKind> {
156
120
    let value = non_recover(boolean_option, reader)?;
157
120
    Ok(OptionKind::Http10(value))
158
}
159

            
160
90
fn option_http_11(reader: &mut Reader) -> ParseResult<OptionKind> {
161
90
    let value = non_recover(boolean_option, reader)?;
162
90
    Ok(OptionKind::Http11(value))
163
}
164

            
165
50
fn option_http_2(reader: &mut Reader) -> ParseResult<OptionKind> {
166
50
    let value = non_recover(boolean_option, reader)?;
167
50
    Ok(OptionKind::Http2(value))
168
}
169

            
170
30
fn option_http_3(reader: &mut Reader) -> ParseResult<OptionKind> {
171
30
    let value = non_recover(boolean_option, reader)?;
172
30
    Ok(OptionKind::Http3(value))
173
}
174

            
175
95
fn option_insecure(reader: &mut Reader) -> ParseResult<OptionKind> {
176
95
    let value = non_recover(boolean_option, reader)?;
177
90
    Ok(OptionKind::Insecure(value))
178
}
179

            
180
30
fn option_ipv4(reader: &mut Reader) -> ParseResult<OptionKind> {
181
30
    let value = non_recover(boolean_option, reader)?;
182
30
    Ok(OptionKind::IpV4(value))
183
}
184

            
185
30
fn option_ipv6(reader: &mut Reader) -> ParseResult<OptionKind> {
186
30
    let value = non_recover(boolean_option, reader)?;
187
30
    Ok(OptionKind::IpV6(value))
188
}
189

            
190
50
fn option_key(reader: &mut Reader) -> ParseResult<OptionKind> {
191
50
    let value = filename::parse(reader)?;
192
50
    Ok(OptionKind::ClientKey(value))
193
}
194

            
195
35
fn option_limit_rate(reader: &mut Reader) -> ParseResult<OptionKind> {
196
35
    let value = non_recover(natural_option, reader)?;
197
35
    Ok(OptionKind::LimitRate(value))
198
}
199

            
200
105
fn option_max_redirect(reader: &mut Reader) -> ParseResult<OptionKind> {
201
105
    let value = non_recover(count_option, reader)?;
202
105
    Ok(OptionKind::MaxRedirect(value))
203
}
204

            
205
30
fn option_netrc(reader: &mut Reader) -> ParseResult<OptionKind> {
206
30
    let value = non_recover(boolean_option, reader)?;
207
30
    Ok(OptionKind::NetRc(value))
208
}
209

            
210
35
fn option_netrc_file(reader: &mut Reader) -> ParseResult<OptionKind> {
211
35
    let value = unquoted_template(reader)?;
212
35
    Ok(OptionKind::NetRcFile(value))
213
}
214

            
215
30
fn option_netrc_optional(reader: &mut Reader) -> ParseResult<OptionKind> {
216
30
    let value = non_recover(boolean_option, reader)?;
217
30
    Ok(OptionKind::NetRcOptional(value))
218
}
219

            
220
130
fn option_output(reader: &mut Reader) -> ParseResult<OptionKind> {
221
130
    let value = filename::parse(reader)?;
222
130
    Ok(OptionKind::Output(value))
223
}
224

            
225
35
fn option_path_as_is(reader: &mut Reader) -> ParseResult<OptionKind> {
226
35
    let value = non_recover(boolean_option, reader)?;
227
35
    Ok(OptionKind::PathAsIs(value))
228
}
229

            
230
70
fn option_proxy(reader: &mut Reader) -> ParseResult<OptionKind> {
231
70
    let value = unquoted_template(reader)?;
232
70
    Ok(OptionKind::Proxy(value))
233
}
234

            
235
80
fn option_repeat(reader: &mut Reader) -> ParseResult<OptionKind> {
236
80
    let value = non_recover(count_option, reader)?;
237
80
    Ok(OptionKind::Repeat(value))
238
}
239

            
240
50
fn option_resolve(reader: &mut Reader) -> ParseResult<OptionKind> {
241
50
    let value = unquoted_template(reader)?;
242
50
    Ok(OptionKind::Resolve(value))
243
}
244

            
245
150
fn option_retry(reader: &mut Reader) -> ParseResult<OptionKind> {
246
150
    let value = non_recover(count_option, reader)?;
247
145
    Ok(OptionKind::Retry(value))
248
}
249

            
250
110
fn option_retry_interval(reader: &mut Reader) -> ParseResult<OptionKind> {
251
110
    let value = non_recover(duration_option, reader)?;
252
110
    Ok(OptionKind::RetryInterval(value))
253
}
254

            
255
40
fn option_skip(reader: &mut Reader) -> ParseResult<OptionKind> {
256
40
    let value = non_recover(boolean_option, reader)?;
257
40
    Ok(OptionKind::Skip(value))
258
}
259

            
260
100
fn option_user(reader: &mut Reader) -> ParseResult<OptionKind> {
261
100
    let value = unquoted_template(reader)?;
262
100
    Ok(OptionKind::User(value))
263
}
264

            
265
30
fn option_unix_socket(reader: &mut Reader) -> ParseResult<OptionKind> {
266
30
    let value = unquoted_template(reader)?;
267
30
    Ok(OptionKind::UnixSocket(value))
268
}
269

            
270
575
fn option_variable(reader: &mut Reader) -> ParseResult<OptionKind> {
271
575
    let value = variable_definition(reader)?;
272
570
    Ok(OptionKind::Variable(value))
273
}
274

            
275
110
fn option_verbose(reader: &mut Reader) -> ParseResult<OptionKind> {
276
110
    let value = non_recover(boolean_option, reader)?;
277
110
    Ok(OptionKind::Verbose(value))
278
}
279

            
280
50
fn option_very_verbose(reader: &mut Reader) -> ParseResult<OptionKind> {
281
50
    let value = non_recover(boolean_option, reader)?;
282
50
    Ok(OptionKind::VeryVerbose(value))
283
}
284

            
285
335
fn count(reader: &mut Reader) -> ParseResult<Count> {
286
335
    let start = reader.cursor();
287
335
    let value = non_recover(integer, reader)?;
288
235
    if value.as_i64() == -1 {
289
40
        Ok(Count::Infinite)
290
195
    } else if value.as_i64() >= 0 {
291
190
        Ok(Count::Finite(value.as_i64() as usize))
292
    } else {
293
5
        let kind = ParseErrorKind::Expecting {
294
5
            value: "Expecting a count value".to_string(),
295
5
        };
296
5
        Err(ParseError::new(start.pos, false, kind))
297
    }
298
}
299

            
300
1600
fn boolean_option(reader: &mut Reader) -> ParseResult<BooleanOption> {
301
1600
    let start = reader.cursor();
302
1600
    match boolean(reader) {
303
1315
        Ok(v) => Ok(BooleanOption::Literal(v)),
304
        Err(_) => {
305
285
            reader.seek(start);
306
286
            let exp = placeholder::parse(reader).map_err(|e| {
307
5
                let kind = ParseErrorKind::Expecting {
308
5
                    value: "true|false".to_string(),
309
5
                };
310
5
                ParseError::new(e.pos, false, kind)
311
286
            })?;
312
280
            Ok(BooleanOption::Placeholder(exp))
313
        }
314
    }
315
}
316

            
317
35
fn natural_option(reader: &mut Reader) -> ParseResult<NaturalOption> {
318
35
    let start = reader.cursor();
319
35
    match natural(reader) {
320
20
        Ok(v) => Ok(NaturalOption::Literal(v)),
321
        Err(_) => {
322
15
            reader.seek(start);
323
15
            let placeholder = placeholder::parse(reader).map_err(|e| {
324
                let kind = ParseErrorKind::Expecting {
325
                    value: "integer >= 0".to_string(),
326
                };
327
                ParseError::new(e.pos, false, kind)
328
15
            })?;
329
15
            Ok(NaturalOption::Placeholder(placeholder))
330
        }
331
    }
332
}
333

            
334
335
fn count_option(reader: &mut Reader) -> ParseResult<CountOption> {
335
335
    let start = reader.cursor();
336
335
    match count(reader) {
337
230
        Ok(v) => Ok(CountOption::Literal(v)),
338
        Err(_) => {
339
105
            reader.seek(start);
340
106
            let placeholder = placeholder::parse(reader).map_err(|e| {
341
5
                let kind = ParseErrorKind::Expecting {
342
5
                    value: "integer >= -1".to_string(),
343
5
                };
344
5
                ParseError::new(e.pos, false, kind)
345
106
            })?;
346
100
            Ok(CountOption::Placeholder(placeholder))
347
        }
348
    }
349
}
350

            
351
235
fn duration_option(reader: &mut Reader) -> ParseResult<DurationOption> {
352
235
    let start = reader.cursor();
353
235
    match duration(reader) {
354
180
        Ok(v) => Ok(DurationOption::Literal(v)),
355
55
        Err(e) => {
356
55
            if e.recoverable {
357
50
                reader.seek(start);
358
50
                let placeholder = placeholder::parse(reader).map_err(|e| {
359
                    let kind = ParseErrorKind::Expecting {
360
                        value: "integer".to_string(),
361
                    };
362
                    ParseError::new(e.pos, false, kind)
363
50
                })?;
364
50
                Ok(DurationOption::Placeholder(placeholder))
365
            } else {
366
5
                Err(e)
367
            }
368
        }
369
    }
370
}
371

            
372
575
fn variable_definition(reader: &mut Reader) -> ParseResult<VariableDefinition> {
373
575
    let start = reader.cursor();
374
575
    let name = variable_name(reader)?;
375
570
    let space0 = zero_or_more_spaces(reader)?;
376
570
    literal("=", reader)?;
377
570
    let space1 = zero_or_more_spaces(reader)?;
378
570
    let value = variable_value(reader)?;
379
570
    let end = reader.cursor();
380
570
    let source_info = SourceInfo {
381
570
        start: start.pos,
382
570
        end: end.pos,
383
570
    };
384
570
    Ok(VariableDefinition {
385
570
        source_info,
386
570
        name,
387
570
        space0,
388
570
        space1,
389
570
        value,
390
570
    })
391
}
392

            
393
575
fn variable_name(reader: &mut Reader) -> ParseResult<String> {
394
575
    let start = reader.cursor();
395
3260
    let name = reader.read_while(|c| c.is_alphanumeric() || c == '_' || c == '-');
396
575
    if name.is_empty() {
397
        let kind = ParseErrorKind::Expecting {
398
            value: "variable name".to_string(),
399
        };
400
        return Err(ParseError::new(start.pos, false, kind));
401
575
    } else if is_variable_reserved(&name) {
402
5
        let kind = ParseErrorKind::Variable(format!(
403
5
            "conflicts with the {name} function, use a different name"
404
5
        ));
405
5
        return Err(ParseError::new(start.pos, false, kind));
406
    }
407
570
    Ok(name)
408
}
409

            
410
570
fn variable_value(reader: &mut Reader) -> ParseResult<VariableValue> {
411
570
    choice(
412
570
        &[
413
684
            |p1| match null(p1) {
414
15
                Ok(()) => Ok(VariableValue::Null),
415
555
                Err(e) => Err(e),
416
684
            },
417
681
            |p1| match boolean(p1) {
418
20
                Ok(value) => Ok(VariableValue::Bool(value)),
419
535
                Err(e) => Err(e),
420
681
            },
421
677
            |p1| match number(p1) {
422
195
                Ok(value) => Ok(VariableValue::Number(value)),
423
340
                Err(e) => Err(e),
424
677
            },
425
638
            |p1| match quoted_template(p1) {
426
                Ok(value) => Ok(VariableValue::String(value)),
427
340
                Err(e) => Err(e),
428
638
            },
429
638
            |p1| match unquoted_template(p1) {
430
340
                Ok(value) => Ok(VariableValue::String(value)),
431
                Err(e) => Err(e),
432
638
            },
433
570
        ],
434
570
        reader,
435
570
    )
436
570
    .map_err(|e| {
437
        let kind = ParseErrorKind::Expecting {
438
            value: "variable value".to_string(),
439
        };
440
        ParseError::new(e.pos, false, kind)
441
570
    })
442
}
443

            
444
#[cfg(test)]
445
mod tests {
446
    use super::*;
447
    use crate::ast::{LineTerminator, Number, Template, TemplateElement, Whitespace, I64};
448
    use crate::reader::Pos;
449
    use crate::typing::ToSource;
450

            
451
    #[test]
452
    fn test_option_insecure() {
453
        let mut reader = Reader::new("insecure: true");
454
        let option = parse(&mut reader).unwrap();
455
        assert_eq!(
456
            option,
457
            EntryOption {
458
                line_terminators: vec![],
459
                space0: Whitespace {
460
                    value: String::new(),
461
                    source_info: SourceInfo {
462
                        start: Pos { line: 1, column: 1 },
463
                        end: Pos { line: 1, column: 1 },
464
                    },
465
                },
466
                space1: Whitespace {
467
                    value: String::new(),
468
                    source_info: SourceInfo {
469
                        start: Pos { line: 1, column: 9 },
470
                        end: Pos { line: 1, column: 9 },
471
                    },
472
                },
473
                space2: Whitespace {
474
                    value: " ".to_string(),
475
                    source_info: SourceInfo {
476
                        start: Pos {
477
                            line: 1,
478
                            column: 10,
479
                        },
480
                        end: Pos {
481
                            line: 1,
482
                            column: 11,
483
                        },
484
                    },
485
                },
486
                kind: OptionKind::Insecure(BooleanOption::Literal(true)),
487
                line_terminator0: LineTerminator {
488
                    space0: Whitespace {
489
                        value: String::new(),
490
                        source_info: SourceInfo {
491
                            start: Pos {
492
                                line: 1,
493
                                column: 15,
494
                            },
495
                            end: Pos {
496
                                line: 1,
497
                                column: 15,
498
                            },
499
                        },
500
                    },
501
                    comment: None,
502
                    newline: Whitespace {
503
                        value: String::new(),
504
                        source_info: SourceInfo {
505
                            start: Pos {
506
                                line: 1,
507
                                column: 15,
508
                            },
509
                            end: Pos {
510
                                line: 1,
511
                                column: 15,
512
                            },
513
                        },
514
                    },
515
                },
516
            }
517
        );
518
    }
519

            
520
    #[test]
521
    fn test_option_insecure_error() {
522
        let mut reader = Reader::new("insecure: error");
523
        let error = parse(&mut reader).err().unwrap();
524
        assert!(!error.recoverable);
525
    }
526

            
527
    #[test]
528
    fn test_option_cacert() {
529
        let mut reader = Reader::new("cacert: /home/foo/cert.pem");
530
        let option = parse(&mut reader).unwrap();
531
        assert_eq!(
532
            option,
533
            EntryOption {
534
                line_terminators: vec![],
535
                space0: Whitespace {
536
                    value: String::new(),
537
                    source_info: SourceInfo {
538
                        start: Pos { line: 1, column: 1 },
539
                        end: Pos { line: 1, column: 1 },
540
                    },
541
                },
542
                space1: Whitespace {
543
                    value: String::new(),
544
                    source_info: SourceInfo {
545
                        start: Pos { line: 1, column: 7 },
546
                        end: Pos { line: 1, column: 7 },
547
                    },
548
                },
549
                space2: Whitespace {
550
                    value: " ".to_string(),
551
                    source_info: SourceInfo {
552
                        start: Pos { line: 1, column: 8 },
553
                        end: Pos { line: 1, column: 9 },
554
                    },
555
                },
556
                kind: OptionKind::CaCertificate(Template::new(
557
                    None,
558
                    vec![TemplateElement::String {
559
                        value: "/home/foo/cert.pem".to_string(),
560
                        source: "/home/foo/cert.pem".to_source()
561
                    }],
562
                    SourceInfo {
563
                        start: Pos { line: 1, column: 9 },
564
                        end: Pos {
565
                            line: 1,
566
                            column: 27,
567
                        },
568
                    }
569
                )),
570
                line_terminator0: LineTerminator {
571
                    space0: Whitespace {
572
                        value: String::new(),
573
                        source_info: SourceInfo {
574
                            start: Pos {
575
                                line: 1,
576
                                column: 27,
577
                            },
578
                            end: Pos {
579
                                line: 1,
580
                                column: 27,
581
                            },
582
                        },
583
                    },
584
                    comment: None,
585
                    newline: Whitespace {
586
                        value: String::new(),
587
                        source_info: SourceInfo {
588
                            start: Pos {
589
                                line: 1,
590
                                column: 27,
591
                            },
592
                            end: Pos {
593
                                line: 1,
594
                                column: 27,
595
                            },
596
                        },
597
                    },
598
                },
599
            }
600
        );
601
    }
602

            
603
    #[test]
604
    fn test_option_cacert_error() {
605
        let mut reader = Reader::new("cacert: ###");
606
        let error = parse(&mut reader).err().unwrap();
607
        assert!(!error.recoverable);
608
    }
609

            
610
    #[test]
611
    fn test_option_cert() {
612
        let mut reader = Reader::new("/etc/client-cert.pem #foo");
613

            
614
        assert_eq!(
615
            option_cert(&mut reader).unwrap(),
616
            OptionKind::ClientCert(Template::new(
617
                None,
618
                vec![TemplateElement::String {
619
                    value: "/etc/client-cert.pem".to_string(),
620
                    source: "/etc/client-cert.pem".to_source()
621
                }],
622
                SourceInfo {
623
                    start: Pos { line: 1, column: 1 },
624
                    end: Pos {
625
                        line: 1,
626
                        column: 21,
627
                    },
628
                },
629
            )),
630
        );
631
    }
632

            
633
    #[test]
634
    fn test_option_retry_error() {
635
        let mut reader = Reader::new("retry: ###");
636
        let error = parse(&mut reader).err().unwrap();
637
        assert!(!error.recoverable);
638
        assert_eq!(error.pos, Pos { line: 1, column: 8 });
639
        assert_eq!(
640
            error.kind,
641
            ParseErrorKind::Expecting {
642
                value: "integer >= -1".to_string()
643
            }
644
        );
645
    }
646

            
647
    #[test]
648
    fn test_variable_definition() {
649
        let mut reader = Reader::new("a=1");
650
        assert_eq!(
651
            variable_definition(&mut reader).unwrap(),
652
            VariableDefinition {
653
                source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 4)),
654
                name: "a".to_string(),
655
                space0: Whitespace {
656
                    value: String::new(),
657
                    source_info: SourceInfo::new(Pos::new(1, 2), Pos::new(1, 2)),
658
                },
659
                space1: Whitespace {
660
                    value: String::new(),
661
                    source_info: SourceInfo::new(Pos::new(1, 3), Pos::new(1, 3)),
662
                },
663
                value: VariableValue::Number(Number::Integer(I64::new(1, "1".to_source()))),
664
            }
665
        );
666
    }
667

            
668
    #[test]
669
    fn test_variable_value() {
670
        let mut reader = Reader::new("null");
671
        assert_eq!(variable_value(&mut reader).unwrap(), VariableValue::Null);
672

            
673
        let mut reader = Reader::new("true");
674
        assert_eq!(
675
            variable_value(&mut reader).unwrap(),
676
            VariableValue::Bool(true)
677
        );
678

            
679
        let mut reader = Reader::new("1");
680
        assert_eq!(
681
            variable_value(&mut reader).unwrap(),
682
            VariableValue::Number(Number::Integer(I64::new(1, "1".to_source())))
683
        );
684

            
685
        let mut reader = Reader::new("toto");
686
        assert_eq!(
687
            variable_value(&mut reader).unwrap(),
688
            VariableValue::String(Template::new(
689
                None,
690
                vec![TemplateElement::String {
691
                    value: "toto".to_string(),
692
                    source: "toto".to_source(),
693
                }],
694
                SourceInfo::new(Pos::new(1, 1), Pos::new(1, 5)),
695
            ))
696
        );
697
        let mut reader = Reader::new("\"123\"");
698
        assert_eq!(
699
            variable_value(&mut reader).unwrap(),
700
            VariableValue::String(Template::new(
701
                Some('"'),
702
                vec![TemplateElement::String {
703
                    value: "123".to_string(),
704
                    source: "123".to_source(),
705
                }],
706
                SourceInfo::new(Pos::new(1, 1), Pos::new(1, 6))
707
            ))
708
        );
709
    }
710
}