From 7535a1ade8c1ee392ae1aa337821cf8f7c7c4c9a Mon Sep 17 00:00:00 2001 From: Jonas Malaco Date: Wed, 23 Jun 2021 12:54:25 -0300 Subject: [PATCH 01/11] Note that Form cannot require data ordering (#2283) --- src/types/form.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/types/form.rs b/src/types/form.rs index 44d1b952e..4ce075d99 100644 --- a/src/types/form.rs +++ b/src/types/form.rs @@ -80,6 +80,10 @@ use crate::{ /// }) /// } /// ``` +/// +/// # Panics +/// URL encoded forms consist of unordered `key=value` pairs, therefore they cannot be decoded into +/// any type which depends upon data ordering (eg. tuples). Trying to do so will result in a panic. #[derive(PartialEq, Eq, PartialOrd, Ord, Debug)] pub struct Form(pub T); From ed0516d724ed225b0b83c01f9278e47bd5f7f104 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 23 Jun 2021 20:47:17 +0100 Subject: [PATCH 02/11] try to fix doc test failures (#2284) --- .cargo/config.toml | 7 ++++--- .github/workflows/ci.yml | 36 ++++++++++++-------------------- .github/workflows/clippy-fmt.yml | 2 +- tests/test_server.rs | 2 +- 4 files changed, 19 insertions(+), 28 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index 0cf09f710..72f445d8a 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -3,6 +3,7 @@ chk = "check --workspace --all-features --tests --examples --bins" lint = "clippy --workspace --tests --examples" ci-min = "hack check --workspace --no-default-features" ci-min-test = "hack check --workspace --no-default-features --tests --examples" -ci-default = "hack check --workspace" -ci-full = "check --workspace --bins --examples --tests" -ci-test = "test --workspace --all-features --no-fail-fast" +ci-default = "check --workspace --bins --tests --examples" +ci-full = "check --workspace --all-features --bins --tests --examples" +ci-test = "test --workspace --all-features --lib --tests --no-fail-fast -- --nocapture" +ci-doctest = "hack test --workspace --all-features --doc --no-fail-fast -- --nocapture" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c57db463a..be595e35c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -66,43 +66,33 @@ jobs: - name: check minimal uses: actions-rs/cargo@v1 - with: - command: hack - args: check --workspace --no-default-features + with: { command: ci-min } - name: check minimal + tests uses: actions-rs/cargo@v1 - with: - command: hack - args: check --workspace --no-default-features --tests --examples + with: { command: ci-min-test } + - name: check default + uses: actions-rs/cargo@v1 + with: { command: ci-default } + - name: check full uses: actions-rs/cargo@v1 - with: - command: check - args: --workspace --bins --examples --tests + with: { command: ci-full } - name: tests - uses: actions-rs/cargo@v1 - with: - command: test - args: --workspace --all-features --no-fail-fast -- --nocapture - --skip=test_h2_content_length - --skip=test_reading_deflate_encoding_large_random_rustls - - - name: tests (actix-http) uses: actions-rs/cargo@v1 timeout-minutes: 40 with: - command: test - args: --package=actix-http --no-default-features --features=rustls -- --nocapture + command: ci-test + args: --skip=test_reading_deflate_encoding_large_random_rustls - - name: tests (awc) + - name: doc tests + # due to unknown issue with running doc tests on macOS + if: matrix.target.os == 'ubuntu-latest' uses: actions-rs/cargo@v1 timeout-minutes: 40 - with: - command: test - args: --package=awc --no-default-features --features=rustls -- --nocapture + with: { command: ci-doctest } - name: Generate coverage file if: > diff --git a/.github/workflows/clippy-fmt.yml b/.github/workflows/clippy-fmt.yml index e966fa4ab..957256d32 100644 --- a/.github/workflows/clippy-fmt.yml +++ b/.github/workflows/clippy-fmt.yml @@ -36,4 +36,4 @@ jobs: uses: actions-rs/clippy-check@v1 with: token: ${{ secrets.GITHUB_TOKEN }} - args: --workspace --tests --all-features + args: --workspace --all-features --tests diff --git a/tests/test_server.rs b/tests/test_server.rs index 9131d1f29..afea39dd9 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -879,7 +879,7 @@ async fn test_brotli_encoding_large_openssl() { assert_eq!(bytes, Bytes::from(data)); } -#[cfg(all(feature = "rustls", feature = "openssl"))] +#[cfg(feature = "rustls")] mod plus_rustls { use std::io::BufReader; From 083ee05d50519897db96fb1873b6eebd5f6df186 Mon Sep 17 00:00:00 2001 From: Ibraheem Ahmed Date: Wed, 23 Jun 2021 16:30:06 -0400 Subject: [PATCH 03/11] `Route::service` (#2262) Co-authored-by: Rob Ede --- CHANGES.md | 4 +- Cargo.toml | 1 - src/extract.rs | 8 ++-- src/lib.rs | 4 +- src/request.rs | 2 +- src/route.rs | 122 +++++++++++++++++++++++++++++++++++++++++++++++-- 6 files changed, 128 insertions(+), 13 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 31fa4690f..876e1c03d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,7 +4,8 @@ ### Added * Add `ServiceRequest::parts_mut`. [#2177] * Add extractors for `Uri` and `Method`. [#2263] -* Add extractor for `ConnectionInfo` and `PeerAddr`. [#2263] +* Add extractors for `ConnectionInfo` and `PeerAddr`. [#2263] +* Add `Route::service` for using hand-written services as handlers. [#2262] ### Changed * Change compression algorithm features flags. [#2250] @@ -13,6 +14,7 @@ [#2177]: https://github.com/actix/actix-web/pull/2177 [#2250]: https://github.com/actix/actix-web/pull/2250 [#2271]: https://github.com/actix/actix-web/pull/2271 +[#2262]: https://github.com/actix/actix-web/pull/2262 [#2263]: https://github.com/actix/actix-web/pull/2263 diff --git a/Cargo.toml b/Cargo.toml index 320751c66..779b52255 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -113,7 +113,6 @@ flate2 = "1.0.13" zstd = "0.7" rand = "0.8" rcgen = "0.8" -serde_derive = "1.0" tls-openssl = { package = "openssl", version = "0.10.9" } tls-rustls = { package = "rustls", version = "0.19.0" } diff --git a/src/extract.rs b/src/extract.rs index d7b67cd90..592f7ab83 100644 --- a/src/extract.rs +++ b/src/extract.rs @@ -54,7 +54,7 @@ pub trait FromRequest: Sized { /// use actix_web::{web, dev, App, Error, HttpRequest, FromRequest}; /// use actix_web::error::ErrorBadRequest; /// use futures_util::future::{ok, err, Ready}; -/// use serde_derive::Deserialize; +/// use serde::Deserialize; /// use rand; /// /// #[derive(Debug, Deserialize)] @@ -145,7 +145,7 @@ where /// use actix_web::{web, dev, App, Result, Error, HttpRequest, FromRequest}; /// use actix_web::error::ErrorBadRequest; /// use futures_util::future::{ok, err, Ready}; -/// use serde_derive::Deserialize; +/// use serde::Deserialize; /// use rand; /// /// #[derive(Debug, Deserialize)] @@ -265,7 +265,7 @@ impl FromRequest for Method { #[doc(hidden)] impl FromRequest for () { type Error = Infallible; - type Future = Ready>; + type Future = Ready>; type Config = (); fn from_request(_: &HttpRequest, _: &mut Payload) -> Self::Future { @@ -376,7 +376,7 @@ mod m { mod tests { use actix_http::http::header; use bytes::Bytes; - use serde_derive::Deserialize; + use serde::Deserialize; use super::*; use crate::test::TestRequest; diff --git a/src/lib.rs b/src/lib.rs index 9d8bf62e7..920abccb6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -149,7 +149,9 @@ pub mod dev { pub use actix_http::{Extensions, Payload, PayloadStream, RequestHead, ResponseHead}; pub use actix_router::{Path, ResourceDef, ResourcePath, Url}; pub use actix_server::Server; - pub use actix_service::{always_ready, forward_ready, Service, Transform}; + pub use actix_service::{ + always_ready, fn_factory, fn_service, forward_ready, Service, Transform, + }; pub(crate) fn insert_slash(mut patterns: Vec) -> Vec { for path in &mut patterns { diff --git a/src/request.rs b/src/request.rs index bff66f08e..5c5c43d26 100644 --- a/src/request.rs +++ b/src/request.rs @@ -347,7 +347,7 @@ impl Drop for HttpRequest { /// # Examples /// ``` /// use actix_web::{web, App, HttpRequest}; -/// use serde_derive::Deserialize; +/// use serde::Deserialize; /// /// /// extract `Thing` from request /// async fn index(req: HttpRequest) -> String { diff --git a/src/route.rs b/src/route.rs index 44f7e30b8..d85b940bd 100644 --- a/src/route.rs +++ b/src/route.rs @@ -5,7 +5,7 @@ use std::{future::Future, rc::Rc}; use actix_http::http::Method; use actix_service::{ boxed::{self, BoxService, BoxServiceFactory}, - Service, ServiceFactory, + Service, ServiceFactory, ServiceFactoryExt, }; use futures_core::future::LocalBoxFuture; @@ -128,9 +128,10 @@ impl Route { /// Set handler function, use request extractors for parameters. /// + /// # Examples /// ``` /// use actix_web::{web, http, App}; - /// use serde_derive::Deserialize; + /// use serde::Deserialize; /// /// #[derive(Deserialize)] /// struct Info { @@ -154,7 +155,7 @@ impl Route { /// /// ``` /// # use std::collections::HashMap; - /// # use serde_derive::Deserialize; + /// # use serde::Deserialize; /// use actix_web::{web, App}; /// /// #[derive(Deserialize)] @@ -184,6 +185,53 @@ impl Route { self.service = boxed::factory(HandlerService::new(handler)); self } + + /// Set raw service to be constructed and called as the request handler. + /// + /// # Examples + /// ``` + /// # use std::convert::Infallible; + /// # use futures_util::future::LocalBoxFuture; + /// # use actix_web::{*, dev::*, http::header}; + /// struct HelloWorld; + /// + /// impl Service for HelloWorld { + /// type Response = ServiceResponse; + /// type Error = Infallible; + /// type Future = LocalBoxFuture<'static, Result>; + /// + /// always_ready!(); + /// + /// fn call(&self, req: ServiceRequest) -> Self::Future { + /// let (req, _) = req.into_parts(); + /// + /// let res = HttpResponse::Ok() + /// .insert_header(header::ContentType::plaintext()) + /// .body("Hello world!"); + /// + /// Box::pin(async move { Ok(ServiceResponse::new(req, res)) }) + /// } + /// } + /// + /// App::new().route( + /// "/", + /// web::get().service(fn_factory(|| async { Ok(HelloWorld) })), + /// ); + /// ``` + pub fn service(mut self, service_factory: S) -> Self + where + S: ServiceFactory< + ServiceRequest, + Response = ServiceResponse, + Error = E, + InitError = (), + Config = (), + > + 'static, + E: Into + 'static, + { + self.service = boxed::factory(service_factory.map_err(Into::into)); + self + } } #[cfg(test)] @@ -192,9 +240,12 @@ mod tests { use actix_rt::time::sleep; use bytes::Bytes; - use serde_derive::Serialize; + use futures_core::future::LocalBoxFuture; + use serde::Serialize; - use crate::http::{Method, StatusCode}; + use crate::dev::{always_ready, fn_factory, fn_service, Service}; + use crate::http::{header, Method, StatusCode}; + use crate::service::{ServiceRequest, ServiceResponse}; use crate::test::{call_service, init_service, read_body, TestRequest}; use crate::{error, web, App, HttpResponse}; @@ -268,4 +319,65 @@ mod tests { let body = read_body(resp).await; assert_eq!(body, Bytes::from_static(b"{\"name\":\"test\"}")); } + + #[actix_rt::test] + async fn test_service_handler() { + struct HelloWorld; + + impl Service for HelloWorld { + type Response = ServiceResponse; + type Error = crate::Error; + type Future = LocalBoxFuture<'static, Result>; + + always_ready!(); + + fn call(&self, req: ServiceRequest) -> Self::Future { + let (req, _) = req.into_parts(); + + let res = HttpResponse::Ok() + .insert_header(header::ContentType::plaintext()) + .body("Hello world!"); + + Box::pin(async move { Ok(ServiceResponse::new(req, res)) }) + } + } + + let srv = init_service( + App::new() + .route( + "/hello", + web::get().service(fn_factory(|| async { Ok(HelloWorld) })), + ) + .route( + "/bye", + web::get().service(fn_factory(|| async { + Ok::<_, ()>(fn_service(|req: ServiceRequest| async { + let (req, _) = req.into_parts(); + + let res = HttpResponse::Ok() + .insert_header(header::ContentType::plaintext()) + .body("Goodbye, and thanks for all the fish!"); + + Ok::<_, Infallible>(ServiceResponse::new(req, res)) + })) + })), + ), + ) + .await; + + let req = TestRequest::get().uri("/hello").to_request(); + let resp = call_service(&srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); + let body = read_body(resp).await; + assert_eq!(body, Bytes::from_static(b"Hello world!")); + + let req = TestRequest::get().uri("/bye").to_request(); + let resp = call_service(&srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); + let body = read_body(resp).await; + assert_eq!( + body, + Bytes::from_static(b"Goodbye, and thanks for all the fish!") + ); + } } From 2d8d2f5ab08c7db6ccfeba5a15a5085165a06ed3 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 24 Jun 2021 15:10:51 +0100 Subject: [PATCH 04/11] app data doc improvements --- src/app.rs | 85 +++++++++++++++++++----------- src/app_service.rs | 9 +++- src/config.rs | 11 ++-- src/data.rs | 5 ++ src/resource.rs | 76 ++++++++++++++------------- src/scope.rs | 128 +++++++++++++++++++++++---------------------- src/service.rs | 3 +- src/web.rs | 1 - 8 files changed, 181 insertions(+), 137 deletions(-) diff --git a/src/app.rs b/src/app.rs index 8c622dd36..677f73805 100644 --- a/src/app.rs +++ b/src/app.rs @@ -68,36 +68,71 @@ where InitError = (), >, { - /// Set application data. Application data could be accessed - /// by using `Data` extractor where `T` is data type. + /// Set application (root level) data. /// - /// **Note**: HTTP server accepts an application factory rather than - /// an application instance. Http server constructs an application - /// instance for each thread, thus application data must be constructed - /// multiple times. If you want to share data between different - /// threads, a shared object should be used, e.g. `Arc`. Internally `Data` type - /// uses `Arc` so data could be created outside of app factory and clones could - /// be stored via `App::app_data()` method. + /// Application data stored with `App::app_data()` method is available through the + /// [`HttpRequest::app_data`](crate::HttpRequest::app_data) method at runtime. + /// + /// # [`Data`] + /// Any [`Data`] type added here can utilize it's extractor implementation in handlers. + /// Types not wrapped in `Data` cannot use this extractor. See [its docs](Data) for more + /// about its usage and patterns. /// /// ``` /// use std::cell::Cell; - /// use actix_web::{web, App, HttpResponse, Responder}; + /// use actix_web::{web, App, HttpRequest, HttpResponse, Responder}; /// /// struct MyData { - /// counter: Cell, + /// count: std::cell::Cell, /// } /// - /// async fn index(data: web::Data) -> impl Responder { - /// data.counter.set(data.counter.get() + 1); - /// HttpResponse::Ok() + /// async fn handler(req: HttpRequest, counter: web::Data) -> impl Responder { + /// // note this cannot use the Data extractor because it was not added with it + /// let incr = *req.app_data::().unwrap(); + /// assert_eq!(incr, 3); + /// + /// // update counter using other value from app data + /// counter.count.set(counter.count.get() + incr); + /// + /// HttpResponse::Ok().body(counter.count.get().to_string()) /// } /// - /// let app = App::new() - /// .data(MyData{ counter: Cell::new(0) }) - /// .service( - /// web::resource("/index.html").route( - /// web::get().to(index))); + /// let app = App::new().service( + /// web::resource("/") + /// .app_data(3usize) + /// .app_data(web::Data::new(MyData { count: Default::default() })) + /// .route(web::get().to(handler)) + /// ); /// ``` + /// + /// # Shared Mutable State + /// [`HttpServer::new`](crate::HttpServer::new) accepts an application factory rather than an + /// application instance; the factory closure is called on each worker thread independently. + /// Therefore, if you want to share a data object between different workers, a shareable object + /// needs to be created first, outside the `HttpServer::new` closure and cloned into it. + /// [`Data`] is an example of such a sharable object. + /// + /// ```ignore + /// let counter = web::Data::new(AppStateWithCounter { + /// counter: Mutex::new(0), + /// }); + /// + /// HttpServer::new(move || { + /// // move counter object into the closure and clone for each worker + /// + /// App::new() + /// .app_data(counter.clone()) + /// .route("/", web::get().to(handler)) + /// }) + /// ``` + pub fn app_data(mut self, ext: U) -> Self { + self.extensions.insert(ext); + self + } + + /// Add application (root) data after wrapping in `Data`. + /// + /// Deprecated in favor of [`app_data`](Self::app_data). #[deprecated(since = "4.0.0", note = "Use `.app_data(Data::new(val))` instead.")] pub fn data(self, data: U) -> Self { self.app_data(Data::new(data)) @@ -138,18 +173,6 @@ where self } - /// Set application level arbitrary data item. - /// - /// Application data stored with `App::app_data()` method is available - /// via `HttpRequest::app_data()` method at runtime. - /// - /// This method could be used for storing `Data` as well, in that case - /// data could be accessed by using `Data` extractor. - pub fn app_data(mut self, ext: U) -> Self { - self.extensions.insert(ext); - self - } - /// Run external configuration as part of the application building /// process /// diff --git a/src/app_service.rs b/src/app_service.rs index a9247b19f..feb9f22a4 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -144,7 +144,9 @@ where } } -/// Service that takes a [`Request`] and delegates to a service that take a [`ServiceRequest`]. +/// The [`Service`] that is passed to `actix-http`'s server builder. +/// +/// Wraps a service receiving a [`ServiceRequest`] into one receiving a [`Request`]. pub struct AppInitService where T: Service, Error = Error>, @@ -275,6 +277,7 @@ impl ServiceFactory for AppRoutingFactory { } } +/// The Actix Web router default entry point. pub struct AppRouting { router: Router, default: HttpService, @@ -299,6 +302,10 @@ impl Service for AppRouting { true }); + // you might expect to find `req.add_data_container()` called here but `HttpRequest` objects + // are created with the root data already set (in `AppInitService::call`) and root data is + // retained when releasing requests back to the pool + if let Some((srv, _info)) = res { srv.call(req) } else { diff --git a/src/config.rs b/src/config.rs index d22bc856e..966141193 100644 --- a/src/config.rs +++ b/src/config.rs @@ -62,6 +62,8 @@ impl AppService { (self.config, self.services) } + /// Clones inner config and default service, returning new `AppService` with empty service list + /// marked as non-root. pub(crate) fn clone_config(&self) -> Self { AppService { config: self.config.clone(), @@ -71,12 +73,12 @@ impl AppService { } } - /// Service configuration + /// Returns reference to configuration. pub fn config(&self) -> &AppConfig { &self.config } - /// Default resource + /// Returns default handler factory. pub fn default_service(&self) -> Rc { self.default.clone() } @@ -116,7 +118,7 @@ impl AppConfig { AppConfig { secure, host, addr } } - /// Needed in actix-test crate. + /// Needed in actix-test crate. Semver exempt. #[doc(hidden)] pub fn __priv_test_new(secure: bool, host: String, addr: SocketAddr) -> Self { AppConfig::new(secure, host, addr) @@ -192,6 +194,7 @@ impl ServiceConfig { /// Add shared app data item. /// /// Counterpart to [`App::data()`](crate::App::data). + #[deprecated(since = "4.0.0", note = "Use `.app_data(Data::new(val))` instead.")] pub fn data(&mut self, data: U) -> &mut Self { self.app_data(Data::new(data)); self @@ -257,6 +260,8 @@ mod tests { use crate::test::{call_service, init_service, read_body, TestRequest}; use crate::{web, App, HttpRequest, HttpResponse}; + // allow deprecated `ServiceConfig::data` + #[allow(deprecated)] #[actix_rt::test] async fn test_data() { let cfg = |cfg: &mut ServiceConfig| { diff --git a/src/data.rs b/src/data.rs index 51db6ce4c..174faba37 100644 --- a/src/data.rs +++ b/src/data.rs @@ -36,6 +36,11 @@ pub(crate) type FnDataFactory = /// If route data is not set for a handler, using `Data` extractor would cause *Internal /// Server Error* response. /// +// TODO: document `dyn T` functionality through converting an Arc +// TODO: note equivalence of req.app_data> and Data extractor +// TODO: note that data must be inserted using Data in order to extract it +/// +/// # Examples /// ``` /// use std::sync::Mutex; /// use actix_web::{web, App, HttpResponse, Responder}; diff --git a/src/resource.rs b/src/resource.rs index 9455895e9..7a4c1248b 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -169,41 +169,38 @@ where self } - /// Provide resource specific data. This method allows to add extractor - /// configuration or specific state available via `Data` extractor. - /// Provided data is available for all routes registered for the current resource. - /// Resource data overrides data registered by `App::data()` method. - /// - /// ``` - /// use actix_web::{web, App, FromRequest}; - /// - /// /// extract text data from request - /// async fn index(body: String) -> String { - /// format!("Body {}!", body) - /// } - /// - /// fn main() { - /// let app = App::new().service( - /// web::resource("/index.html") - /// // limit size of the payload - /// .data(String::configure(|cfg| { - /// cfg.limit(4096) - /// })) - /// .route( - /// web::get() - /// // register handler - /// .to(index) - /// )); - /// } - /// ``` - #[deprecated(since = "4.0.0", note = "Use `.app_data(Data::new(val))` instead.")] - pub fn data(self, data: U) -> Self { - self.app_data(Data::new(data)) - } - /// Add resource data. /// - /// Data of different types from parent contexts will still be accessible. + /// Data of different types from parent contexts will still be accessible. Any `Data` types + /// set here can be extracted in handlers using the `Data` extractor. + /// + /// # Examples + /// ``` + /// use std::cell::Cell; + /// use actix_web::{web, App, HttpRequest, HttpResponse, Responder}; + /// + /// struct MyData { + /// count: std::cell::Cell, + /// } + /// + /// async fn handler(req: HttpRequest, counter: web::Data) -> impl Responder { + /// // note this cannot use the Data extractor because it was not added with it + /// let incr = *req.app_data::().unwrap(); + /// assert_eq!(incr, 3); + /// + /// // update counter using other value from app data + /// counter.count.set(counter.count.get() + incr); + /// + /// HttpResponse::Ok().body(counter.count.get().to_string()) + /// } + /// + /// let app = App::new().service( + /// web::resource("/") + /// .app_data(3usize) + /// .app_data(web::Data::new(MyData { count: Default::default() })) + /// .route(web::get().to(handler)) + /// ); + /// ``` pub fn app_data(mut self, data: U) -> Self { self.app_data .get_or_insert_with(Extensions::new) @@ -212,6 +209,14 @@ where self } + /// Add resource data after wrapping in `Data`. + /// + /// Deprecated in favor of [`app_data`](Self::app_data). + #[deprecated(since = "4.0.0", note = "Use `.app_data(Data::new(val))` instead.")] + pub fn data(self, data: U) -> Self { + self.app_data(Data::new(data)) + } + /// Register a new route and add handler. This route matches all requests. /// /// ``` @@ -227,7 +232,6 @@ where /// This is shortcut for: /// /// ``` - /// # extern crate actix_web; /// # use actix_web::*; /// # fn index(req: HttpRequest) -> HttpResponse { unimplemented!() } /// App::new().service(web::resource("/").route(web::route().to(index))); @@ -695,7 +699,7 @@ mod tests { assert_eq!(resp.status(), StatusCode::NO_CONTENT); } - // allow deprecated App::data + // allow deprecated `{App, Resource}::data` #[allow(deprecated)] #[actix_rt::test] async fn test_data() { @@ -729,7 +733,7 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); } - // allow deprecated App::data + // allow deprecated `{App, Resource}::data` #[allow(deprecated)] #[actix_rt::test] async fn test_data_default_service() { diff --git a/src/scope.rs b/src/scope.rs index 86304074b..0caf06ee3 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -1,28 +1,23 @@ -use std::cell::RefCell; -use std::fmt; -use std::future::Future; -use std::rc::Rc; +use std::{cell::RefCell, fmt, future::Future, mem, rc::Rc}; use actix_http::Extensions; use actix_router::{ResourceDef, Router}; -use actix_service::boxed::{self, BoxService, BoxServiceFactory}; use actix_service::{ - apply, apply_fn_factory, IntoServiceFactory, Service, ServiceFactory, ServiceFactoryExt, - Transform, + apply, apply_fn_factory, + boxed::{self, BoxService, BoxServiceFactory}, + IntoServiceFactory, Service, ServiceFactory, ServiceFactoryExt, Transform, }; use futures_core::future::LocalBoxFuture; use futures_util::future::join_all; -use crate::config::ServiceConfig; -use crate::data::Data; -use crate::dev::{AppService, HttpServiceFactory}; -use crate::error::Error; -use crate::guard::Guard; -use crate::resource::Resource; -use crate::rmap::ResourceMap; -use crate::route::Route; -use crate::service::{ - AppServiceFactory, ServiceFactoryWrapper, ServiceRequest, ServiceResponse, +use crate::{ + config::ServiceConfig, + data::Data, + dev::{AppService, HttpServiceFactory}, + guard::Guard, + rmap::ResourceMap, + service::{AppServiceFactory, ServiceFactoryWrapper, ServiceRequest, ServiceResponse}, + Error, Resource, Route, }; type Guards = Vec>; @@ -71,16 +66,17 @@ pub struct Scope { impl Scope { /// Create a new scope pub fn new(path: &str) -> Scope { - let fref = Rc::new(RefCell::new(None)); + let factory_ref = Rc::new(RefCell::new(None)); + Scope { - endpoint: ScopeEndpoint::new(fref.clone()), + endpoint: ScopeEndpoint::new(Rc::clone(&factory_ref)), rdef: path.to_string(), app_data: None, guards: Vec::new(), services: Vec::new(), default: None, external: Vec::new(), - factory_ref: fref, + factory_ref, } } } @@ -120,40 +116,38 @@ where self } - /// Set or override application data. Application data could be accessed - /// by using `Data` extractor where `T` is data type. - /// - /// ``` - /// use std::cell::Cell; - /// use actix_web::{web, App, HttpResponse, Responder}; - /// - /// struct MyData { - /// counter: Cell, - /// } - /// - /// async fn index(data: web::Data) -> impl Responder { - /// data.counter.set(data.counter.get() + 1); - /// HttpResponse::Ok() - /// } - /// - /// fn main() { - /// let app = App::new().service( - /// web::scope("/app") - /// .data(MyData{ counter: Cell::new(0) }) - /// .service( - /// web::resource("/index.html").route( - /// web::get().to(index))) - /// ); - /// } - /// ``` - #[deprecated(since = "4.0.0", note = "Use `.app_data(Data::new(val))` instead.")] - pub fn data(self, data: U) -> Self { - self.app_data(Data::new(data)) - } - /// Add scope data. /// - /// Data of different types from parent contexts will still be accessible. + /// Data of different types from parent contexts will still be accessible. Any `Data` types + /// set here can be extracted in handlers using the `Data` extractor. + /// + /// # Examples + /// ``` + /// use std::cell::Cell; + /// use actix_web::{web, App, HttpRequest, HttpResponse, Responder}; + /// + /// struct MyData { + /// count: std::cell::Cell, + /// } + /// + /// async fn handler(req: HttpRequest, counter: web::Data) -> impl Responder { + /// // note this cannot use the Data extractor because it was not added with it + /// let incr = *req.app_data::().unwrap(); + /// assert_eq!(incr, 3); + /// + /// // update counter using other value from app data + /// counter.count.set(counter.count.get() + incr); + /// + /// HttpResponse::Ok().body(counter.count.get().to_string()) + /// } + /// + /// let app = App::new().service( + /// web::scope("/app") + /// .app_data(3usize) + /// .app_data(web::Data::new(MyData { count: Default::default() })) + /// .route("/", web::get().to(handler)) + /// ); + /// ``` pub fn app_data(mut self, data: U) -> Self { self.app_data .get_or_insert_with(Extensions::new) @@ -162,15 +156,20 @@ where self } - /// Run external configuration as part of the scope building - /// process + /// Add scope data after wrapping in `Data`. /// - /// This function is useful for moving parts of configuration to a - /// different module or even library. For example, - /// some of the resource's configuration could be moved to different module. + /// Deprecated in favor of [`app_data`](Self::app_data). + #[deprecated(since = "4.0.0", note = "Use `.app_data(Data::new(val))` instead.")] + pub fn data(self, data: U) -> Self { + self.app_data(Data::new(data)) + } + + /// Run external configuration as part of the scope building process. + /// + /// This function is useful for moving parts of configuration to a different module or library. + /// For example, some of the resource's configuration could be moved to different module. /// /// ``` - /// # extern crate actix_web; /// use actix_web::{web, middleware, App, HttpResponse}; /// /// // this function could be located in different module @@ -191,18 +190,21 @@ where /// .route("/index.html", web::get().to(|| HttpResponse::Ok())); /// } /// ``` - pub fn configure(mut self, f: F) -> Self + pub fn configure(mut self, cfg_fn: F) -> Self where F: FnOnce(&mut ServiceConfig), { let mut cfg = ServiceConfig::new(); - f(&mut cfg); + cfg_fn(&mut cfg); + self.services.extend(cfg.services); self.external.extend(cfg.external); + // TODO: add Extensions::is_empty check and conditionally insert data self.app_data .get_or_insert_with(Extensions::new) .extend(cfg.app_data); + self } @@ -419,7 +421,7 @@ where let mut rmap = ResourceMap::new(ResourceDef::root_prefix(&self.rdef)); // external resources - for mut rdef in std::mem::take(&mut self.external) { + for mut rdef in mem::take(&mut self.external) { rmap.add(&mut rdef, None); } @@ -991,7 +993,7 @@ mod tests { ); } - // allow deprecated App::data + // allow deprecated {App, Scope}::data #[allow(deprecated)] #[actix_rt::test] async fn test_override_data() { @@ -1011,7 +1013,7 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); } - // allow deprecated App::data + // allow deprecated `{App, Scope}::data` #[allow(deprecated)] #[actix_rt::test] async fn test_override_data_default_service() { diff --git a/src/service.rs b/src/service.rs index 8d73a87fa..592577467 100644 --- a/src/service.rs +++ b/src/service.rs @@ -17,9 +17,8 @@ use crate::{ dev::insert_slash, guard::Guard, info::ConnectionInfo, - request::HttpRequest, rmap::ResourceMap, - Error, HttpResponse, + Error, HttpRequest, HttpResponse, }; pub trait HttpServiceFactory { diff --git a/src/web.rs b/src/web.rs index 8662848a4..40ac46275 100644 --- a/src/web.rs +++ b/src/web.rs @@ -43,7 +43,6 @@ pub use crate::types::*; /// the exposed `Params` object: /// /// ``` -/// # extern crate actix_web; /// use actix_web::{web, App, HttpResponse}; /// /// let app = App::new().service( From 93aa86e30bd2bc664aac80d7f6e02769f9a64750 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 24 Jun 2021 15:11:01 +0100 Subject: [PATCH 05/11] clippy --- awc/src/request.rs | 7 ++++++- tests/test_error_propagation.rs | 29 ++++++++++++++--------------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/awc/src/request.rs b/awc/src/request.rs index 46dae7fa3..3f312f6e7 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -480,6 +480,7 @@ impl ClientRequest { // supported, so we cannot guess Accept-Encoding HTTP header. if slf.response_decompress { // Set Accept-Encoding with compression algorithm awc is built with. + #[allow(clippy::vec_init_then_push)] #[cfg(feature = "__compress")] let accept_encoding = { let mut encoding = vec![]; @@ -496,7 +497,11 @@ impl ClientRequest { #[cfg(feature = "compress-zstd")] encoding.push("zstd"); - assert!(!encoding.is_empty(), "encoding cannot be empty unless __compress feature has been explictily enabled."); + assert!( + !encoding.is_empty(), + "encoding can not be empty unless __compress feature has been explicitly enabled" + ); + encoding.join(", ") }; diff --git a/tests/test_error_propagation.rs b/tests/test_error_propagation.rs index 1b56615a0..3e7320920 100644 --- a/tests/test_error_propagation.rs +++ b/tests/test_error_propagation.rs @@ -1,12 +1,14 @@ -use actix_utils::future::{ok, Ready}; -use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform}; -use actix_web::test::{call_service, init_service, TestRequest}; -use actix_web::{HttpResponse, ResponseError}; -use futures_util::lock::Mutex; -use std::future::Future; -use std::pin::Pin; use std::sync::Arc; -use std::task::{Context, Poll}; + +use actix_utils::future::{ok, Ready}; +use actix_web::{ + dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform}, + get, + test::{call_service, init_service, TestRequest}, + ResponseError, +}; +use futures_core::future::LocalBoxFuture; +use futures_util::lock::Mutex; #[derive(Debug, Clone)] pub struct MyError; @@ -19,10 +21,9 @@ impl std::fmt::Display for MyError { } } -#[actix_web::get("/test")] +#[get("/test")] async fn test() -> Result { - Err(MyError)?; - Ok(HttpResponse::NoContent().finish()) + return Err(MyError.into()); } #[derive(Clone)] @@ -62,11 +63,9 @@ where { type Response = ServiceResponse; type Error = actix_web::Error; - type Future = Pin>>>; + type Future = LocalBoxFuture<'static, Result>; - fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { - self.service.poll_ready(cx) - } + forward_ready!(service); fn call(&self, req: ServiceRequest) -> Self::Future { let lock = self.was_error.clone(); From e559a197cc909a6ec9f60900645dfa26a195ca03 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 24 Jun 2021 15:30:11 +0100 Subject: [PATCH 06/11] remove comment --- src/app_service.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/app_service.rs b/src/app_service.rs index feb9f22a4..0e590e2b7 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -302,10 +302,6 @@ impl Service for AppRouting { true }); - // you might expect to find `req.add_data_container()` called here but `HttpRequest` objects - // are created with the root data already set (in `AppInitService::call`) and root data is - // retained when releasing requests back to the pool - if let Some((srv, _info)) = res { srv.call(req) } else { From 767e4efe224fe52e66fcb4378a25076ec8beeb9a Mon Sep 17 00:00:00 2001 From: Ibraheem Ahmed Date: Fri, 25 Jun 2021 05:53:53 -0400 Subject: [PATCH 07/11] Remove downcast macro from actix-http (#2291) --- actix-http/CHANGES.md | 4 ++ actix-http/src/lib.rs | 3 -- actix-http/src/macros.rs | 110 --------------------------------------- 3 files changed, 4 insertions(+), 113 deletions(-) delete mode 100644 actix-http/src/macros.rs diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index c8d65e393..435607463 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -4,6 +4,10 @@ ### Changed * Change compression algorithm features flags. [#2250] +### Removed +* `downcast` and `downcast_get_type_id` macros. [#2291] + +[#2291]: https://github.com/actix/actix-web/pull/2291 [#2250]: https://github.com/actix/actix-web/pull/2250 diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index 924d5441f..d22e1ee44 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -27,9 +27,6 @@ #[macro_use] extern crate log; -#[macro_use] -mod macros; - pub mod body; mod builder; pub mod client; diff --git a/actix-http/src/macros.rs b/actix-http/src/macros.rs deleted file mode 100644 index be8e63d6e..000000000 --- a/actix-http/src/macros.rs +++ /dev/null @@ -1,110 +0,0 @@ -#[macro_export] -#[doc(hidden)] -macro_rules! downcast_get_type_id { - () => { - /// A helper method to get the type ID of the type - /// this trait is implemented on. - /// This method is unsafe to *implement*, since `downcast_ref` relies - /// on the returned `TypeId` to perform a cast. - /// - /// Unfortunately, Rust has no notion of a trait method that is - /// unsafe to implement (marking it as `unsafe` makes it unsafe - /// to *call*). As a workaround, we require this method - /// to return a private type along with the `TypeId`. This - /// private type (`PrivateHelper`) has a private constructor, - /// making it impossible for safe code to construct outside of - /// this module. This ensures that safe code cannot violate - /// type-safety by implementing this method. - /// - /// We also take `PrivateHelper` as a parameter, to ensure that - /// safe code cannot obtain a `PrivateHelper` instance by - /// delegating to an existing implementation of `__private_get_type_id__` - #[doc(hidden)] - fn __private_get_type_id__( - &self, - _: PrivateHelper, - ) -> (std::any::TypeId, PrivateHelper) - where - Self: 'static, - { - (std::any::TypeId::of::(), PrivateHelper(())) - } - }; -} - -//Generate implementation for dyn $name -#[doc(hidden)] -#[macro_export] -macro_rules! downcast { - ($name:ident) => { - /// A struct with a private constructor, for use with - /// `__private_get_type_id__`. Its single field is private, - /// ensuring that it can only be constructed from this module - #[doc(hidden)] - pub struct PrivateHelper(()); - - impl dyn $name + 'static { - /// Downcasts generic body to a specific type. - pub fn downcast_ref(&self) -> Option<&T> { - if self.__private_get_type_id__(PrivateHelper(())).0 - == std::any::TypeId::of::() - { - // SAFETY: external crates cannot override the default - // implementation of `__private_get_type_id__`, since - // it requires returning a private type. We can therefore - // rely on the returned `TypeId`, which ensures that this - // case is correct. - unsafe { Some(&*(self as *const dyn $name as *const T)) } - } else { - None - } - } - - /// Downcasts a generic body to a mutable specific type. - pub fn downcast_mut(&mut self) -> Option<&mut T> { - if self.__private_get_type_id__(PrivateHelper(())).0 - == std::any::TypeId::of::() - { - // SAFETY: external crates cannot override the default - // implementation of `__private_get_type_id__`, since - // it requires returning a private type. We can therefore - // rely on the returned `TypeId`, which ensures that this - // case is correct. - unsafe { - Some(&mut *(self as *const dyn $name as *const T as *mut T)) - } - } else { - None - } - } - } - }; -} - -#[cfg(test)] -mod tests { - #![allow(clippy::upper_case_acronyms)] - - trait MB { - downcast_get_type_id!(); - } - - downcast!(MB); - - impl MB for String {} - impl MB for () {} - - #[actix_rt::test] - async fn test_any_casting() { - let mut body = String::from("hello cast"); - let resp_body: &mut dyn MB = &mut body; - let body = resp_body.downcast_ref::().unwrap(); - assert_eq!(body, "hello cast"); - let body = &mut resp_body.downcast_mut::().unwrap(); - body.push('!'); - let body = resp_body.downcast_ref::().unwrap(); - assert_eq!(body, "hello cast!"); - let not_body = resp_body.downcast_ref::<()>(); - assert!(not_body.is_none()); - } -} From 2eacb735a4f2f284eeaf80244a119ca89621234d Mon Sep 17 00:00:00 2001 From: Ibraheem Ahmed Date: Fri, 25 Jun 2021 07:25:50 -0400 Subject: [PATCH 08/11] Don't leak internal macros (#2290) --- src/error/macros.rs | 16 ++++------ src/error/mod.rs | 1 + src/error/response_error.rs | 6 ++-- src/http/header/accept.rs | 10 +++--- src/http/header/accept_charset.rs | 4 +-- src/http/header/accept_encoding.rs | 10 +++--- src/http/header/accept_language.rs | 6 ++-- src/http/header/allow.rs | 8 ++--- src/http/header/cache_control.rs | 4 +-- src/http/header/content_language.rs | 6 ++-- src/http/header/content_range.rs | 24 +++++++------- src/http/header/content_type.rs | 4 +-- src/http/header/date.rs | 4 +-- src/http/header/etag.rs | 32 +++++++++---------- src/http/header/expires.rs | 4 +-- src/http/header/if_match.rs | 8 ++--- src/http/header/if_modified_since.rs | 4 +-- src/http/header/if_none_match.rs | 12 +++---- src/http/header/if_range.rs | 6 ++-- src/http/header/if_unmodified_since.rs | 4 +-- src/http/header/last_modified.rs | 4 +-- src/http/header/macros.rs | 44 ++++++++++++-------------- src/http/header/mod.rs | 4 +++ 23 files changed, 113 insertions(+), 112 deletions(-) diff --git a/src/error/macros.rs b/src/error/macros.rs index aeab74308..38650c5e8 100644 --- a/src/error/macros.rs +++ b/src/error/macros.rs @@ -1,6 +1,4 @@ -#[macro_export] -#[doc(hidden)] -macro_rules! __downcast_get_type_id { +macro_rules! downcast_get_type_id { () => { /// A helper method to get the type ID of the type /// this trait is implemented on. @@ -30,10 +28,8 @@ macro_rules! __downcast_get_type_id { }; } -//Generate implementation for dyn $name -#[doc(hidden)] -#[macro_export] -macro_rules! __downcast_dyn { +// Generate implementation for dyn $name +macro_rules! downcast_dyn { ($name:ident) => { /// A struct with a private constructor, for use with /// `__private_get_type_id__`. Its single field is private, @@ -80,15 +76,17 @@ macro_rules! __downcast_dyn { }; } +pub(crate) use {downcast_dyn, downcast_get_type_id}; + #[cfg(test)] mod tests { #![allow(clippy::upper_case_acronyms)] trait MB { - __downcast_get_type_id!(); + downcast_get_type_id!(); } - __downcast_dyn!(MB); + downcast_dyn!(MB); impl MB for String {} impl MB for () {} diff --git a/src/error/mod.rs b/src/error/mod.rs index 637d6ff16..3ccd5bba6 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -18,6 +18,7 @@ mod response_error; pub use self::error::Error; pub use self::internal::*; pub use self::response_error::ResponseError; +pub(crate) use macros::{downcast_dyn, downcast_get_type_id}; /// A convenience [`Result`](std::result::Result) for Actix Web operations. /// diff --git a/src/error/response_error.rs b/src/error/response_error.rs index c58fff8be..41cf20eba 100644 --- a/src/error/response_error.rs +++ b/src/error/response_error.rs @@ -9,7 +9,7 @@ use std::{ use actix_http::{body::AnyBody, header, Response, StatusCode}; use bytes::BytesMut; -use crate::{__downcast_dyn, __downcast_get_type_id}; +use crate::error::{downcast_dyn, downcast_get_type_id}; use crate::{helpers, HttpResponse}; /// Errors that can generate responses. @@ -41,10 +41,10 @@ pub trait ResponseError: fmt::Debug + fmt::Display { res.set_body(AnyBody::from(buf)) } - __downcast_get_type_id!(); + downcast_get_type_id!(); } -__downcast_dyn!(ResponseError); +downcast_dyn!(ResponseError); impl ResponseError for Box {} diff --git a/src/http/header/accept.rs b/src/http/header/accept.rs index 1b6a963da..75366dfae 100644 --- a/src/http/header/accept.rs +++ b/src/http/header/accept.rs @@ -5,7 +5,7 @@ use mime::Mime; use super::{qitem, QualityItem}; use crate::http::header; -crate::__define_common_header! { +crate::http::header::common_header! { /// `Accept` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.2) /// /// The `Accept` header field can be used by user agents to specify @@ -81,14 +81,14 @@ crate::__define_common_header! { test_accept { // Tests from the RFC - crate::__common_header_test!( + crate::http::header::common_header_test!( test1, vec![b"audio/*; q=0.2, audio/basic"], Some(Accept(vec![ QualityItem::new("audio/*".parse().unwrap(), q(200)), qitem("audio/basic".parse().unwrap()), ]))); - crate::__common_header_test!( + crate::http::header::common_header_test!( test2, vec![b"text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c"], Some(Accept(vec![ @@ -100,13 +100,13 @@ crate::__define_common_header! { qitem("text/x-c".parse().unwrap()), ]))); // Custom tests - crate::__common_header_test!( + crate::http::header::common_header_test!( test3, vec![b"text/plain; charset=utf-8"], Some(Accept(vec![ qitem(mime::TEXT_PLAIN_UTF_8), ]))); - crate::__common_header_test!( + crate::http::header::common_header_test!( test4, vec![b"text/plain; charset=utf-8; q=0.5"], Some(Accept(vec![ diff --git a/src/http/header/accept_charset.rs b/src/http/header/accept_charset.rs index 2c6a0b9f6..bb7d86516 100644 --- a/src/http/header/accept_charset.rs +++ b/src/http/header/accept_charset.rs @@ -1,6 +1,6 @@ use super::{Charset, QualityItem, ACCEPT_CHARSET}; -crate::__define_common_header! { +crate::http::header::common_header! { /// `Accept-Charset` header, defined in /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.3) /// @@ -57,6 +57,6 @@ crate::__define_common_header! { test_accept_charset { // Test case from RFC - crate::__common_header_test!(test1, vec![b"iso-8859-5, unicode-1-1;q=0.8"]); + crate::http::header::common_header_test!(test1, vec![b"iso-8859-5, unicode-1-1;q=0.8"]); } } diff --git a/src/http/header/accept_encoding.rs b/src/http/header/accept_encoding.rs index 734a435b3..cfd29bf77 100644 --- a/src/http/header/accept_encoding.rs +++ b/src/http/header/accept_encoding.rs @@ -64,12 +64,12 @@ header! { test_accept_encoding { // From the RFC - crate::__common_header_test!(test1, vec![b"compress, gzip"]); - crate::__common_header_test!(test2, vec![b""], Some(AcceptEncoding(vec![]))); - crate::__common_header_test!(test3, vec![b"*"]); + crate::http::header::common_header_test!(test1, vec![b"compress, gzip"]); + crate::http::header::common_header_test!(test2, vec![b""], Some(AcceptEncoding(vec![]))); + crate::http::header::common_header_test!(test3, vec![b"*"]); // Note: Removed quality 1 from gzip - crate::__common_header_test!(test4, vec![b"compress;q=0.5, gzip"]); + crate::http::header::common_header_test!(test4, vec![b"compress;q=0.5, gzip"]); // Note: Removed quality 1 from gzip - crate::__common_header_test!(test5, vec![b"gzip, identity; q=0.5, *;q=0"]); + crate::http::header::common_header_test!(test5, vec![b"gzip, identity; q=0.5, *;q=0"]); } } diff --git a/src/http/header/accept_language.rs b/src/http/header/accept_language.rs index 034946d4d..1552f6578 100644 --- a/src/http/header/accept_language.rs +++ b/src/http/header/accept_language.rs @@ -2,7 +2,7 @@ use language_tags::LanguageTag; use super::{QualityItem, ACCEPT_LANGUAGE}; -crate::__define_common_header! { +crate::http::header::common_header! { /// `Accept-Language` header, defined in /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.5) /// @@ -53,9 +53,9 @@ crate::__define_common_header! { test_accept_language { // From the RFC - crate::__common_header_test!(test1, vec![b"da, en-gb;q=0.8, en;q=0.7"]); + crate::http::header::common_header_test!(test1, vec![b"da, en-gb;q=0.8, en;q=0.7"]); // Own test - crate::__common_header_test!( + crate::http::header::common_header_test!( test2, vec![b"en-US, en; q=0.5, fr"], Some(AcceptLanguage(vec![ qitem("en-US".parse().unwrap()), diff --git a/src/http/header/allow.rs b/src/http/header/allow.rs index 15a627b8f..946f70e0a 100644 --- a/src/http/header/allow.rs +++ b/src/http/header/allow.rs @@ -1,7 +1,7 @@ use crate::http::header; use actix_http::http::Method; -crate::__define_common_header! { +crate::http::header::common_header! { /// `Allow` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.4.1) /// /// The `Allow` header field lists the set of methods advertised as @@ -49,12 +49,12 @@ crate::__define_common_header! { test_allow { // From the RFC - crate::__common_header_test!( + crate::http::header::common_header_test!( test1, vec![b"GET, HEAD, PUT"], Some(HeaderField(vec![Method::GET, Method::HEAD, Method::PUT]))); // Own tests - crate::__common_header_test!( + crate::http::header::common_header_test!( test2, vec![b"OPTIONS, GET, PUT, POST, DELETE, HEAD, TRACE, CONNECT, PATCH"], Some(HeaderField(vec![ @@ -67,7 +67,7 @@ crate::__define_common_header! { Method::TRACE, Method::CONNECT, Method::PATCH]))); - crate::__common_header_test!( + crate::http::header::common_header_test!( test3, vec![b""], Some(HeaderField(Vec::::new()))); diff --git a/src/http/header/cache_control.rs b/src/http/header/cache_control.rs index 620c576ae..05903e3a3 100644 --- a/src/http/header/cache_control.rs +++ b/src/http/header/cache_control.rs @@ -49,9 +49,9 @@ use crate::http::header; #[derive(PartialEq, Clone, Debug)] pub struct CacheControl(pub Vec); -crate::__common_header_deref!(CacheControl => Vec); +crate::http::header::common_header_deref!(CacheControl => Vec); -// TODO: this could just be the __define_common_header! macro +// TODO: this could just be the crate::http::header::common_header! macro impl Header for CacheControl { fn name() -> header::HeaderName { header::CACHE_CONTROL diff --git a/src/http/header/content_language.rs b/src/http/header/content_language.rs index c2469edd1..604ada83c 100644 --- a/src/http/header/content_language.rs +++ b/src/http/header/content_language.rs @@ -1,7 +1,7 @@ use super::{QualityItem, CONTENT_LANGUAGE}; use language_tags::LanguageTag; -crate::__define_common_header! { +crate::http::header::common_header! { /// `Content-Language` header, defined in /// [RFC7231](https://tools.ietf.org/html/rfc7231#section-3.1.3.2) /// @@ -50,7 +50,7 @@ crate::__define_common_header! { (ContentLanguage, CONTENT_LANGUAGE) => (QualityItem)+ test_content_language { - crate::__common_header_test!(test1, vec![b"da"]); - crate::__common_header_test!(test2, vec![b"mi, en"]); + crate::http::header::common_header_test!(test1, vec![b"da"]); + crate::http::header::common_header_test!(test2, vec![b"mi, en"]); } } diff --git a/src/http/header/content_range.rs b/src/http/header/content_range.rs index ba0d51742..3bdead2c0 100644 --- a/src/http/header/content_range.rs +++ b/src/http/header/content_range.rs @@ -4,65 +4,65 @@ use std::str::FromStr; use super::{HeaderValue, IntoHeaderValue, InvalidHeaderValue, Writer, CONTENT_RANGE}; use crate::error::ParseError; -crate::__define_common_header! { +crate::http::header::common_header! { /// `Content-Range` header, defined in /// [RFC7233](http://tools.ietf.org/html/rfc7233#section-4.2) (ContentRange, CONTENT_RANGE) => [ContentRangeSpec] test_content_range { - crate::__common_header_test!(test_bytes, + crate::http::header::common_header_test!(test_bytes, vec![b"bytes 0-499/500"], Some(ContentRange(ContentRangeSpec::Bytes { range: Some((0, 499)), instance_length: Some(500) }))); - crate::__common_header_test!(test_bytes_unknown_len, + crate::http::header::common_header_test!(test_bytes_unknown_len, vec![b"bytes 0-499/*"], Some(ContentRange(ContentRangeSpec::Bytes { range: Some((0, 499)), instance_length: None }))); - crate::__common_header_test!(test_bytes_unknown_range, + crate::http::header::common_header_test!(test_bytes_unknown_range, vec![b"bytes */500"], Some(ContentRange(ContentRangeSpec::Bytes { range: None, instance_length: Some(500) }))); - crate::__common_header_test!(test_unregistered, + crate::http::header::common_header_test!(test_unregistered, vec![b"seconds 1-2"], Some(ContentRange(ContentRangeSpec::Unregistered { unit: "seconds".to_owned(), resp: "1-2".to_owned() }))); - crate::__common_header_test!(test_no_len, + crate::http::header::common_header_test!(test_no_len, vec![b"bytes 0-499"], None::); - crate::__common_header_test!(test_only_unit, + crate::http::header::common_header_test!(test_only_unit, vec![b"bytes"], None::); - crate::__common_header_test!(test_end_less_than_start, + crate::http::header::common_header_test!(test_end_less_than_start, vec![b"bytes 499-0/500"], None::); - crate::__common_header_test!(test_blank, + crate::http::header::common_header_test!(test_blank, vec![b""], None::); - crate::__common_header_test!(test_bytes_many_spaces, + crate::http::header::common_header_test!(test_bytes_many_spaces, vec![b"bytes 1-2/500 3"], None::); - crate::__common_header_test!(test_bytes_many_slashes, + crate::http::header::common_header_test!(test_bytes_many_slashes, vec![b"bytes 1-2/500/600"], None::); - crate::__common_header_test!(test_bytes_many_dashes, + crate::http::header::common_header_test!(test_bytes_many_dashes, vec![b"bytes 1-2-3/500"], None::); diff --git a/src/http/header/content_type.rs b/src/http/header/content_type.rs index 65cb2a986..e1c419c22 100644 --- a/src/http/header/content_type.rs +++ b/src/http/header/content_type.rs @@ -1,7 +1,7 @@ use super::CONTENT_TYPE; use mime::Mime; -crate::__define_common_header! { +crate::http::header::common_header! { /// `Content-Type` header, defined in /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-3.1.1.5) /// @@ -52,7 +52,7 @@ crate::__define_common_header! { (ContentType, CONTENT_TYPE) => [Mime] test_content_type { - crate::__common_header_test!( + crate::http::header::common_header_test!( test1, vec![b"text/html"], Some(HeaderField(mime::TEXT_HTML))); diff --git a/src/http/header/date.rs b/src/http/header/date.rs index 982a1455c..4d1717886 100644 --- a/src/http/header/date.rs +++ b/src/http/header/date.rs @@ -1,7 +1,7 @@ use super::{HttpDate, DATE}; use std::time::SystemTime; -crate::__define_common_header! { +crate::http::header::common_header! { /// `Date` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.1.1.2) /// /// The `Date` header field represents the date and time at which the @@ -32,7 +32,7 @@ crate::__define_common_header! { (Date, DATE) => [HttpDate] test_date { - crate::__common_header_test!(test1, vec![b"Tue, 15 Nov 1994 08:12:31 GMT"]); + crate::http::header::common_header_test!(test1, vec![b"Tue, 15 Nov 1994 08:12:31 GMT"]); } } diff --git a/src/http/header/etag.rs b/src/http/header/etag.rs index b121fe26f..aded72665 100644 --- a/src/http/header/etag.rs +++ b/src/http/header/etag.rs @@ -1,6 +1,6 @@ use super::{EntityTag, ETAG}; -crate::__define_common_header! { +crate::http::header::common_header! { /// `ETag` header, defined in [RFC7232](http://tools.ietf.org/html/rfc7232#section-2.3) /// /// The `ETag` header field in a response provides the current entity-tag @@ -50,50 +50,50 @@ crate::__define_common_header! { test_etag { // From the RFC - crate::__common_header_test!(test1, + crate::http::header::common_header_test!(test1, vec![b"\"xyzzy\""], Some(ETag(EntityTag::new(false, "xyzzy".to_owned())))); - crate::__common_header_test!(test2, + crate::http::header::common_header_test!(test2, vec![b"W/\"xyzzy\""], Some(ETag(EntityTag::new(true, "xyzzy".to_owned())))); - crate::__common_header_test!(test3, + crate::http::header::common_header_test!(test3, vec![b"\"\""], Some(ETag(EntityTag::new(false, "".to_owned())))); // Own tests - crate::__common_header_test!(test4, + crate::http::header::common_header_test!(test4, vec![b"\"foobar\""], Some(ETag(EntityTag::new(false, "foobar".to_owned())))); - crate::__common_header_test!(test5, + crate::http::header::common_header_test!(test5, vec![b"\"\""], Some(ETag(EntityTag::new(false, "".to_owned())))); - crate::__common_header_test!(test6, + crate::http::header::common_header_test!(test6, vec![b"W/\"weak-etag\""], Some(ETag(EntityTag::new(true, "weak-etag".to_owned())))); - crate::__common_header_test!(test7, + crate::http::header::common_header_test!(test7, vec![b"W/\"\x65\x62\""], Some(ETag(EntityTag::new(true, "\u{0065}\u{0062}".to_owned())))); - crate::__common_header_test!(test8, + crate::http::header::common_header_test!(test8, vec![b"W/\"\""], Some(ETag(EntityTag::new(true, "".to_owned())))); - crate::__common_header_test!(test9, + crate::http::header::common_header_test!(test9, vec![b"no-dquotes"], None::); - crate::__common_header_test!(test10, + crate::http::header::common_header_test!(test10, vec![b"w/\"the-first-w-is-case-sensitive\""], None::); - crate::__common_header_test!(test11, + crate::http::header::common_header_test!(test11, vec![b""], None::); - crate::__common_header_test!(test12, + crate::http::header::common_header_test!(test12, vec![b"\"unmatched-dquotes1"], None::); - crate::__common_header_test!(test13, + crate::http::header::common_header_test!(test13, vec![b"unmatched-dquotes2\""], None::); - crate::__common_header_test!(test14, + crate::http::header::common_header_test!(test14, vec![b"matched-\"dquotes\""], None::); - crate::__common_header_test!(test15, + crate::http::header::common_header_test!(test15, vec![b"\""], None::); } diff --git a/src/http/header/expires.rs b/src/http/header/expires.rs index 759e7d280..e810fe267 100644 --- a/src/http/header/expires.rs +++ b/src/http/header/expires.rs @@ -1,6 +1,6 @@ use super::{HttpDate, EXPIRES}; -crate::__define_common_header! { +crate::http::header::common_header! { /// `Expires` header, defined in [RFC7234](http://tools.ietf.org/html/rfc7234#section-5.3) /// /// The `Expires` header field gives the date/time after which the @@ -36,6 +36,6 @@ crate::__define_common_header! { test_expires { // Test case from RFC - crate::__common_header_test!(test1, vec![b"Thu, 01 Dec 1994 16:00:00 GMT"]); + crate::http::header::common_header_test!(test1, vec![b"Thu, 01 Dec 1994 16:00:00 GMT"]); } } diff --git a/src/http/header/if_match.rs b/src/http/header/if_match.rs index d4402715d..87a94a809 100644 --- a/src/http/header/if_match.rs +++ b/src/http/header/if_match.rs @@ -1,6 +1,6 @@ use super::{EntityTag, IF_MATCH}; -crate::__define_common_header! { +crate::http::header::common_header! { /// `If-Match` header, defined in /// [RFC7232](https://tools.ietf.org/html/rfc7232#section-3.1) /// @@ -53,18 +53,18 @@ crate::__define_common_header! { (IfMatch, IF_MATCH) => {Any / (EntityTag)+} test_if_match { - crate::__common_header_test!( + crate::http::header::common_header_test!( test1, vec![b"\"xyzzy\""], Some(HeaderField::Items( vec![EntityTag::new(false, "xyzzy".to_owned())]))); - crate::__common_header_test!( + crate::http::header::common_header_test!( test2, vec![b"\"xyzzy\", \"r2d2xxxx\", \"c3piozzzz\""], Some(HeaderField::Items( vec![EntityTag::new(false, "xyzzy".to_owned()), EntityTag::new(false, "r2d2xxxx".to_owned()), EntityTag::new(false, "c3piozzzz".to_owned())]))); - crate::__common_header_test!(test3, vec![b"*"], Some(IfMatch::Any)); + crate::http::header::common_header_test!(test3, vec![b"*"], Some(IfMatch::Any)); } } diff --git a/src/http/header/if_modified_since.rs b/src/http/header/if_modified_since.rs index ba393032d..254003523 100644 --- a/src/http/header/if_modified_since.rs +++ b/src/http/header/if_modified_since.rs @@ -1,6 +1,6 @@ use super::{HttpDate, IF_MODIFIED_SINCE}; -crate::__define_common_header! { +crate::http::header::common_header! { /// `If-Modified-Since` header, defined in /// [RFC7232](http://tools.ietf.org/html/rfc7232#section-3.3) /// @@ -36,6 +36,6 @@ crate::__define_common_header! { test_if_modified_since { // Test case from RFC - crate::__common_header_test!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); + crate::http::header::common_header_test!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); } } diff --git a/src/http/header/if_none_match.rs b/src/http/header/if_none_match.rs index f16b196cc..e1422bd36 100644 --- a/src/http/header/if_none_match.rs +++ b/src/http/header/if_none_match.rs @@ -1,6 +1,6 @@ use super::{EntityTag, IF_NONE_MATCH}; -crate::__define_common_header! { +crate::http::header::common_header! { /// `If-None-Match` header, defined in /// [RFC7232](https://tools.ietf.org/html/rfc7232#section-3.2) /// @@ -55,11 +55,11 @@ crate::__define_common_header! { (IfNoneMatch, IF_NONE_MATCH) => {Any / (EntityTag)+} test_if_none_match { - crate::__common_header_test!(test1, vec![b"\"xyzzy\""]); - crate::__common_header_test!(test2, vec![b"W/\"xyzzy\""]); - crate::__common_header_test!(test3, vec![b"\"xyzzy\", \"r2d2xxxx\", \"c3piozzzz\""]); - crate::__common_header_test!(test4, vec![b"W/\"xyzzy\", W/\"r2d2xxxx\", W/\"c3piozzzz\""]); - crate::__common_header_test!(test5, vec![b"*"]); + crate::http::header::common_header_test!(test1, vec![b"\"xyzzy\""]); + crate::http::header::common_header_test!(test2, vec![b"W/\"xyzzy\""]); + crate::http::header::common_header_test!(test3, vec![b"\"xyzzy\", \"r2d2xxxx\", \"c3piozzzz\""]); + crate::http::header::common_header_test!(test4, vec![b"W/\"xyzzy\", W/\"r2d2xxxx\", W/\"c3piozzzz\""]); + crate::http::header::common_header_test!(test5, vec![b"*"]); } } diff --git a/src/http/header/if_range.rs b/src/http/header/if_range.rs index 9612405e8..cf69e7269 100644 --- a/src/http/header/if_range.rs +++ b/src/http/header/if_range.rs @@ -113,7 +113,7 @@ mod test_if_range { use crate::http::header::*; use std::str; - crate::__common_header_test!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); - crate::__common_header_test!(test2, vec![b"\"abc\""]); - crate::__common_header_test!(test3, vec![b"this-is-invalid"], None::); + crate::http::header::common_header_test!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); + crate::http::header::common_header_test!(test2, vec![b"\"abc\""]); + crate::http::header::common_header_test!(test3, vec![b"this-is-invalid"], None::); } diff --git a/src/http/header/if_unmodified_since.rs b/src/http/header/if_unmodified_since.rs index 26b16b513..1cc7b304e 100644 --- a/src/http/header/if_unmodified_since.rs +++ b/src/http/header/if_unmodified_since.rs @@ -1,6 +1,6 @@ use super::{HttpDate, IF_UNMODIFIED_SINCE}; -crate::__define_common_header! { +crate::http::header::common_header! { /// `If-Unmodified-Since` header, defined in /// [RFC7232](http://tools.ietf.org/html/rfc7232#section-3.4) /// @@ -37,6 +37,6 @@ crate::__define_common_header! { test_if_unmodified_since { // Test case from RFC - crate::__common_header_test!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); + crate::http::header::common_header_test!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); } } diff --git a/src/http/header/last_modified.rs b/src/http/header/last_modified.rs index 0de2fc06b..c43bf3ac9 100644 --- a/src/http/header/last_modified.rs +++ b/src/http/header/last_modified.rs @@ -1,6 +1,6 @@ use super::{HttpDate, LAST_MODIFIED}; -crate::__define_common_header! { +crate::http::header::common_header! { /// `Last-Modified` header, defined in /// [RFC7232](http://tools.ietf.org/html/rfc7232#section-2.2) /// @@ -36,6 +36,6 @@ crate::__define_common_header! { test_last_modified { // Test case from RFC - crate::__common_header_test!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); + crate::http::header::common_header_test!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); } } diff --git a/src/http/header/macros.rs b/src/http/header/macros.rs index 1718a8663..419d4fb6e 100644 --- a/src/http/header/macros.rs +++ b/src/http/header/macros.rs @@ -1,6 +1,4 @@ -#[doc(hidden)] -#[macro_export] -macro_rules! __common_header_deref { +macro_rules! common_header_deref { ($from:ty => $to:ty) => { impl ::std::ops::Deref for $from { type Target = $to; @@ -20,9 +18,7 @@ macro_rules! __common_header_deref { }; } -#[doc(hidden)] -#[macro_export] -macro_rules! __common_header_test_module { +macro_rules! common_header_test_module { ($id:ident, $tm:ident{$($tf:item)*}) => { #[allow(unused_imports)] #[cfg(test)] @@ -37,9 +33,8 @@ macro_rules! __common_header_test_module { } } -#[doc(hidden)] -#[macro_export] -macro_rules! __common_header_test { +#[cfg(test)] +macro_rules! common_header_test { ($id:ident, $raw:expr) => { #[test] fn $id() { @@ -99,9 +94,7 @@ macro_rules! __common_header_test { }; } -#[doc(hidden)] -#[macro_export] -macro_rules! __define_common_header { +macro_rules! common_header { // $a:meta: Attributes associated with the header item (usually docs) // $id:ident: Identifier of the header // $n:expr: Lowercase name of the header @@ -112,7 +105,7 @@ macro_rules! __define_common_header { $(#[$a])* #[derive(Clone, Debug, PartialEq)] pub struct $id(pub Vec<$item>); - crate::__common_header_deref!($id => Vec<$item>); + crate::http::header::common_header_deref!($id => Vec<$item>); impl $crate::http::header::Header for $id { #[inline] fn name() -> $crate::http::header::HeaderName { @@ -148,7 +141,7 @@ macro_rules! __define_common_header { $(#[$a])* #[derive(Clone, Debug, PartialEq)] pub struct $id(pub Vec<$item>); - crate::__common_header_deref!($id => Vec<$item>); + crate::http::header::common_header_deref!($id => Vec<$item>); impl $crate::http::header::Header for $id { #[inline] fn name() -> $crate::http::header::HeaderName { @@ -184,7 +177,7 @@ macro_rules! __define_common_header { $(#[$a])* #[derive(Clone, Debug, PartialEq)] pub struct $id(pub $value); - crate::__common_header_deref!($id => $value); + crate::http::header::common_header_deref!($id => $value); impl $crate::http::header::Header for $id { #[inline] fn name() -> $crate::http::header::HeaderName { @@ -267,34 +260,39 @@ macro_rules! __define_common_header { // optional test module ($(#[$a:meta])*($id:ident, $name:expr) => ($item:ty)* $tm:ident{$($tf:item)*}) => { - crate::__define_common_header! { + crate::http::header::common_header! { $(#[$a])* ($id, $name) => ($item)* } - crate::__common_header_test_module! { $id, $tm { $($tf)* }} + crate::http::header::common_header_test_module! { $id, $tm { $($tf)* }} }; ($(#[$a:meta])*($id:ident, $n:expr) => ($item:ty)+ $tm:ident{$($tf:item)*}) => { - crate::__define_common_header! { + crate::http::header::common_header! { $(#[$a])* ($id, $n) => ($item)+ } - crate::__common_header_test_module! { $id, $tm { $($tf)* }} + crate::http::header::common_header_test_module! { $id, $tm { $($tf)* }} }; ($(#[$a:meta])*($id:ident, $name:expr) => [$item:ty] $tm:ident{$($tf:item)*}) => { - crate::__define_common_header! { + crate::http::header::common_header! { $(#[$a])* ($id, $name) => [$item] } - crate::__common_header_test_module! { $id, $tm { $($tf)* }} + crate::http::header::common_header_test_module! { $id, $tm { $($tf)* }} }; ($(#[$a:meta])*($id:ident, $name:expr) => {Any / ($item:ty)+} $tm:ident{$($tf:item)*}) => { - crate::__define_common_header! { + crate::http::header::common_header! { $(#[$a])* ($id, $name) => {Any / ($item)+} } - crate::__common_header_test_module! { $id, $tm { $($tf)* }} + crate::http::header::common_header_test_module! { $id, $tm { $($tf)* }} }; } + +pub(crate) use {common_header, common_header_deref, common_header_test_module}; + +#[cfg(test)] +pub(crate) use common_header_test; diff --git a/src/http/header/mod.rs b/src/http/header/mod.rs index 0e5651a77..79ba5772b 100644 --- a/src/http/header/mod.rs +++ b/src/http/header/mod.rs @@ -84,4 +84,8 @@ mod if_none_match; mod if_range; mod if_unmodified_since; mod last_modified; + mod macros; +#[cfg(test)] +pub(crate) use macros::common_header_test; +pub(crate) use macros::{common_header, common_header_deref, common_header_test_module}; From 9a263933757ab6e0bd1a6a688b8bce04584e7dfe Mon Sep 17 00:00:00 2001 From: Igor Aleksanov Date: Fri, 25 Jun 2021 15:27:22 +0400 Subject: [PATCH 09/11] Remove duplicated step from CI workflow (#2289) --- .github/workflows/ci.yml | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index be595e35c..8bc04dbd7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -44,17 +44,11 @@ jobs: profile: minimal override: true - - name: Install ${{ matrix.version }} - uses: actions-rs/toolchain@v1 - with: - toolchain: ${{ matrix.version }}-${{ matrix.target.triple }} - profile: minimal - override: true - - name: Generate Cargo.lock uses: actions-rs/cargo@v1 with: command: generate-lockfile + - name: Cache Dependencies uses: Swatinem/rust-cache@v1.2.0 @@ -102,6 +96,7 @@ jobs: run: | cargo install cargo-tarpaulin --vers "^0.13" cargo tarpaulin --out Xml --verbose + - name: Upload to Codecov if: > matrix.target.os == 'ubuntu-latest' From 5a480d1d789fc55e9c26434f4294d8ac13eea1bf Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 25 Jun 2021 12:28:04 +0100 Subject: [PATCH 10/11] re-add serde error impls --- src/error/response_error.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/error/response_error.rs b/src/error/response_error.rs index 41cf20eba..c3c543419 100644 --- a/src/error/response_error.rs +++ b/src/error/response_error.rs @@ -57,6 +57,10 @@ impl ResponseError for serde::de::value::Error { } } +impl ResponseError for serde_json::Error {} + +impl ResponseError for serde_urlencoded::ser::Error {} + impl ResponseError for std::str::Utf8Error { fn status_code(&self) -> StatusCode { StatusCode::BAD_REQUEST From 539697292adabb0f682004f47d81b9ccd9eba121 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 25 Jun 2021 13:19:42 +0100 Subject: [PATCH 11/11] fix scope and resource middleware data access (#2288) --- CHANGES.md | 4 ++ src/app.rs | 7 ++-- src/app_service.rs | 26 ++++++------- src/config.rs | 2 +- src/resource.rs | 95 +++++++++++++++++++++++++++------------------- src/scope.rs | 83 ++++++++++++++++++++++++++++------------ 6 files changed, 136 insertions(+), 81 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 876e1c03d..3da742f9d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -11,11 +11,15 @@ * Change compression algorithm features flags. [#2250] * Deprecate `App::data` and `App::data_factory`. [#2271] +### Fixed +* Scope and Resource middleware can access data items set on their own layer. [#2288] + [#2177]: https://github.com/actix/actix-web/pull/2177 [#2250]: https://github.com/actix/actix-web/pull/2250 [#2271]: https://github.com/actix/actix-web/pull/2271 [#2262]: https://github.com/actix/actix-web/pull/2262 [#2263]: https://github.com/actix/actix-web/pull/2263 +[#2288]: https://github.com/actix/actix-web/pull/2288 ## 4.0.0-beta.7 - 2021-06-17 diff --git a/src/app.rs b/src/app.rs index 677f73805..5cff20568 100644 --- a/src/app.rs +++ b/src/app.rs @@ -43,13 +43,14 @@ impl App { /// Create application builder. Application can be configured with a builder-like pattern. #[allow(clippy::new_without_default)] pub fn new() -> Self { - let fref = Rc::new(RefCell::new(None)); + let factory_ref = Rc::new(RefCell::new(None)); + App { - endpoint: AppEntry::new(fref.clone()), + endpoint: AppEntry::new(factory_ref.clone()), data_factories: Vec::new(), services: Vec::new(), default: None, - factory_ref: fref, + factory_ref, external: Vec::new(), extensions: Extensions::new(), _phantom: PhantomData, diff --git a/src/app_service.rs b/src/app_service.rs index 0e590e2b7..bdb7ec433 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -1,22 +1,22 @@ -use std::cell::RefCell; -use std::rc::Rc; +use std::{cell::RefCell, mem, rc::Rc}; use actix_http::{Extensions, Request}; use actix_router::{Path, ResourceDef, Router, Url}; -use actix_service::boxed::{self, BoxService, BoxServiceFactory}; -use actix_service::{fn_service, Service, ServiceFactory}; +use actix_service::{ + boxed::{self, BoxService, BoxServiceFactory}, + fn_service, Service, ServiceFactory, +}; use futures_core::future::LocalBoxFuture; use futures_util::future::join_all; -use crate::data::FnDataFactory; -use crate::error::Error; -use crate::guard::Guard; -use crate::request::{HttpRequest, HttpRequestPool}; -use crate::rmap::ResourceMap; -use crate::service::{AppServiceFactory, ServiceRequest, ServiceResponse}; use crate::{ config::{AppConfig, AppService}, - HttpResponse, + data::FnDataFactory, + guard::Guard, + request::{HttpRequest, HttpRequestPool}, + rmap::ResourceMap, + service::{AppServiceFactory, ServiceRequest, ServiceResponse}, + Error, HttpResponse, }; type Guards = Vec>; @@ -75,7 +75,7 @@ where let mut config = AppService::new(config, default.clone()); // register services - std::mem::take(&mut *self.services.borrow_mut()) + mem::take(&mut *self.services.borrow_mut()) .into_iter() .for_each(|mut srv| srv.register(&mut config)); @@ -98,7 +98,7 @@ where }); // external resources - for mut rdef in std::mem::take(&mut *self.external.borrow_mut()) { + for mut rdef in mem::take(&mut *self.external.borrow_mut()) { rmap.add(&mut rdef, None); } diff --git a/src/config.rs b/src/config.rs index 966141193..884128308 100644 --- a/src/config.rs +++ b/src/config.rs @@ -94,9 +94,9 @@ impl AppService { F: IntoServiceFactory, S: ServiceFactory< ServiceRequest, - Config = (), Response = ServiceResponse, Error = Error, + Config = (), InitError = (), > + 'static, { diff --git a/src/resource.rs b/src/resource.rs index 7a4c1248b..9f5cf3cb2 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -400,34 +400,28 @@ where *rdef.name_mut() = name.clone(); } - config.register_service(rdef, guards, self, None) - } -} - -impl IntoServiceFactory for Resource -where - T: ServiceFactory< - ServiceRequest, - Config = (), - Response = ServiceResponse, - Error = Error, - InitError = (), - >, -{ - fn into_factory(self) -> T { *self.factory_ref.borrow_mut() = Some(ResourceFactory { routes: self.routes, - app_data: self.app_data.map(Rc::new), default: self.default, }); - self.endpoint + let resource_data = self.app_data.map(Rc::new); + + // wraps endpoint service (including middleware) call and injects app data for this scope + let endpoint = apply_fn_factory(self.endpoint, move |mut req: ServiceRequest, srv| { + if let Some(ref data) = resource_data { + req.add_data_container(Rc::clone(data)); + } + + srv.call(req) + }); + + config.register_service(rdef, guards, endpoint, None) } } pub struct ResourceFactory { routes: Vec, - app_data: Option>, default: HttpNewService, } @@ -446,8 +440,6 @@ impl ServiceFactory for ResourceFactory { // construct route service factory futures let factory_fut = join_all(self.routes.iter().map(|route| route.new_service(()))); - let app_data = self.app_data.clone(); - Box::pin(async move { let default = default_fut.await?; let routes = factory_fut @@ -455,18 +447,13 @@ impl ServiceFactory for ResourceFactory { .into_iter() .collect::, _>>()?; - Ok(ResourceService { - routes, - app_data, - default, - }) + Ok(ResourceService { routes, default }) }) } } pub struct ResourceService { routes: Vec, - app_data: Option>, default: HttpService, } @@ -480,18 +467,10 @@ impl Service for ResourceService { fn call(&self, mut req: ServiceRequest) -> Self::Future { for route in self.routes.iter() { if route.check(&mut req) { - if let Some(ref app_data) = self.app_data { - req.add_data_container(app_data.clone()); - } - return route.call(req); } } - if let Some(ref app_data) = self.app_data { - req.add_data_container(app_data.clone()); - } - self.default.call(req) } } @@ -528,11 +507,14 @@ mod tests { use actix_service::Service; use actix_utils::future::ok; - use crate::http::{header, HeaderValue, Method, StatusCode}; - use crate::middleware::DefaultHeaders; - use crate::service::ServiceRequest; - use crate::test::{call_service, init_service, TestRequest}; - use crate::{guard, web, App, Error, HttpResponse}; + use crate::{ + guard, + http::{header, HeaderValue, Method, StatusCode}, + middleware::DefaultHeaders, + service::{ServiceRequest, ServiceResponse}, + test::{call_service, init_service, TestRequest}, + web, App, Error, HttpMessage, HttpResponse, + }; #[actix_rt::test] async fn test_middleware() { @@ -753,4 +735,39 @@ mod tests { let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); } + + #[actix_rt::test] + async fn test_middleware_app_data() { + let srv = init_service( + App::new().service( + web::resource("test") + .app_data(1usize) + .wrap_fn(|req, srv| { + assert_eq!(req.app_data::(), Some(&1usize)); + req.extensions_mut().insert(1usize); + srv.call(req) + }) + .route(web::get().to(HttpResponse::Ok)) + .default_service(|req: ServiceRequest| async move { + let (req, _) = req.into_parts(); + + assert_eq!(req.extensions().get::(), Some(&1)); + + Ok(ServiceResponse::new( + req, + HttpResponse::BadRequest().finish(), + )) + }), + ), + ) + .await; + + let req = TestRequest::get().uri("/test").to_request(); + let resp = call_service(&srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); + + let req = TestRequest::post().uri("/test").to_request(); + let resp = call_service(&srv, req).await; + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + } } diff --git a/src/scope.rs b/src/scope.rs index 0caf06ee3..aa546c422 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -427,7 +427,6 @@ where // complete scope pipeline creation *self.factory_ref.borrow_mut() = Some(ScopeFactory { - app_data: self.app_data.take().map(Rc::new), default, services: cfg .into_services() @@ -449,18 +448,28 @@ where Some(self.guards) }; + let scope_data = self.app_data.map(Rc::new); + + // wraps endpoint service (including middleware) call and injects app data for this scope + let endpoint = apply_fn_factory(self.endpoint, move |mut req: ServiceRequest, srv| { + if let Some(ref data) = scope_data { + req.add_data_container(Rc::clone(data)); + } + + srv.call(req) + }); + // register final service config.register_service( ResourceDef::root_prefix(&self.rdef), guards, - self.endpoint, + endpoint, Some(Rc::new(rmap)), ) } } pub struct ScopeFactory { - app_data: Option>, services: Rc<[(ResourceDef, HttpNewService, RefCell>)]>, default: Rc, } @@ -488,8 +497,6 @@ impl ServiceFactory for ScopeFactory { } })); - let app_data = self.app_data.clone(); - Box::pin(async move { let default = default_fut.await?; @@ -505,17 +512,12 @@ impl ServiceFactory for ScopeFactory { }) .finish(); - Ok(ScopeService { - app_data, - router, - default, - }) + Ok(ScopeService { router, default }) }) } } pub struct ScopeService { - app_data: Option>, router: Router>>, default: HttpService, } @@ -539,10 +541,6 @@ impl Service for ScopeService { true }); - if let Some(ref app_data) = self.app_data { - req.add_data_container(app_data.clone()); - } - if let Some((srv, _info)) = res { srv.call(req) } else { @@ -581,12 +579,15 @@ mod tests { use actix_utils::future::ok; use bytes::Bytes; - use crate::dev::Body; - use crate::http::{header, HeaderValue, Method, StatusCode}; - use crate::middleware::DefaultHeaders; - use crate::service::ServiceRequest; - use crate::test::{call_service, init_service, read_body, TestRequest}; - use crate::{guard, web, App, HttpRequest, HttpResponse}; + use crate::{ + dev::Body, + guard, + http::{header, HeaderValue, Method, StatusCode}, + middleware::DefaultHeaders, + service::{ServiceRequest, ServiceResponse}, + test::{call_service, init_service, read_body, TestRequest}, + web, App, HttpMessage, HttpRequest, HttpResponse, + }; #[actix_rt::test] async fn test_scope() { @@ -918,10 +919,7 @@ mod tests { async fn test_default_resource_propagation() { let srv = init_service( App::new() - .service( - web::scope("/app1") - .default_service(web::resource("").to(HttpResponse::BadRequest)), - ) + .service(web::scope("/app1").default_service(web::to(HttpResponse::BadRequest))) .service(web::scope("/app2")) .default_service(|r: ServiceRequest| { ok(r.into_response(HttpResponse::MethodNotAllowed())) @@ -993,6 +991,41 @@ mod tests { ); } + #[actix_rt::test] + async fn test_middleware_app_data() { + let srv = init_service( + App::new().service( + web::scope("app") + .app_data(1usize) + .wrap_fn(|req, srv| { + assert_eq!(req.app_data::(), Some(&1usize)); + req.extensions_mut().insert(1usize); + srv.call(req) + }) + .route("/test", web::get().to(HttpResponse::Ok)) + .default_service(|req: ServiceRequest| async move { + let (req, _) = req.into_parts(); + + assert_eq!(req.extensions().get::(), Some(&1)); + + Ok(ServiceResponse::new( + req, + HttpResponse::BadRequest().finish(), + )) + }), + ), + ) + .await; + + let req = TestRequest::with_uri("/app/test").to_request(); + let resp = call_service(&srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); + + let req = TestRequest::with_uri("/app/default").to_request(); + let resp = call_service(&srv, req).await; + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + } + // allow deprecated {App, Scope}::data #[allow(deprecated)] #[actix_rt::test]