/// current directory, a `Parameter` entry is used. Note that the parameter name is fixed at this
/// point, so all method calls beneath will receive a parameter ot that particular name.
pub enum SubRoute {
+ /// Call this router for any further subdirectory paths, and provide the relative path via the
+ /// given parameter.
+ Wildcard(&'static str),
+
/// This is used for plain subdirectories.
Directories(HashMap<&'static str, Router>),
// The actual implementation taking the parameter as &str
fn lookup_do(&self, path: &str) -> Option<(&Self, Option<Value>)> {
let mut matched_params = None;
+ let mut matched_wildcard: Option<String> = None;
let mut this = self;
for component in path.split('/') {
+ if let Some(ref mut relative_path) = matched_wildcard {
+ relative_path.push('/');
+ relative_path.push_str(component);
+ continue;
+ }
if component.is_empty() {
// `foo//bar` or the first `/` in `/foo`
continue;
}
this = match &this.subroute {
+ Some(SubRoute::Wildcard(_)) => {
+ matched_wildcard = Some(component.to_string());
+ continue;
+ }
Some(SubRoute::Directories(subdirs)) => subdirs.get(component)?,
Some(SubRoute::Parameter(param_name, router)) => {
let previous = matched_params
};
}
+ if let Some(SubRoute::Wildcard(param_name)) = &this.subroute {
+ matched_params
+ .get_or_insert_with(serde_json::Map::new)
+ .insert(
+ param_name.to_string(),
+ Value::String(matched_wildcard.unwrap_or(String::new())),
+ );
+ }
+
Some((this, matched_params.map(Value::Object)))
}
self
}
- /// Builder method to add a regular directory entro to this router.
+ /// Builder method to add a regular directory entry to this router.
///
/// This is supposed to be used statically (via `lazy_static!), therefore we panic if we
/// already have a subdir entry!
}
self
}
-}
+ /// Builder method to match the rest of the path into a parameter.
+ ///
+ /// This is supposed to be used statically (via `lazy_static!), therefore we panic if we
+ /// already have a subdir entry!
+ pub fn wildcard(mut self, path_parameter_name: &'static str) -> Self {
+ if self.subroute.is_some() {
+ panic!("'wildcard' and other sub routers are mutually exclusive");
+ }
+
+ self.subroute = Some(SubRoute::Wildcard(path_parameter_name));
+
+ self
+ }
+}
#[test]
fn basic() {
let info: &proxmox_api::ApiMethod = &methods::GET_PEOPLE;
- let router = Router::new().subdir(
- "people",
- Router::new().parameter_subdir("person", Router::new().get(info)),
+ let get_subpath: &proxmox_api::ApiMethod = &methods::GET_SUBPATH;
+ let router = Router::new()
+ .subdir(
+ "people",
+ Router::new().parameter_subdir("person", Router::new().get(info)),
+ )
+ .subdir(
+ "wildcard",
+ Router::new().wildcard("subpath").get(get_subpath),
+ );
+
+ check_with_matched_params(&router, "people/foo", "person", "foo", "foo");
+ check_with_matched_params(&router, "people//foo", "person", "foo", "foo");
+ check_with_matched_params(&router, "wildcard", "subpath", "", "");
+ check_with_matched_params(&router, "wildcard/", "subpath", "", "");
+ check_with_matched_params(&router, "wildcard//", "subpath", "", "");
+ check_with_matched_params(&router, "wildcard/dir1", "subpath", "dir1", "dir1");
+ check_with_matched_params(
+ &router,
+ "wildcard/dir1/dir2",
+ "subpath",
+ "dir1/dir2",
+ "dir1/dir2",
);
+ check_with_matched_params(&router, "wildcard/dir1//2", "subpath", "dir1//2", "dir1//2");
+}
+fn check_with_matched_params(
+ router: &Router,
+ path: &str,
+ param_name: &str,
+ param_value: &str,
+ expected_body: &str,
+) {
let (target, params) = router
- .lookup("people/foo")
- .expect("must be able to lookup 'people/foo'");
+ .lookup(path)
+ .expect(&format!("must be able to lookup '{}'", path));
- let params = params.expect("expected people/foo to create a parameter object");
+ let params = params.expect(&format!(
+ "expected parameters to be matched into '{}'",
+ param_name,
+ ));
let apifn = target
.get
.as_ref()
- .expect("expected GET method on people/foo")
+ .expect(&format!("expected GET method on {}", path))
.handler();
- let person = params["person"]
- .as_str()
- .expect("expected lookup() to fill the 'person' parameter");
+ let arg = params[param_name].as_str().expect(&format!(
+ "expected lookup() to fill the '{}' parameter",
+ param_name
+ ));
- assert!(
- person == "foo",
- "lookup of 'people/foo' should set 'person' to 'foo'"
+ assert_eq!(
+ arg, param_value,
+ "lookup of '{}' should set '{}' to '{}'",
+ path, param_name, param_value,
);
let response = futures::executor::block_on(Pin::from(apifn(params)))
.expect("expected the simple test api function to be ready immediately");
- assert!(response.status() == 200, "response status must be 200");
+ assert_eq!(response.status(), 200, "response status must be 200");
let body =
std::str::from_utf8(response.body().as_ref()).expect("expected a valid utf8 repsonse body");
- assert!(
- body == "foo",
- "repsonse of people/foo should simply be 'foo'"
+ assert_eq!(
+ body, expected_body,
+ "response of {} should be '{}', got '{}'",
+ path, expected_body, body,
);
}
.body(value["person"].as_str().unwrap().into())?)
}
+ pub async fn get_subpath(value: Value) -> ApiOutput {
+ Ok(Response::builder()
+ .status(200)
+ .header("content-type", "application/json")
+ .body(value["subpath"].as_str().unwrap().into())?)
+ }
+
lazy_static! {
static ref GET_PEOPLE_PARAMS: Vec<Parameter> = {
vec![Parameter {
handler: |value: Value| -> ApiFuture { Box::pin(get_people(value)) },
}
};
+ static ref GET_SUBPATH_PARAMS: Vec<Parameter> = {
+ vec![Parameter {
+ name: "subpath",
+ description: "the matched relative subdir path",
+ type_info: get_type_info::<String>(),
+ }]
+ };
+ pub static ref GET_SUBPATH: ApiMethod = {
+ ApiMethod {
+ description: "get the 'subpath' parameter returned back",
+ parameters: &GET_SUBPATH_PARAMS,
+ return_type: get_type_info::<String>(),
+ protected: false,
+ reload_timezone: false,
+ handler: |value: Value| -> ApiFuture { Box::pin(get_subpath(value)) },
+ }
+ };
}
#[derive(Deserialize, Serialize)]