mirror of https://github.com/fafhrd91/actix-web
wip
This commit is contained in:
parent
37799df978
commit
cd499f47c2
|
@ -22,7 +22,7 @@ pub use self::pattern::{IntoPatterns, Patterns};
|
||||||
pub use self::quoter::Quoter;
|
pub use self::quoter::Quoter;
|
||||||
pub use self::resource::ResourceDef;
|
pub use self::resource::ResourceDef;
|
||||||
pub use self::resource_path::{Resource, ResourcePath};
|
pub use self::resource_path::{Resource, ResourcePath};
|
||||||
pub use self::router::{ResourceInfo, Router, RouterBuilder};
|
pub use self::router::{ResourceId, Router, RouterBuilder};
|
||||||
|
|
||||||
#[cfg(feature = "http")]
|
#[cfg(feature = "http")]
|
||||||
pub use self::url::Url;
|
pub use self::url::Url;
|
||||||
|
|
|
@ -8,10 +8,7 @@ use std::{
|
||||||
use firestorm::{profile_fn, profile_method, profile_section};
|
use firestorm::{profile_fn, profile_method, profile_section};
|
||||||
use regex::{escape, Regex, RegexSet};
|
use regex::{escape, Regex, RegexSet};
|
||||||
|
|
||||||
use crate::{
|
use crate::{path::PathItem, IntoPatterns, Patterns, Resource, ResourcePath};
|
||||||
path::{Path, PathItem},
|
|
||||||
IntoPatterns, Patterns, Resource, ResourcePath,
|
|
||||||
};
|
|
||||||
|
|
||||||
const MAX_DYNAMIC_SEGMENTS: usize = 16;
|
const MAX_DYNAMIC_SEGMENTS: usize = 16;
|
||||||
|
|
||||||
|
@ -615,7 +612,7 @@ impl ResourceDef {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Collects dynamic segment values into `path`.
|
/// Collects dynamic segment values into `resource`.
|
||||||
///
|
///
|
||||||
/// Returns `true` if `path` matches this resource.
|
/// Returns `true` if `path` matches this resource.
|
||||||
///
|
///
|
||||||
|
@ -635,9 +632,9 @@ impl ResourceDef {
|
||||||
/// assert_eq!(path.get("path").unwrap(), "HEAD/Cargo.toml");
|
/// assert_eq!(path.get("path").unwrap(), "HEAD/Cargo.toml");
|
||||||
/// assert_eq!(path.unprocessed(), "");
|
/// assert_eq!(path.unprocessed(), "");
|
||||||
/// ```
|
/// ```
|
||||||
pub fn capture_match_info<T: ResourcePath>(&self, path: &mut Path<T>) -> bool {
|
pub fn capture_match_info<R: Resource>(&self, path: &mut R) -> bool {
|
||||||
profile_method!(capture_match_info);
|
profile_method!(capture_match_info);
|
||||||
self.capture_match_info_fn(path, |_, _| true, ())
|
self.capture_match_info_fn(path, |_| true)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Collects dynamic segment values into `resource` after matching paths and executing
|
/// Collects dynamic segment values into `resource` after matching paths and executing
|
||||||
|
@ -655,13 +652,12 @@ impl ResourceDef {
|
||||||
/// use actix_router::{Path, ResourceDef};
|
/// use actix_router::{Path, ResourceDef};
|
||||||
///
|
///
|
||||||
/// fn try_match(resource: &ResourceDef, path: &mut Path<&str>) -> bool {
|
/// fn try_match(resource: &ResourceDef, path: &mut Path<&str>) -> bool {
|
||||||
/// let admin_allowed = std::env::var("ADMIN_ALLOWED").ok();
|
/// let admin_allowed = std::env::var("ADMIN_ALLOWED").is_ok();
|
||||||
///
|
///
|
||||||
/// resource.capture_match_info_fn(
|
/// resource.capture_match_info_fn(
|
||||||
/// path,
|
/// path,
|
||||||
/// // when env var is not set, reject when path contains "admin"
|
/// // when env var is not set, reject when path contains "admin"
|
||||||
/// |res, admin_allowed| !res.path().contains("admin"),
|
/// |res| !(!admin_allowed && res.path().contains("admin")),
|
||||||
/// &admin_allowed
|
|
||||||
/// )
|
/// )
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
|
@ -678,15 +674,10 @@ impl ResourceDef {
|
||||||
/// assert!(!try_match(&resource, &mut path));
|
/// assert!(!try_match(&resource, &mut path));
|
||||||
/// assert_eq!(path.unprocessed(), "/user/admin/stars");
|
/// assert_eq!(path.unprocessed(), "/user/admin/stars");
|
||||||
/// ```
|
/// ```
|
||||||
pub fn capture_match_info_fn<R, F, U>(
|
pub fn capture_match_info_fn<R, F>(&self, resource: &mut R, check_fn: F) -> bool
|
||||||
&self,
|
|
||||||
resource: &mut R,
|
|
||||||
check_fn: F,
|
|
||||||
user_data: U,
|
|
||||||
) -> bool
|
|
||||||
where
|
where
|
||||||
R: Resource,
|
R: Resource,
|
||||||
F: FnOnce(&R, U) -> bool,
|
F: FnOnce(&R) -> bool,
|
||||||
{
|
{
|
||||||
profile_method!(capture_match_info_fn);
|
profile_method!(capture_match_info_fn);
|
||||||
|
|
||||||
|
@ -762,7 +753,7 @@ impl ResourceDef {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if !check_fn(resource, user_data) {
|
if !check_fn(resource) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -857,7 +848,7 @@ impl ResourceDef {
|
||||||
S: BuildHasher,
|
S: BuildHasher,
|
||||||
{
|
{
|
||||||
profile_method!(resource_path_from_map);
|
profile_method!(resource_path_from_map);
|
||||||
self.build_resource_path(path, |name| values.get(name).map(AsRef::<str>::as_ref))
|
self.build_resource_path(path, |name| values.get(name))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if `prefix` acts as a proper prefix (i.e., separated by a slash) in `path`.
|
/// Returns true if `prefix` acts as a proper prefix (i.e., separated by a slash) in `path`.
|
||||||
|
@ -1156,6 +1147,8 @@ pub(crate) fn insert_slash(path: &str) -> Cow<'_, str> {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use crate::Path;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -5,87 +5,80 @@ use crate::{IntoPatterns, Resource, ResourceDef};
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||||
pub struct ResourceId(pub u16);
|
pub struct ResourceId(pub u16);
|
||||||
|
|
||||||
/// Information about current resource
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct ResourceInfo {
|
|
||||||
#[allow(dead_code)]
|
|
||||||
resource: ResourceId,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Resource router.
|
/// Resource router.
|
||||||
// T is the resource itself
|
///
|
||||||
// U is any other data needed for routing like method guards
|
/// It matches a [routable resource](Resource) to an ordered list of _routes_,
|
||||||
|
/// each is defined by an single [`ResourceDef`] and containes two types of custom data:
|
||||||
|
/// 1. The route _value_, of the generic type `T`.
|
||||||
|
/// 2. Some _context_ data, of the generic type `U`, which is only provided to the check function in
|
||||||
|
/// [`recognize_fn`](Self::recognize_fn).
|
||||||
pub struct Router<T, U = ()> {
|
pub struct Router<T, U = ()> {
|
||||||
routes: Vec<(ResourceDef, T, Option<U>)>,
|
routes: Vec<(ResourceDef, (T, U))>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, U> Router<T, U> {
|
impl<T, U> Router<T, U> {
|
||||||
pub fn build() -> RouterBuilder<T, U> {
|
pub fn build() -> RouterBuilder<T, U> {
|
||||||
RouterBuilder {
|
RouterBuilder { routes: Vec::new() }
|
||||||
resources: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Finds the value in the router that matches a given [routable resource](Resource)
|
||||||
|
/// and stores the match result, including the captured dynamic segments, into the `resource`.
|
||||||
pub fn recognize<R>(&self, resource: &mut R) -> Option<(&T, ResourceId)>
|
pub fn recognize<R>(&self, resource: &mut R) -> Option<(&T, ResourceId)>
|
||||||
where
|
where
|
||||||
R: Resource,
|
R: Resource,
|
||||||
{
|
{
|
||||||
profile_method!(recognize);
|
profile_method!(recognize);
|
||||||
|
self.recognize_fn(resource, |_, _| true)
|
||||||
for item in self.routes.iter() {
|
|
||||||
if item.0.capture_match_info(resource.resource_path()) {
|
|
||||||
return Some((&item.1, ResourceId(item.0.id())));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Similar to [`recognize`](Self::recognize),
|
||||||
|
/// but returns a mutable reference to the matching value.
|
||||||
pub fn recognize_mut<R>(&mut self, resource: &mut R) -> Option<(&mut T, ResourceId)>
|
pub fn recognize_mut<R>(&mut self, resource: &mut R) -> Option<(&mut T, ResourceId)>
|
||||||
where
|
where
|
||||||
R: Resource,
|
R: Resource,
|
||||||
{
|
{
|
||||||
profile_method!(recognize_mut);
|
profile_method!(recognize_mut);
|
||||||
|
self.recognize_mut_fn(resource, |_, _| true)
|
||||||
for item in self.routes.iter_mut() {
|
|
||||||
if item.0.capture_match_info(resource.resource_path()) {
|
|
||||||
return Some((&mut item.1, ResourceId(item.0.id())));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
/// Similar to [`recognize`](Self::recognize),
|
||||||
}
|
/// but executes an additional check function before accepting the route
|
||||||
|
/// as fully matched and storing the match result into `resource`.
|
||||||
pub fn recognize_fn<R, F>(&self, resource: &mut R, check: F) -> Option<(&T, ResourceId)>
|
///
|
||||||
|
/// The check function is provided with a reference to the `resource` itself
|
||||||
|
/// and to the context data of the route being matched.
|
||||||
|
pub fn recognize_fn<R, F>(&self, resource: &mut R, mut check: F) -> Option<(&T, ResourceId)>
|
||||||
where
|
where
|
||||||
F: Fn(&R, &Option<U>) -> bool,
|
|
||||||
R: Resource,
|
R: Resource,
|
||||||
|
F: FnMut(&R, &U) -> bool,
|
||||||
{
|
{
|
||||||
profile_method!(recognize_checked);
|
profile_method!(recognize_checked);
|
||||||
|
|
||||||
for item in self.routes.iter() {
|
for (rdef, (val, ctx)) in self.routes.iter() {
|
||||||
if item.0.capture_match_info_fn(resource, &check, &item.2) {
|
if rdef.capture_match_info_fn(resource, |res| check(res, ctx)) {
|
||||||
return Some((&item.1, ResourceId(item.0.id())));
|
return Some((val, ResourceId(rdef.id())));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Similar to [`recognize_fn`](Self::recognize),
|
||||||
|
/// but returns a mutable reference to the matching value.
|
||||||
pub fn recognize_mut_fn<R, F>(
|
pub fn recognize_mut_fn<R, F>(
|
||||||
&mut self,
|
&mut self,
|
||||||
resource: &mut R,
|
resource: &mut R,
|
||||||
check: F,
|
mut check: F,
|
||||||
) -> Option<(&mut T, ResourceId)>
|
) -> Option<(&mut T, ResourceId)>
|
||||||
where
|
where
|
||||||
F: Fn(&R, &Option<U>) -> bool,
|
|
||||||
R: Resource,
|
R: Resource,
|
||||||
|
F: FnMut(&R, &U) -> bool,
|
||||||
{
|
{
|
||||||
profile_method!(recognize_mut_checked);
|
profile_method!(recognize_mut_checked);
|
||||||
|
|
||||||
for item in self.routes.iter_mut() {
|
for (rdef, (val, ctx)) in self.routes.iter_mut() {
|
||||||
if item.0.capture_match_info_fn(resource, &check, &item.2) {
|
if rdef.capture_match_info_fn(resource, |res| check(res, ctx)) {
|
||||||
return Some((&mut item.1, ResourceId(item.0.id())));
|
return Some((val, ResourceId(rdef.id())));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,49 +86,68 @@ impl<T, U> Router<T, U> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Builds the list of routes for a [resource router](Router).
|
||||||
pub struct RouterBuilder<T, U = ()> {
|
pub struct RouterBuilder<T, U = ()> {
|
||||||
resources: Vec<(ResourceDef, T, Option<U>)>,
|
routes: Vec<(ResourceDef, (T, U))>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, U> RouterBuilder<T, U> {
|
impl<T, U> RouterBuilder<T, U> {
|
||||||
/// Register resource for specified path.
|
/// Adds a new route at the back of the routes list.
|
||||||
pub fn path<P: IntoPatterns>(
|
///
|
||||||
|
/// Returns a mutable reference to elements of the new route.
|
||||||
|
pub fn push(
|
||||||
&mut self,
|
&mut self,
|
||||||
path: P,
|
rdef: ResourceDef,
|
||||||
resource: T,
|
val: T,
|
||||||
) -> &mut (ResourceDef, T, Option<U>) {
|
ctx: U,
|
||||||
profile_method!(path);
|
) -> (&mut ResourceDef, &mut T, &mut U) {
|
||||||
|
self.routes.push((rdef, (val, ctx)));
|
||||||
self.resources
|
self.routes
|
||||||
.push((ResourceDef::new(path), resource, None));
|
.last_mut()
|
||||||
self.resources.last_mut().unwrap()
|
.map(|(rdef, (val, ctx))| (rdef, val, ctx))
|
||||||
}
|
.unwrap()
|
||||||
|
|
||||||
/// Register resource for specified path prefix.
|
|
||||||
pub fn prefix(&mut self, prefix: &str, resource: T) -> &mut (ResourceDef, T, Option<U>) {
|
|
||||||
profile_method!(prefix);
|
|
||||||
|
|
||||||
self.resources
|
|
||||||
.push((ResourceDef::prefix(prefix), resource, None));
|
|
||||||
self.resources.last_mut().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Register resource for ResourceDef
|
|
||||||
pub fn rdef(&mut self, rdef: ResourceDef, resource: T) -> &mut (ResourceDef, T, Option<U>) {
|
|
||||||
profile_method!(rdef);
|
|
||||||
|
|
||||||
self.resources.push((rdef, resource, None));
|
|
||||||
self.resources.last_mut().unwrap()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Finish configuration and create router instance.
|
/// Finish configuration and create router instance.
|
||||||
pub fn finish(self) -> Router<T, U> {
|
pub fn finish(self) -> Router<T, U> {
|
||||||
Router {
|
Router {
|
||||||
routes: self.resources,
|
routes: self.routes,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convenience methods provided when context data impls [`Default`]
|
||||||
|
impl<T, U> RouterBuilder<T, U>
|
||||||
|
where
|
||||||
|
U: Default,
|
||||||
|
{
|
||||||
|
/// Registers resource for specified path.
|
||||||
|
pub fn path(
|
||||||
|
&mut self,
|
||||||
|
path: impl IntoPatterns,
|
||||||
|
val: T,
|
||||||
|
) -> (&mut ResourceDef, &mut T, &mut U) {
|
||||||
|
profile_method!(path);
|
||||||
|
self.push(ResourceDef::new(path), val, U::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Registers resource for specified path prefix.
|
||||||
|
pub fn prefix(
|
||||||
|
&mut self,
|
||||||
|
prefix: impl IntoPatterns,
|
||||||
|
val: T,
|
||||||
|
) -> (&mut ResourceDef, &mut T, &mut U) {
|
||||||
|
profile_method!(prefix);
|
||||||
|
self.push(ResourceDef::prefix(prefix), val, U::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Registers resource for ResourceDef.
|
||||||
|
pub fn rdef(&mut self, rdef: ResourceDef, val: T) -> (&mut ResourceDef, &mut T, &mut U) {
|
||||||
|
profile_method!(rdef);
|
||||||
|
self.push(rdef, val, U::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::path::Path;
|
use crate::path::Path;
|
||||||
|
|
|
@ -283,7 +283,7 @@ impl ServiceFactory<ServiceRequest> for AppRoutingFactory {
|
||||||
.collect::<Result<Vec<_>, _>>()?
|
.collect::<Result<Vec<_>, _>>()?
|
||||||
.drain(..)
|
.drain(..)
|
||||||
.fold(Router::build(), |mut router, (path, guards, service)| {
|
.fold(Router::build(), |mut router, (path, guards, service)| {
|
||||||
router.rdef(path, service).2 = guards;
|
router.push(path, service, guards);
|
||||||
router
|
router
|
||||||
})
|
})
|
||||||
.finish();
|
.finish();
|
||||||
|
@ -295,7 +295,7 @@ impl ServiceFactory<ServiceRequest> for AppRoutingFactory {
|
||||||
|
|
||||||
/// The Actix Web router default entry point.
|
/// The Actix Web router default entry point.
|
||||||
pub struct AppRouting {
|
pub struct AppRouting {
|
||||||
router: Router<BoxedHttpService, Guards>,
|
router: Router<BoxedHttpService, Option<Guards>>,
|
||||||
default: BoxedHttpService,
|
default: BoxedHttpService,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -308,17 +308,8 @@ impl Service<ServiceRequest> for AppRouting {
|
||||||
|
|
||||||
fn call(&self, mut req: ServiceRequest) -> Self::Future {
|
fn call(&self, mut req: ServiceRequest) -> Self::Future {
|
||||||
let res = self.router.recognize_fn(&mut req, |req, guards| {
|
let res = self.router.recognize_fn(&mut req, |req, guards| {
|
||||||
if let Some(ref guards) = guards {
|
|
||||||
let guard_ctx = req.guard_ctx();
|
let guard_ctx = req.guard_ctx();
|
||||||
|
guards.iter().flatten().all(|guard| guard.check(&guard_ctx))
|
||||||
for guard in guards {
|
|
||||||
if !guard.check(&guard_ctx) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some((srv, _info)) = res {
|
if let Some((srv, _info)) = res {
|
||||||
|
|
15
src/scope.rs
15
src/scope.rs
|
@ -484,7 +484,7 @@ impl ServiceFactory<ServiceRequest> for ScopeFactory {
|
||||||
.collect::<Result<Vec<_>, _>>()?
|
.collect::<Result<Vec<_>, _>>()?
|
||||||
.drain(..)
|
.drain(..)
|
||||||
.fold(Router::build(), |mut router, (path, guards, service)| {
|
.fold(Router::build(), |mut router, (path, guards, service)| {
|
||||||
router.rdef(path, service).2 = guards;
|
router.push(path, service, guards);
|
||||||
router
|
router
|
||||||
})
|
})
|
||||||
.finish();
|
.finish();
|
||||||
|
@ -495,7 +495,7 @@ impl ServiceFactory<ServiceRequest> for ScopeFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ScopeService {
|
pub struct ScopeService {
|
||||||
router: Router<BoxedHttpService, Vec<Box<dyn Guard>>>,
|
router: Router<BoxedHttpService, Option<Vec<Box<dyn Guard>>>>,
|
||||||
default: BoxedHttpService,
|
default: BoxedHttpService,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -508,17 +508,8 @@ impl Service<ServiceRequest> for ScopeService {
|
||||||
|
|
||||||
fn call(&self, mut req: ServiceRequest) -> Self::Future {
|
fn call(&self, mut req: ServiceRequest) -> Self::Future {
|
||||||
let res = self.router.recognize_fn(&mut req, |req, guards| {
|
let res = self.router.recognize_fn(&mut req, |req, guards| {
|
||||||
if let Some(ref guards) = guards {
|
|
||||||
let guard_ctx = req.guard_ctx();
|
let guard_ctx = req.guard_ctx();
|
||||||
|
guards.iter().flatten().all(|guard| guard.check(&guard_ctx))
|
||||||
for guard in guards {
|
|
||||||
if !guard.check(&guard_ctx) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some((srv, _info)) = res {
|
if let Some((srv, _info)) = res {
|
||||||
|
|
Loading…
Reference in New Issue