yew_router_nested_route_parser/
optimizer.rs

1use crate::{
2    error::PrettyParseError,
3    parser::{parse, CaptureOrExact, RefCaptureVariant, RouteParserToken},
4};
5
6use crate::{core::FieldNamingScheme, CaptureVariant, MatcherToken};
7
8impl<'a> From<RefCaptureVariant<'a>> for CaptureVariant {
9    fn from(v: RefCaptureVariant<'a>) -> Self {
10        match v {
11            RefCaptureVariant::Named(s) => CaptureVariant::Named(s.to_string()),
12            RefCaptureVariant::ManyNamed(s) => CaptureVariant::ManyNamed(s.to_string()),
13            RefCaptureVariant::NumberedNamed { sections, name } => CaptureVariant::NumberedNamed {
14                sections,
15                name: name.to_string(),
16            },
17            RefCaptureVariant::Unnamed => CaptureVariant::Unnamed,
18            RefCaptureVariant::ManyUnnamed => CaptureVariant::ManyUnnamed,
19            RefCaptureVariant::NumberedUnnamed { sections } => {
20                CaptureVariant::NumberedUnnamed { sections }
21            }
22        }
23    }
24}
25
26impl<'a> From<CaptureOrExact<'a>> for MatcherToken {
27    fn from(value: CaptureOrExact<'a>) -> Self {
28        match value {
29            CaptureOrExact::Exact(m) => MatcherToken::Exact(m.to_string()),
30            CaptureOrExact::Capture(v) => MatcherToken::Capture(v.into()),
31        }
32    }
33}
34
35impl<'a> RouteParserToken<'a> {
36    fn as_str(&self) -> &str {
37        match self {
38            RouteParserToken::Separator => "/",
39            RouteParserToken::Exact(literal) => &literal,
40            RouteParserToken::QueryBegin => "?",
41            RouteParserToken::QuerySeparator => "&",
42            RouteParserToken::FragmentBegin => "#",
43            RouteParserToken::Nothing
44            | RouteParserToken::Capture { .. }
45            | RouteParserToken::Query { .. }
46            | RouteParserToken::End => unreachable!(),
47        }
48    }
49}
50
51/// Parse the provided "matcher string" and then optimize the tokens.
52pub fn parse_str_and_optimize_tokens(
53    i: &str,
54    field_naming_scheme: FieldNamingScheme,
55) -> Result<Vec<MatcherToken>, PrettyParseError> {
56    let tokens = parse(i, field_naming_scheme)?;
57    Ok(convert_tokens(&tokens))
58}
59
60/// Converts a slice of `RouteParserToken` into a Vec of MatcherTokens.
61///
62/// In the process of converting the tokens, this function will condense multiple RouteParserTokens
63/// that represent literals into one Exact variant if multiple reducible tokens happen to occur in a row.
64pub fn convert_tokens(tokens: &[RouteParserToken]) -> Vec<MatcherToken> {
65    let mut new_tokens: Vec<MatcherToken> = vec![];
66    let mut run: Vec<RouteParserToken> = vec![];
67
68    fn empty_run(run: &mut Vec<RouteParserToken>) -> Option<MatcherToken> {
69        let segment = run.iter().map(RouteParserToken::as_str).collect::<String>();
70        run.clear();
71
72        if !segment.is_empty() {
73            Some(MatcherToken::Exact(segment))
74        } else {
75            None
76        }
77    }
78
79    fn empty_run_with_query_cap_at_end(
80        run: &mut Vec<RouteParserToken>,
81        query_lhs: &str,
82    ) -> MatcherToken {
83        let segment = run
84            .iter()
85            .map(RouteParserToken::as_str)
86            .chain(Some(query_lhs))
87            .chain(Some("="))
88            .collect::<String>();
89        run.clear();
90
91        MatcherToken::Exact(segment)
92    }
93
94    for token in tokens.iter() {
95        match token {
96            RouteParserToken::QueryBegin
97            | RouteParserToken::FragmentBegin
98            | RouteParserToken::Separator
99            | RouteParserToken::QuerySeparator
100            | RouteParserToken::Exact(_) => run.push(*token),
101            RouteParserToken::Capture(cap) => {
102                if let Some(current_run) = empty_run(&mut run) {
103                    new_tokens.push(current_run);
104                }
105                new_tokens.push(MatcherToken::Capture(CaptureVariant::from(*cap)))
106            }
107            RouteParserToken::Query {
108                ident,
109                capture_or_exact,
110            } => match capture_or_exact {
111                CaptureOrExact::Exact(s) => {
112                    run.push(RouteParserToken::Exact(ident));
113                    run.push(RouteParserToken::Exact("="));
114                    run.push(RouteParserToken::Exact(s));
115                }
116                CaptureOrExact::Capture(cap) => {
117                    new_tokens.push(empty_run_with_query_cap_at_end(&mut run, *ident));
118                    new_tokens.push(MatcherToken::Capture(CaptureVariant::from(*cap)))
119                }
120            },
121            RouteParserToken::End => {
122                if let Some(current_run) = empty_run(&mut run) {
123                    new_tokens.push(current_run);
124                }
125                new_tokens.push(MatcherToken::End);
126            }
127            RouteParserToken::Nothing => {}
128        }
129    }
130
131    // Empty the run at the end.
132    if !run.is_empty() {
133        if let Some(current_run) = empty_run(&mut run) {
134            new_tokens.push(current_run);
135        }
136    }
137
138    new_tokens
139}
140
141#[cfg(test)]
142mod tests {
143    use super::*;
144
145    #[test]
146    fn empty_creates_empty_token_list() {
147        let tokens = parse_str_and_optimize_tokens("", FieldNamingScheme::Unit).unwrap();
148        assert_eq!(tokens, vec![])
149    }
150}