fix tests and add docs

This commit is contained in:
Rob Ede 2021-12-13 05:44:55 +00:00
parent b1dfec2e87
commit afc9aa4c92
No known key found for this signature in database
GPG Key ID: 97C636207D3EF933
11 changed files with 82 additions and 61 deletions

View File

@ -333,7 +333,7 @@ impl HeaderMap {
} }
} }
/// Inserts a name-value pair into the map. /// Inserts (overrides) a name-value pair in the map.
/// ///
/// If the map already contained this key, the new value is associated with the key and all /// If the map already contained this key, the new value is associated with the key and all
/// previous values are removed and returned as a `Removed` iterator. The key is not updated; /// previous values are removed and returned as a `Removed` iterator. The key is not updated;
@ -372,7 +372,7 @@ impl HeaderMap {
Removed::new(value) Removed::new(value)
} }
/// Inserts a name-value pair into the map. /// Appends a name-value pair to the map.
/// ///
/// If the map already contained this key, the new value is added to the list of values /// If the map already contained this key, the new value is added to the list of values
/// currently associated with the key. The key is not updated; this matters for types that can /// currently associated with the key. The key is not updated; this matters for types that can

View File

@ -22,14 +22,14 @@ async fn main() -> std::io::Result<()> {
HttpServer::new(|| { HttpServer::new(|| {
App::new() App::new()
.wrap(middleware::DefaultHeaders::new().insert(("X-Version", "0.2"))) .wrap(middleware::DefaultHeaders::new().add(("X-Version", "0.2")))
.wrap(middleware::Compress::default()) .wrap(middleware::Compress::default())
.wrap(middleware::Logger::default()) .wrap(middleware::Logger::default())
.service(index) .service(index)
.service(no_params) .service(no_params)
.service( .service(
web::resource("/resource2/index.html") web::resource("/resource2/index.html")
.wrap(middleware::DefaultHeaders::new().insert(("X-Version-R2", "0.3"))) .wrap(middleware::DefaultHeaders::new().add(("X-Version-R2", "0.3")))
.default_service(web::route().to(HttpResponse::MethodNotAllowed)) .default_service(web::route().to(HttpResponse::MethodNotAllowed))
.route(web::get().to(index_async)), .route(web::get().to(index_async)),
) )

View File

@ -26,14 +26,14 @@ async fn main() -> std::io::Result<()> {
HttpServer::new(|| { HttpServer::new(|| {
App::new() App::new()
.wrap(middleware::DefaultHeaders::new().insert(("X-Version", "0.2"))) .wrap(middleware::DefaultHeaders::new().add(("X-Version", "0.2")))
.wrap(middleware::Compress::default()) .wrap(middleware::Compress::default())
.wrap(middleware::Logger::default()) .wrap(middleware::Logger::default())
.service(index) .service(index)
.service(no_params) .service(no_params)
.service( .service(
web::resource("/resource2/index.html") web::resource("/resource2/index.html")
.wrap(middleware::DefaultHeaders::new().insert(("X-Version-R2", "0.3"))) .wrap(middleware::DefaultHeaders::new().add(("X-Version-R2", "0.3")))
.default_service(web::route().to(HttpResponse::MethodNotAllowed)) .default_service(web::route().to(HttpResponse::MethodNotAllowed))
.route(web::get().to(index_async)), .route(web::get().to(index_async)),
) )

View File

@ -602,7 +602,7 @@ mod tests {
App::new() App::new()
.wrap( .wrap(
DefaultHeaders::new() DefaultHeaders::new()
.insert((header::CONTENT_TYPE, HeaderValue::from_static("0001"))), .add((header::CONTENT_TYPE, HeaderValue::from_static("0001"))),
) )
.route("/test", web::get().to(HttpResponse::Ok)), .route("/test", web::get().to(HttpResponse::Ok)),
) )
@ -623,7 +623,7 @@ mod tests {
.route("/test", web::get().to(HttpResponse::Ok)) .route("/test", web::get().to(HttpResponse::Ok))
.wrap( .wrap(
DefaultHeaders::new() DefaultHeaders::new()
.insert((header::CONTENT_TYPE, HeaderValue::from_static("0001"))), .add((header::CONTENT_TYPE, HeaderValue::from_static("0001"))),
), ),
) )
.await; .await;

View File

@ -30,7 +30,7 @@ use crate::{
/// use actix_web::{web, http, middleware, App, HttpResponse}; /// use actix_web::{web, http, middleware, App, HttpResponse};
/// ///
/// let app = App::new() /// let app = App::new()
/// .wrap(middleware::DefaultHeaders::new().insert("X-Version", "0.2")) /// .wrap(middleware::DefaultHeaders::new().add(("X-Version", "0.2")))
/// .service( /// .service(
/// web::resource("/test") /// web::resource("/test")
/// .route(web::get().to(|| HttpResponse::Ok())) /// .route(web::get().to(|| HttpResponse::Ok()))
@ -58,12 +58,14 @@ impl DefaultHeaders {
/// ///
/// # Panics /// # Panics
/// Panics when resolved header name or value is invalid. /// Panics when resolved header name or value is invalid.
pub fn insert(mut self, header: impl TryIntoHeaderPair) -> Self { #[allow(clippy::should_implement_trait)]
pub fn add(mut self, header: impl TryIntoHeaderPair) -> Self {
// standard header terminology `insert` or `append` for this method would make the behavior
// of this middleware less obvious since it only adds the headers if they are not present
match header.try_into_header_pair() { match header.try_into_header_pair() {
Ok((key, value)) => Rc::get_mut(&mut self.inner) Ok((key, value)) => Rc::get_mut(&mut self.inner)
.expect( .expect("All default headers must be added before cloning.")
"DefaultHeaders has been cloned. Add all default headers before cloning.",
)
.headers .headers
.append(key, value), .append(key, value),
Err(err) => panic!("Invalid header: {}", err.into()), Err(err) => panic!("Invalid header: {}", err.into()),
@ -73,7 +75,7 @@ impl DefaultHeaders {
} }
#[doc(hidden)] #[doc(hidden)]
#[deprecated(since = "4.0.0", note = "Prefer `insert`.")] #[deprecated(since = "4.0.0", note = "Prefer `add`.")]
pub fn header<K, V>(self, key: K, value: V) -> Self pub fn header<K, V>(self, key: K, value: V) -> Self
where where
HeaderName: TryFrom<K>, HeaderName: TryFrom<K>,
@ -81,7 +83,7 @@ impl DefaultHeaders {
HeaderValue: TryFrom<V>, HeaderValue: TryFrom<V>,
<HeaderValue as TryFrom<V>>::Error: Into<HttpError>, <HeaderValue as TryFrom<V>>::Error: Into<HttpError>,
{ {
self.insert(( self.add((
HeaderName::try_from(key) HeaderName::try_from(key)
.map_err(Into::into) .map_err(Into::into)
.expect("Invalid header name"), .expect("Invalid header name"),
@ -94,18 +96,12 @@ impl DefaultHeaders {
/// Adds a default *Content-Type* header if response does not contain one. /// Adds a default *Content-Type* header if response does not contain one.
/// ///
/// Default is `application/octet-stream`. /// Default is `application/octet-stream`.
pub fn insert_content_type(self) -> Self { pub fn add_content_type(self) -> Self {
self.insert(( self.add((
CONTENT_TYPE, CONTENT_TYPE,
HeaderValue::from_static("application/octet-stream"), HeaderValue::from_static("application/octet-stream"),
)) ))
} }
#[doc(hidden)]
#[deprecated(since = "4.0.0", note = "Renamed to `insert_content_type`.")]
pub fn add_content_type(self) -> Self {
self.insert_content_type()
}
} }
impl<S, B> Transform<S, ServiceRequest> for DefaultHeaders impl<S, B> Transform<S, ServiceRequest> for DefaultHeaders
@ -202,8 +198,8 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn adding_default_headers() { async fn adding_default_headers() {
let mw = DefaultHeaders::new() let mw = DefaultHeaders::new()
.insert(("X-TEST", "0001")) .add(("X-TEST", "0001"))
.insert(("X-TEST-TWO", HeaderValue::from_static("123"))) .add(("X-TEST-TWO", HeaderValue::from_static("123")))
.new_transform(ok_service()) .new_transform(ok_service())
.await .await
.unwrap(); .unwrap();
@ -225,7 +221,7 @@ mod tests {
)) ))
}; };
let mw = DefaultHeaders::new() let mw = DefaultHeaders::new()
.insert((CONTENT_TYPE, "0001")) .add((CONTENT_TYPE, "0001"))
.new_transform(srv.into_service()) .new_transform(srv.into_service())
.await .await
.unwrap(); .unwrap();
@ -237,7 +233,7 @@ mod tests {
async fn adding_content_type() { async fn adding_content_type() {
let srv = |req: ServiceRequest| ok(req.into_response(HttpResponse::Ok().finish())); let srv = |req: ServiceRequest| ok(req.into_response(HttpResponse::Ok().finish()));
let mw = DefaultHeaders::new() let mw = DefaultHeaders::new()
.insert_content_type() .add_content_type()
.new_transform(srv.into_service()) .new_transform(srv.into_service())
.await .await
.unwrap(); .unwrap();
@ -253,12 +249,12 @@ mod tests {
#[test] #[test]
#[should_panic] #[should_panic]
fn invalid_header_name() { fn invalid_header_name() {
DefaultHeaders::new().insert((":", "hello")); DefaultHeaders::new().add((":", "hello"));
} }
#[test] #[test]
#[should_panic] #[should_panic]
fn invalid_header_value() { fn invalid_header_value() {
DefaultHeaders::new().insert(("x-test", "\n")); DefaultHeaders::new().add(("x-test", "\n"));
} }
} }

View File

@ -33,7 +33,7 @@ mod tests {
let _ = App::new() let _ = App::new()
.wrap(Compat::new(Logger::default())) .wrap(Compat::new(Logger::default()))
.wrap(Condition::new(true, DefaultHeaders::new())) .wrap(Condition::new(true, DefaultHeaders::new()))
.wrap(DefaultHeaders::new().insert(("X-Test2", "X-Value2"))) .wrap(DefaultHeaders::new().add(("X-Test2", "X-Value2")))
.wrap(ErrorHandlers::new().handler(StatusCode::FORBIDDEN, |res| { .wrap(ErrorHandlers::new().handler(StatusCode::FORBIDDEN, |res| {
Ok(ErrorHandlerResponse::Response(res)) Ok(ErrorHandlerResponse::Response(res))
})) }))
@ -46,7 +46,7 @@ mod tests {
.wrap(ErrorHandlers::new().handler(StatusCode::FORBIDDEN, |res| { .wrap(ErrorHandlers::new().handler(StatusCode::FORBIDDEN, |res| {
Ok(ErrorHandlerResponse::Response(res)) Ok(ErrorHandlerResponse::Response(res))
})) }))
.wrap(DefaultHeaders::new().insert(("X-Test2", "X-Value2"))) .wrap(DefaultHeaders::new().add(("X-Test2", "X-Value2")))
.wrap(Condition::new(true, DefaultHeaders::new())) .wrap(Condition::new(true, DefaultHeaders::new()))
.wrap(Compat::new(Logger::default())); .wrap(Compat::new(Logger::default()));

View File

@ -17,7 +17,7 @@ use crate::{dev::Payload, error::ErrorInternalServerError, Error, FromRequest, H
/// # Mutating Request Data /// # Mutating Request Data
/// Note that since extractors must output owned data, only types that `impl Clone` can use this /// Note that since extractors must output owned data, only types that `impl Clone` can use this
/// extractor. A clone is taken of the required request data and can, therefore, not be directly /// extractor. A clone is taken of the required request data and can, therefore, not be directly
/// mutated in-place. To mutate request data, continue to use [`HttpRequest::extensions_mut`] or /// mutated in-place. To mutate request data, continue to use [`HttpRequest::req_data_mut`] or
/// re-insert the cloned data back into the extensions map. A `DerefMut` impl is intentionally not /// re-insert the cloned data back into the extensions map. A `DerefMut` impl is intentionally not
/// provided to make this potential foot-gun more obvious. /// provided to make this potential foot-gun more obvious.
/// ///

View File

@ -525,7 +525,7 @@ mod tests {
.name("test") .name("test")
.wrap( .wrap(
DefaultHeaders::new() DefaultHeaders::new()
.insert((header::CONTENT_TYPE, HeaderValue::from_static("0001"))), .add((header::CONTENT_TYPE, HeaderValue::from_static("0001"))),
) )
.route(web::get().to(HttpResponse::Ok)), .route(web::get().to(HttpResponse::Ok)),
), ),

View File

@ -8,7 +8,9 @@ use actix_http::{
use crate::{BoxError, HttpRequest, HttpResponse, Responder}; use crate::{BoxError, HttpRequest, HttpResponse, Responder};
/// Allows overriding status code and headers for a responder. /// Allows overriding status code and headers for a [`Responder`].
///
/// Created by the [`Responder::customize`] method.
pub struct CustomizeResponder<R> { pub struct CustomizeResponder<R> {
inner: CustomizeResponderInner<R>, inner: CustomizeResponderInner<R>,
error: Option<HttpError>, error: Option<HttpError>,
@ -36,12 +38,15 @@ impl<R: Responder> CustomizeResponder<R> {
/// Override a status code for the Responder's response. /// Override a status code for the Responder's response.
/// ///
/// # Examples
/// ``` /// ```
/// use actix_web::{HttpRequest, Responder, http::StatusCode}; /// use actix_web::{Responder, http::StatusCode, test::TestRequest};
/// ///
/// fn index(req: HttpRequest) -> impl Responder { /// let responder = "Welcome!".customize().with_status(StatusCode::ACCEPTED);
/// "Welcome!".with_status(StatusCode::OK) ///
/// } /// let request = TestRequest::default().to_http_request();
/// let response = responder.respond_to(&request);
/// assert_eq!(response.status(), StatusCode::ACCEPTED);
/// ``` /// ```
pub fn with_status(mut self, status: StatusCode) -> Self { pub fn with_status(mut self, status: StatusCode) -> Self {
if let Some(inner) = self.inner() { if let Some(inner) = self.inner() {
@ -51,24 +56,26 @@ impl<R: Responder> CustomizeResponder<R> {
self self
} }
/// Insert header to the final response. /// Insert (override) header in the final response.
/// ///
/// Overrides other headers with the same name. /// Overrides other headers with the same name.
/// See [`HeaderMap::insert`](crate::http::header::HeaderMap::insert).
/// ///
/// Headers added with this method will be inserted before those added
/// with [`append_header`](Self::append_header). As such, header(s) can be overridden with more
/// than one new header by first calling `insert_header` followed by `append_header`.
///
/// # Examples
/// ``` /// ```
/// use actix_web::{web, HttpRequest, Responder}; /// use actix_web::{Responder, test::TestRequest};
/// use serde::Serialize;
/// ///
/// #[derive(Serialize)] /// let responder = "Hello world!"
/// struct MyObj { /// .customize()
/// name: String, /// .insert_header(("x-version", "1.2.3"));
/// }
/// ///
/// fn index(req: HttpRequest) -> impl Responder { /// let request = TestRequest::default().to_http_request();
/// web::Json(MyObj { name: "Name".to_string() }) /// let response = responder.respond_to(&request);
/// .with_header(("x-version", "1.2.3")) /// assert_eq!(response.headers().get("x-version").unwrap(), "1.2.3");
/// .with_header(("x-version", "1.2.3"))
/// }
/// ``` /// ```
pub fn insert_header(mut self, header: impl TryIntoHeaderPair) -> Self { pub fn insert_header(mut self, header: impl TryIntoHeaderPair) -> Self {
if let Some(inner) = self.inner() { if let Some(inner) = self.inner() {
@ -83,6 +90,25 @@ impl<R: Responder> CustomizeResponder<R> {
self self
} }
/// Append header to the final response.
///
/// Unlike [`insert_header`](Self::insert_header), this will not override existing headers.
/// See [`HeaderMap::append`](crate::http::header::HeaderMap::append).
///
/// Headers added here are appended _after_ additions/overrides from `insert_header`.
///
/// # Examples
/// ```
/// use actix_web::{Responder, test::TestRequest};
///
/// let responder = "Hello world!"
/// .customize()
/// .append_header(("x-version", "1.2.3"));
///
/// let request = TestRequest::default().to_http_request();
/// let response = responder.respond_to(&request);
/// assert_eq!(response.headers().get("x-version").unwrap(), "1.2.3");
/// ```
pub fn append_header(mut self, header: impl TryIntoHeaderPair) -> Self { pub fn append_header(mut self, header: impl TryIntoHeaderPair) -> Self {
if let Some(inner) = self.inner() { if let Some(inner) = self.inner() {
match header.try_into_header_pair() { match header.try_into_header_pair() {

View File

@ -21,24 +21,23 @@ pub trait Responder {
/// Convert self to `HttpResponse`. /// Convert self to `HttpResponse`.
fn respond_to(self, req: &HttpRequest) -> HttpResponse<Self::Body>; fn respond_to(self, req: &HttpRequest) -> HttpResponse<Self::Body>;
/// Wraps responder in [`CustomizeResponder`] that allows modification of response details. /// Wraps responder to allow alteration of its response.
/// ///
/// See [`CustomizeResponder`] docs for more. /// See [`CustomizeResponder`] docs for its capabilities.
/// ///
/// # Examples /// # Examples
/// ``` /// ```
/// use actix_web::{Responder, test}; /// use actix_web::{Responder, http::StatusCode, test::TestRequest};
///
/// let request = test::TestRequest::default().to_http_request();
/// ///
/// let responder = "Hello world!" /// let responder = "Hello world!"
/// .customize() /// .customize()
/// .with_status(400) /// .with_status(StatusCode::BAD_REQUEST)
/// .insert_header(("x-hello", "world")) /// .insert_header(("x-hello", "world"));
/// ///
/// let response = res.respond_to(&req); /// let request = TestRequest::default().to_http_request();
/// assert_eq!(res.status(), 400); /// let response = responder.respond_to(&request);
/// assert_eq!(res.headers().get("x-hello").unwrap(), "world"); /// assert_eq!(response.status(), StatusCode::BAD_REQUEST);
/// assert_eq!(response.headers().get("x-hello").unwrap(), "world");
/// ``` /// ```
#[inline] #[inline]
fn customize(self) -> CustomizeResponder<Self> fn customize(self) -> CustomizeResponder<Self>

View File

@ -935,7 +935,7 @@ mod tests {
web::scope("app") web::scope("app")
.wrap( .wrap(
DefaultHeaders::new() DefaultHeaders::new()
.insert((header::CONTENT_TYPE, HeaderValue::from_static("0001"))), .add((header::CONTENT_TYPE, HeaderValue::from_static("0001"))),
) )
.service(web::resource("/test").route(web::get().to(HttpResponse::Ok))), .service(web::resource("/test").route(web::get().to(HttpResponse::Ok))),
), ),