yew_router_min_route_parser/
parser.rs

1//! Parser that consumes a string and produces the first representation of the matcher.
2use crate::{
3    core::{
4        capture, capture_single, exact, fragment_exact, get_and, get_end, get_hash, get_question,
5        get_slash, nothing, query,
6    },
7    error::{get_reason, ParseError, ParserErrorReason, PrettyParseError},
8    FieldNamingScheme,
9};
10use nom::{branch::alt, IResult};
11// use crate::core::escaped_item;
12
13/// Tokens generated from parsing a route matcher string.
14/// They will be optimized to another token type that is used to match URLs.
15#[derive(Debug, Clone, Copy, PartialEq)]
16pub enum RouteParserToken<'a> {
17    /// Generated by the empty string `""`.
18    Nothing,
19    /// Match /
20    Separator,
21    /// Match a specific string.
22    Exact(&'a str),
23    /// Match {_}. See `RefCaptureVariant` for more.
24    Capture(RefCaptureVariant<'a>),
25    /// Match ?
26    QueryBegin,
27    /// Match &
28    QuerySeparator,
29    /// Match x=y
30    Query {
31        /// Identifier
32        ident: &'a str,
33        /// Capture or match
34        capture_or_exact: CaptureOrExact<'a>,
35    },
36    /// Match \#
37    FragmentBegin,
38    /// Match !
39    End,
40}
41
42/// Token representing various types of captures.
43///
44/// It can capture and discard for unnamed variants, or capture and store in the `Matches` for the
45/// named variants.
46///
47/// Its name stems from the fact that it does not have ownership over all its values.
48/// It gets converted to CaptureVariant, a nearly identical enum that has owned Strings instead.
49#[derive(Debug, Clone, Copy, PartialEq)]
50pub enum RefCaptureVariant<'a> {
51    /// {}
52    Unnamed,
53    /// {*}
54    ManyUnnamed,
55    /// {5}
56    NumberedUnnamed {
57        /// Number of sections to match.
58        sections: usize,
59    },
60    /// {name} - captures a section and adds it to the map with a given name.
61    Named(&'a str),
62    /// {*:name} - captures over many sections and adds it to the map with a given name.
63    ManyNamed(&'a str),
64    /// {2:name} - captures a fixed number of sections with a given name.
65    NumberedNamed {
66        /// Number of sections to match.
67        sections: usize,
68        /// The key to be entered in the `Matches` map.
69        name: &'a str,
70    },
71}
72
73/// Either a Capture, or an Exact match
74#[derive(Debug, Clone, Copy, PartialEq)]
75pub enum CaptureOrExact<'a> {
76    /// Match a specific string.
77    Exact(&'a str),
78    /// Match a capture variant.
79    Capture(RefCaptureVariant<'a>),
80}
81
82/// Represents the states the parser can be in.
83#[derive(Clone, PartialEq)]
84enum ParserState<'a> {
85    None,
86    Path { prev_token: RouteParserToken<'a> },
87    FirstQuery { prev_token: RouteParserToken<'a> },
88    NthQuery { prev_token: RouteParserToken<'a> },
89    Fragment { prev_token: RouteParserToken<'a> },
90    End,
91}
92impl<'a> ParserState<'a> {
93    /// Given a new route parser token, transition to a new state.
94    ///
95    /// This will set the prev token to a token able to be handled by the new state,
96    /// so the new state does not need to handle arbitrary "from" states.
97    ///
98    /// This function represents the valid state transition graph.
99    fn transition(self, token: RouteParserToken<'a>) -> Result<Self, ParserErrorReason> {
100        match self {
101            ParserState::None => match token {
102                RouteParserToken::Separator
103                | RouteParserToken::Exact(_)
104                | RouteParserToken::Capture(_) => Ok(ParserState::Path { prev_token: token }),
105                RouteParserToken::QueryBegin => Ok(ParserState::FirstQuery { prev_token: token }),
106                RouteParserToken::QuerySeparator => Ok(ParserState::NthQuery { prev_token: token }),
107                RouteParserToken::Query { .. } => Err(ParserErrorReason::NotAllowedStateTransition),
108                RouteParserToken::FragmentBegin => Ok(ParserState::Fragment { prev_token: token }),
109                RouteParserToken::Nothing | RouteParserToken::End => Ok(ParserState::End),
110            },
111            ParserState::Path { prev_token } => {
112                match prev_token {
113                    RouteParserToken::Separator => match token {
114                        RouteParserToken::Exact(_) | RouteParserToken::Capture(_) => {
115                            Ok(ParserState::Path { prev_token: token })
116                        }
117                        RouteParserToken::QueryBegin => {
118                            Ok(ParserState::FirstQuery { prev_token: token })
119                        }
120                        RouteParserToken::FragmentBegin => {
121                            Ok(ParserState::Fragment { prev_token: token })
122                        }
123                        RouteParserToken::End => Ok(ParserState::End),
124                        _ => Err(ParserErrorReason::NotAllowedStateTransition),
125                    },
126                    RouteParserToken::Exact(_) => match token {
127                        RouteParserToken::Exact(_)
128                        | RouteParserToken::Separator
129                        | RouteParserToken::Capture(_) => {
130                            Ok(ParserState::Path { prev_token: token })
131                        }
132                        RouteParserToken::QueryBegin => {
133                            Ok(ParserState::FirstQuery { prev_token: token })
134                        }
135                        RouteParserToken::FragmentBegin => {
136                            Ok(ParserState::Fragment { prev_token: token })
137                        }
138                        RouteParserToken::End => Ok(ParserState::End),
139                        _ => Err(ParserErrorReason::NotAllowedStateTransition),
140                    },
141                    RouteParserToken::Capture(_) => match token {
142                        RouteParserToken::Separator | RouteParserToken::Exact(_) => {
143                            Ok(ParserState::Path { prev_token: token })
144                        }
145                        RouteParserToken::QueryBegin => {
146                            Ok(ParserState::FirstQuery { prev_token: token })
147                        }
148                        RouteParserToken::FragmentBegin => {
149                            Ok(ParserState::Fragment { prev_token: token })
150                        }
151                        RouteParserToken::End => Ok(ParserState::End),
152                        _ => Err(ParserErrorReason::NotAllowedStateTransition),
153                    },
154                    _ => Err(ParserErrorReason::InvalidState), /* Other previous token types are
155                                                                * invalid within a Path state. */
156                }
157            }
158            ParserState::FirstQuery { prev_token } => match prev_token {
159                RouteParserToken::QueryBegin => match token {
160                    RouteParserToken::Query { .. } => {
161                        Ok(ParserState::FirstQuery { prev_token: token })
162                    }
163                    _ => Err(ParserErrorReason::NotAllowedStateTransition),
164                },
165                RouteParserToken::Query { .. } => match token {
166                    RouteParserToken::QuerySeparator => {
167                        Ok(ParserState::NthQuery { prev_token: token })
168                    }
169                    RouteParserToken::FragmentBegin => {
170                        Ok(ParserState::Fragment { prev_token: token })
171                    }
172                    RouteParserToken::End => Ok(ParserState::End),
173                    _ => Err(ParserErrorReason::NotAllowedStateTransition),
174                },
175                _ => Err(ParserErrorReason::InvalidState),
176            },
177            ParserState::NthQuery { prev_token } => match prev_token {
178                RouteParserToken::QuerySeparator => match token {
179                    RouteParserToken::Query { .. } => {
180                        Ok(ParserState::NthQuery { prev_token: token })
181                    }
182                    _ => Err(ParserErrorReason::NotAllowedStateTransition),
183                },
184                RouteParserToken::Query { .. } => match token {
185                    RouteParserToken::QuerySeparator => {
186                        Ok(ParserState::NthQuery { prev_token: token })
187                    }
188                    RouteParserToken::FragmentBegin => {
189                        Ok(ParserState::Fragment { prev_token: token })
190                    }
191                    RouteParserToken::End => Ok(ParserState::End),
192                    _ => Err(ParserErrorReason::NotAllowedStateTransition),
193                },
194                _ => Err(ParserErrorReason::InvalidState),
195            },
196            ParserState::Fragment { prev_token } => match prev_token {
197                RouteParserToken::FragmentBegin
198                | RouteParserToken::Exact(_)
199                | RouteParserToken::Capture(_) => Ok(ParserState::Fragment { prev_token: token }),
200                RouteParserToken::End => Ok(ParserState::End),
201                _ => Err(ParserErrorReason::InvalidState),
202            },
203            ParserState::End => Err(ParserErrorReason::TokensAfterEndToken),
204        }
205    }
206}
207
208/// Parse a matching string into a vector of RouteParserTokens.
209///
210/// The parsing logic involves using a state machine.
211/// After a token is read, this token is fed into the state machine, causing it to transition to a new state or throw an error.
212/// Because the tokens that can be parsed in each state are limited, errors are not actually thrown in the state transition,
213/// due to the fact that erroneous tokens can't be fed into the transition function.
214///
215/// This continues until the string is exhausted, or none of the parsers for the current state can parse the current input.
216pub fn parse(
217    mut i: &str,
218    field_naming_scheme: FieldNamingScheme,
219) -> Result<Vec<RouteParserToken>, PrettyParseError> {
220    let input = i;
221    let mut tokens: Vec<RouteParserToken> = vec![];
222    let mut state = ParserState::None;
223
224    loop {
225        let (ii, token) = parse_impl(i, &state, field_naming_scheme).map_err(|e| match e {
226            nom::Err::Error(e) | nom::Err::Failure(e) => PrettyParseError {
227                error: e,
228                input,
229                remaining: i,
230            },
231            _ => panic!("parser should not be incomplete"),
232        })?;
233        i = ii;
234        state = state.transition(token.clone()).map_err(|reason| {
235            let error = ParseError {
236                reason: Some(reason),
237                expected: vec![],
238                offset: 0,
239            };
240            PrettyParseError {
241                error,
242                input,
243                remaining: i,
244            }
245        })?;
246        tokens.push(token);
247
248        // If there is no more input, break out of the loop
249        if i.is_empty() {
250            break;
251        }
252    }
253    Ok(tokens)
254}
255
256fn parse_impl<'a>(
257    i: &'a str,
258    state: &ParserState,
259    field_naming_scheme: FieldNamingScheme,
260) -> IResult<&'a str, RouteParserToken<'a>, ParseError> {
261    match state {
262        ParserState::None => alt((
263            get_slash,
264            get_question,
265            get_and,
266            get_hash,
267            capture(field_naming_scheme),
268            exact,
269            get_end,
270            nothing,
271        ))(i),
272        ParserState::Path { prev_token } => match prev_token {
273            RouteParserToken::Separator => {
274                alt((
275                    exact,
276                    capture(field_naming_scheme),
277                    get_question,
278                    get_hash,
279                    get_end,
280                ))(i)
281                .map_err(|mut e: nom::Err<ParseError>| {
282                    // Detect likely failures if the above failed to match.
283                    let reason: &mut Option<ParserErrorReason> = get_reason(&mut e);
284                    *reason = get_slash(i)
285                        .map(|_| ParserErrorReason::DoubleSlash)
286                        .or_else(|_| get_and(i).map(|_| ParserErrorReason::AndBeforeQuestion))
287                        .ok()
288                        .or(*reason);
289                    e
290                })
291            }
292            RouteParserToken::Exact(_) => {
293                alt((
294                    get_slash,
295                    exact, // This will handle escaped items
296                    capture(field_naming_scheme),
297                    get_question,
298                    get_hash,
299                    get_end,
300                ))(i)
301                .map_err(|mut e: nom::Err<ParseError>| {
302                    // Detect likely failures if the above failed to match.
303                    let reason: &mut Option<ParserErrorReason> = get_reason(&mut e);
304                    *reason = get_and(i)
305                        .map(|_| ParserErrorReason::AndBeforeQuestion)
306                        .ok()
307                        .or(*reason);
308                    e
309                })
310            }
311            RouteParserToken::Capture(_) => {
312                alt((get_slash, exact, get_question, get_hash, get_end))(i).map_err(
313                    |mut e: nom::Err<ParseError>| {
314                        // Detect likely failures if the above failed to match.
315                        let reason: &mut Option<ParserErrorReason> = get_reason(&mut e);
316                        *reason = capture(field_naming_scheme)(i)
317                            .map(|_| ParserErrorReason::AdjacentCaptures)
318                            .or_else(|_| get_and(i).map(|_| ParserErrorReason::AndBeforeQuestion))
319                            .ok()
320                            .or(*reason);
321                        e
322                    },
323                )
324            }
325            _ => Err(nom::Err::Failure(ParseError {
326                reason: Some(ParserErrorReason::InvalidState),
327                expected: vec![],
328                offset: 0,
329            })),
330        },
331        ParserState::FirstQuery { prev_token } => match prev_token {
332            RouteParserToken::QueryBegin => {
333                query(field_naming_scheme)(i).map_err(|mut e: nom::Err<ParseError>| {
334                    // Detect likely failures if the above failed to match.
335                    let reason: &mut Option<ParserErrorReason> = get_reason(&mut e);
336                    *reason = get_question(i)
337                        .map(|_| ParserErrorReason::MultipleQuestions)
338                        .ok()
339                        .or(*reason);
340                    e
341                })
342            }
343            RouteParserToken::Query { .. } => {
344                alt((get_and, get_hash, get_end))(i).map_err(|mut e: nom::Err<ParseError>| {
345                    // Detect likely failures if the above failed to match.
346                    let reason: &mut Option<ParserErrorReason> = get_reason(&mut e);
347                    *reason = get_question(i)
348                        .map(|_| ParserErrorReason::MultipleQuestions)
349                        .ok()
350                        .or(*reason);
351                    e
352                })
353            }
354            _ => Err(nom::Err::Failure(ParseError {
355                reason: Some(ParserErrorReason::InvalidState),
356                expected: vec![],
357                offset: 0,
358            })),
359        },
360        ParserState::NthQuery { prev_token } => match prev_token {
361            RouteParserToken::QuerySeparator => {
362                query(field_naming_scheme)(i).map_err(|mut e: nom::Err<ParseError>| {
363                    // Detect likely failures if the above failed to match.
364                    let reason: &mut Option<ParserErrorReason> = get_reason(&mut e);
365                    *reason = get_question(i)
366                        .map(|_| ParserErrorReason::MultipleQuestions)
367                        .ok()
368                        .or(*reason);
369                    e
370                })
371            }
372            RouteParserToken::Query { .. } => {
373                alt((get_and, get_hash, get_end))(i).map_err(|mut e: nom::Err<ParseError>| {
374                    // Detect likely failures if the above failed to match.
375                    let reason: &mut Option<ParserErrorReason> = get_reason(&mut e);
376                    *reason = get_question(i)
377                        .map(|_| ParserErrorReason::MultipleQuestions)
378                        .ok()
379                        .or(*reason);
380                    e
381                })
382            }
383            _ => Err(nom::Err::Failure(ParseError {
384                reason: Some(ParserErrorReason::InvalidState),
385                expected: vec![],
386                offset: 0,
387            })),
388        },
389        ParserState::Fragment { prev_token } => match prev_token {
390            RouteParserToken::FragmentBegin => {
391                alt((fragment_exact, capture_single(field_naming_scheme), get_end))(i)
392            }
393            RouteParserToken::Exact(_) => alt((capture_single(field_naming_scheme), get_end))(i),
394            RouteParserToken::Capture(_) => alt((fragment_exact, get_end))(i),
395            _ => Err(nom::Err::Failure(ParseError {
396                reason: Some(ParserErrorReason::InvalidState),
397                expected: vec![],
398                offset: 0,
399            })),
400        },
401        ParserState::End => Err(nom::Err::Failure(ParseError {
402            reason: Some(ParserErrorReason::TokensAfterEndToken),
403            expected: vec![],
404            offset: 0,
405        })),
406    }
407}
408
409#[cfg(test)]
410mod test {
411    //    use super::*;
412    use super::parse as actual_parse;
413    use crate::{parser::RouteParserToken, FieldNamingScheme, PrettyParseError};
414
415    // Call all tests to parse with the Unnamed variant
416    fn parse(i: &str) -> Result<Vec<RouteParserToken>, PrettyParseError> {
417        actual_parse(i, FieldNamingScheme::Unnamed)
418    }
419
420    mod does_parse {
421        use super::*;
422
423
424        #[test]
425        fn empty() {
426            let x = parse("").expect("Should parse");
427            assert_eq!(x, vec![RouteParserToken::Nothing])
428        }
429
430        #[test]
431        fn slash() {
432            parse("/").expect("should parse");
433        }
434
435        #[test]
436        fn slash_exact() {
437            parse("/hello").expect("should parse");
438        }
439
440        #[test]
441        fn multiple_exact() {
442            parse("/lorem/ipsum").expect("should parse");
443        }
444
445        #[test]
446        fn capture_in_path() {
447            parse("/lorem/{ipsum}").expect("should parse");
448        }
449
450        #[test]
451        fn capture_rest_in_path() {
452            parse("/lorem/{*:ipsum}").expect("should parse");
453        }
454
455        #[test]
456        fn capture_numbered_in_path() {
457            parse("/lorem/{5:ipsum}").expect("should parse");
458        }
459
460        #[test]
461        fn exact_query_after_path() {
462            parse("/lorem?ipsum=dolor").expect("should parse");
463        }
464
465        #[test]
466        fn leading_query_separator() {
467            parse("&lorem=ipsum").expect("Should parse");
468        }
469
470        #[test]
471        fn exact_query() {
472            parse("?lorem=ipsum").expect("should parse");
473        }
474
475        #[test]
476        fn capture_query() {
477            parse("?lorem={ipsum}").expect("should parse");
478        }
479
480        #[test]
481        fn multiple_queries() {
482            parse("?lorem=ipsum&dolor=sit").expect("should parse");
483        }
484
485        #[test]
486        fn query_and_exact_fragment() {
487            parse("?lorem=ipsum#dolor").expect("should parse");
488        }
489
490        #[test]
491        fn query_with_exact_and_capture_fragment() {
492            parse("?lorem=ipsum#dolor{sit}").expect("should parse");
493        }
494
495        #[test]
496        fn query_with_capture_fragment() {
497            parse("?lorem=ipsum#{dolor}").expect("should parse");
498        }
499
500        #[test]
501        fn escaped_backslash() {
502            let tokens = parse(r#"/escaped\\backslash"#).expect("should parse");
503            let expected = vec![
504                RouteParserToken::Separator,
505                RouteParserToken::Exact(r#"escaped\\backslash"#),
506            ];
507            assert_eq!(tokens, expected);
508        }
509
510        #[test]
511        fn escaped_exclamation() {
512            let tokens = parse(r#"/escaped!!exclamation"#).expect("should parse");
513            let expected = vec![
514                RouteParserToken::Separator,
515                RouteParserToken::Exact(r#"escaped"#),
516                RouteParserToken::Exact(r#"!"#),
517                RouteParserToken::Exact(r#"exclamation"#),
518            ];
519            assert_eq!(tokens, expected);
520        }
521
522        #[test]
523        fn escaped_open_bracket() {
524            let tokens = parse(r#"/escaped{{bracket"#).expect("should parse");
525            let expected = vec![
526                RouteParserToken::Separator,
527                RouteParserToken::Exact(r#"escaped"#),
528                RouteParserToken::Exact(r#"{"#),
529                RouteParserToken::Exact(r#"bracket"#),
530            ];
531            assert_eq!(tokens, expected);
532        }
533
534        #[test]
535        fn escaped_close_bracket() {
536            let tokens = parse(r#"/escaped}}bracket"#).expect("should parse");
537            let expected = vec![
538                RouteParserToken::Separator,
539                RouteParserToken::Exact(r#"escaped"#),
540                RouteParserToken::Exact(r#"}"#),
541                RouteParserToken::Exact(r#"bracket"#),
542            ];
543            assert_eq!(tokens, expected);
544        }
545    }
546
547    mod does_not_parse {
548        use super::*;
549        use crate::error::{ExpectedToken, ParserErrorReason};
550
551
552        #[test]
553        fn double_slash() {
554            let x = parse("//").expect_err("Should not parse");
555            assert_eq!(x.error.reason, Some(ParserErrorReason::DoubleSlash))
556        }
557
558        #[test]
559        fn slash_ampersand() {
560            let x = parse("/&lorem=ipsum").expect_err("Should not parse");
561            assert_eq!(x.error.reason, Some(ParserErrorReason::AndBeforeQuestion))
562        }
563
564        #[test]
565        fn non_ident_capture() {
566            let x = parse("/{lor#m}").expect_err("Should not parse");
567            assert_eq!(x.error.reason, Some(ParserErrorReason::BadRustIdent('#')));
568            assert_eq!(
569                x.error.expected,
570                vec![ExpectedToken::CloseBracket, ExpectedToken::Ident]
571            )
572        }
573
574
575        #[test]
576        fn after_end() {
577            let x = parse("/lorem/ipsum!/dolor").expect_err("Should not parse");
578            assert_eq!(x.error.reason, Some(ParserErrorReason::TokensAfterEndToken));
579        }
580    }
581
582    mod correct_parse {
583        use super::*;
584        use crate::parser::{CaptureOrExact, RefCaptureVariant};
585
586        #[test]
587        fn starting_literal() {
588            let parsed = parse("lorem").unwrap();
589            let expected = vec![RouteParserToken::Exact("lorem")];
590            assert_eq!(parsed, expected);
591        }
592
593        #[test]
594        fn minimal_path() {
595            let parsed = parse("/lorem").unwrap();
596            let expected = vec![
597                RouteParserToken::Separator,
598                RouteParserToken::Exact("lorem"),
599            ];
600            assert_eq!(parsed, expected);
601        }
602
603        #[test]
604        fn multiple_path() {
605            let parsed = parse("/lorem/ipsum/dolor/sit").unwrap();
606            let expected = vec![
607                RouteParserToken::Separator,
608                RouteParserToken::Exact("lorem"),
609                RouteParserToken::Separator,
610                RouteParserToken::Exact("ipsum"),
611                RouteParserToken::Separator,
612                RouteParserToken::Exact("dolor"),
613                RouteParserToken::Separator,
614                RouteParserToken::Exact("sit"),
615            ];
616            assert_eq!(parsed, expected);
617        }
618
619        #[test]
620        fn capture_path() {
621            let parsed = parse("/{lorem}/{ipsum}").unwrap();
622            let expected = vec![
623                RouteParserToken::Separator,
624                RouteParserToken::Capture(RefCaptureVariant::Named("lorem")),
625                RouteParserToken::Separator,
626                RouteParserToken::Capture(RefCaptureVariant::Named("ipsum")),
627            ];
628            assert_eq!(parsed, expected);
629        }
630
631        #[test]
632        fn query() {
633            let parsed = parse("?query=this").unwrap();
634            let expected = vec![
635                RouteParserToken::QueryBegin,
636                RouteParserToken::Query {
637                    ident: "query",
638                    capture_or_exact: CaptureOrExact::Exact("this"),
639                },
640            ];
641            assert_eq!(parsed, expected);
642        }
643
644        #[test]
645        fn query_2_part() {
646            let parsed = parse("?lorem=ipsum&dolor=sit").unwrap();
647            let expected = vec![
648                RouteParserToken::QueryBegin,
649                RouteParserToken::Query {
650                    ident: "lorem",
651                    capture_or_exact: CaptureOrExact::Exact("ipsum"),
652                },
653                RouteParserToken::QuerySeparator,
654                RouteParserToken::Query {
655                    ident: "dolor",
656                    capture_or_exact: CaptureOrExact::Exact("sit"),
657                },
658            ];
659            assert_eq!(parsed, expected);
660        }
661
662        #[test]
663        fn query_3_part() {
664            let parsed = parse("?lorem=ipsum&dolor=sit&amet=consectetur").unwrap();
665            let expected = vec![
666                RouteParserToken::QueryBegin,
667                RouteParserToken::Query {
668                    ident: "lorem",
669                    capture_or_exact: CaptureOrExact::Exact("ipsum"),
670                },
671                RouteParserToken::QuerySeparator,
672                RouteParserToken::Query {
673                    ident: "dolor",
674                    capture_or_exact: CaptureOrExact::Exact("sit"),
675                },
676                RouteParserToken::QuerySeparator,
677                RouteParserToken::Query {
678                    ident: "amet",
679                    capture_or_exact: CaptureOrExact::Exact("consectetur"),
680                },
681            ];
682            assert_eq!(parsed, expected);
683        }
684
685        #[test]
686        fn exact_fragment() {
687            let parsed = parse("#lorem").unwrap();
688            let expected = vec![
689                RouteParserToken::FragmentBegin,
690                RouteParserToken::Exact("lorem"),
691            ];
692            assert_eq!(parsed, expected);
693        }
694
695        #[test]
696        fn capture_fragment() {
697            let parsed = parse("#{lorem}").unwrap();
698            let expected = vec![
699                RouteParserToken::FragmentBegin,
700                RouteParserToken::Capture(RefCaptureVariant::Named("lorem")),
701            ];
702            assert_eq!(parsed, expected);
703        }
704
705        #[test]
706        fn mixed_fragment() {
707            let parsed = parse("#{lorem}ipsum{dolor}").unwrap();
708            let expected = vec![
709                RouteParserToken::FragmentBegin,
710                RouteParserToken::Capture(RefCaptureVariant::Named("lorem")),
711                RouteParserToken::Exact("ipsum"),
712                RouteParserToken::Capture(RefCaptureVariant::Named("dolor")),
713            ];
714            assert_eq!(parsed, expected);
715        }
716
717        #[test]
718        fn end_after_path() {
719            let parsed = parse("/lorem!").unwrap();
720            let expected = vec![
721                RouteParserToken::Separator,
722                RouteParserToken::Exact("lorem"),
723                RouteParserToken::End,
724            ];
725            assert_eq!(parsed, expected);
726        }
727
728        #[test]
729        fn end_after_path_separator() {
730            let parsed = parse("/lorem/!").unwrap();
731            let expected = vec![
732                RouteParserToken::Separator,
733                RouteParserToken::Exact("lorem"),
734                RouteParserToken::Separator,
735                RouteParserToken::End,
736            ];
737            assert_eq!(parsed, expected);
738        }
739
740        #[test]
741        fn end_after_path_capture() {
742            let parsed = parse("/lorem/{cap}!").unwrap();
743            let expected = vec![
744                RouteParserToken::Separator,
745                RouteParserToken::Exact("lorem"),
746                RouteParserToken::Separator,
747                RouteParserToken::Capture(RefCaptureVariant::Named("cap")),
748                RouteParserToken::End,
749            ];
750            assert_eq!(parsed, expected);
751        }
752
753        #[test]
754        fn end_after_query_capture() {
755            let parsed = parse("?lorem={cap}!").unwrap();
756            let expected = vec![
757                RouteParserToken::QueryBegin,
758                RouteParserToken::Query {
759                    ident: "lorem",
760                    capture_or_exact: CaptureOrExact::Capture(RefCaptureVariant::Named("cap")),
761                },
762                RouteParserToken::End,
763            ];
764            assert_eq!(parsed, expected);
765        }
766
767        #[test]
768        fn end_after_frag_capture() {
769            let parsed = parse("#{cap}!").unwrap();
770            let expected = vec![
771                RouteParserToken::FragmentBegin,
772                RouteParserToken::Capture(RefCaptureVariant::Named("cap")),
773                RouteParserToken::End,
774            ];
775            assert_eq!(parsed, expected);
776        }
777
778        #[test]
779        fn just_end() {
780            let parsed = parse("!").unwrap();
781            assert_eq!(parsed, vec![RouteParserToken::End]);
782        }
783    }
784}