yew_router_min_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![];
66    let mut run: Vec<RouteParserToken> = vec![];
67
68    fn empty_run(run: &mut Vec<RouteParserToken>) -> MatcherToken {
69        let segment = run.iter().map(RouteParserToken::as_str).collect::<String>();
70        run.clear();
71
72        MatcherToken::Exact(segment)
73    }
74
75    fn empty_run_with_query_cap_at_end(
76        run: &mut Vec<RouteParserToken>,
77        query_lhs: &str,
78    ) -> MatcherToken {
79        let segment = run
80            .iter()
81            .map(RouteParserToken::as_str)
82            .chain(Some(query_lhs))
83            .chain(Some("="))
84            .collect::<String>();
85        run.clear();
86
87        MatcherToken::Exact(segment)
88    }
89
90    for token in tokens.iter() {
91        match token {
92            RouteParserToken::QueryBegin
93            | RouteParserToken::FragmentBegin
94            | RouteParserToken::Separator
95            | RouteParserToken::QuerySeparator
96            | RouteParserToken::Exact(_) => run.push(*token),
97            RouteParserToken::Capture(cap) => {
98                new_tokens.push(empty_run(&mut run));
99                new_tokens.push(MatcherToken::Capture(CaptureVariant::from(*cap)))
100            }
101            RouteParserToken::Query {
102                ident,
103                capture_or_exact,
104            } => match capture_or_exact {
105                CaptureOrExact::Exact(s) => {
106                    run.push(RouteParserToken::Exact(ident));
107                    run.push(RouteParserToken::Exact("="));
108                    run.push(RouteParserToken::Exact(s));
109                }
110                CaptureOrExact::Capture(cap) => {
111                    new_tokens.push(empty_run_with_query_cap_at_end(&mut run, *ident));
112                    new_tokens.push(MatcherToken::Capture(CaptureVariant::from(*cap)))
113                }
114            },
115            RouteParserToken::End => {
116                new_tokens.push(empty_run(&mut run));
117                new_tokens.push(MatcherToken::End);
118            }
119            RouteParserToken::Nothing => {}
120        }
121    }
122
123    // Empty the run at the end.
124    if !run.is_empty() {
125        new_tokens.push(empty_run(&mut run));
126    }
127
128    new_tokens
129}
130
131
132#[cfg(test)]
133mod tests {
134    use super::*;
135
136    #[test]
137    fn empty_creates_empty_token_list() {
138        let tokens = parse_str_and_optimize_tokens("", FieldNamingScheme::Unit).unwrap();
139        assert_eq!(tokens, vec![])
140    }
141}