diff --git a/proxmox-api-macro/src/router_macro.rs b/proxmox-api-macro/src/router_macro.rs index 034db0ff..fd7bcd63 100644 --- a/proxmox-api-macro/src/router_macro.rs +++ b/proxmox-api-macro/src/router_macro.rs @@ -41,6 +41,7 @@ pub fn router_macro(input: TokenStream) -> Result { pub enum SubRoute { Directories(HashMap), Parameter(LitStr, Box), + Wildcard(LitStr), } impl SubRoute { @@ -80,8 +81,12 @@ enum Entry { enum Component { /// This component is a fixed sub directory name. Eg. `foo` or `baz` in `/foo/{bar}/baz`. Name(LitStr), + /// This component matches everything into a parameter. Eg. `bar` in `/foo/{bar}/baz`. Match(LitStr), + + /// Matches the rest of the path into a parameters + Wildcard(LitStr), } /// A path is just a list of components. @@ -91,7 +96,7 @@ impl Router { /// Insert a new router at a specific path. /// /// 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 created = false; for component in path { @@ -106,9 +111,12 @@ impl Router { Router::default() }); } - SubRoute::Parameter(_param, _router) => { + SubRoute::Parameter(_, _) => { bail!("subdir '{}' clashes with matched parameter", name.value()); } + SubRoute::Wildcard(_) => { + bail!("cannot add subdir '{}', it is already matched by a wildcard"); + } } } Component::Match(name) => { @@ -117,12 +125,6 @@ impl Router { SubRoute::parameter(name.clone()) }); match subroute { - SubRoute::Directories(_) => { - bail!( - "parameter matcher '{}' clashes with existing directory", - name.value() - ); - } SubRoute::Parameter(existing_name, router) => { if name != *existing_name { bail!( @@ -133,8 +135,27 @@ impl Router { } 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 { @@ -364,6 +390,18 @@ fn parse_path_name(tokens: &mut TokenIter) -> Result { push_component(&mut path, &mut component, &mut span); // `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), }, Some(other) => bail!( diff --git a/proxmox-api-macro/tests/basic.rs b/proxmox-api-macro/tests/basic.rs index ac10f39d..1ebce5e7 100644 --- a/proxmox-api-macro/tests/basic.rs +++ b/proxmox-api-macro/tests/basic.rs @@ -90,6 +90,8 @@ proxmox_api_macro::router! { /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/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... let res = futures::executor::block_on(get_loopback("FOO".to_string())) .expect("expected result from get_loopback");