1
/*
2
 * Hurl (https://hurl.dev)
3
 * Copyright (C) 2026 Orange
4
 *
5
 * Licensed under the Apache License, Version 2.0 (the "License");
6
 * you may not use this file except in compliance with the License.
7
 * You may obtain a copy of the License at
8
 *
9
 *          http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 *
17
 */
18
use super::placeholder;
19
use crate::ast::{
20
    is_variable_reserved, BooleanOption, CountOption, DurationOption, EntryOption, NaturalOption,
21
    OptionKind, SourceInfo, VariableDefinition, VariableValue, VerbosityOption,
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::types::Count;
34

            
35
/// Parse an option in an `[Options]` section.
36
6920
pub fn parse(reader: &mut Reader) -> ParseResult<EntryOption> {
37
6920
    let line_terminators = optional_line_terminators(reader)?;
38
6920
    let space0 = zero_or_more_spaces(reader)?;
39
6920
    let start = reader.cursor();
40
    // We accept '_' even if there is no option name with this character. We do this to be able to
41
    // enter the parsing of the option name and to have better error description (ex: 'max-redirs'
42
    // vs 'max_redirs').
43
6920
    let option =
44
51859
        reader.read_while(|c| c.is_ascii_alphanumeric() || c == '-' || c == '.' || c == '_');
45
6920
    let space1 = zero_or_more_spaces(reader)?;
46
6920
    try_literal(":", reader)?;
47
4805
    let space2 = zero_or_more_spaces(reader)?;
48
4805
    let kind = match option.as_str() {
49
4805
        "aws-sigv4" => option_aws_sigv4(reader)?,
50
4755
        "cacert" => option_cacert(reader)?,
51
4675
        "cert" => option_cert(reader)?,
52
4595
        "compressed" => option_compressed(reader)?,
53
4175
        "connect-to" => option_connect_to(reader)?,
54
4100
        "connect-timeout" => option_connect_timeout(reader)?,
55
4060
        "delay" => option_delay(reader)?,
56
3685
        "digest" => option_digest(reader)?,
57
3640
        "insecure" => option_insecure(reader)?,
58
3530
        "header" => option_header(reader)?,
59
3465
        "http1.0" => option_http_10(reader)?,
60
3335
        "http1.1" => option_http_11(reader)?,
61
3235
        "http2" => option_http_2(reader)?,
62
3175
        "http3" => option_http_3(reader)?,
63
3135
        "ipv4" => option_ipv4(reader)?,
64
3095
        "ipv6" => option_ipv6(reader)?,
65
3055
        "key" => option_key(reader)?,
66
2995
        "limit-rate" => option_limit_rate(reader)?,
67
2950
        "location" => option_follow_location(reader)?,
68
2485
        "location-trusted" => option_follow_location_trusted(reader)?,
69
2425
        "max-redirs" => option_max_redirect(reader)?,
70
2310
        "max-time" => option_max_time(reader)?,
71
2270
        "negotiate" => option_negotiate(reader)?,
72
2220
        "netrc" => option_netrc(reader)?,
73
2180
        "netrc-file" => option_netrc_file(reader)?,
74
2135
        "netrc-optional" => option_netrc_optional(reader)?,
75
2095
        "ntlm" => option_ntlm(reader)?,
76
2040
        "output" => option_output(reader)?,
77
1900
        "path-as-is" => option_path_as_is(reader)?,
78
1855
        "pinnedpubkey" => option_pinned_pub_key(reader)?,
79
1805
        "proxy" => option_proxy(reader)?,
80
1725
        "repeat" => option_repeat(reader)?,
81
1610
        "resolve" => option_resolve(reader)?,
82
1550
        "retry" => option_retry(reader)?,
83
1375
        "retry-interval" => option_retry_interval(reader)?,
84
1245
        "skip" => option_skip(reader)?,
85
1195
        "unix-socket" => option_unix_socket(reader)?,
86
1155
        "user" => option_user(reader)?,
87
1015
        "variable" => option_variable(reader)?,
88
310
        "verbose" => option_verbose(reader)?,
89
130
        "verbosity" => option_verbosity(reader)?,
90
70
        "very-verbose" => option_very_verbose(reader)?,
91
        _ => {
92
10
            return Err(ParseError::new(
93
10
                start.pos,
94
10
                false,
95
10
                ParseErrorKind::InvalidOption(option.to_string()),
96
10
            ))
97
        }
98
    };
99

            
100
4765
    let line_terminator0 = line_terminator(reader)?;
101
4765
    Ok(EntryOption {
102
4765
        line_terminators,
103
4765
        space0,
104
4765
        space1,
105
4765
        space2,
106
4765
        kind,
107
4765
        line_terminator0,
108
4765
    })
109
}
110

            
111
50
fn option_aws_sigv4(reader: &mut Reader) -> ParseResult<OptionKind> {
112
50
    let value = unquoted_template(reader)?;
113
50
    Ok(OptionKind::AwsSigV4(value))
114
}
115

            
116
80
fn option_cacert(reader: &mut Reader) -> ParseResult<OptionKind> {
117
80
    let value = filename::parse(reader)?;
118
75
    Ok(OptionKind::CaCertificate(value))
119
}
120

            
121
80
fn option_cert(reader: &mut Reader) -> ParseResult<OptionKind> {
122
80
    let value = filename_password::parse(reader)?;
123
80
    Ok(OptionKind::ClientCert(value))
124
}
125

            
126
420
fn option_compressed(reader: &mut Reader) -> ParseResult<OptionKind> {
127
420
    let value = non_recover(boolean_option, reader)?;
128
420
    Ok(OptionKind::Compressed(value))
129
}
130

            
131
75
fn option_connect_to(reader: &mut Reader) -> ParseResult<OptionKind> {
132
75
    let value = unquoted_template(reader)?;
133
75
    Ok(OptionKind::ConnectTo(value))
134
}
135

            
136
40
fn option_connect_timeout(reader: &mut Reader) -> ParseResult<OptionKind> {
137
40
    let value = duration_option(reader)?;
138
40
    Ok(OptionKind::ConnectTimeout(value))
139
}
140

            
141
375
fn option_delay(reader: &mut Reader) -> ParseResult<OptionKind> {
142
375
    let value = duration_option(reader)?;
143
370
    Ok(OptionKind::Delay(value))
144
}
145

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

            
151
465
fn option_follow_location(reader: &mut Reader) -> ParseResult<OptionKind> {
152
465
    let value = non_recover(boolean_option, reader)?;
153
465
    Ok(OptionKind::FollowLocation(value))
154
}
155

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

            
161
65
fn option_header(reader: &mut Reader) -> ParseResult<OptionKind> {
162
65
    let value = unquoted_template(reader)?;
163
65
    Ok(OptionKind::Header(value))
164
}
165

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

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

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

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

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

            
191
40
fn option_ipv4(reader: &mut Reader) -> ParseResult<OptionKind> {
192
40
    let value = non_recover(boolean_option, reader)?;
193
40
    Ok(OptionKind::IpV4(value))
194
}
195

            
196
40
fn option_ipv6(reader: &mut Reader) -> ParseResult<OptionKind> {
197
40
    let value = non_recover(boolean_option, reader)?;
198
40
    Ok(OptionKind::IpV6(value))
199
}
200

            
201
60
fn option_key(reader: &mut Reader) -> ParseResult<OptionKind> {
202
60
    let value = filename::parse(reader)?;
203
60
    Ok(OptionKind::ClientKey(value))
204
}
205

            
206
45
fn option_limit_rate(reader: &mut Reader) -> ParseResult<OptionKind> {
207
45
    let value = non_recover(natural_option, reader)?;
208
45
    Ok(OptionKind::LimitRate(value))
209
}
210

            
211
115
fn option_max_redirect(reader: &mut Reader) -> ParseResult<OptionKind> {
212
115
    let value = non_recover(count_option, reader)?;
213
115
    Ok(OptionKind::MaxRedirect(value))
214
}
215

            
216
40
fn option_max_time(reader: &mut Reader) -> ParseResult<OptionKind> {
217
40
    let value = duration_option(reader)?;
218
40
    Ok(OptionKind::MaxTime(value))
219
}
220

            
221
50
fn option_negotiate(reader: &mut Reader) -> ParseResult<OptionKind> {
222
50
    let value = boolean_option(reader)?;
223
50
    Ok(OptionKind::Negotiate(value))
224
}
225

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

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

            
236
40
fn option_netrc_optional(reader: &mut Reader) -> ParseResult<OptionKind> {
237
40
    let value = non_recover(boolean_option, reader)?;
238
40
    Ok(OptionKind::NetRcOptional(value))
239
}
240

            
241
55
fn option_ntlm(reader: &mut Reader) -> ParseResult<OptionKind> {
242
55
    let value = non_recover(boolean_option, reader)?;
243
55
    Ok(OptionKind::Ntlm(value))
244
}
245

            
246
140
fn option_output(reader: &mut Reader) -> ParseResult<OptionKind> {
247
140
    let value = filename::parse(reader)?;
248
140
    Ok(OptionKind::Output(value))
249
}
250

            
251
45
fn option_path_as_is(reader: &mut Reader) -> ParseResult<OptionKind> {
252
45
    let value = non_recover(boolean_option, reader)?;
253
45
    Ok(OptionKind::PathAsIs(value))
254
}
255

            
256
50
fn option_pinned_pub_key(reader: &mut Reader) -> ParseResult<OptionKind> {
257
50
    let value = unquoted_template(reader)?;
258
50
    Ok(OptionKind::PinnedPublicKey(value))
259
}
260

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

            
266
115
fn option_repeat(reader: &mut Reader) -> ParseResult<OptionKind> {
267
115
    let value = non_recover(count_option, reader)?;
268
115
    Ok(OptionKind::Repeat(value))
269
}
270

            
271
60
fn option_resolve(reader: &mut Reader) -> ParseResult<OptionKind> {
272
60
    let value = unquoted_template(reader)?;
273
60
    Ok(OptionKind::Resolve(value))
274
}
275

            
276
175
fn option_retry(reader: &mut Reader) -> ParseResult<OptionKind> {
277
175
    let value = non_recover(count_option, reader)?;
278
170
    Ok(OptionKind::Retry(value))
279
}
280

            
281
130
fn option_retry_interval(reader: &mut Reader) -> ParseResult<OptionKind> {
282
130
    let value = non_recover(duration_option, reader)?;
283
130
    Ok(OptionKind::RetryInterval(value))
284
}
285

            
286
50
fn option_skip(reader: &mut Reader) -> ParseResult<OptionKind> {
287
50
    let value = non_recover(boolean_option, reader)?;
288
50
    Ok(OptionKind::Skip(value))
289
}
290

            
291
140
fn option_user(reader: &mut Reader) -> ParseResult<OptionKind> {
292
140
    let value = unquoted_template(reader)?;
293
140
    Ok(OptionKind::User(value))
294
}
295

            
296
40
fn option_unix_socket(reader: &mut Reader) -> ParseResult<OptionKind> {
297
40
    let value = unquoted_template(reader)?;
298
40
    Ok(OptionKind::UnixSocket(value))
299
}
300

            
301
705
fn option_variable(reader: &mut Reader) -> ParseResult<OptionKind> {
302
705
    let value = variable_definition(reader)?;
303
700
    Ok(OptionKind::Variable(value))
304
}
305

            
306
180
fn option_verbose(reader: &mut Reader) -> ParseResult<OptionKind> {
307
180
    let value = non_recover(boolean_option, reader)?;
308
180
    Ok(OptionKind::Verbose(value))
309
}
310

            
311
60
fn option_verbosity(reader: &mut Reader) -> ParseResult<OptionKind> {
312
60
    let start = reader.cursor();
313
372
    let name = reader.read_while(|c| c.is_ascii_alphabetic());
314
60
    match name.as_str() {
315
60
        "brief" => Ok(OptionKind::Verbosity(VerbosityOption::Brief)),
316
15
        "verbose" => Ok(OptionKind::Verbosity(VerbosityOption::Verbose)),
317
10
        "debug" => Ok(OptionKind::Verbosity(VerbosityOption::Debug)),
318
        _ => {
319
5
            reader.seek(start);
320
5
            let kind = ParseErrorKind::Expecting {
321
5
                value: "brief|verbose|debug".to_string(),
322
5
            };
323
5
            Err(ParseError::new(start.pos, false, kind))
324
        }
325
    }
326
}
327

            
328
60
fn option_very_verbose(reader: &mut Reader) -> ParseResult<OptionKind> {
329
60
    let value = non_recover(boolean_option, reader)?;
330
60
    Ok(OptionKind::VeryVerbose(value))
331
}
332

            
333
405
fn count(reader: &mut Reader) -> ParseResult<Count> {
334
405
    let start = reader.cursor();
335
405
    let value = non_recover(integer, reader)?;
336
290
    if value.as_i64() == -1 {
337
50
        Ok(Count::Infinite)
338
240
    } else if value.as_i64() >= 0 {
339
235
        Ok(Count::Finite(value.as_i64() as usize))
340
    } else {
341
5
        let kind = ParseErrorKind::Expecting {
342
5
            value: "Expecting a count value".to_string(),
343
5
        };
344
5
        Err(ParseError::new(start.pos, false, kind))
345
    }
346
}
347

            
348
2030
fn boolean_option(reader: &mut Reader) -> ParseResult<BooleanOption> {
349
2030
    let start = reader.cursor();
350
2030
    match boolean(reader) {
351
1605
        Ok(v) => Ok(BooleanOption::Literal(v)),
352
        Err(_) => {
353
425
            reader.seek(start);
354
426
            let exp = placeholder::parse(reader).map_err(|e| {
355
5
                let kind = ParseErrorKind::Expecting {
356
5
                    value: "true|false".to_string(),
357
5
                };
358
5
                ParseError::new(e.pos, false, kind)
359
6
            })?;
360
420
            Ok(BooleanOption::Placeholder(exp))
361
        }
362
    }
363
}
364

            
365
45
fn natural_option(reader: &mut Reader) -> ParseResult<NaturalOption> {
366
45
    let start = reader.cursor();
367
45
    match natural(reader) {
368
25
        Ok(v) => Ok(NaturalOption::Literal(v)),
369
        Err(_) => {
370
20
            reader.seek(start);
371
20
            let placeholder = placeholder::parse(reader).map_err(|e| {
372
                let kind = ParseErrorKind::Expecting {
373
                    value: "integer >= 0".to_string(),
374
                };
375
                ParseError::new(e.pos, false, kind)
376
            })?;
377
20
            Ok(NaturalOption::Placeholder(placeholder))
378
        }
379
    }
380
}
381

            
382
405
fn count_option(reader: &mut Reader) -> ParseResult<CountOption> {
383
405
    let start = reader.cursor();
384
405
    match count(reader) {
385
285
        Ok(v) => Ok(CountOption::Literal(v)),
386
        Err(_) => {
387
120
            reader.seek(start);
388
121
            let placeholder = placeholder::parse(reader).map_err(|e| {
389
5
                let kind = ParseErrorKind::Expecting {
390
5
                    value: "integer >= -1".to_string(),
391
5
                };
392
5
                ParseError::new(e.pos, false, kind)
393
6
            })?;
394
115
            Ok(CountOption::Placeholder(placeholder))
395
        }
396
    }
397
}
398

            
399
585
fn duration_option(reader: &mut Reader) -> ParseResult<DurationOption> {
400
585
    let start = reader.cursor();
401
585
    match duration(reader) {
402
495
        Ok(v) => Ok(DurationOption::Literal(v)),
403
90
        Err(e) => {
404
90
            if e.recoverable {
405
85
                reader.seek(start);
406
85
                let placeholder = placeholder::parse(reader).map_err(|e| {
407
                    let kind = ParseErrorKind::Expecting {
408
                        value: "integer".to_string(),
409
                    };
410
                    ParseError::new(e.pos, false, kind)
411
                })?;
412
85
                Ok(DurationOption::Placeholder(placeholder))
413
            } else {
414
5
                Err(e)
415
            }
416
        }
417
    }
418
}
419

            
420
705
fn variable_definition(reader: &mut Reader) -> ParseResult<VariableDefinition> {
421
705
    let start = reader.cursor();
422
705
    let name = variable_name(reader)?;
423
700
    let space0 = zero_or_more_spaces(reader)?;
424
700
    literal("=", reader)?;
425
700
    let space1 = zero_or_more_spaces(reader)?;
426
700
    let value = variable_value(reader)?;
427
700
    let end = reader.cursor();
428
700
    let source_info = SourceInfo {
429
700
        start: start.pos,
430
700
        end: end.pos,
431
700
    };
432
700
    Ok(VariableDefinition {
433
700
        source_info,
434
700
        name,
435
700
        space0,
436
700
        space1,
437
700
        value,
438
700
    })
439
}
440

            
441
705
fn variable_name(reader: &mut Reader) -> ParseResult<String> {
442
705
    let start = reader.cursor();
443
4346
    let name = reader.read_while(|c| c.is_alphanumeric() || c == '_' || c == '-');
444
705
    if name.is_empty() {
445
        let kind = ParseErrorKind::Expecting {
446
            value: "variable name".to_string(),
447
        };
448
        return Err(ParseError::new(start.pos, false, kind));
449
705
    } else if is_variable_reserved(&name) {
450
5
        let kind = ParseErrorKind::Variable(format!(
451
5
            "conflicts with the {name} function, use a different name"
452
5
        ));
453
5
        return Err(ParseError::new(start.pos, false, kind));
454
    }
455
700
    Ok(name)
456
}
457

            
458
700
fn variable_value(reader: &mut Reader) -> ParseResult<VariableValue> {
459
700
    choice(
460
        &[
461
700
            |p1| match null(p1) {
462
20
                Ok(()) => Ok(VariableValue::Null),
463
680
                Err(e) => Err(e),
464
700
            },
465
680
            |p1| match boolean(p1) {
466
25
                Ok(value) => Ok(VariableValue::Bool(value)),
467
655
                Err(e) => Err(e),
468
680
            },
469
655
            |p1| match number(p1) {
470
245
                Ok(value) => Ok(VariableValue::Number(value)),
471
410
                Err(e) => Err(e),
472
655
            },
473
410
            |p1| match quoted_template(p1) {
474
                Ok(value) => Ok(VariableValue::String(value)),
475
410
                Err(e) => Err(e),
476
410
            },
477
410
            |p1| match unquoted_template(p1) {
478
410
                Ok(value) => Ok(VariableValue::String(value)),
479
                Err(e) => Err(e),
480
410
            },
481
        ],
482
700
        reader,
483
    )
484
700
    .map_err(|e| {
485
        let kind = ParseErrorKind::Expecting {
486
            value: "variable value".to_string(),
487
        };
488
        ParseError::new(e.pos, false, kind)
489
    })
490
}
491

            
492
#[cfg(test)]
493
mod tests {
494
    use super::*;
495
    use crate::ast::{LineTerminator, Number, Template, TemplateElement, Whitespace, I64};
496
    use crate::reader::Pos;
497
    use crate::types::ToSource;
498

            
499
    #[test]
500
    fn test_option_insecure() {
501
        let mut reader = Reader::new("insecure: true");
502
        let option = parse(&mut reader).unwrap();
503
        assert_eq!(
504
            option,
505
            EntryOption {
506
                line_terminators: vec![],
507
                space0: Whitespace {
508
                    value: String::new(),
509
                    source_info: SourceInfo {
510
                        start: Pos { line: 1, column: 1 },
511
                        end: Pos { line: 1, column: 1 },
512
                    },
513
                },
514
                space1: Whitespace {
515
                    value: String::new(),
516
                    source_info: SourceInfo {
517
                        start: Pos { line: 1, column: 9 },
518
                        end: Pos { line: 1, column: 9 },
519
                    },
520
                },
521
                space2: Whitespace {
522
                    value: " ".to_string(),
523
                    source_info: SourceInfo {
524
                        start: Pos {
525
                            line: 1,
526
                            column: 10,
527
                        },
528
                        end: Pos {
529
                            line: 1,
530
                            column: 11,
531
                        },
532
                    },
533
                },
534
                kind: OptionKind::Insecure(BooleanOption::Literal(true)),
535
                line_terminator0: LineTerminator {
536
                    space0: Whitespace {
537
                        value: String::new(),
538
                        source_info: SourceInfo {
539
                            start: Pos {
540
                                line: 1,
541
                                column: 15,
542
                            },
543
                            end: Pos {
544
                                line: 1,
545
                                column: 15,
546
                            },
547
                        },
548
                    },
549
                    comment: None,
550
                    newline: Whitespace {
551
                        value: String::new(),
552
                        source_info: SourceInfo {
553
                            start: Pos {
554
                                line: 1,
555
                                column: 15,
556
                            },
557
                            end: Pos {
558
                                line: 1,
559
                                column: 15,
560
                            },
561
                        },
562
                    },
563
                },
564
            }
565
        );
566
    }
567

            
568
    #[test]
569
    fn test_option_insecure_error() {
570
        let mut reader = Reader::new("insecure: error");
571
        let error = parse(&mut reader).err().unwrap();
572
        assert!(!error.recoverable);
573
    }
574

            
575
    #[test]
576
    fn test_option_cacert() {
577
        let mut reader = Reader::new("cacert: /home/foo/cert.pem");
578
        let option = parse(&mut reader).unwrap();
579
        assert_eq!(
580
            option,
581
            EntryOption {
582
                line_terminators: vec![],
583
                space0: Whitespace {
584
                    value: String::new(),
585
                    source_info: SourceInfo {
586
                        start: Pos { line: 1, column: 1 },
587
                        end: Pos { line: 1, column: 1 },
588
                    },
589
                },
590
                space1: Whitespace {
591
                    value: String::new(),
592
                    source_info: SourceInfo {
593
                        start: Pos { line: 1, column: 7 },
594
                        end: Pos { line: 1, column: 7 },
595
                    },
596
                },
597
                space2: Whitespace {
598
                    value: " ".to_string(),
599
                    source_info: SourceInfo {
600
                        start: Pos { line: 1, column: 8 },
601
                        end: Pos { line: 1, column: 9 },
602
                    },
603
                },
604
                kind: OptionKind::CaCertificate(Template::new(
605
                    None,
606
                    vec![TemplateElement::String {
607
                        value: "/home/foo/cert.pem".to_string(),
608
                        source: "/home/foo/cert.pem".to_source()
609
                    }],
610
                    SourceInfo {
611
                        start: Pos { line: 1, column: 9 },
612
                        end: Pos {
613
                            line: 1,
614
                            column: 27,
615
                        },
616
                    }
617
                )),
618
                line_terminator0: LineTerminator {
619
                    space0: Whitespace {
620
                        value: String::new(),
621
                        source_info: SourceInfo {
622
                            start: Pos {
623
                                line: 1,
624
                                column: 27,
625
                            },
626
                            end: Pos {
627
                                line: 1,
628
                                column: 27,
629
                            },
630
                        },
631
                    },
632
                    comment: None,
633
                    newline: Whitespace {
634
                        value: String::new(),
635
                        source_info: SourceInfo {
636
                            start: Pos {
637
                                line: 1,
638
                                column: 27,
639
                            },
640
                            end: Pos {
641
                                line: 1,
642
                                column: 27,
643
                            },
644
                        },
645
                    },
646
                },
647
            }
648
        );
649
    }
650

            
651
    #[test]
652
    fn test_option_cacert_error() {
653
        let mut reader = Reader::new("cacert: ###");
654
        let error = parse(&mut reader).err().unwrap();
655
        assert!(!error.recoverable);
656
    }
657

            
658
    #[test]
659
    fn test_option_cert() {
660
        let mut reader = Reader::new("/etc/client-cert.pem #foo");
661

            
662
        assert_eq!(
663
            option_cert(&mut reader).unwrap(),
664
            OptionKind::ClientCert(Template::new(
665
                None,
666
                vec![TemplateElement::String {
667
                    value: "/etc/client-cert.pem".to_string(),
668
                    source: "/etc/client-cert.pem".to_source()
669
                }],
670
                SourceInfo {
671
                    start: Pos { line: 1, column: 1 },
672
                    end: Pos {
673
                        line: 1,
674
                        column: 21,
675
                    },
676
                },
677
            )),
678
        );
679
    }
680

            
681
    #[test]
682
    fn test_option_retry_error() {
683
        let mut reader = Reader::new("retry: ###");
684
        let error = parse(&mut reader).err().unwrap();
685
        assert!(!error.recoverable);
686
        assert_eq!(error.pos, Pos { line: 1, column: 8 });
687
        assert_eq!(
688
            error.kind,
689
            ParseErrorKind::Expecting {
690
                value: "integer >= -1".to_string()
691
            }
692
        );
693
    }
694

            
695
    #[test]
696
    fn test_variable_definition() {
697
        let mut reader = Reader::new("a=1");
698
        assert_eq!(
699
            variable_definition(&mut reader).unwrap(),
700
            VariableDefinition {
701
                source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 4)),
702
                name: "a".to_string(),
703
                space0: Whitespace {
704
                    value: String::new(),
705
                    source_info: SourceInfo::new(Pos::new(1, 2), Pos::new(1, 2)),
706
                },
707
                space1: Whitespace {
708
                    value: String::new(),
709
                    source_info: SourceInfo::new(Pos::new(1, 3), Pos::new(1, 3)),
710
                },
711
                value: VariableValue::Number(Number::Integer(I64::new(1, "1".to_source()))),
712
            }
713
        );
714
    }
