uri_template_system_core/template/component/expression/
modifier.rs1use crate::template::{
2 ParseError,
3 TryParse,
4};
5
6#[derive(Debug, Eq, PartialEq)]
13pub enum Modifier {
14 Explode,
15 Prefix(usize),
16}
17
18impl<'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, _ => 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, _ => false,
106 }
107}