1
0
mirror of https://github.com/fafhrd91/actix-web synced 2024-11-27 17:52:56 +01:00

improve scope documentation

closes #2389
This commit is contained in:
Rob Ede 2021-12-25 03:44:09 +00:00
parent 34e5c7c799
commit adf9935841
No known key found for this signature in database
GPG Key ID: 97C636207D3EF933
5 changed files with 109 additions and 117 deletions

View File

@ -29,26 +29,25 @@ const REGEX_FLAGS: &str = "(?s-m)";
/// ///
/// ///
/// # Pattern Format and Matching Behavior /// # Pattern Format and Matching Behavior
///
/// Resource pattern is defined as a string of zero or more _segments_ where each segment is /// Resource pattern is defined as a string of zero or more _segments_ where each segment is
/// preceded by a slash `/`. /// preceded by a slash `/`.
/// ///
/// This means that pattern string __must__ either be empty or begin with a slash (`/`). /// This means that pattern string __must__ either be empty or begin with a slash (`/`). This also
/// This also implies that a trailing slash in pattern defines an empty segment. /// implies that a trailing slash in pattern defines an empty segment. For example, the pattern
/// For example, the pattern `"/user/"` has two segments: `["user", ""]` /// `"/user/"` has two segments: `["user", ""]`
/// ///
/// A key point to underhand is that `ResourceDef` matches segments, not strings. /// A key point to understand is that `ResourceDef` matches segments, not strings. Segments are
/// It matches segments individually. /// matched individually. For example, the pattern `/user/` is not considered a prefix for the path
/// For example, the pattern `/user/` is not considered a prefix for the path `/user/123/456`, /// `/user/123/456`, because the second segment doesn't match: `["user", ""]`
/// because the second segment doesn't match: `["user", ""]` vs `["user", "123", "456"]`. /// vs `["user", "123", "456"]`.
/// ///
/// This definition is consistent with the definition of absolute URL path in /// This definition is consistent with the definition of absolute URL path in
/// [RFC 3986 (section 3.3)](https://datatracker.ietf.org/doc/html/rfc3986#section-3.3) /// [RFC 3986 §3.3](https://datatracker.ietf.org/doc/html/rfc3986#section-3.3)
/// ///
/// ///
/// # Static Resources /// # Static Resources
/// A static resource is the most basic type of definition. Pass a pattern to /// A static resource is the most basic type of definition. Pass a pattern to [new][Self::new].
/// [new][Self::new]. Conforming paths must match the pattern exactly. /// Conforming paths must match the pattern exactly.
/// ///
/// ## Examples /// ## Examples
/// ``` /// ```
@ -63,7 +62,6 @@ const REGEX_FLAGS: &str = "(?s-m)";
/// assert!(!resource.is_match("/search")); /// assert!(!resource.is_match("/search"));
/// ``` /// ```
/// ///
///
/// # Dynamic Segments /// # Dynamic Segments
/// Also known as "path parameters". Resources can define sections of a pattern that be extracted /// Also known as "path parameters". Resources can define sections of a pattern that be extracted
/// from a conforming path, if it conforms to (one of) the resource pattern(s). /// from a conforming path, if it conforms to (one of) the resource pattern(s).
@ -102,15 +100,15 @@ const REGEX_FLAGS: &str = "(?s-m)";
/// assert_eq!(path.get("id").unwrap(), "123"); /// assert_eq!(path.get("id").unwrap(), "123");
/// ``` /// ```
/// ///
///
/// # Prefix Resources /// # Prefix Resources
/// A prefix resource is defined as pattern that can match just the start of a path, up to a /// A prefix resource is defined as pattern that can match just the start of a path, up to a
/// segment boundary. /// segment boundary.
/// ///
/// Prefix patterns with a trailing slash may have an unexpected, though correct, behavior. /// Prefix patterns with a trailing slash may have an unexpected, though correct, behavior.
/// They define and therefore require an empty segment in order to match. Examples are given below. /// They define and therefore require an empty segment in order to match. It is easier to understand
/// this behavior after reading the [matching behavior section]. Examples are given below.
/// ///
/// Empty pattern matches any path as a prefix. /// The empty pattern (`""`), as a prefix, matches any path.
/// ///
/// Prefix resources can contain dynamic segments. /// Prefix resources can contain dynamic segments.
/// ///
@ -130,7 +128,6 @@ const REGEX_FLAGS: &str = "(?s-m)";
/// assert!(!resource.is_match("/user/123")); /// assert!(!resource.is_match("/user/123"));
/// ``` /// ```
/// ///
///
/// # Custom Regex Segments /// # Custom Regex Segments
/// Dynamic segments can be customised to only match a specific regular expression. It can be /// Dynamic segments can be customised to only match a specific regular expression. It can be
/// helpful to do this if resource definitions would otherwise conflict and cause one to /// helpful to do this if resource definitions would otherwise conflict and cause one to
@ -158,7 +155,6 @@ const REGEX_FLAGS: &str = "(?s-m)";
/// assert!(!resource.is_match("/user/abc")); /// assert!(!resource.is_match("/user/abc"));
/// ``` /// ```
/// ///
///
/// # Tail Segments /// # Tail Segments
/// As a shortcut to defining a custom regex for matching _all_ remaining characters (not just those /// As a shortcut to defining a custom regex for matching _all_ remaining characters (not just those
/// up until a `/` character), there is a special pattern to match (and capture) the remaining /// up until a `/` character), there is a special pattern to match (and capture) the remaining
@ -179,7 +175,6 @@ const REGEX_FLAGS: &str = "(?s-m)";
/// assert_eq!(path.get("tail").unwrap(), "main/LICENSE"); /// assert_eq!(path.get("tail").unwrap(), "main/LICENSE");
/// ``` /// ```
/// ///
///
/// # Multi-Pattern Resources /// # Multi-Pattern Resources
/// For resources that can map to multiple distinct paths, it may be suitable to use /// For resources that can map to multiple distinct paths, it may be suitable to use
/// multi-pattern resources by passing an array/vec to [`new`][Self::new]. They will be combined /// multi-pattern resources by passing an array/vec to [`new`][Self::new]. They will be combined
@ -198,7 +193,6 @@ const REGEX_FLAGS: &str = "(?s-m)";
/// assert!(resource.is_match("/index")); /// assert!(resource.is_match("/index"));
/// ``` /// ```
/// ///
///
/// # Trailing Slashes /// # Trailing Slashes
/// It should be noted that this library takes no steps to normalize intra-path or trailing slashes. /// It should be noted that this library takes no steps to normalize intra-path or trailing slashes.
/// As such, all resource definitions implicitly expect a pre-processing step to normalize paths if /// As such, all resource definitions implicitly expect a pre-processing step to normalize paths if
@ -212,6 +206,8 @@ const REGEX_FLAGS: &str = "(?s-m)";
/// assert!(!ResourceDef::new("/root/").is_match("/root")); /// assert!(!ResourceDef::new("/root/").is_match("/root"));
/// assert!(!ResourceDef::prefix("/root/").is_match("/root")); /// assert!(!ResourceDef::prefix("/root/").is_match("/root"));
/// ``` /// ```
///
/// [matching behavior section]: #pattern-format-and-matching-behavior
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct ResourceDef { pub struct ResourceDef {
id: u16, id: u16,
@ -279,7 +275,7 @@ impl ResourceDef {
/// ``` /// ```
pub fn new<T: IntoPatterns>(paths: T) -> Self { pub fn new<T: IntoPatterns>(paths: T) -> Self {
profile_method!(new); profile_method!(new);
Self::new2(paths, false) Self::construct(paths, false)
} }
/// Constructs a new resource definition using a pattern that performs prefix matching. /// Constructs a new resource definition using a pattern that performs prefix matching.
@ -292,7 +288,7 @@ impl ResourceDef {
/// resource definition with a tail segment; use [`new`][Self::new] in this case. /// resource definition with a tail segment; use [`new`][Self::new] in this case.
/// ///
/// # Panics /// # Panics
/// Panics if path regex pattern is malformed. /// Panics if path pattern is malformed.
/// ///
/// # Examples /// # Examples
/// ``` /// ```
@ -307,14 +303,14 @@ impl ResourceDef {
/// ``` /// ```
pub fn prefix<T: IntoPatterns>(paths: T) -> Self { pub fn prefix<T: IntoPatterns>(paths: T) -> Self {
profile_method!(prefix); profile_method!(prefix);
ResourceDef::new2(paths, true) ResourceDef::construct(paths, true)
} }
/// Constructs a new resource definition using a string pattern that performs prefix matching, /// Constructs a new resource definition using a string pattern that performs prefix matching,
/// inserting a `/` to beginning of the pattern if absent and pattern is not empty. /// ensuring a leading `/` if pattern is not empty.
/// ///
/// # Panics /// # Panics
/// Panics if path regex pattern is malformed. /// Panics if path pattern is malformed.
/// ///
/// # Examples /// # Examples
/// ``` /// ```
@ -515,8 +511,8 @@ impl ResourceDef {
.collect::<Vec<_>>(); .collect::<Vec<_>>();
match patterns.len() { match patterns.len() {
1 => ResourceDef::new2(&patterns[0], other.is_prefix()), 1 => ResourceDef::construct(&patterns[0], other.is_prefix()),
_ => ResourceDef::new2(patterns, other.is_prefix()), _ => ResourceDef::construct(patterns, other.is_prefix()),
} }
} }
@ -881,8 +877,8 @@ impl ResourceDef {
} }
} }
fn new2<T: IntoPatterns>(paths: T, is_prefix: bool) -> Self { fn construct<T: IntoPatterns>(paths: T, is_prefix: bool) -> Self {
profile_method!(new2); profile_method!(construct);
let patterns = paths.patterns(); let patterns = paths.patterns();
let (pat_type, segments) = match &patterns { let (pat_type, segments) = match &patterns {
@ -1814,7 +1810,7 @@ mod tests {
#[test] #[test]
#[should_panic] #[should_panic]
fn prefix_plus_tail_match_is_allowed() { fn prefix_plus_tail_match_disallowed() {
ResourceDef::prefix("/user/{id}*"); ResourceDef::prefix("/user/{id}*");
} }
} }

View File

@ -23,28 +23,25 @@ use crate::{
BoxError, Error, FromRequest, HttpResponse, Responder, BoxError, Error, FromRequest, HttpResponse, Responder,
}; };
/// *Resource* is an entry in resources table which corresponds to requested URL. /// A collection of [`Route`]s that respond to the same path pattern.
/// ///
/// Resource in turn has at least one route. /// Resource in turn has at least one route. Route consists of an handlers objects and list of
/// Route consists of an handlers objects and list of guards /// guards (objects that implement `Guard` trait). Resources and routes uses builder-like pattern
/// (objects that implement `Guard` trait). /// for configuration. During request handling, resource object iterate through all routes and check
/// Resources and routes uses builder-like pattern for configuration. /// guards for specific route, if request matches all guards, route considered matched and route
/// During request handling, resource object iterate through all routes /// handler get called.
/// and check guards for specific route, if request matches all
/// guards, route considered matched and route handler get called.
/// ///
/// # Examples
/// ``` /// ```
/// use actix_web::{web, App, HttpResponse}; /// use actix_web::{web, App, HttpResponse};
/// ///
/// fn main() { /// let app = App::new().service(
/// let app = App::new().service( /// web::resource("/")
/// web::resource("/") /// .route(web::get().to(|| HttpResponse::Ok())));
/// .route(web::get().to(|| HttpResponse::Ok())));
/// }
/// ``` /// ```
/// ///
/// If no matching route could be found, *405* response code get returned. /// If no matching route could be found, *405* response code get returned. Default behavior could be
/// Default behavior could be overridden with `default_resource()` method. /// overridden with `default_resource()` method.
pub struct Resource<T = ResourceEndpoint, B = BoxBody> { pub struct Resource<T = ResourceEndpoint, B = BoxBody> {
endpoint: T, endpoint: T,
rdef: Patterns, rdef: Patterns,

View File

@ -15,10 +15,10 @@ use crate::{
BoxError, Error, FromRequest, HttpResponse, Responder, BoxError, Error, FromRequest, HttpResponse, Responder,
}; };
/// Resource route definition /// A request handler with [guards](guard).
/// ///
/// Route uses builder-like pattern for configuration. /// Route uses a builder-like pattern for configuration. If handler is not set, a `404 Not Found`
/// If handler is not explicitly set, default *404 Not Found* handler is used. /// handler is used.
pub struct Route { pub struct Route {
service: BoxedHttpServiceFactory, service: BoxedHttpServiceFactory,
guards: Rc<Vec<Box<dyn Guard>>>, guards: Rc<Vec<Box<dyn Guard>>>,

View File

@ -27,34 +27,36 @@ use crate::{
type Guards = Vec<Box<dyn Guard>>; type Guards = Vec<Box<dyn Guard>>;
/// Resources scope. /// A collection of [`Route`]s, [`Resource`]s, or other services that share a common path prefix.
/// ///
/// Scope is a set of resources with common root path. /// The `Scope`'s path can contain [dynamic segments]. The dynamic segments can be extracted from
/// Scopes collect multiple paths under a common path prefix. /// requests using the [`Path`](crate::web::Path) extractor or
/// Scope path can contain variable path segments as resources. /// with [`HttpRequest::match_info()`](crate::HttpRequest::match_info).
/// Scope prefix is always complete path segment, i.e `/app` would
/// be converted to a `/app/` and it would not match `/app` path.
/// ///
/// You can get variable path segments from `HttpRequest::match_info()`. /// # Avoid Trailing Slashes
/// `Path` extractor also is able to extract scope level variable segments. /// Avoid using trailing slashes in the scope prefix (e.g., `web::scope("/scope/")`). It will almost
/// certainly not have the expected behavior. See the [documentation on resource definitions][pat]
/// to understand why this is the case and how to correctly construct scope/prefix definitions.
/// ///
/// # Examples
/// ``` /// ```
/// use actix_web::{web, App, HttpResponse}; /// use actix_web::{web, App, HttpResponse};
/// ///
/// fn main() { /// let app = App::new().service(
/// let app = App::new().service( /// web::scope("/{project_id}/")
/// web::scope("/{project_id}/") /// .service(web::resource("/path1").to(|| async { "OK" }))
/// .service(web::resource("/path1").to(|| async { "OK" })) /// .service(web::resource("/path2").route(web::get().to(|| HttpResponse::Ok())))
/// .service(web::resource("/path2").route(web::get().to(|| HttpResponse::Ok()))) /// .service(web::resource("/path3").route(web::head().to(HttpResponse::MethodNotAllowed)))
/// .service(web::resource("/path3").route(web::head().to(HttpResponse::MethodNotAllowed))) /// );
/// );
/// }
/// ``` /// ```
/// ///
/// In the above example three routes get registered: /// In the above example three routes get registered:
/// * /{project_id}/path1 - responds to all http method /// - /{project_id}/path1 - responds to all HTTP methods
/// * /{project_id}/path2 - `GET` requests /// - /{project_id}/path2 - responds to `GET` requests
/// * /{project_id}/path3 - `HEAD` requests /// - /{project_id}/path3 - responds to `HEAD` requests
///
/// [pat]: crate::dev::ResourceDef#prefix-resources
/// [dynamic segments]: crate::dev::ResourceDef#dynamic-segments
pub struct Scope<T = ScopeEndpoint, B = BoxBody> { pub struct Scope<T = ScopeEndpoint, B = BoxBody> {
endpoint: T, endpoint: T,
rdef: String, rdef: String,
@ -106,16 +108,14 @@ where
/// "Welcome!" /// "Welcome!"
/// } /// }
/// ///
/// fn main() { /// let app = App::new().service(
/// let app = App::new().service( /// web::scope("/app")
/// web::scope("/app") /// .guard(guard::Header("content-type", "text/plain"))
/// .guard(guard::Header("content-type", "text/plain")) /// .route("/test1", web::get().to(index))
/// .route("/test1", web::get().to(index)) /// .route("/test2", web::post().to(|r: HttpRequest| {
/// .route("/test2", web::post().to(|r: HttpRequest| { /// HttpResponse::MethodNotAllowed()
/// HttpResponse::MethodNotAllowed() /// }))
/// })) /// );
/// );
/// }
/// ``` /// ```
pub fn guard<G: Guard + 'static>(mut self, guard: G) -> Self { pub fn guard<G: Guard + 'static>(mut self, guard: G) -> Self {
self.guards.push(Box::new(guard)); self.guards.push(Box::new(guard));
@ -186,15 +186,13 @@ where
/// ); /// );
/// } /// }
/// ///
/// fn main() { /// let app = App::new()
/// let app = App::new() /// .wrap(middleware::Logger::default())
/// .wrap(middleware::Logger::default()) /// .service(
/// .service( /// web::scope("/api")
/// web::scope("/api") /// .configure(config)
/// .configure(config) /// )
/// ) /// .route("/index.html", web::get().to(|| HttpResponse::Ok()));
/// .route("/index.html", web::get().to(|| HttpResponse::Ok()));
/// }
/// ``` /// ```
pub fn configure<F>(mut self, cfg_fn: F) -> Self pub fn configure<F>(mut self, cfg_fn: F) -> Self
where where
@ -233,13 +231,11 @@ where
/// "Welcome!" /// "Welcome!"
/// } /// }
/// ///
/// fn main() { /// let app = App::new().service(
/// let app = App::new().service( /// web::scope("/app").service(
/// web::scope("/app").service( /// web::scope("/v1")
/// web::scope("/v1") /// .service(web::resource("/test1").to(index)))
/// .service(web::resource("/test1").to(index))) /// );
/// );
/// }
/// ``` /// ```
pub fn service<F>(mut self, factory: F) -> Self pub fn service<F>(mut self, factory: F) -> Self
where where
@ -263,13 +259,11 @@ where
/// "Welcome!" /// "Welcome!"
/// } /// }
/// ///
/// fn main() { /// let app = App::new().service(
/// let app = App::new().service( /// web::scope("/app")
/// web::scope("/app") /// .route("/test1", web::get().to(index))
/// .route("/test1", web::get().to(index)) /// .route("/test2", web::post().to(|| HttpResponse::MethodNotAllowed()))
/// .route("/test2", web::post().to(|| HttpResponse::MethodNotAllowed())) /// );
/// );
/// }
/// ``` /// ```
pub fn route(self, path: &str, mut route: Route) -> Self { pub fn route(self, path: &str, mut route: Route) -> Self {
self.service( self.service(
@ -355,21 +349,19 @@ where
/// "Welcome!" /// "Welcome!"
/// } /// }
/// ///
/// fn main() { /// let app = App::new().service(
/// let app = App::new().service( /// web::scope("/app")
/// web::scope("/app") /// .wrap_fn(|req, srv| {
/// .wrap_fn(|req, srv| { /// let fut = srv.call(req);
/// let fut = srv.call(req); /// async {
/// async { /// let mut res = fut.await?;
/// let mut res = fut.await?; /// res.headers_mut().insert(
/// res.headers_mut().insert( /// CONTENT_TYPE, HeaderValue::from_static("text/plain"),
/// CONTENT_TYPE, HeaderValue::from_static("text/plain"), /// );
/// ); /// Ok(res)
/// Ok(res) /// }
/// } /// })
/// }) /// .route("/index.html", web::get().to(index)));
/// .route("/index.html", web::get().to(index)));
/// }
/// ``` /// ```
pub fn wrap_fn<F, R, B1>( pub fn wrap_fn<F, R, B1>(
self, self,

View File

@ -52,11 +52,16 @@ pub fn resource<T: IntoPatterns>(path: T) -> Resource {
/// Scopes collect multiple paths under a common path prefix. The scope's path can contain dynamic /// Scopes collect multiple paths under a common path prefix. The scope's path can contain dynamic
/// path segments. /// path segments.
/// ///
/// # Avoid Trailing Slashes
/// Avoid using trailing slashes in the scope prefix (e.g., `web::scope("/scope/")`). It will almost
/// certainly not have the expected behavior. See the [documentation on resource definitions][pat]
/// to understand why this is the case and how to correctly construct scope/prefix definitions.
///
/// # Examples /// # Examples
/// In this example, three routes are set up (and will handle any method): /// In this example, three routes are set up (and will handle any method):
/// * `/{project_id}/path1` /// - `/{project_id}/path1`
/// * `/{project_id}/path2` /// - `/{project_id}/path2`
/// * `/{project_id}/path3` /// - `/{project_id}/path3`
/// ///
/// ``` /// ```
/// use actix_web::{web, App, HttpResponse}; /// use actix_web::{web, App, HttpResponse};
@ -68,6 +73,8 @@ pub fn resource<T: IntoPatterns>(path: T) -> Resource {
/// .service(web::resource("/path3").to(|| HttpResponse::MethodNotAllowed())) /// .service(web::resource("/path3").to(|| HttpResponse::MethodNotAllowed()))
/// ); /// );
/// ``` /// ```
///
/// [pat]: crate::dev::ResourceDef#prefix-resources
pub fn scope(path: &str) -> Scope { pub fn scope(path: &str) -> Scope {
Scope::new(path) Scope::new(path)
} }