uri_template_system_core/template/component/expression/
modifier.rs

1use crate::template::{
2    ParseError,
3    TryParse,
4};
5
6// =============================================================================
7// Modifier
8// =============================================================================
9
10// Types
11
12#[derive(Debug, Eq, PartialEq)]
13pub enum Modifier {
14    Explode,
15    Prefix(usize),
16}
17
18// -----------------------------------------------------------------------------
19
20// Parse
21
22impl<'t> TryParse<'t> for Option<Modifier> {
23    fn try_parse(raw: &'t str, global: usize) -> Result<(usize, Self), ParseError> {
24        let mut state = ModifierState::default();
25
26        loop {
27            let rest = &raw[state.position..];
28
29            match &state.next {
30                ModifierNext::Symbol if rest.starts_with('*') => {
31                    return Ok((1, Some(Modifier::Explode)));
32                }
33                ModifierNext::Symbol if rest.starts_with(':') => {
34                    state.position += 1;
35                    state.next = ModifierNext::LeadingDigit;
36                }
37                ModifierNext::Symbol => {
38                    return Ok((0, None));
39                }
40                ModifierNext::LeadingDigit if rest.starts_with(is_non_zero_digit) => {
41                    state.position += 1;
42                    state.next = ModifierNext::TrailingDigit;
43                }
44                ModifierNext::LeadingDigit => {
45                    return Err(ParseError::UnexpectedInput {
46                        position: global + state.position,
47                        message: "unexpected input while parsing prefix modifier - invalid character".into(),
48                        expected: "leading integer 1-9 (see: https://datatracker.ietf.org/doc/html/rfc6570#section-2.4.1)".into(),
49                    });
50                }
51                ModifierNext::TrailingDigit if rest.starts_with(is_digit) => {
52                    if state.position < 4 {
53                        state.position += 1;
54                    } else {
55                        return Err(ParseError::UnexpectedInput {
56                            position: global + state.position,
57                            message: "unexpected input while parsing prefix modifier - out of range".into(),
58                            expected: "positive integer < 10000 (see: https://datatracker.ietf.org/doc/html/rfc6570#section-2.4.1)".into(),
59                        });
60                    }
61                }
62                ModifierNext::TrailingDigit => {
63                    return Ok((
64                        state.position,
65                        Some(Modifier::Prefix(
66                            raw[1..state.position].parse::<usize>().unwrap(),
67                        )),
68                    ));
69                }
70            }
71        }
72    }
73}
74
75#[derive(Default)]
76struct ModifierState {
77    next: ModifierNext,
78    position: usize,
79}
80
81#[derive(Default)]
82enum ModifierNext {
83    #[default]
84    Symbol,
85    LeadingDigit,
86    TrailingDigit,
87}
88
89#[rustfmt::skip]
90#[allow(clippy::match_like_matches_macro)]
91#[inline]
92const fn is_digit(c: char) -> bool {
93    match c {
94        | '\x30'..='\x39' => true, // 0..9
95        _ => false,
96    }
97}
98
99#[rustfmt::skip]
100#[allow(clippy::match_like_matches_macro)]
101#[inline]
102const fn is_non_zero_digit(c: char) -> bool {
103    match c {
104        | '\x31'..='\x39' => true, // 1..9
105        _ => false,
106    }
107}