mirror of
https://github.com/fafhrd91/actix-web
synced 2024-11-30 10:42:55 +01:00
add method to extract matched resource pattern (#1566)
This commit is contained in:
parent
a70e599ff5
commit
fa28175a74
@ -5,6 +5,8 @@
|
|||||||
### Added
|
### Added
|
||||||
|
|
||||||
* Re-export `actix_rt::main` as `actix_web::main`.
|
* Re-export `actix_rt::main` as `actix_web::main`.
|
||||||
|
* `HttpRequest::match_pattern` and `ServiceRequest::match_pattern` for extracting the matched
|
||||||
|
resource pattern.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
@ -126,6 +126,17 @@ impl HttpRequest {
|
|||||||
&mut Rc::get_mut(&mut self.0).unwrap().path
|
&mut Rc::get_mut(&mut self.0).unwrap().path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The resource definition pattern that matched the path. Useful for logging and metrics.
|
||||||
|
///
|
||||||
|
/// For example, when a resource with pattern `/user/{id}/profile` is defined and a call is made
|
||||||
|
/// to `/user/123/profile` this function would return `Some("/user/{id}/profile")`.
|
||||||
|
///
|
||||||
|
/// Returns a None when no resource is fully matched, including default services.
|
||||||
|
#[inline]
|
||||||
|
pub fn match_pattern(&self) -> Option<String> {
|
||||||
|
self.0.rmap.match_pattern(self.path())
|
||||||
|
}
|
||||||
|
|
||||||
/// Request extensions
|
/// Request extensions
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn extensions(&self) -> Ref<'_, Extensions> {
|
pub fn extensions(&self) -> Ref<'_, Extensions> {
|
||||||
@ -141,7 +152,6 @@ impl HttpRequest {
|
|||||||
/// Generate url for named resource
|
/// Generate url for named resource
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # extern crate actix_web;
|
|
||||||
/// # use actix_web::{web, App, HttpRequest, HttpResponse};
|
/// # use actix_web::{web, App, HttpRequest, HttpResponse};
|
||||||
/// #
|
/// #
|
||||||
/// fn index(req: HttpRequest) -> HttpResponse {
|
/// fn index(req: HttpRequest) -> HttpResponse {
|
||||||
@ -599,4 +609,36 @@ mod tests {
|
|||||||
|
|
||||||
assert!(tracker.borrow().dropped);
|
assert!(tracker.borrow().dropped);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn extract_path_pattern() {
|
||||||
|
let mut srv = init_service(
|
||||||
|
App::new().service(
|
||||||
|
web::scope("/user/{id}")
|
||||||
|
.service(web::resource("/profile").route(web::get().to(
|
||||||
|
move |req: HttpRequest| {
|
||||||
|
assert_eq!(
|
||||||
|
req.match_pattern(),
|
||||||
|
Some("/user/{id}/profile".to_owned())
|
||||||
|
);
|
||||||
|
|
||||||
|
HttpResponse::Ok().finish()
|
||||||
|
},
|
||||||
|
)))
|
||||||
|
.default_service(web::to(move |req: HttpRequest| {
|
||||||
|
assert!(req.match_pattern().is_none());
|
||||||
|
HttpResponse::Ok().finish()
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let req = TestRequest::get().uri("/user/22/profile").to_request();
|
||||||
|
let res = call_service(&mut srv, req).await;
|
||||||
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
|
|
||||||
|
let req = TestRequest::get().uri("/user/22/not-exist").to_request();
|
||||||
|
let res = call_service(&mut srv, req).await;
|
||||||
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
119
src/rmap.rs
119
src/rmap.rs
@ -43,9 +43,7 @@ impl ResourceMap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl ResourceMap {
|
|
||||||
/// Generate url for named resource
|
/// Generate url for named resource
|
||||||
///
|
///
|
||||||
/// Check [`HttpRequest::url_for()`](../struct.HttpRequest.html#method.
|
/// Check [`HttpRequest::url_for()`](../struct.HttpRequest.html#method.
|
||||||
@ -95,6 +93,45 @@ impl ResourceMap {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the full resource pattern matched against a path or None if no full match
|
||||||
|
/// is possible.
|
||||||
|
pub fn match_pattern(&self, path: &str) -> Option<String> {
|
||||||
|
let path = if path.is_empty() { "/" } else { path };
|
||||||
|
|
||||||
|
// ensure a full match exists
|
||||||
|
if !self.has_resource(path) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(self.traverse_resource_pattern(path))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Takes remaining path and tries to match it up against a resource definition within the
|
||||||
|
/// current resource map recursively, returning a concatenation of all resource prefixes and
|
||||||
|
/// patterns matched in the tree.
|
||||||
|
///
|
||||||
|
/// Should only be used after checking the resource exists in the map so that partial match
|
||||||
|
/// patterns are not returned.
|
||||||
|
fn traverse_resource_pattern(&self, remaining: &str) -> String {
|
||||||
|
for (pattern, rmap) in &self.patterns {
|
||||||
|
if let Some(ref rmap) = rmap {
|
||||||
|
if let Some(prefix_len) = pattern.is_prefix_match(remaining) {
|
||||||
|
let prefix = pattern.pattern().to_owned();
|
||||||
|
|
||||||
|
return [
|
||||||
|
prefix,
|
||||||
|
rmap.traverse_resource_pattern(&remaining[prefix_len..]),
|
||||||
|
]
|
||||||
|
.concat();
|
||||||
|
}
|
||||||
|
} else if pattern.is_match(remaining) {
|
||||||
|
return pattern.pattern().to_owned();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String::new()
|
||||||
|
}
|
||||||
|
|
||||||
fn patterns_for<U, I>(
|
fn patterns_for<U, I>(
|
||||||
&self,
|
&self,
|
||||||
name: &str,
|
name: &str,
|
||||||
@ -188,3 +225,81 @@ impl ResourceMap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn extract_matched_pattern() {
|
||||||
|
let mut root = ResourceMap::new(ResourceDef::root_prefix(""));
|
||||||
|
|
||||||
|
let mut user_map = ResourceMap::new(ResourceDef::root_prefix(""));
|
||||||
|
user_map.add(&mut ResourceDef::new("/"), None);
|
||||||
|
user_map.add(&mut ResourceDef::new("/profile"), None);
|
||||||
|
user_map.add(&mut ResourceDef::new("/article/{id}"), None);
|
||||||
|
user_map.add(&mut ResourceDef::new("/post/{post_id}"), None);
|
||||||
|
user_map.add(
|
||||||
|
&mut ResourceDef::new("/post/{post_id}/comment/{comment_id}"),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
root.add(&mut ResourceDef::new("/info"), None);
|
||||||
|
root.add(&mut ResourceDef::new("/v{version:[[:digit:]]{1}}"), None);
|
||||||
|
root.add(
|
||||||
|
&mut ResourceDef::root_prefix("/user/{id}"),
|
||||||
|
Some(Rc::new(user_map)),
|
||||||
|
);
|
||||||
|
|
||||||
|
let root = Rc::new(root);
|
||||||
|
root.finish(Rc::clone(&root));
|
||||||
|
|
||||||
|
// sanity check resource map setup
|
||||||
|
|
||||||
|
assert!(root.has_resource("/info"));
|
||||||
|
assert!(!root.has_resource("/bar"));
|
||||||
|
|
||||||
|
assert!(root.has_resource("/v1"));
|
||||||
|
assert!(root.has_resource("/v2"));
|
||||||
|
assert!(!root.has_resource("/v33"));
|
||||||
|
|
||||||
|
assert!(root.has_resource("/user/22"));
|
||||||
|
assert!(root.has_resource("/user/22/"));
|
||||||
|
assert!(root.has_resource("/user/22/profile"));
|
||||||
|
|
||||||
|
// extract patterns from paths
|
||||||
|
|
||||||
|
assert!(root.match_pattern("/bar").is_none());
|
||||||
|
assert!(root.match_pattern("/v44").is_none());
|
||||||
|
|
||||||
|
assert_eq!(root.match_pattern("/info"), Some("/info".to_owned()));
|
||||||
|
assert_eq!(
|
||||||
|
root.match_pattern("/v1"),
|
||||||
|
Some("/v{version:[[:digit:]]{1}}".to_owned())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
root.match_pattern("/v2"),
|
||||||
|
Some("/v{version:[[:digit:]]{1}}".to_owned())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
root.match_pattern("/user/22/profile"),
|
||||||
|
Some("/user/{id}/profile".to_owned())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
root.match_pattern("/user/602CFB82-7709-4B17-ADCF-4C347B6F2203/profile"),
|
||||||
|
Some("/user/{id}/profile".to_owned())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
root.match_pattern("/user/22/article/44"),
|
||||||
|
Some("/user/{id}/article/{id}".to_owned())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
root.match_pattern("/user/22/post/my-post"),
|
||||||
|
Some("/user/{id}/post/{post_id}".to_owned())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
root.match_pattern("/user/22/post/other-post/comment/42"),
|
||||||
|
Some("/user/{id}/post/{post_id}/comment/{comment_id}".to_owned())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -195,6 +195,12 @@ impl ServiceRequest {
|
|||||||
pub fn match_info(&self) -> &Path<Url> {
|
pub fn match_info(&self) -> &Path<Url> {
|
||||||
self.0.match_info()
|
self.0.match_info()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Counterpart to [`HttpRequest::match_pattern`](../struct.HttpRequest.html#method.match_pattern).
|
||||||
|
#[inline]
|
||||||
|
pub fn match_pattern(&self) -> Option<String> {
|
||||||
|
self.0.match_pattern()
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
/// Get a mutable reference to the Path parameters.
|
/// Get a mutable reference to the Path parameters.
|
||||||
|
Loading…
Reference in New Issue
Block a user