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

            
34
use super::placeholder;
35

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
445
#[cfg(test)]
446
mod tests {
447
    use super::*;
448
    use crate::ast::{LineTerminator, Number, Template, TemplateElement, Whitespace, I64};
449
    use crate::reader::Pos;
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 {
557
                    delimiter: None,
558
                    elements: vec![TemplateElement::String {
559
                        value: "/home/foo/cert.pem".to_string(),
560
                        encoded: "/home/foo/cert.pem".to_string()
561
                    }],
562
                    source_info: 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 {
617
                delimiter: None,
618
                elements: vec![TemplateElement::String {
619
                    value: "/etc/client-cert.pem".to_string(),
620
                    encoded: "/etc/client-cert.pem".to_string()
621
                }],
622
                source_info: 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_string()))),
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_string())))
683
        );
684

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