715

            
716
    #[test]
717
    fn test_variable_value() {
718
        let mut reader = Reader::new("null");
719
        assert_eq!(variable_value(&mut reader).unwrap(), VariableValue::Null);
720

            
721
        let mut reader = Reader::new("true");
722
        assert_eq!(
723
            variable_value(&mut reader).unwrap(),
724
            VariableValue::Bool(true)
725
        );
726

            
727
        let mut reader = Reader::new("1");
728
        assert_eq!(
729
            variable_value(&mut reader).unwrap(),
730
            VariableValue::Number(Number::Integer(I64::new(1, "1".to_source())))
731
        );
732

            
733
        let mut reader = Reader::new("toto");
734
        assert_eq!(
735
            variable_value(&mut reader).unwrap(),
736
            VariableValue::String(Template::new(
737
                None,
738
                vec![TemplateElement::String {
739
                    value: "toto".to_string(),
740
                    source: "toto".to_source(),
741
                }],
742
                SourceInfo::new(Pos::new(1, 1), Pos::new(1, 5)),
743
            ))
744
        );
745
        let mut reader = Reader::new("\"123\"");
746
        assert_eq!(
747
            variable_value(&mut reader).unwrap(),
748
            VariableValue::String(Template::new(
749
                Some('"'),
750
                vec![TemplateElement::String {
751
                    value: "123".to_string(),
752
                    source: "123".to_source(),
753
                }],
754
                SourceInfo::new(Pos::new(1, 1), Pos::new(1, 6))
755
            ))
756
        );
757
    }
758
}