Skip to content

Commit

Permalink
tweak and document router (actix#2612)
Browse files Browse the repository at this point in the history
Co-authored-by: Rob Ede <[email protected]>
  • Loading branch information
aliemjay and robjtede authored Jan 31, 2022
1 parent fd412a8 commit 9fde5b3
Show file tree
Hide file tree
Showing 7 changed files with 114 additions and 120 deletions.
6 changes: 6 additions & 0 deletions actix-router/CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
# Changes

## Unreleased - 2021-xx-xx
- Remove unused `ResourceInfo`. [#2612]
- Add `RouterBuilder::push`. [#2612]
- Change signature of `ResourceDef::capture_match_info_fn` to remove `user_data` parameter. [#2612]
- Replace `Option<U>` with `U` in `Router` API. [#2612]
- Relax bounds on `Router::recognize*` and `ResourceDef::capture_match_info`. [#2612]
- `Quoter::requote` now returns `Option<Vec<u8>>`. [#2613]

[#2612]: https://github.com/actix/actix-web/pull/2612
[#2613]: https://github.com/actix/actix-web/pull/2613


Expand Down
2 changes: 1 addition & 1 deletion actix-router/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ pub use self::pattern::{IntoPatterns, Patterns};
pub use self::quoter::Quoter;
pub use self::resource::ResourceDef;
pub use self::resource_path::{Resource, ResourcePath};
pub use self::router::{ResourceInfo, Router, RouterBuilder};
pub use self::router::{ResourceId, Router, RouterBuilder};

#[cfg(feature = "http")]
pub use self::url::Url;
6 changes: 3 additions & 3 deletions actix-router/src/quoter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,14 @@ impl Quoter {
quoter
}

/// Re-quotes... ?
/// Decodes safe percent-encoded sequences from `val`.
///
/// Returns `None` when no modification to the original byte string was required.
///
/// Non-ASCII bytes are accepted as valid input.
///
/// Behavior for invalid/incomplete percent-encoding sequences is unspecified and may include removing
/// the invalid sequence from the output or passing it as it is.
/// Behavior for invalid/incomplete percent-encoding sequences is unspecified and may include
/// removing the invalid sequence from the output or passing it as-is.
pub fn requote(&self, val: &[u8]) -> Option<Vec<u8>> {
let mut has_pct = 0;
let mut pct = [b'%', 0, 0];
Expand Down
30 changes: 11 additions & 19 deletions actix-router/src/resource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,7 @@ use std::{
use firestorm::{profile_fn, profile_method, profile_section};
use regex::{escape, Regex, RegexSet};

use crate::{
path::{Path, PathItem},
IntoPatterns, Patterns, Resource, ResourcePath,
};
use crate::{path::PathItem, IntoPatterns, Patterns, Resource, ResourcePath};

const MAX_DYNAMIC_SEGMENTS: usize = 16;

Expand Down Expand Up @@ -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.
///
Expand All @@ -635,9 +632,9 @@ impl ResourceDef {
/// assert_eq!(path.get("path").unwrap(), "HEAD/Cargo.toml");
/// 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, resource: &mut R) -> bool {
profile_method!(capture_match_info);
self.capture_match_info_fn(path, |_, _| true, ())
self.capture_match_info_fn(resource, |_| true)
}

/// Collects dynamic segment values into `resource` after matching paths and executing
Expand All @@ -655,13 +652,12 @@ impl ResourceDef {
/// use actix_router::{Path, ResourceDef};
///
/// 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(
/// path,
/// // when env var is not set, reject when path contains "admin"
/// |res, admin_allowed| !res.path().contains("admin"),
/// &admin_allowed
/// |res| !(!admin_allowed && res.path().contains("admin")),
/// )
/// }
///
Expand All @@ -678,15 +674,10 @@ impl ResourceDef {
/// assert!(!try_match(&resource, &mut path));
/// assert_eq!(path.unprocessed(), "/user/admin/stars");
/// ```
pub fn capture_match_info_fn<R, F, U>(
&self,
resource: &mut R,
check_fn: F,
user_data: U,
) -> bool
pub fn capture_match_info_fn<R, F>(&self, resource: &mut R, check_fn: F) -> bool
where
R: Resource,
F: FnOnce(&R, U) -> bool,
F: FnOnce(&R) -> bool,
{
profile_method!(capture_match_info_fn);

Expand Down Expand Up @@ -762,7 +753,7 @@ impl ResourceDef {
}
};

if !check_fn(resource, user_data) {
if !check_fn(resource) {
return false;
}

Expand Down Expand Up @@ -857,7 +848,7 @@ impl ResourceDef {
S: BuildHasher,
{
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`.
Expand Down Expand Up @@ -1157,6 +1148,7 @@ pub(crate) fn insert_slash(path: &str) -> Cow<'_, str> {
#[cfg(test)]
mod tests {
use super::*;
use crate::Path;

#[test]
fn equivalence() {
Expand Down
150 changes: 83 additions & 67 deletions actix-router/src/router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,134 +5,150 @@ use crate::{IntoPatterns, Resource, ResourceDef};
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct ResourceId(pub u16);

/// Information about current resource
#[derive(Debug, Clone)]
pub struct ResourceInfo {
#[allow(dead_code)]
resource: ResourceId,
}

/// Resource router.
// T is the resource itself
// U is any other data needed for routing like method guards
///
/// It matches a [routing resource](Resource) to an ordered list of _routes_. Each is defined by a
/// single [`ResourceDef`] and contains two types of custom data:
/// 1. The route _value_, of the generic type `T`.
/// 1. Some _context_ data, of the generic type `U`, which is only provided to the check function in
/// [`recognize_fn`](Self::recognize_fn). This parameter defaults to `()` and can be omitted if
/// not required.
pub struct Router<T, U = ()> {
routes: Vec<(ResourceDef, T, Option<U>)>,
routes: Vec<(ResourceDef, T, U)>,
}

impl<T, U> Router<T, U> {
/// Constructs new `RouterBuilder` with empty route list.
pub fn build() -> RouterBuilder<T, U> {
RouterBuilder {
resources: Vec::new(),
}
RouterBuilder { routes: Vec::new() }
}

/// Finds the value in the router that matches a given [routing resource](Resource).
///
/// The match result, including the captured dynamic segments, in the `resource`.
pub fn recognize<R>(&self, resource: &mut R) -> Option<(&T, ResourceId)>
where
R: Resource,
{
profile_method!(recognize);

for item in self.routes.iter() {
if item.0.capture_match_info(resource.resource_path()) {
return Some((&item.1, ResourceId(item.0.id())));
}
}

None
self.recognize_fn(resource, |_, _| true)
}

/// Same as [`recognize`](Self::recognize) but returns a mutable reference to the matched value.
pub fn recognize_mut<R>(&mut self, resource: &mut R) -> Option<(&mut T, ResourceId)>
where
R: Resource,
{
profile_method!(recognize_mut);

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
self.recognize_mut_fn(resource, |_, _| true)
}

pub fn recognize_fn<R, F>(&self, resource: &mut R, check: F) -> Option<(&T, ResourceId)>
/// Finds the value in the router that matches a given [routing resource](Resource) and passes
/// an additional predicate check using context data.
///
/// Similar to [`recognize`](Self::recognize). However, before accepting the route as matched,
/// the `check` closure is executed, passing the resource and each route's context data. If the
/// closure returns true then the match result is stored into `resource` and a reference to
/// the matched _value_ is returned.
pub fn recognize_fn<R, F>(&self, resource: &mut R, mut check: F) -> Option<(&T, ResourceId)>
where
F: Fn(&R, &Option<U>) -> bool,
R: Resource,
F: FnMut(&R, &U) -> bool,
{
profile_method!(recognize_checked);

for item in self.routes.iter() {
if item.0.capture_match_info_fn(resource, &check, &item.2) {
return Some((&item.1, ResourceId(item.0.id())));
for (rdef, val, ctx) in self.routes.iter() {
if rdef.capture_match_info_fn(resource, |res| check(res, ctx)) {
return Some((val, ResourceId(rdef.id())));
}
}

None
}

/// Same as [`recognize_fn`](Self::recognize_fn) but returns a mutable reference to the matched
/// value.
pub fn recognize_mut_fn<R, F>(
&mut self,
resource: &mut R,
check: F,
mut check: F,
) -> Option<(&mut T, ResourceId)>
where
F: Fn(&R, &Option<U>) -> bool,
R: Resource,
F: FnMut(&R, &U) -> bool,
{
profile_method!(recognize_mut_checked);

for item in self.routes.iter_mut() {
if item.0.capture_match_info_fn(resource, &check, &item.2) {
return Some((&mut item.1, ResourceId(item.0.id())));
for (rdef, val, ctx) in self.routes.iter_mut() {
if rdef.capture_match_info_fn(resource, |res| check(res, ctx)) {
return Some((val, ResourceId(rdef.id())));
}
}

None
}
}

/// Builder for an ordered [routing](Router) list.
pub struct RouterBuilder<T, U = ()> {
resources: Vec<(ResourceDef, T, Option<U>)>,
routes: Vec<(ResourceDef, T, U)>,
}

impl<T, U> RouterBuilder<T, U> {
/// Register resource for specified path.
pub fn path<P: IntoPatterns>(
/// Adds a new route to the end of the routing list.
///
/// Returns mutable references to elements of the new route.
pub fn push(
&mut self,
path: P,
resource: T,
) -> &mut (ResourceDef, T, Option<U>) {
profile_method!(path);

self.resources
.push((ResourceDef::new(path), resource, None));
self.resources.last_mut().unwrap()
rdef: ResourceDef,
val: T,
ctx: U,
) -> (&mut ResourceDef, &mut T, &mut U) {
profile_method!(push);
self.routes.push((rdef, val, ctx));
self.routes
.last_mut()
.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()
/// Finish configuration and create router instance.
pub fn finish(self) -> Router<T, U> {
Router {
routes: self.routes,
}
}
}

/// Register resource for ResourceDef
pub fn rdef(&mut self, rdef: ResourceDef, resource: T) -> &mut (ResourceDef, T, Option<U>) {
profile_method!(rdef);
/// 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())
}

self.resources.push((rdef, resource, None));
self.resources.last_mut().unwrap()
/// 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())
}

/// Finish configuration and create router instance.
pub fn finish(self) -> Router<T, U> {
Router {
routes: self.resources,
}
/// 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())
}
}

Expand Down
Loading

0 comments on commit 9fde5b3

Please sign in to comment.