macro: add wildcard matching to router macro

router!{
    /path/{parameter}*: {
        methods...
    }
}

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
Wolfgang Bumiller 2019-06-08 18:53:03 +02:00
parent 8036941977
commit b82b14d947
2 changed files with 54 additions and 8 deletions

View File

@ -41,6 +41,7 @@ pub fn router_macro(input: TokenStream) -> Result<TokenStream, Error> {
pub enum SubRoute { pub enum SubRoute {
Directories(HashMap<LitStr, Router>), Directories(HashMap<LitStr, Router>),
Parameter(LitStr, Box<Router>), Parameter(LitStr, Box<Router>),
Wildcard(LitStr),
} }
impl SubRoute { impl SubRoute {
@ -80,8 +81,12 @@ enum Entry {
enum Component { enum Component {
/// This component is a fixed sub directory name. Eg. `foo` or `baz` in `/foo/{bar}/baz`. /// This component is a fixed sub directory name. Eg. `foo` or `baz` in `/foo/{bar}/baz`.
Name(LitStr), Name(LitStr),
/// This component matches everything into a parameter. Eg. `bar` in `/foo/{bar}/baz`. /// This component matches everything into a parameter. Eg. `bar` in `/foo/{bar}/baz`.
Match(LitStr), Match(LitStr),
/// Matches the rest of the path into a parameters
Wildcard(LitStr),
} }
/// A path is just a list of components. /// A path is just a list of components.
@ -91,7 +96,7 @@ impl Router {
/// Insert a new router at a specific path. /// Insert a new router at a specific path.
/// ///
/// Note that this does not allow replacing an already existing router node. /// Note that this does not allow replacing an already existing router node.
fn insert(&mut self, path: Path, router: Router) -> Result<(), Error> { fn insert(&mut self, path: Path, mut router: Router) -> Result<(), Error> {
let mut at = self; let mut at = self;
let mut created = false; let mut created = false;
for component in path { for component in path {
@ -106,9 +111,12 @@ impl Router {
Router::default() Router::default()
}); });
} }
SubRoute::Parameter(_param, _router) => { SubRoute::Parameter(_, _) => {
bail!("subdir '{}' clashes with matched parameter", name.value()); bail!("subdir '{}' clashes with matched parameter", name.value());
} }
SubRoute::Wildcard(_) => {
bail!("cannot add subdir '{}', it is already matched by a wildcard");
}
} }
} }
Component::Match(name) => { Component::Match(name) => {
@ -117,12 +125,6 @@ impl Router {
SubRoute::parameter(name.clone()) SubRoute::parameter(name.clone())
}); });
match subroute { match subroute {
SubRoute::Directories(_) => {
bail!(
"parameter matcher '{}' clashes with existing directory",
name.value()
);
}
SubRoute::Parameter(existing_name, router) => { SubRoute::Parameter(existing_name, router) => {
if name != *existing_name { if name != *existing_name {
bail!( bail!(
@ -133,7 +135,26 @@ impl Router {
} }
at = router.as_mut(); at = router.as_mut();
} }
SubRoute::Directories(_) => {
bail!(
"parameter matcher '{}' clashes with existing directory",
name.value()
);
} }
SubRoute::Wildcard(_) => {
bail!("parameter matcher '{}' clashes with wildcard", name.value());
}
}
}
Component::Wildcard(name) => {
if at.subroute.is_some() {
bail!("wildcard clashes with existing subdirectory");
}
created = true;
if router.subroute.is_some() {
bail!("wildcard sub router cannot have subdirectories!");
}
router.subroute = Some(SubRoute::Wildcard(name.clone()));
} }
} }
} }
@ -188,6 +209,11 @@ impl Router {
}); });
} }
} }
Some(SubRoute::Wildcard(name)) => {
out.extend(quote! {
.wildcard(#name)
});
}
} }
if let Some(name) = name { if let Some(name) = name {
@ -364,6 +390,18 @@ fn parse_path_name(tokens: &mut TokenIter) -> Result<Path, Error> {
push_component(&mut path, &mut component, &mut span); push_component(&mut path, &mut component, &mut span);
// `component` is cleared, we start the next one // `component` is cleared, we start the next one
} }
'*' => {
// must be the last component, after a matcher
if !component.is_empty() {
bail!("wildcard must be the final matcher");
}
if let Some(Component::Match(name)) = path.pop() {
path.push(Component::Wildcard(name));
match_colon(&mut *tokens)?;
break;
}
bail!("asterisk only allowed at the end of a match pattern");
}
other => bail!("invalid punctuation in path: {:?}", other), other => bail!("invalid punctuation in path: {:?}", other),
}, },
Some(other) => bail!( Some(other) => bail!(

View File

@ -90,6 +90,8 @@ proxmox_api_macro::router! {
/dir: { GET: non_async_test }, /dir: { GET: non_async_test },
}, },
/wild/{param}*: { GET: get_loopback },
}; };
} }
@ -128,6 +130,12 @@ fn router() {
check_body(&TEST_ROUTER, "/another/foo", r#"{"data":"foo"}"#); check_body(&TEST_ROUTER, "/another/foo", r#"{"data":"foo"}"#);
check_body(&TEST_ROUTER, "/another/foo/dir", r#"{"data":"foo"}"#); check_body(&TEST_ROUTER, "/another/foo/dir", r#"{"data":"foo"}"#);
check_body(&TEST_ROUTER, "/wild", r#"{"data":""}"#);
check_body(&TEST_ROUTER, "/wild/", r#"{"data":""}"#);
check_body(&TEST_ROUTER, "/wild/asdf", r#"{"data":"asdf"}"#);
check_body(&TEST_ROUTER, "/wild//asdf", r#"{"data":"asdf"}"#);
check_body(&TEST_ROUTER, "/wild/asdf/poiu", r#"{"data":"asdf/poiu"}"#);
// And can I... // And can I...
let res = futures::executor::block_on(get_loopback("FOO".to_string())) let res = futures::executor::block_on(get_loopback("FOO".to_string()))
.expect("expected result from get_loopback"); .expect("expected result from get_loopback");