diff --git a/crates/yew_router_macro/src/lib.rs b/crates/yew_router_macro/src/lib.rs index 6d7d6a1..9bef84d 100644 --- a/crates/yew_router_macro/src/lib.rs +++ b/crates/yew_router_macro/src/lib.rs @@ -26,8 +26,9 @@ mod switch; /// `{1:field_name}` is the same as `{field_name}`. /// /// Tuple-structs and Tuple-enum-variants are also supported. -/// At the moment, dummy field-names still need to be provided to capture sections, but in the future, -/// `{}`, `{*}`, and `{4}` will be valid matchers when used on structs and variants without named fields. +/// If you don't want to specify keys that don't correspond to any specific field, +/// `{}`, `{*}`, and `{4}` also denote valid capture sections when used on structs and variants without named fields. +/// In datastructures without field names, the captures will be assigned in order - left to right. /// /// # Note /// It should be mentioned that the derived function for matching will try enum variants in order, @@ -55,21 +56,23 @@ mod switch; /// /// #[derive(Switch)] /// enum AppRoute { -/// #[to="/some/simple/route"] +/// #[to = "/some/simple/route"] /// SomeSimpleRoute, -/// #[to="/capture/{cap}"] +/// #[to = "/capture/{}"] /// Capture(String), -/// #[to="/convert/{id}"] -/// Convert{id: usize}, -/// #[rest] // #[to="{*:rest}"] would work just as well here -/// Inner(InnerRoute) +/// #[to = "/named/capture/{name}"] +/// NamedCapture { name: String }, +/// #[to = "/convert/{id}"] +/// Convert { id: usize }, +/// #[rest] // shorthand for #[to="{*}"] +/// Inner(InnerRoute), /// } /// /// #[derive(Switch)] -/// #[to="/inner/route/{first}/{second}"] +/// #[to = "/inner/route/{first}/{second}"] /// struct InnerRoute { /// first: String, -/// second: String +/// second: String, /// } /// ``` /// Check out the examples directory in the repository to see some more usages of the routing syntax. diff --git a/crates/yew_router_macro/src/switch.rs b/crates/yew_router_macro/src/switch.rs index 7dd6e3a..2d5a479 100644 --- a/crates/yew_router_macro/src/switch.rs +++ b/crates/yew_router_macro/src/switch.rs @@ -29,10 +29,14 @@ pub fn switch_impl(input: TokenStream) -> TokenStream { match input.data { Data::Struct(ds) => { + let field_type = match ds.fields { + Fields::Unnamed(_) | Fields::Unit => yew_router_route_parser::FieldType::Unnamed, + Fields::Named(_) => yew_router_route_parser::FieldType::Named, + }; let matcher = AttrToken::convert_attributes_to_tokens(input.attrs) .into_iter() .enumerate() - .map(|(index, at)| at.into_shadow_matcher_tokens(index)) + .map(|(index, at)| at.into_shadow_matcher_tokens(index, field_type)) .flatten() .collect::>(); let switch_item = SwitchItem { @@ -47,10 +51,16 @@ pub fn switch_impl(input: TokenStream) -> TokenStream { .variants .into_iter() .map(|variant: Variant| { + let field_type = match variant.fields { + Fields::Unnamed(_) | Fields::Unit => { + yew_router_route_parser::FieldType::Unnamed + } + Fields::Named(_) => yew_router_route_parser::FieldType::Named, + }; let matcher = AttrToken::convert_attributes_to_tokens(variant.attrs) .into_iter() .enumerate() - .map(|(index, at)| at.into_shadow_matcher_tokens(index)) + .map(|(index, at)| at.into_shadow_matcher_tokens(index, field_type)) .flatten() .collect::>(); SwitchItem { @@ -120,17 +130,18 @@ fn write_for_token(token: &ShadowMatcherToken, naming_scheme: FieldType) -> Toke state = state.or(#name.build_route_section(buf)); } } - }, - FieldType::Unnamed { index } => match &capture { - ShadowCaptureVariant::Named(_) - | ShadowCaptureVariant::ManyNamed(_) - | ShadowCaptureVariant::NumberedNamed { .. } => { - let name = unnamed_field_index_item(index); - quote! { - state = state.or(#name.build_route_section(&mut buf)); - } + ShadowCaptureVariant::Unnamed + | ShadowCaptureVariant::ManyUnnamed + | ShadowCaptureVariant::NumberedUnnamed { .. } => { + panic!("Unnamed matcher sections not allowed for named field types") } }, + FieldType::Unnamed { index } => { + let name = unnamed_field_index_item(index); + quote! { + state = state.or(#name.build_route_section(&mut buf)); + } + } }, ShadowMatcherToken::End => quote! {}, } diff --git a/crates/yew_router_macro/src/switch/attribute.rs b/crates/yew_router_macro/src/switch/attribute.rs index 29ed1df..48717a0 100644 --- a/crates/yew_router_macro/src/switch/attribute.rs +++ b/crates/yew_router_macro/src/switch/attribute.rs @@ -53,10 +53,14 @@ impl AttrToken { /// The id is an unique identifier that allows otherwise unnamed captures to still be captured /// with unique names. - pub fn into_shadow_matcher_tokens(self, id: usize) -> Vec { + pub fn into_shadow_matcher_tokens( + self, + id: usize, + field_type: yew_router_route_parser::FieldType, + ) -> Vec { match self { AttrToken::To(matcher_string) => { - yew_router_route_parser::parse_str_and_optimize_tokens(&matcher_string) + yew_router_route_parser::parse_str_and_optimize_tokens(&matcher_string, field_type) .expect("Invalid Matcher") // This is the point where users should see an error message if their matcher string has some syntax error. .into_iter() .map(crate::switch::shadow::ShadowMatcherToken::from) diff --git a/crates/yew_router_macro/src/switch/shadow.rs b/crates/yew_router_macro/src/switch/shadow.rs index b3a55a3..097c603 100644 --- a/crates/yew_router_macro/src/switch/shadow.rs +++ b/crates/yew_router_macro/src/switch/shadow.rs @@ -29,6 +29,15 @@ pub enum ShadowMatcherToken { } pub enum ShadowCaptureVariant { + /// {} + Unnamed, + /// {*} + ManyUnnamed, + /// {5} + NumberedUnnamed { + /// Number of sections to match. + sections: usize, + }, /// {name} - captures a section and adds it to the map with a given name Named(String), /// {*:name} - captures over many sections and adds it to the map with a given name. @@ -49,6 +58,15 @@ impl ToTokens for ShadowCaptureVariant { ShadowCaptureVariant::NumberedNamed { sections, name } => { quote! {::yew_router::matcher::CaptureVariant::NumberedNamed{sections: #sections, name: #name.to_string()}} } + ShadowCaptureVariant::Unnamed => { + quote! {::yew_router::matcher::CaptureVariant::Unnamed} + } + ShadowCaptureVariant::ManyUnnamed => { + quote! {::yew_router::matcher::CaptureVariant::ManyUnnamed} + } + ShadowCaptureVariant::NumberedUnnamed { sections } => { + quote! {::yew_router::matcher::CaptureVariant::NumberedUnnamed{sections: #sections}} + } }; ts.extend(t) } @@ -75,6 +93,9 @@ impl From for ShadowCaptureVariant { CaptureVariant::NumberedNamed { sections, name } => { SCV::NumberedNamed { sections, name } } + CaptureVariant::Unnamed => SCV::Unnamed, + CaptureVariant::ManyUnnamed => SCV::ManyUnnamed, + CaptureVariant::NumberedUnnamed { sections } => SCV::NumberedUnnamed { sections }, } } } diff --git a/crates/yew_router_route_parser/src/core.rs b/crates/yew_router_route_parser/src/core.rs index 6c61b84..ef5e377 100644 --- a/crates/yew_router_route_parser/src/core.rs +++ b/crates/yew_router_route_parser/src/core.rs @@ -1,15 +1,29 @@ -use nom::IResult; -use crate::parser::{RouteParserToken, RefCaptureVariant, CaptureOrExact}; -use nom::sequence::{separated_pair, delimited, pair}; -use nom::combinator::{map, map_opt, map_parser}; -use nom::branch::alt; -use nom::character::complete::digit1; -use crate::error::{ParserErrorReason, ExpectedToken}; -use nom::bytes::complete::{take_until, tag, take_till1}; -use nom::character::complete::char; -use crate::ParseError; -use nom::error::ErrorKind; -use nom::character::is_digit; +use crate::{ + error::{ExpectedToken, ParserErrorReason}, + parser::{CaptureOrExact, RefCaptureVariant, RouteParserToken}, + ParseError, +}; +use nom::{ + branch::alt, + bytes::complete::take_till1, + character::{ + complete::{char, digit1}, + is_digit, + }, + combinator::{map, map_parser}, + error::ErrorKind, + sequence::{delimited, separated_pair}, + IResult, +}; + +/// Indicates if the parser is working to create a matcher for a datastructure with named or unnamed fields. +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Ord, PartialOrd)] +pub enum FieldType { + /// For Thing { field: String } + Named, + /// for Thing(String) + Unnamed, +} pub fn get_slash(i: &str) -> IResult<&str, RouteParserToken, ParseError> { map(char('/'), |_: char| RouteParserToken::Separator)(i) @@ -22,14 +36,16 @@ pub fn get_question(i: &str) -> IResult<&str, RouteParserToken, ParseError> { } pub fn get_and(i: &str) -> IResult<&str, RouteParserToken, ParseError> { - map(char('&'), |_: char| RouteParserToken::QuerySeparator)(i) - .map_err(|_: nom::Err<()>| nom::Err::Error(ParseError::expected(ExpectedToken::QuerySeparator))) + map(char('&'), |_: char| RouteParserToken::QuerySeparator)(i).map_err(|_: nom::Err<()>| { + nom::Err::Error(ParseError::expected(ExpectedToken::QuerySeparator)) + }) } /// Returns a FragmentBegin variant if the next character is '\#'. pub fn get_hash(i: &str) -> IResult<&str, RouteParserToken, ParseError> { - map(char('#'), |_: char| RouteParserToken::FragmentBegin)(i) - .map_err(|_: nom::Err<()>| nom::Err::Error(ParseError::expected(ExpectedToken::FragmentBegin))) + map(char('#'), |_: char| RouteParserToken::FragmentBegin)(i).map_err(|_: nom::Err<()>| { + nom::Err::Error(ParseError::expected(ExpectedToken::FragmentBegin)) + }) } /// Returns an End variant if the next character is a '!`. @@ -39,152 +55,209 @@ pub fn get_end(i: &str) -> IResult<&str, RouteParserToken, ParseError> { } /// Returns an End variant if the next character is a '!`. -fn get_open_bracket(i: &str) -> IResult<&str, RouteParserToken, ParseError> { - map(char('{'), |_: char| RouteParserToken::End)(i) - .map_err(|_: nom::Err<()>| nom::Err::Error(ParseError::expected(ExpectedToken::OpenBracket))) +fn get_open_bracket(i: &str) -> IResult<&str, (), ParseError> { + map(char('{'), |_: char| ())(i).map_err(|_: nom::Err<()>| { + nom::Err::Error(ParseError::expected(ExpectedToken::OpenBracket)) + }) } -fn get_close_bracket(i: &str) -> IResult<&str, RouteParserToken, ParseError> { - map(char('}'), |_: char| RouteParserToken::End)(i) - .map_err(|_: nom::Err<()>| nom::Err::Error(ParseError::expected(ExpectedToken::CloseBracket))) +fn get_close_bracket(i: &str) -> IResult<&str, (), ParseError> { + map(char('}'), |_: char| ())(i).map_err(|_: nom::Err<()>| { + nom::Err::Error(ParseError::expected(ExpectedToken::CloseBracket)) + }) } -fn get_eq(i: &str) -> IResult<&str, RouteParserToken, ParseError> { - map(char('='), |_: char| RouteParserToken::End)(i) +fn get_eq(i: &str) -> IResult<&str, (), ParseError> { + map(char('='), |_: char| ())(i) .map_err(|_: nom::Err<()>| nom::Err::Error(ParseError::expected(ExpectedToken::Equals))) } -fn get_star(i: &str) -> IResult<&str, RouteParserToken, ParseError> { - map(char('*'), |_: char| RouteParserToken::End)(i) +fn get_star(i: &str) -> IResult<&str, (), ParseError> { + map(char('*'), |_: char| ())(i) .map_err(|_: nom::Err<()>| nom::Err::Error(ParseError::expected(ExpectedToken::Star))) } -fn get_colon(i: &str) -> IResult<&str, RouteParserToken, ParseError> { - map(char(':'), |_: char| RouteParserToken::End)(i) +fn get_colon(i: &str) -> IResult<&str, (), ParseError> { + map(char(':'), |_: char| ())(i) .map_err(|_: nom::Err<()>| nom::Err::Error(ParseError::expected(ExpectedToken::Colon))) } -pub fn rust_ident(i: &str) -> IResult<&str, &str, ParseError> { - +fn rust_ident(i: &str) -> IResult<&str, &str, ParseError> { let invalid_ident_chars = r##" \|/{[]()?+=-!@#$%^&*~`'";:"##; - map_parser( - take_till1(move |c| { - c == '}' - }), - move |i: &str| { - match take_till1::<_,_,()>(|c| invalid_ident_chars.contains(c))(i) { - Ok((remain, got)) => { - if got.len() > 0 && got.starts_with(|c: char|is_digit(c as u8)) { - Err(nom::Err::Failure(ParseError { - reason: Some(ParserErrorReason::BadRustIdent(got.chars().next().unwrap())), - expected: vec![ExpectedToken::Ident], - offset: 1 - })) - } - else if remain.len() > 0 { - Err(nom::Err::Failure(ParseError { - reason: Some(ParserErrorReason::BadRustIdent(remain.chars().next().unwrap())), - expected: vec![ExpectedToken::CloseBracket, ExpectedToken::Ident], - offset: got.len() + 1 - })) - } else { - Ok((i,i)) - } - }, - Err(_) => { - Ok((i,i)) // TODO this might be right? + map_parser(take_till1(move |c| c == '}'), move |i: &str| { + match take_till1::<_, _, ()>(|c| invalid_ident_chars.contains(c))(i) { + Ok((remain, got)) => { + if got.len() > 0 && got.starts_with(|c: char| is_digit(c as u8)) { + Err(nom::Err::Failure(ParseError { + reason: Some(ParserErrorReason::BadRustIdent(got.chars().next().unwrap())), + expected: vec![ExpectedToken::Ident], + offset: 1, + })) + } else if remain.len() > 0 { + Err(nom::Err::Failure(ParseError { + reason: Some(ParserErrorReason::BadRustIdent( + remain.chars().next().unwrap(), + )), + expected: vec![ExpectedToken::CloseBracket, ExpectedToken::Ident], + offset: got.len() + 1, + })) + } else { + Ok((i, i)) } } + Err(_) => { + Ok((i, i)) // TODO this might be right? + } } - )(i) + })(i) } -pub fn exact_impl(i: &str) -> IResult<&str, &str, ParseError> { +fn exact_impl(i: &str) -> IResult<&str, &str, ParseError> { let special_chars = r##"/?&#={}!"##; // TODO these might allow escaping one day. - take_till1(move |c| special_chars.contains(c))(i) - .map_err(|x: nom::Err<(&str, ErrorKind)>| { - let s = match x { - nom::Err::Error((s,_)) => s, - nom::Err::Failure((s,_)) => s, - nom::Err::Incomplete(_) => panic!(), - }; - nom::Err::Error(ParseError{ - reason: Some(ParserErrorReason::BadLiteral), - expected: vec![ExpectedToken::Literal], - offset: 1 + i.len() - s.len() - }) + take_till1(move |c| special_chars.contains(c))(i).map_err(|x: nom::Err<(&str, ErrorKind)>| { + let s = match x { + nom::Err::Error((s, _)) => s, + nom::Err::Failure((s, _)) => s, + nom::Err::Incomplete(_) => panic!(), + }; + nom::Err::Error(ParseError { + reason: Some(ParserErrorReason::BadLiteral), + expected: vec![ExpectedToken::Literal], + offset: 1 + i.len() - s.len(), }) + }) } pub fn exact(i: &str) -> IResult<&str, RouteParserToken, ParseError> { - map(exact_impl, |s| RouteParserToken::Exact(s))(i) + map(exact_impl, RouteParserToken::Exact)(i) } -pub fn capture(i: &str) -> IResult<&str, RouteParserToken, ParseError> { - map(named_capture_impl, |cv: RefCaptureVariant| { - RouteParserToken::Capture(cv) - })(i) +pub fn capture<'a>( + field_type: FieldType, +) -> impl Fn(&'a str) -> IResult<&'a str, RouteParserToken<'a>, ParseError> { + map(capture_impl(field_type), RouteParserToken::Capture) } -pub fn capture_single(i: &str) -> IResult<&str, RouteParserToken, ParseError> { - map( - delimited(get_open_bracket, single_capture_impl, get_close_bracket), - RouteParserToken::Capture, - )(i) +pub fn capture_single<'a>( + field_type: FieldType, +) -> impl Fn(&'a str) -> IResult<&'a str, RouteParserToken<'a>, ParseError> { + map(capture_single_impl(field_type), RouteParserToken::Capture) } -/// Captures {ident}, {*:ident}, {:ident} -pub fn named_capture_impl(i: &str) -> IResult<&str, RefCaptureVariant, ParseError> { - let inner = alt(( - many_capture_impl, - numbered_capture_impl, - single_capture_impl, - )); - delimited(get_open_bracket, inner, get_close_bracket)(i) +fn capture_single_impl<'a>( + field_type: FieldType, +) -> impl Fn(&'a str) -> IResult<&'a str, RefCaptureVariant<'a>, ParseError> { + move |i: &str| match field_type { + FieldType::Named => delimited( + get_open_bracket, + named::single_capture_impl, + get_close_bracket, + )(i), + FieldType::Unnamed => delimited( + get_open_bracket, + alt((named::single_capture_impl, unnamed::single_capture_impl)), + get_close_bracket, + )(i), + } } -fn single_capture_impl(i: &str) -> IResult<&str, RefCaptureVariant, ParseError> { - map(rust_ident, |key| RefCaptureVariant::Named(key))(i) +/// Captures {ident}, {*:ident}, {:ident} +fn capture_impl<'a>( + field_type: FieldType, +) -> impl Fn(&'a str) -> IResult<&'a str, RefCaptureVariant, ParseError> { + move |i: &str| match field_type { + FieldType::Named => { + let inner = alt(( + named::many_capture_impl, + named::numbered_capture_impl, + named::single_capture_impl, + )); + delimited(get_open_bracket, inner, get_close_bracket)(i) + } + FieldType::Unnamed => { + let inner = alt(( + named::many_capture_impl, + unnamed::many_capture_impl, + named::numbered_capture_impl, + unnamed::numbered_capture_impl, + named::single_capture_impl, + unnamed::single_capture_impl, + )); + delimited(get_open_bracket, inner, get_close_bracket)(i) + } + } } -fn many_capture_impl(i: &str) -> IResult<&str, RefCaptureVariant, ParseError> { - map( - separated_pair(get_star, get_colon, rust_ident), - |(_, key)| RefCaptureVariant::ManyNamed(key), - )(i) +mod named { + use super::*; + pub fn single_capture_impl(i: &str) -> IResult<&str, RefCaptureVariant, ParseError> { + map(rust_ident, |key| RefCaptureVariant::Named(key))(i) + } + + pub fn many_capture_impl(i: &str) -> IResult<&str, RefCaptureVariant, ParseError> { + map( + separated_pair(get_star, get_colon, rust_ident), + |(_, key)| RefCaptureVariant::ManyNamed(key), + )(i) + } + + pub fn numbered_capture_impl(i: &str) -> IResult<&str, RefCaptureVariant, ParseError> { + map( + separated_pair(digit1, get_colon, rust_ident), + |(number, key)| RefCaptureVariant::NumberedNamed { + sections: number.parse().unwrap(), + name: key, + }, + )(i) + } } -fn numbered_capture_impl(i: &str) -> IResult<&str, RefCaptureVariant, ParseError> { - map( - separated_pair(digit1, get_colon, rust_ident), - |(number, key)| RefCaptureVariant::NumberedNamed { +mod unnamed { + use super::*; + + /// #Note + /// because this always succeeds, try this last + pub fn single_capture_impl(i: &str) -> IResult<&str, RefCaptureVariant, ParseError> { + Ok((i, RefCaptureVariant::Unnamed)) + } + + pub fn many_capture_impl(i: &str) -> IResult<&str, RefCaptureVariant, ParseError> { + map(get_star, |_| RefCaptureVariant::ManyUnnamed)(i) + } + + pub fn numbered_capture_impl(i: &str) -> IResult<&str, RefCaptureVariant, ParseError> { + map(digit1, |number: &str| RefCaptureVariant::NumberedUnnamed { sections: number.parse().unwrap(), - name: key, - }, - )(i) + })(i) + } } /// Gets a capture or exact, mapping it to the CaptureOrExact enum - to provide a limited subset. -pub fn cap_or_exact(i: &str) -> IResult<&str, CaptureOrExact, ParseError> { - alt(( - map( - delimited(get_open_bracket, single_capture_impl, get_close_bracket), - CaptureOrExact::Capture, - ), - map(exact_impl, |exact| CaptureOrExact::Exact(exact)), - ))(i) +fn cap_or_exact<'a>( + field_type: FieldType, +) -> impl Fn(&'a str) -> IResult<&'a str, CaptureOrExact<'a>, ParseError> { + move |i: &str| { + alt(( + map(capture_single_impl(field_type), CaptureOrExact::Capture), + map(exact_impl, CaptureOrExact::Exact), + ))(i) + } } /// Matches a query -pub fn query(i: &str) -> IResult<&str, RouteParserToken, ParseError> { - map( - separated_pair(exact_impl, get_eq, cap_or_exact), - |(ident, capture_or_exact)| RouteParserToken::Query { - ident, - capture_or_exact, - }, - )(i) +pub fn query<'a>( + field_type: FieldType, +) -> impl Fn(&'a str) -> IResult<&'a str, RouteParserToken<'a>, ParseError> { + move |i: &str| { + map( + separated_pair(exact_impl, get_eq, cap_or_exact(field_type)), + |(ident, capture_or_exact)| RouteParserToken::Query { + ident, + capture_or_exact, + }, + )(i) + } } @@ -194,15 +267,29 @@ mod test { #[test] fn cap_or_exact_match_lit() { - cap_or_exact("lorem").expect("Should parse"); + cap_or_exact(FieldType::Named)("lorem").expect("Should parse"); } #[test] fn cap_or_exact_match_cap() { - cap_or_exact("{lorem}").expect("Should parse"); + cap_or_exact(FieldType::Named)("{lorem}").expect("Should parse"); + } + + #[test] + fn query_section_exact() { + query(FieldType::Named)("lorem=ipsum").expect("should parse"); + } + + #[test] + fn query_section_capture_named() { + query(FieldType::Named)("lorem={ipsum}").expect("should parse"); + } + #[test] + fn query_section_capture_named_fails_without_key() { + query(FieldType::Named)("lorem={}").expect_err("should not parse"); } #[test] - fn query_section() { - query("lorem=ipsum").expect("should parse"); + fn query_section_capture_unnamed_succeeds_without_key() { + query(FieldType::Unnamed)("lorem={}").expect("should parse"); } #[test] diff --git a/crates/yew_router_route_parser/src/error.rs b/crates/yew_router_route_parser/src/error.rs index 86955dc..55d08f6 100644 --- a/crates/yew_router_route_parser/src/error.rs +++ b/crates/yew_router_route_parser/src/error.rs @@ -1,5 +1,5 @@ -use std::fmt; use nom::error::ErrorKind; +use std::fmt; /// Parser error that can print itself in a human-readable format. #[derive(Clone, PartialEq)] @@ -74,21 +74,21 @@ pub struct ParseError { } impl ParseError { - pub (crate) fn expected(expected: ExpectedToken) -> Self { + pub(crate) fn expected(expected: ExpectedToken) -> Self { ParseError { reason: None, expected: vec![expected], - offset: 0 + offset: 0, } } } -impl nom::error::ParseError<&str> for ParseError { +impl nom::error::ParseError<&str> for ParseError { fn from_error_kind(_input: &str, _kind: ErrorKind) -> Self { ParseError { reason: None, expected: vec![], - offset: 0 + offset: 0, } } @@ -98,12 +98,12 @@ impl nom::error::ParseError<&str> for ParseError { fn or(mut self, other: Self) -> Self { self.expected.extend(other.expected); -// self.expected.dedup(); // TODO enforce that these are actually sorted + // self.expected.dedup(); // TODO enforce that these are actually sorted ParseError { reason: other.reason.or(self.reason), // Take the right most reason expected: self.expected, - offset: other.offset // TODO panicing might be an option if the offsets aren't the same, Maybe add them? eeeh?, maybe create layers of expected with specific offsets? + offset: other.offset, /* TODO panicing might be an option if the offsets aren't the same, Maybe add them? eeeh?, maybe create layers of expected with specific offsets? */ } } } @@ -143,7 +143,7 @@ pub enum ExpectedToken { /// * Star, /// : - Colon + Colon, } impl fmt::Display for ExpectedToken { @@ -195,7 +195,7 @@ pub enum ParserErrorReason { /// This should never actually be created. NotAllowedStateTransition, /// Expected a specific token - Expected(ExpectedToken) + Expected(ExpectedToken), } impl fmt::Display for ParserErrorReason { @@ -242,11 +242,9 @@ impl fmt::Display for ParserErrorReason { } } -pub (crate) fn get_reason(err: &mut nom::Err) -> &mut Option { +pub(crate) fn get_reason(err: &mut nom::Err) -> &mut Option { match err { - nom::Err::Error(err) - | nom::Err::Failure(err) - => &mut err.reason, - nom::Err::Incomplete(_) => panic!("Incomplete not possible") + nom::Err::Error(err) | nom::Err::Failure(err) => &mut err.reason, + nom::Err::Incomplete(_) => panic!("Incomplete not possible"), } -} \ No newline at end of file +} diff --git a/crates/yew_router_route_parser/src/lib.rs b/crates/yew_router_route_parser/src/lib.rs index e3dd1f2..e3109b8 100644 --- a/crates/yew_router_route_parser/src/lib.rs +++ b/crates/yew_router_route_parser/src/lib.rs @@ -13,9 +13,10 @@ unused_qualifications )] +mod core; mod error; pub mod parser; -mod core; +pub use crate::core::FieldType; pub use error::{ParseError, PrettyParseError}; mod optimizer; pub use optimizer::{convert_tokens, parse_str_and_optimize_tokens}; @@ -43,6 +44,15 @@ pub enum MatcherToken { /// Variants that indicate how part of a string should be captured. #[derive(Debug, PartialEq, Clone)] pub enum CaptureVariant { + /// {} + Unnamed, + /// {*} + ManyUnnamed, + /// {5} + NumberedUnnamed { + /// Number of sections to match. + sections: usize, + }, /// {name} - captures a section and adds it to the map with a given name. Named(String), /// {*:name} - captures over many sections and adds it to the map with a given name. diff --git a/crates/yew_router_route_parser/src/optimizer.rs b/crates/yew_router_route_parser/src/optimizer.rs index 409f9f4..c451a64 100644 --- a/crates/yew_router_route_parser/src/optimizer.rs +++ b/crates/yew_router_route_parser/src/optimizer.rs @@ -3,7 +3,7 @@ use crate::{ parser::{parse, CaptureOrExact, RefCaptureVariant, RouteParserToken}, }; -use crate::{CaptureVariant, MatcherToken}; +use crate::{core::FieldType, CaptureVariant, MatcherToken}; impl<'a> From> for CaptureVariant { fn from(v: RefCaptureVariant<'a>) -> Self { @@ -14,6 +14,11 @@ impl<'a> From> for CaptureVariant { sections, name: name.to_string(), }, + RefCaptureVariant::Unnamed => CaptureVariant::Unnamed, + RefCaptureVariant::ManyUnnamed => CaptureVariant::ManyUnnamed, + RefCaptureVariant::NumberedUnnamed { sections } => { + CaptureVariant::NumberedUnnamed { sections } + } } } } @@ -43,8 +48,11 @@ impl<'a> RouteParserToken<'a> { } /// Parse the provided "matcher string" and then optimize the tokens. -pub fn parse_str_and_optimize_tokens(i: &str) -> Result, PrettyParseError> { - let tokens = parse(i)?; +pub fn parse_str_and_optimize_tokens( + i: &str, + field_type: FieldType, +) -> Result, PrettyParseError> { + let tokens = parse(i, field_type)?; Ok(convert_tokens(&tokens)) } diff --git a/crates/yew_router_route_parser/src/parser.rs b/crates/yew_router_route_parser/src/parser.rs index 913b067..3d426e8 100644 --- a/crates/yew_router_route_parser/src/parser.rs +++ b/crates/yew_router_route_parser/src/parser.rs @@ -1,14 +1,12 @@ //! Parser that consumes a string and produces the first representation of the matcher. -use crate::error::{ParseError, ParserErrorReason, PrettyParseError, get_reason}; -use nom::{ - branch::alt, -// bytes::complete::{tag, take_till1, take_until}, -// character::complete::{char, digit1}, -// combinator::{map, map_opt}, -// sequence::{delimited, pair, separated_pair}, - IResult, +use crate::{ + core::{ + capture, capture_single, exact, get_and, get_end, get_hash, get_question, get_slash, query, + }, + error::{get_reason, ParseError, ParserErrorReason, PrettyParseError}, + FieldType, }; -use crate::core::{get_slash, capture, get_question, get_end, get_hash, get_and, exact, query, capture_single}; +use nom::{branch::alt, IResult}; /// Tokens generated from parsing a route matcher string. /// They will be optimized to another token type that is used to match URLs. @@ -46,6 +44,15 @@ pub enum RouteParserToken<'a> { /// It gets converted to CaptureVariant, a nearly identical enum that has owned Strings instead. #[derive(Debug, Clone, Copy, PartialEq)] pub enum RefCaptureVariant<'a> { + /// {} + Unnamed, + /// {*} + ManyUnnamed, + /// {5} + NumberedUnnamed { + /// Number of sections to match. + sections: usize, + }, /// {name} - captures a section and adds it to the map with a given name. Named(&'a str), /// {*:name} - captures over many sections and adds it to the map with a given name. @@ -200,13 +207,16 @@ impl<'a> ParserState<'a> { /// due to the fact that erroneous tokens can't be fed into the transition function. /// /// This continues until the string is exhausted, or none of the parsers for the current state can parse the current input. -pub fn parse(mut i: &str) -> Result, PrettyParseError> { +pub fn parse( + mut i: &str, + field_type: FieldType, +) -> Result, PrettyParseError> { let input = i; let mut tokens: Vec = vec![]; let mut state = ParserState::None; loop { - let (ii, token) = parse_impl(i, &state).map_err(|e| match e { + let (ii, token) = parse_impl(i, &state, field_type).map_err(|e| match e { nom::Err::Error(e) | nom::Err::Failure(e) => PrettyParseError { error: e, input, @@ -219,7 +229,7 @@ pub fn parse(mut i: &str) -> Result, PrettyParseError> { let error = ParseError { reason: Some(reason), expected: vec![], - offset: 0 + offset: 0, }; PrettyParseError { error, @@ -240,22 +250,30 @@ pub fn parse(mut i: &str) -> Result, PrettyParseError> { fn parse_impl<'a>( i: &'a str, state: &ParserState, + field_type: FieldType, ) -> IResult<&'a str, RouteParserToken<'a>, ParseError> { match state { - ParserState::None => alt((get_slash, get_question, get_hash, capture, exact, get_end))(i) - .map_err(|mut e: nom::Err| { - // Detect likely failures if the above failed to match. - let reason: &mut Option = get_reason(&mut e); - *reason = get_and(i).map(|_| ParserErrorReason::AndBeforeQuestion) // TODO, technically, a sub-switch may want to start with a &query=something, so enabling this might make sense. + ParserState::None => alt(( + get_slash, + get_question, + get_hash, + capture(field_type), + exact, + get_end, + ))(i) + .map_err(|mut e: nom::Err| { + // Detect likely failures if the above failed to match. + let reason: &mut Option = get_reason(&mut e); + *reason = get_and(i).map(|_| ParserErrorReason::AndBeforeQuestion) // TODO, technically, a sub-switch may want to start with a &query=something, so enabling this might make sense. // .or_else(|_| bad_capture(i).map(|(_, reason)| reason)) .ok() .or(*reason); - e - }), + e + }), ParserState::Path { prev_token } => match prev_token { RouteParserToken::Separator => { - alt((exact, capture, get_question, get_hash, get_end))(i) - .map_err(|mut e: nom::Err| { + alt((exact, capture(field_type), get_question, get_hash, get_end))(i).map_err( + |mut e: nom::Err| { // Detect likely failures if the above failed to match. let reason: &mut Option = get_reason(&mut e); *reason = get_slash(i) @@ -265,41 +283,51 @@ fn parse_impl<'a>( .ok() .or(*reason); e - }) + }, + ) } RouteParserToken::Exact(_) => { - alt((get_slash, capture, get_question, get_hash, get_end))(i) - .map_err(|mut e: nom::Err| { - // Detect likely failures if the above failed to match. - let reason: &mut Option = get_reason(&mut e); - *reason = get_and(i) + alt(( + get_slash, + capture(field_type), + get_question, + get_hash, + get_end, + ))(i) + .map_err(|mut e: nom::Err| { + // Detect likely failures if the above failed to match. + let reason: &mut Option = get_reason(&mut e); + *reason = get_and(i) .map(|_| ParserErrorReason::AndBeforeQuestion) // .or_else(|_| bad_capture(i).map(|(_, reason)| reason)) .ok() .or(*reason); + e + }) + } + RouteParserToken::Capture(_) => { + alt((get_slash, exact, get_question, get_hash, get_end))(i).map_err( + |mut e: nom::Err| { + // Detect likely failures if the above failed to match. + let reason: &mut Option = get_reason(&mut e); + *reason = capture(field_type)(i) + .map(|_| ParserErrorReason::AdjacentCaptures) + .or_else(|_| get_and(i).map(|_| ParserErrorReason::AndBeforeQuestion)) + .ok() + .or(*reason); e - }) + }, + ) } - RouteParserToken::Capture(_) => alt((get_slash, exact, get_question, get_hash, get_end))(i) - .map_err(|mut e: nom::Err| { - // Detect likely failures if the above failed to match. - let reason: &mut Option = get_reason(&mut e); - *reason = capture(i) - .map(|_| ParserErrorReason::AdjacentCaptures) - .or_else(|_| get_and(i).map(|_| ParserErrorReason::AndBeforeQuestion)) - .ok() - .or(*reason); - e - }), _ => Err(nom::Err::Failure(ParseError { reason: Some(ParserErrorReason::InvalidState), expected: vec![], - offset: 0 + offset: 0, })), }, ParserState::FirstQuery { prev_token } => match prev_token { - RouteParserToken::QueryBegin => query(i) - .map_err(|mut e: nom::Err| { + RouteParserToken::QueryBegin => { + query(field_type)(i).map_err(|mut e: nom::Err| { // Detect likely failures if the above failed to match. let reason: &mut Option = get_reason(&mut e); *reason = get_question(i) @@ -307,9 +335,10 @@ fn parse_impl<'a>( .ok() .or(*reason); e - }), - RouteParserToken::Query { .. } => alt((get_and, get_hash, get_end))(i) - .map_err(|mut e: nom::Err| { + }) + } + RouteParserToken::Query { .. } => { + alt((get_and, get_hash, get_end))(i).map_err(|mut e: nom::Err| { // Detect likely failures if the above failed to match. let reason: &mut Option = get_reason(&mut e); *reason = get_question(i) @@ -317,16 +346,17 @@ fn parse_impl<'a>( .ok() .or(*reason); e - }), + }) + } _ => Err(nom::Err::Failure(ParseError { reason: Some(ParserErrorReason::InvalidState), expected: vec![], - offset: 0 + offset: 0, })), }, ParserState::NthQuery { prev_token } => match prev_token { - RouteParserToken::QuerySeparator => query(i) - .map_err(|mut e: nom::Err| { + RouteParserToken::QuerySeparator => { + query(field_type)(i).map_err(|mut e: nom::Err| { // Detect likely failures if the above failed to match. let reason: &mut Option = get_reason(&mut e); *reason = get_question(i) @@ -334,61 +364,67 @@ fn parse_impl<'a>( .ok() .or(*reason); e - }), + }) + } RouteParserToken::Query { .. } => { - alt((get_and, get_hash, get_end))(i) - .map_err(|mut e: nom::Err| { - // Detect likely failures if the above failed to match. - let reason: &mut Option = get_reason(&mut e); - *reason = get_question(i) - .map(|_| ParserErrorReason::MultipleQuestions) - .ok() - .or(*reason); - e - }) + alt((get_and, get_hash, get_end))(i).map_err(|mut e: nom::Err| { + // Detect likely failures if the above failed to match. + let reason: &mut Option = get_reason(&mut e); + *reason = get_question(i) + .map(|_| ParserErrorReason::MultipleQuestions) + .ok() + .or(*reason); + e + }) } _ => Err(nom::Err::Failure(ParseError { reason: Some(ParserErrorReason::InvalidState), expected: vec![], - offset: 0 + offset: 0, })), }, ParserState::Fragment { prev_token } => match prev_token { - RouteParserToken::FragmentBegin => alt((exact, capture_single, get_end))(i), - RouteParserToken::Exact(_) => alt((capture_single, get_end))(i), + RouteParserToken::FragmentBegin => alt((exact, capture_single(field_type), get_end))(i), + RouteParserToken::Exact(_) => alt((capture_single(field_type), get_end))(i), RouteParserToken::Capture(_) => alt((exact, get_end))(i), -// .map_err(|mut e: nom::Err| { -// // Detect likely failures if the above failed to match. -// let reason: &mut Option = get_reason(&mut e); -// *reason = bad_capture(i).map(|(_, reason)| reason).ok() -// .or(*reason); -// e -// }), + // .map_err(|mut e: nom::Err| { + // // Detect likely failures if the above failed to match. + // let reason: &mut Option = get_reason(&mut e); + // *reason = bad_capture(i).map(|(_, reason)| reason).ok() + // .or(*reason); + // e + // }), _ => Err(nom::Err::Failure(ParseError { reason: Some(ParserErrorReason::InvalidState), expected: vec![], - offset: 0 + offset: 0, })), }, ParserState::End => Err(nom::Err::Failure(ParseError { reason: Some(ParserErrorReason::TokensAfterEndToken), expected: vec![], - offset: 0 + offset: 0, })), } } - #[cfg(test)] mod test { - use super::*; + // use super::*; + use super::parse as actual_parse; + use crate::{parser::RouteParserToken, FieldType, PrettyParseError}; + // Call all tests to parse with the Unnamed variant + fn parse(i: &str) -> Result, PrettyParseError> { + actual_parse(i, FieldType::Unnamed) + } mod does_parse { use super::*; + #[test] fn slash() { parse("/").expect("should parse"); @@ -457,7 +493,7 @@ mod test { mod does_not_parse { use super::*; - use crate::error::ExpectedToken; + use crate::error::{ExpectedToken, ParserErrorReason}; // TODO, should empty be ok? #[test] @@ -481,7 +517,10 @@ mod test { fn non_ident_capture() { let x = parse("/{lor#m}").expect_err("Should not parse"); assert_eq!(x.error.reason, Some(ParserErrorReason::BadRustIdent('#'))); - assert_eq!(x.error.expected, vec![ExpectedToken::CloseBracket, ExpectedToken::Ident]) + assert_eq!( + x.error.expected, + vec![ExpectedToken::CloseBracket, ExpectedToken::Ident] + ) } #[test] @@ -505,6 +544,7 @@ mod test { mod correct_parse { use super::*; + use crate::parser::{CaptureOrExact, RefCaptureVariant}; #[test] fn starting_literal() { @@ -678,9 +718,9 @@ mod test { let parsed = parse("?lorem={cap}!").unwrap(); let expected = vec![ RouteParserToken::QueryBegin, - RouteParserToken::Query{ + RouteParserToken::Query { ident: "lorem", - capture_or_exact: CaptureOrExact::Capture(RefCaptureVariant::Named("cap")) + capture_or_exact: CaptureOrExact::Capture(RefCaptureVariant::Named("cap")), }, RouteParserToken::End, ]; diff --git a/examples/switch/src/main.rs b/examples/switch/src/main.rs index f20f18e..4b136c2 100644 --- a/examples/switch/src/main.rs +++ b/examples/switch/src/main.rs @@ -55,20 +55,22 @@ pub enum AppRoute { #[to = "/some/route"] SomeRoute, #[to = "/some/{thing}/{other}"] + // If you have a variant with named fields, the field names should appear in the matcher string. Something { thing: String, other: String }, - #[to = "/another/{thing}"] + #[to = "/another/{}"] // Tuple-enums don't need names in the capture groups. Another(String), - #[to = "/doot/{one}/{two}"] + #[to = "/doot/{}/{something}"] + // You can still puts names in the capture groups to improve readability. Yeet(String, String), #[to = "/inner"] - #[rest] + #[rest] // same as /inner{*} Nested(InnerRoute), #[rest] // Rest delegates the remaining input to the next attribute Single(Single), #[rest] OtherSingle(OtherSingle), /// Because this is an option, the inner item doesn't have to match. - #[to = "/option/{thing}"] + #[to = "/option/{}"] Optional(Option), /// Because this is an option, a corresponding capture group doesn't need to exist #[to = "/missing/capture"] diff --git a/src/matcher/matcher_impl.rs b/src/matcher/matcher_impl.rs index d853abe..d46ecff 100644 --- a/src/matcher/matcher_impl.rs +++ b/src/matcher/matcher_impl.rs @@ -91,6 +91,11 @@ fn matcher_impl<'a, 'b: 'a, CAP: CaptureCollection<'b>>( CaptureVariant::NumberedNamed { sections, name } => { capture_numbered_named(i, &mut iter, Some((&name, &mut captures)), *sections)? } + CaptureVariant::Unnamed => capture_named(i, &mut iter, "", &mut captures)?, + CaptureVariant::ManyUnnamed => capture_many_named(i, &mut iter, "", &mut captures)?, + CaptureVariant::NumberedUnnamed { sections } => { + capture_numbered_named(i, &mut iter, Some(("", &mut captures)), *sections)? + } }, MatcherToken::End => { if !i.is_empty() { @@ -210,39 +215,51 @@ fn valid_many_capture_characters(i: &str) -> IResult<&str, &str> { mod integration_test { use super::*; - use yew_router_route_parser; + use yew_router_route_parser::{self, FieldType}; use super::super::Captures; // use nom::combinator::all_consuming; #[test] fn match_query_after_path() { - let x = yew_router_route_parser::parse_str_and_optimize_tokens("/a/path?lorem=ipsum") - .expect("Should parse"); + let x = yew_router_route_parser::parse_str_and_optimize_tokens( + "/a/path?lorem=ipsum", + FieldType::Unnamed, + ) + .expect("Should parse"); matcher_impl::(&x, MatcherSettings::default(), "/a/path?lorem=ipsum") .expect("should match"); } #[test] fn match_query_after_path_trailing_slash() { - let x = yew_router_route_parser::parse_str_and_optimize_tokens("/a/path/?lorem=ipsum") - .expect("Should parse"); + let x = yew_router_route_parser::parse_str_and_optimize_tokens( + "/a/path/?lorem=ipsum", + FieldType::Unnamed, + ) + .expect("Should parse"); matcher_impl::(&x, MatcherSettings::default(), "/a/path/?lorem=ipsum") .expect("should match"); } #[test] fn match_query() { - let x = yew_router_route_parser::parse_str_and_optimize_tokens("?lorem=ipsum") - .expect("Should parse"); + let x = yew_router_route_parser::parse_str_and_optimize_tokens( + "?lorem=ipsum", + FieldType::Unnamed, + ) + .expect("Should parse"); matcher_impl::(&x, MatcherSettings::default(), "?lorem=ipsum") .expect("should match"); } #[test] fn named_capture_query() { - let x = yew_router_route_parser::parse_str_and_optimize_tokens("?lorem={ipsum}") - .expect("Should parse"); + let x = yew_router_route_parser::parse_str_and_optimize_tokens( + "?lorem={ipsum}", + FieldType::Unnamed, + ) + .expect("Should parse"); let (_, matches) = matcher_impl::(&x, MatcherSettings::default(), "?lorem=ipsum") .expect("should match"); assert_eq!(matches["ipsum"], "ipsum".to_string()) @@ -250,8 +267,11 @@ mod integration_test { #[test] fn match_n_paths_3() { - let x = yew_router_route_parser::parse_str_and_optimize_tokens("/{*:cap}/thing") - .expect("Should parse"); + let x = yew_router_route_parser::parse_str_and_optimize_tokens( + "/{*:cap}/thing", + FieldType::Unnamed, + ) + .expect("Should parse"); let matches: Captures = matcher_impl(&x, MatcherSettings::default(), "/anything/other/thing") .expect("should match") @@ -261,8 +281,11 @@ mod integration_test { #[test] fn match_n_paths_4() { - let x = yew_router_route_parser::parse_str_and_optimize_tokens("/{*:cap}/thing") - .expect("Should parse"); + let x = yew_router_route_parser::parse_str_and_optimize_tokens( + "/{*:cap}/thing", + FieldType::Unnamed, + ) + .expect("Should parse"); let matches: Captures = matcher_impl(&x, MatcherSettings::default(), "/anything/thing/thing") .expect("should match") @@ -272,8 +295,11 @@ mod integration_test { #[test] fn match_path_5() { - let x = yew_router_route_parser::parse_str_and_optimize_tokens("/{cap}/thing") - .expect("Should parse"); + let x = yew_router_route_parser::parse_str_and_optimize_tokens( + "/{cap}/thing", + FieldType::Unnamed, + ) + .expect("Should parse"); let matches: Captures = matcher_impl(&x, MatcherSettings::default(), "/anything/thing/thing") .expect("should match") @@ -283,48 +309,59 @@ mod integration_test { #[test] fn match_fragment() { - let x = - yew_router_route_parser::parse_str_and_optimize_tokens("#test").expect("Should parse"); + let x = yew_router_route_parser::parse_str_and_optimize_tokens("#test", FieldType::Unnamed) + .expect("Should parse"); matcher_impl::(&x, MatcherSettings::default(), "#test").expect("should match"); } #[test] fn match_fragment_after_path() { - let x = yew_router_route_parser::parse_str_and_optimize_tokens("/a/path/#test") - .expect("Should parse"); + let x = yew_router_route_parser::parse_str_and_optimize_tokens( + "/a/path/#test", + FieldType::Unnamed, + ) + .expect("Should parse"); matcher_impl::(&x, MatcherSettings::default(), "/a/path/#test") .expect("should match"); } #[test] fn match_fragment_after_path_no_slash() { - let x = yew_router_route_parser::parse_str_and_optimize_tokens("/a/path#test") - .expect("Should parse"); + let x = yew_router_route_parser::parse_str_and_optimize_tokens( + "/a/path#test", + FieldType::Unnamed, + ) + .expect("Should parse"); matcher_impl::(&x, MatcherSettings::default(), "/a/path#test") .expect("should match"); } #[test] fn match_fragment_after_query() { - let x = yew_router_route_parser::parse_str_and_optimize_tokens("/a/path?query=thing#test") - .expect("Should parse"); + let x = yew_router_route_parser::parse_str_and_optimize_tokens( + "/a/path?query=thing#test", + FieldType::Unnamed, + ) + .expect("Should parse"); matcher_impl::(&x, MatcherSettings::default(), "/a/path?query=thing#test") .expect("should match"); } #[test] fn match_fragment_after_query_capture() { - let x = - yew_router_route_parser::parse_str_and_optimize_tokens("/a/path?query={capture}#test") - .expect("Should parse"); + let x = yew_router_route_parser::parse_str_and_optimize_tokens( + "/a/path?query={capture}#test", + FieldType::Unnamed, + ) + .expect("Should parse"); matcher_impl::(&x, MatcherSettings::default(), "/a/path?query=thing#test") .expect("should match"); } #[test] fn capture_as_only_token() { - let x = - yew_router_route_parser::parse_str_and_optimize_tokens("{any}").expect("Should parse"); + let x = yew_router_route_parser::parse_str_and_optimize_tokens("{any}", FieldType::Unnamed) + .expect("Should parse"); matcher_impl::(&x, MatcherSettings::default(), "literally_anything") .expect("should match"); } @@ -332,7 +369,8 @@ mod integration_test { #[test] fn case_insensitive() { let x = - yew_router_route_parser::parse_str_and_optimize_tokens("/hello").expect("Should parse"); + yew_router_route_parser::parse_str_and_optimize_tokens("/hello", FieldType::Unnamed) + .expect("Should parse"); let settings = MatcherSettings { case_insensitive: true, ..Default::default() @@ -342,8 +380,9 @@ mod integration_test { #[test] fn end_token() { - let x = yew_router_route_parser::parse_str_and_optimize_tokens("/lorem!") - .expect("Should parse"); + let x = + yew_router_route_parser::parse_str_and_optimize_tokens("/lorem!", FieldType::Unnamed) + .expect("Should parse"); matcher_impl::(&x, Default::default(), "/lorem/ipsum") .expect_err("should not match"); diff --git a/src/matcher/mod.rs b/src/matcher/mod.rs index 2aa0edc..98424ed 100644 --- a/src/matcher/mod.rs +++ b/src/matcher/mod.rs @@ -47,7 +47,7 @@ impl RouteMatcher { /// Creates a new Matcher with settings. pub fn new(i: &str, settings: MatcherSettings) -> Result { Ok(RouteMatcher { - tokens: parse_str_and_optimize_tokens(i)?, + tokens: parse_str_and_optimize_tokens(i, yew_router_route_parser::FieldType::Unnamed)?, /* TODO this field type should be a superset of Named, but it would be better to source this from settings, and make sure that the macro generates settings as such. */ settings, }) } @@ -93,6 +93,9 @@ impl RouteMatcher { | CaptureVariant::NumberedNamed { name, .. } => { acc.insert(&name); } + CaptureVariant::Unnamed + | CaptureVariant::ManyUnnamed + | CaptureVariant::NumberedUnnamed { .. } => {} }, } acc diff --git a/src/matcher/util.rs b/src/matcher/util.rs index 1342d88..f9fd99c 100644 --- a/src/matcher/util.rs +++ b/src/matcher/util.rs @@ -1,7 +1,7 @@ use nom::{ bytes::complete::{tag, tag_no_case}, character::complete::anychar, - combinator::{cond, map, peek}, + combinator::{cond, map, peek, rest}, error::{ErrorKind, ParseError}, multi::many_till, sequence::pair, @@ -9,7 +9,6 @@ use nom::{ }; use std::{iter::Peekable, rc::Rc, slice::Iter}; use yew_router_route_parser::MatcherToken; -use nom::combinator::rest; /// Allows a configurable tag that can optionally be case insensitive. pub fn tag_possibly_case_sensitive<'a, 'b: 'a>( @@ -74,13 +73,11 @@ pub fn next_delimiter<'a>( .cloned() .expect("There must be at least one token to peak in next_delimiter"); - move |i: &'a str| { - match &t { - MatcherToken::Exact(sequence) => tag(sequence.as_str())(i), - MatcherToken::End => rest(i), - MatcherToken::Capture(_) => { - panic!("underlying parser should not allow two captures in a row") - } + move |i: &'a str| match &t { + MatcherToken::Exact(sequence) => tag(sequence.as_str())(i), + MatcherToken::End => rest(i), + MatcherToken::Capture(_) => { + panic!("underlying parser should not allow two captures in a row") } } }