1
0
Fork 0
mirror of https://github.com/actix/actix-web.git synced 2024-06-11 01:39:33 +00: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
///
/// Resource pattern is defined as a string of zero or more _segments_ where each segment is
/// preceded by a slash `/`.
///
/// This means that pattern string __must__ either be empty or begin with a slash (`/`).
/// This also implies that a trailing slash in pattern defines an empty segment.
/// For example, the pattern `"/user/"` has two segments: `["user", ""]`
/// This means that pattern string __must__ either be empty or begin with a slash (`/`). This also
/// implies that a trailing slash in pattern defines an empty segment. For example, the pattern
/// `"/user/"` has two segments: `["user", ""]`
///
/// A key point to underhand is that `ResourceDef` matches segments, not strings.
/// It matches segments individually.
/// For example, the pattern `/user/` is not considered a prefix for the path `/user/123/456`,
/// because the second segment doesn't match: `["user", ""]` vs `["user", "123", "456"]`.
/// A key point to understand is that `ResourceDef` matches segments, not strings. Segments are
/// matched individually. For example, the pattern `/user/` is not considered a prefix for the path
/// `/user/123/456`, because the second segment doesn't match: `["user", ""]`
/// vs `["user", "123", "456"]`.
///
/// 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
/// A static resource is the most basic type of definition. Pass a pattern to
/// [new][Self::new]. Conforming paths must match the pattern exactly.
/// A static resource is the most basic type of definition. Pass a pattern to [new][Self::new].
/// Conforming paths must match the pattern exactly.
///
/// ## Examples
/// ```
@ -63,7 +62,6 @@ const REGEX_FLAGS: &str = "(?s-m)";
/// assert!(!resource.is_match("/search"));
/// ```
///
///
/// # Dynamic Segments
/// 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).
@ -102,15 +100,15 @@ const REGEX_FLAGS: &str = "(?s-m)";
/// assert_eq!(path.get("id").unwrap(), "123");
/// ```
///
///
/// # Prefix Resources
/// A prefix resource is defined as pattern that can match just the start of a path, up to a
/// segment boundary.
///
/// 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.
///
@ -130,7 +128,6 @@ const REGEX_FLAGS: &str = "(?s-m)";
/// assert!(!resource.is_match("/user/123"));
/// ```
///
///
/// # Custom Regex Segments
/// 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
@ -158,7 +155,6 @@ const REGEX_FLAGS: &str = "(?s-m)";
/// assert!(!resource.is_match("/user/abc"));
/// ```
///
///
/// # Tail Segments
/// 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
@ -179,7 +175,6 @@ const REGEX_FLAGS: &str = "(?s-m)";
/// assert_eq!(path.get("tail").unwrap(), "main/LICENSE");
/// ```
///
///
/// # Multi-Pattern Resources
/// 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
@ -198,7 +193,6 @@ const REGEX_FLAGS: &str = "(?s-m)";
/// assert!(resource.is_match("/index"));
/// ```
///
///
/// # 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
@ -212,6 +206,8 @@ const REGEX_FLAGS: &str = "(?s-m)";
/// assert!(!ResourceDef::new("/root/").is_match("/root"));
/// assert!(!ResourceDef::prefix("/root/").is_match("/root"));
/// ```
///
/// [matching behavior section]: #pattern-format-and-matching-behavior
#[derive(Clone, Debug)]
pub struct ResourceDef {
id: u16,
@ -279,7 +275,7 @@ impl ResourceDef {
/// ```
pub fn new<T: IntoPatterns>(paths: T) -> Self {
profile_method!(new);
Self::new2(paths, false)
Self::construct(paths, false)
}
/// 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.
///
/// # Panics
/// Panics if path regex pattern is malformed.
/// Panics if path pattern is malformed.
///
/// # Examples
/// ```
@ -307,14 +303,14 @@ impl ResourceDef {
/// ```
pub fn prefix<T: IntoPatterns>(paths: T) -> Self {
profile_method!(prefix);
ResourceDef::new2(paths, true)
ResourceDef::construct(paths, true)
}
/// 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 if path regex pattern is malformed.
/// Panics if path pattern is malformed.
///
/// # Examples
/// ```
@ -515,8 +511,8 @@ impl ResourceDef {
.collect::<Vec<_>>();
match patterns.len() {
1 => ResourceDef::new2(&patterns[0], other.is_prefix()),
_ => ResourceDef::new2(patterns, other.is_prefix()),
1 => ResourceDef::construct(&patterns[0], 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 {
profile_method!(new2);
fn construct<T: IntoPatterns>(paths: T, is_prefix: bool) -> Self {
profile_method!(construct);
let patterns = paths.patterns();
let (pat_type, segments) = match &patterns {
@ -1814,7 +1810,7 @@ mod tests {
#[test]
#[should_panic]
fn prefix_plus_tail_match_is_allowed() {
fn prefix_plus_tail_match_disallowed() {
ResourceDef::prefix("/user/{id}*");
}
}

View file

@ -23,28 +23,25 @@ use crate::{
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.
/// Route consists of an handlers objects and list of guards
/// (objects that implement `Guard` trait).
/// Resources and routes uses builder-like pattern for configuration.
/// During request handling, resource object iterate through all routes
/// and check guards for specific route, if request matches all
/// guards, route considered matched and route handler get called.
/// Resource in turn has at least one route. Route consists of an handlers objects and list of
/// guards (objects that implement `Guard` trait). Resources and routes uses builder-like pattern
/// for configuration. During request handling, resource object iterate through all routes 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};
///
/// fn main() {
/// let app = App::new().service(
/// web::resource("/")
/// .route(web::get().to(|| HttpResponse::Ok())));
/// }
/// let app = App::new().service(
/// web::resource("/")
/// .route(web::get().to(|| HttpResponse::Ok())));
/// ```
///
/// If no matching route could be found, *405* response code get returned.
/// Default behavior could be overridden with `default_resource()` method.
/// If no matching route could be found, *405* response code get returned. Default behavior could be
/// overridden with `default_resource()` method.
pub struct Resource<T = ResourceEndpoint, B = BoxBody> {
endpoint: T,
rdef: Patterns,

View file

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

View file

@ -27,34 +27,36 @@ use crate::{
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.
/// Scopes collect multiple paths under a common path prefix.
/// Scope path can contain variable path segments as resources.
/// Scope prefix is always complete path segment, i.e `/app` would
/// be converted to a `/app/` and it would not match `/app` path.
/// The `Scope`'s path can contain [dynamic segments]. The dynamic segments can be extracted from
/// requests using the [`Path`](crate::web::Path) extractor or
/// with [`HttpRequest::match_info()`](crate::HttpRequest::match_info).
///
/// You can get variable path segments from `HttpRequest::match_info()`.
/// `Path` extractor also is able to extract scope level variable 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
/// ```
/// use actix_web::{web, App, HttpResponse};
///
/// fn main() {
/// let app = App::new().service(
/// web::scope("/{project_id}/")
/// .service(web::resource("/path1").to(|| async { "OK" }))
/// .service(web::resource("/path2").route(web::get().to(|| HttpResponse::Ok())))
/// .service(web::resource("/path3").route(web::head().to(HttpResponse::MethodNotAllowed)))
/// );
/// }
/// let app = App::new().service(
/// web::scope("/{project_id}/")
/// .service(web::resource("/path1").to(|| async { "OK" }))
/// .service(web::resource("/path2").route(web::get().to(|| HttpResponse::Ok())))
/// .service(web::resource("/path3").route(web::head().to(HttpResponse::MethodNotAllowed)))
/// );
/// ```
///
/// In the above example three routes get registered:
/// * /{project_id}/path1 - responds to all http method
/// * /{project_id}/path2 - `GET` requests
/// * /{project_id}/path3 - `HEAD` requests
/// - /{project_id}/path1 - responds to all HTTP methods
/// - /{project_id}/path2 - responds to `GET` 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> {
endpoint: T,
rdef: String,
@ -106,16 +108,14 @@ where
/// "Welcome!"
/// }
///
/// fn main() {
/// let app = App::new().service(
/// web::scope("/app")
/// .guard(guard::Header("content-type", "text/plain"))
/// .route("/test1", web::get().to(index))
/// .route("/test2", web::post().to(|r: HttpRequest| {
/// HttpResponse::MethodNotAllowed()
/// }))
/// );
/// }
/// let app = App::new().service(
/// web::scope("/app")
/// .guard(guard::Header("content-type", "text/plain"))
/// .route("/test1", web::get().to(index))
/// .route("/test2", web::post().to(|r: HttpRequest| {
/// HttpResponse::MethodNotAllowed()
/// }))
/// );
/// ```
pub fn guard<G: Guard + 'static>(mut self, guard: G) -> Self {
self.guards.push(Box::new(guard));
@ -186,15 +186,13 @@ where
/// );
/// }
///
/// fn main() {
/// let app = App::new()
/// .wrap(middleware::Logger::default())
/// .service(
/// web::scope("/api")
/// .configure(config)
/// )
/// .route("/index.html", web::get().to(|| HttpResponse::Ok()));
/// }
/// let app = App::new()
/// .wrap(middleware::Logger::default())
/// .service(
/// web::scope("/api")
/// .configure(config)
/// )
/// .route("/index.html", web::get().to(|| HttpResponse::Ok()));
/// ```
pub fn configure<F>(mut self, cfg_fn: F) -> Self
where
@ -233,13 +231,11 @@ where
/// "Welcome!"
/// }
///
/// fn main() {
/// let app = App::new().service(
/// web::scope("/app").service(
/// web::scope("/v1")
/// .service(web::resource("/test1").to(index)))
/// );
/// }
/// let app = App::new().service(
/// web::scope("/app").service(
/// web::scope("/v1")
/// .service(web::resource("/test1").to(index)))
/// );
/// ```
pub fn service<F>(mut self, factory: F) -> Self
where
@ -263,13 +259,11 @@ where
/// "Welcome!"
/// }
///
/// fn main() {
/// let app = App::new().service(
/// web::scope("/app")
/// .route("/test1", web::get().to(index))
/// .route("/test2", web::post().to(|| HttpResponse::MethodNotAllowed()))
/// );
/// }
/// let app = App::new().service(
/// web::scope("/app")
/// .route("/test1", web::get().to(index))
/// .route("/test2", web::post().to(|| HttpResponse::MethodNotAllowed()))
/// );
/// ```
pub fn route(self, path: &str, mut route: Route) -> Self {
self.service(
@ -355,21 +349,19 @@ where
/// "Welcome!"
/// }
///
/// fn main() {
/// let app = App::new().service(
/// web::scope("/app")
/// .wrap_fn(|req, srv| {
/// let fut = srv.call(req);
/// async {
/// let mut res = fut.await?;
/// res.headers_mut().insert(
/// CONTENT_TYPE, HeaderValue::from_static("text/plain"),
/// );
/// Ok(res)
/// }
/// })
/// .route("/index.html", web::get().to(index)));
/// }
/// let app = App::new().service(
/// web::scope("/app")
/// .wrap_fn(|req, srv| {
/// let fut = srv.call(req);
/// async {
/// let mut res = fut.await?;
/// res.headers_mut().insert(
/// CONTENT_TYPE, HeaderValue::from_static("text/plain"),
/// );
/// Ok(res)
/// }
/// })
/// .route("/index.html", web::get().to(index)));
/// ```
pub fn wrap_fn<F, R, B1>(
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
/// 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
/// In this example, three routes are set up (and will handle any method):
/// * `/{project_id}/path1`
/// * `/{project_id}/path2`
/// * `/{project_id}/path3`
/// - `/{project_id}/path1`
/// - `/{project_id}/path2`
/// - `/{project_id}/path3`
///
/// ```
/// 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()))
/// );
/// ```
///
/// [pat]: crate::dev::ResourceDef#prefix-resources
pub fn scope(path: &str) -> Scope {
Scope::new(path)
}