1
0
mirror of https://github.com/fafhrd91/actix-web synced 2024-12-18 01:43:58 +01:00

remove json and url encoded form support from -http (#2148)

This commit is contained in:
Rob Ede 2021-04-12 10:30:28 +01:00 committed by GitHub
parent 44c55dd036
commit 981c54432c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 137 additions and 258 deletions

View File

@ -4,7 +4,12 @@
### Added ### Added
* `HttpResponse` and `HttpResponseBuilder` structs. [#2065] * `HttpResponse` and `HttpResponseBuilder` structs. [#2065]
### Changed
* Most error types are now marked `#[non_exhaustive]`. [#2148]
[#2065]: https://github.com/actix/actix-web/pull/2065 [#2065]: https://github.com/actix/actix-web/pull/2065
[#2148]: https://github.com/actix/actix-web/pull/2148
## 4.0.0-beta.5 - 2021-04-02 ## 4.0.0-beta.5 - 2021-04-02
### Added ### Added

View File

@ -6,8 +6,13 @@
* Top-level `cookies` mod (re-export). [#2065] * Top-level `cookies` mod (re-export). [#2065]
* `HttpMessage` trait loses the `cookies` and `cookie` methods. [#2065] * `HttpMessage` trait loses the `cookies` and `cookie` methods. [#2065]
* `impl ResponseError for CookieParseError`. [#2065] * `impl ResponseError for CookieParseError`. [#2065]
* Deprecated methods on `ResponseBuilder`: `if_true`, `if_some`. [#2148]
* `ResponseBuilder::json`. [#2148]
* `ResponseBuilder::{set_header, header}`. [#2148]
* `impl From<serde_json::Value> for Body`. [#2148]
[#2065]: https://github.com/actix/actix-web/pull/2065 [#2065]: https://github.com/actix/actix-web/pull/2065
[#2148]: https://github.com/actix/actix-web/pull/2148
## 3.0.0-beta.5 - 2021-04-02 ## 3.0.0-beta.5 - 2021-04-02

View File

@ -68,8 +68,6 @@ pin-project-lite = "0.2"
rand = "0.8" rand = "0.8"
regex = "1.3" regex = "1.3"
serde = "1.0" serde = "1.0"
serde_json = "1.0"
serde_urlencoded = "0.7"
sha-1 = "0.9" sha-1 = "0.9"
smallvec = "1.6" smallvec = "1.6"
time = { version = "0.2.23", default-features = false, features = ["std"] } time = { version = "0.2.23", default-features = false, features = ["std"] }
@ -89,6 +87,7 @@ criterion = "0.3"
env_logger = "0.8" env_logger = "0.8"
rcgen = "0.8" rcgen = "0.8"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tls-openssl = { version = "0.10", package = "openssl" } tls-openssl = { version = "0.10", package = "openssl" }
tls-rustls = { version = "0.19", package = "rustls" } tls-rustls = { version = "0.19", package = "rustls" }

View File

@ -132,12 +132,6 @@ impl From<BytesMut> for Body {
} }
} }
impl From<serde_json::Value> for Body {
fn from(v: serde_json::Value) -> Body {
Body::Bytes(v.to_string().into())
}
}
impl<S> From<SizedStream<S>> for Body impl<S> From<SizedStream<S>> for Body
where where
S: Stream<Item = Result<Bytes, Error>> + Unpin + 'static, S: Stream<Item = Result<Bytes, Error>> + Unpin + 'static,

View File

@ -174,13 +174,15 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn test_serde_json() { async fn test_serde_json() {
use serde_json::json; use serde_json::{json, Value};
assert_eq!( assert_eq!(
Body::from(serde_json::Value::String("test".into())).size(), Body::from(serde_json::to_vec(&Value::String("test".to_owned())).unwrap())
.size(),
BodySize::Sized(6) BodySize::Sized(6)
); );
assert_eq!( assert_eq!(
Body::from(json!({"test-key":"test-value"})).size(), Body::from(serde_json::to_vec(&json!({"test-key":"test-value"})).unwrap())
.size(),
BodySize::Sized(25) BodySize::Sized(25)
); );
} }

View File

@ -1,18 +1,18 @@
//! Error and Result module //! Error and Result module
use std::cell::RefCell; use std::{
use std::io::Write; cell::RefCell,
use std::str::Utf8Error; fmt,
use std::string::FromUtf8Error; io::{self, Write as _},
use std::{fmt, io, result}; str::Utf8Error,
string::FromUtf8Error,
};
use bytes::BytesMut; use bytes::BytesMut;
use derive_more::{Display, From}; use derive_more::{Display, Error, From};
use http::uri::InvalidUri; use http::uri::InvalidUri;
use http::{header, Error as HttpError, StatusCode}; use http::{header, Error as HttpError, StatusCode};
use serde::de::value::Error as DeError; use serde::de::value::Error as DeError;
use serde_json::error::Error as JsonError;
use serde_urlencoded::ser::Error as FormError;
use crate::{body::Body, helpers::Writer, Response, ResponseBuilder}; use crate::{body::Body, helpers::Writer, Response, ResponseBuilder};
@ -22,7 +22,7 @@ use crate::{body::Body, helpers::Writer, Response, ResponseBuilder};
/// This typedef is generally used to avoid writing out /// This typedef is generally used to avoid writing out
/// `actix_http::error::Error` directly and is otherwise a direct mapping to /// `actix_http::error::Error` directly and is otherwise a direct mapping to
/// `Result`. /// `Result`.
pub type Result<T, E = Error> = result::Result<T, E>; pub type Result<T, E = Error> = std::result::Result<T, E>;
/// General purpose actix web error. /// General purpose actix web error.
/// ///
@ -147,14 +147,8 @@ struct UnitError;
/// Returns [`StatusCode::INTERNAL_SERVER_ERROR`] for [`UnitError`]. /// Returns [`StatusCode::INTERNAL_SERVER_ERROR`] for [`UnitError`].
impl ResponseError for UnitError {} impl ResponseError for UnitError {}
/// Returns [`StatusCode::INTERNAL_SERVER_ERROR`] for [`JsonError`].
impl ResponseError for JsonError {}
/// Returns [`StatusCode::INTERNAL_SERVER_ERROR`] for [`FormError`].
impl ResponseError for FormError {}
#[cfg(feature = "openssl")]
/// Returns [`StatusCode::INTERNAL_SERVER_ERROR`] for [`actix_tls::accept::openssl::SslError`]. /// Returns [`StatusCode::INTERNAL_SERVER_ERROR`] for [`actix_tls::accept::openssl::SslError`].
#[cfg(feature = "openssl")]
impl ResponseError for actix_tls::accept::openssl::SslError {} impl ResponseError for actix_tls::accept::openssl::SslError {}
/// Returns [`StatusCode::BAD_REQUEST`] for [`DeError`]. /// Returns [`StatusCode::BAD_REQUEST`] for [`DeError`].
@ -421,18 +415,17 @@ pub enum DispatchError {
} }
/// A set of error that can occur during parsing content type /// A set of error that can occur during parsing content type
#[derive(PartialEq, Debug, Display)] #[derive(Debug, PartialEq, Display, Error)]
pub enum ContentTypeError { pub enum ContentTypeError {
/// Can not parse content type /// Can not parse content type
#[display(fmt = "Can not parse content type")] #[display(fmt = "Can not parse content type")]
ParseError, ParseError,
/// Unknown content encoding /// Unknown content encoding
#[display(fmt = "Unknown content encoding")] #[display(fmt = "Unknown content encoding")]
UnknownEncoding, UnknownEncoding,
} }
impl std::error::Error for ContentTypeError {}
/// Return `BadRequest` for `ContentTypeError` /// Return `BadRequest` for `ContentTypeError`
impl ResponseError for ContentTypeError { impl ResponseError for ContentTypeError {
fn status_code(&self) -> StatusCode { fn status_code(&self) -> StatusCode {

View File

@ -2,7 +2,6 @@
use std::{ use std::{
cell::{Ref, RefMut}, cell::{Ref, RefMut},
convert::TryInto,
fmt, fmt,
future::Future, future::Future,
pin::Pin, pin::Pin,
@ -12,17 +11,13 @@ use std::{
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use futures_core::Stream; use futures_core::Stream;
use serde::Serialize;
use crate::{ use crate::{
body::{Body, BodyStream, MessageBody, ResponseBody}, body::{Body, BodyStream, MessageBody, ResponseBody},
error::Error, error::Error,
extensions::Extensions, extensions::Extensions,
header::{IntoHeaderPair, IntoHeaderValue}, header::{IntoHeaderPair, IntoHeaderValue},
http::{ http::{header, Error as HttpError, HeaderMap, StatusCode},
header::{self, HeaderName},
Error as HttpError, HeaderMap, StatusCode,
},
message::{BoxedResponseHead, ConnectionType, ResponseHead}, message::{BoxedResponseHead, ConnectionType, ResponseHead},
}; };
@ -335,54 +330,6 @@ impl ResponseBuilder {
self self
} }
/// Replaced with [`Self::insert_header()`].
#[deprecated(
since = "4.0.0",
note = "Replaced with `insert_header((key, value))`. Will be removed in v5."
)]
pub fn set_header<K, V>(&mut self, key: K, value: V) -> &mut Self
where
K: TryInto<HeaderName>,
K::Error: Into<HttpError>,
V: IntoHeaderValue,
{
if self.err.is_some() {
return self;
}
match (key.try_into(), value.try_into_value()) {
(Ok(name), Ok(value)) => return self.insert_header((name, value)),
(Err(err), _) => self.err = Some(err.into()),
(_, Err(err)) => self.err = Some(err.into()),
}
self
}
/// Replaced with [`Self::append_header()`].
#[deprecated(
since = "4.0.0",
note = "Replaced with `append_header((key, value))`. Will be removed in v5."
)]
pub fn header<K, V>(&mut self, key: K, value: V) -> &mut Self
where
K: TryInto<HeaderName>,
K::Error: Into<HttpError>,
V: IntoHeaderValue,
{
if self.err.is_some() {
return self;
}
match (key.try_into(), value.try_into_value()) {
(Ok(name), Ok(value)) => return self.append_header((name, value)),
(Err(err), _) => self.err = Some(err.into()),
(_, Err(err)) => self.err = Some(err.into()),
}
self
}
/// Set the custom reason for the response. /// Set the custom reason for the response.
#[inline] #[inline]
pub fn reason(&mut self, reason: &'static str) -> &mut Self { pub fn reason(&mut self, reason: &'static str) -> &mut Self {
@ -456,32 +403,6 @@ impl ResponseBuilder {
self self
} }
/// This method calls provided closure with builder reference if value is `true`.
#[doc(hidden)]
#[deprecated = "Use an if statement."]
pub fn if_true<F>(&mut self, value: bool, f: F) -> &mut Self
where
F: FnOnce(&mut ResponseBuilder),
{
if value {
f(self);
}
self
}
/// This method calls provided closure with builder reference if value is `Some`.
#[doc(hidden)]
#[deprecated = "Use an if-let construction."]
pub fn if_some<T, F>(&mut self, value: Option<T>, f: F) -> &mut Self
where
F: FnOnce(T, &mut ResponseBuilder),
{
if let Some(val) = value {
f(val, self);
}
self
}
/// Responses extensions /// Responses extensions
#[inline] #[inline]
pub fn extensions(&self) -> Ref<'_, Extensions> { pub fn extensions(&self) -> Ref<'_, Extensions> {
@ -496,10 +417,10 @@ impl ResponseBuilder {
head.extensions.borrow_mut() head.extensions.borrow_mut()
} }
#[inline]
/// Set a body and generate `Response`. /// Set a body and generate `Response`.
/// ///
/// `ResponseBuilder` can not be used after this call. /// `ResponseBuilder` can not be used after this call.
#[inline]
pub fn body<B: Into<Body>>(&mut self, body: B) -> Response { pub fn body<B: Into<Body>>(&mut self, body: B) -> Response {
self.message_body(body.into()) self.message_body(body.into())
} }
@ -521,10 +442,10 @@ impl ResponseBuilder {
} }
} }
#[inline]
/// Set a streaming body and generate `Response`. /// Set a streaming body and generate `Response`.
/// ///
/// `ResponseBuilder` can not be used after this call. /// `ResponseBuilder` can not be used after this call.
#[inline]
pub fn streaming<S, E>(&mut self, stream: S) -> Response pub fn streaming<S, E>(&mut self, stream: S) -> Response
where where
S: Stream<Item = Result<Bytes, E>> + Unpin + 'static, S: Stream<Item = Result<Bytes, E>> + Unpin + 'static,
@ -533,32 +454,10 @@ impl ResponseBuilder {
self.body(Body::from_message(BodyStream::new(stream))) self.body(Body::from_message(BodyStream::new(stream)))
} }
/// Set a json body and generate `Response`
///
/// `ResponseBuilder` can not be used after this call.
pub fn json(&mut self, value: impl Serialize) -> Response {
match serde_json::to_string(&value) {
Ok(body) => {
let contains = if let Some(parts) = parts(&mut self.head, &self.err) {
parts.headers.contains_key(header::CONTENT_TYPE)
} else {
true
};
if !contains {
self.insert_header((header::CONTENT_TYPE, mime::APPLICATION_JSON));
}
self.body(Body::from(body))
}
Err(e) => Error::from(e).into(),
}
}
#[inline]
/// Set an empty body and generate `Response` /// Set an empty body and generate `Response`
/// ///
/// `ResponseBuilder` can not be used after this call. /// `ResponseBuilder` can not be used after this call.
#[inline]
pub fn finish(&mut self) -> Response { pub fn finish(&mut self) -> Response {
self.body(Body::Empty) self.body(Body::Empty)
} }
@ -706,11 +605,9 @@ impl From<BytesMut> for Response {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use serde_json::json;
use super::*; use super::*;
use crate::body::Body; use crate::body::Body;
use crate::http::header::{HeaderValue, CONTENT_TYPE, COOKIE}; use crate::http::header::{HeaderName, HeaderValue, CONTENT_TYPE, COOKIE};
#[test] #[test]
fn test_debug() { fn test_debug() {
@ -754,38 +651,6 @@ mod tests {
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain") assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain")
} }
#[test]
fn test_json() {
let resp = Response::Ok().json(vec!["v1", "v2", "v3"]);
let ct = resp.headers().get(CONTENT_TYPE).unwrap();
assert_eq!(ct, HeaderValue::from_static("application/json"));
assert_eq!(resp.body().get_ref(), b"[\"v1\",\"v2\",\"v3\"]");
let resp = Response::Ok().json(&["v1", "v2", "v3"]);
let ct = resp.headers().get(CONTENT_TYPE).unwrap();
assert_eq!(ct, HeaderValue::from_static("application/json"));
assert_eq!(resp.body().get_ref(), b"[\"v1\",\"v2\",\"v3\"]");
}
#[test]
fn test_json_ct() {
let resp = Response::build(StatusCode::OK)
.insert_header((CONTENT_TYPE, "text/json"))
.json(&vec!["v1", "v2", "v3"]);
let ct = resp.headers().get(CONTENT_TYPE).unwrap();
assert_eq!(ct, HeaderValue::from_static("text/json"));
assert_eq!(resp.body().get_ref(), b"[\"v1\",\"v2\",\"v3\"]");
}
#[test]
fn test_serde_json_in_body() {
use serde_json::json;
let resp =
Response::build(StatusCode::OK).body(json!({"test-key":"test-value"}));
assert_eq!(resp.body().get_ref(), br#"{"test-key":"test-value"}"#);
}
#[test] #[test]
fn test_into_response() { fn test_into_response() {
let resp: Response = "test".into(); let resp: Response = "test".into();

View File

@ -1,6 +1,10 @@
# Changes # Changes
## Unreleased - 2021-xx-xx ## Unreleased - 2021-xx-xx
### Removed
* Deprecated methods on `ClientRequest`: `if_true`, `if_some`. [#2148]
[#2148]: https://github.com/actix/actix-web/pull/2148
## 3.0.0-beta.4 - 2021-04-02 ## 3.0.0-beta.4 - 2021-04-02

View File

@ -311,34 +311,6 @@ impl ClientRequest {
self self
} }
/// This method calls provided closure with builder reference if value is `true`.
#[doc(hidden)]
#[deprecated = "Use an if statement."]
pub fn if_true<F>(self, value: bool, f: F) -> Self
where
F: FnOnce(ClientRequest) -> ClientRequest,
{
if value {
f(self)
} else {
self
}
}
/// This method calls provided closure with builder reference if value is `Some`.
#[doc(hidden)]
#[deprecated = "Use an if-let construction."]
pub fn if_some<T, F>(self, value: Option<T>, f: F) -> Self
where
F: FnOnce(T, ClientRequest) -> ClientRequest,
{
if let Some(val) = value {
f(val, self)
} else {
self
}
}
/// Sets the query part of the request /// Sets the query part of the request
pub fn query<T: Serialize>( pub fn query<T: Serialize>(
mut self, mut self,

View File

@ -1,6 +1,6 @@
use std::{ use std::{
future::Future, future::Future,
net, io, net,
pin::Pin, pin::Pin,
rc::Rc, rc::Rc,
task::{Context, Poll}, task::{Context, Poll},
@ -209,7 +209,8 @@ impl RequestSender {
) -> SendClientRequest { ) -> SendClientRequest {
let body = match serde_json::to_string(value) { let body = match serde_json::to_string(value) {
Ok(body) => body, Ok(body) => body,
Err(e) => return Error::from(e).into(), // TODO: own error type
Err(e) => return Error::from(io::Error::new(io::ErrorKind::Other, e)).into(),
}; };
if let Err(e) = self.set_header_if_none(header::CONTENT_TYPE, "application/json") { if let Err(e) = self.set_header_if_none(header::CONTENT_TYPE, "application/json") {
@ -235,7 +236,8 @@ impl RequestSender {
) -> SendClientRequest { ) -> SendClientRequest {
let body = match serde_urlencoded::to_string(value) { let body = match serde_urlencoded::to_string(value) {
Ok(body) => body, Ok(body) => body,
Err(e) => return Error::from(e).into(), // TODO: own error type
Err(e) => return Error::from(io::Error::new(io::ErrorKind::Other, e)).into(),
}; };
// set content-type // set content-type

View File

@ -3,12 +3,15 @@
pub use actix_http::error::*; pub use actix_http::error::*;
use derive_more::{Display, Error, From}; use derive_more::{Display, Error, From};
use serde_json::error::Error as JsonError; use serde_json::error::Error as JsonError;
use serde_urlencoded::de::Error as FormDeError;
use serde_urlencoded::ser::Error as FormError;
use url::ParseError as UrlParseError; use url::ParseError as UrlParseError;
use crate::http::StatusCode; use crate::http::StatusCode;
/// Errors which can occur when attempting to generate resource uri. /// Errors which can occur when attempting to generate resource uri.
#[derive(Debug, PartialEq, Display, From)] #[derive(Debug, PartialEq, Display, Error, From)]
#[non_exhaustive]
pub enum UrlGenerationError { pub enum UrlGenerationError {
/// Resource not found /// Resource not found
#[display(fmt = "Resource not found")] #[display(fmt = "Resource not found")]
@ -23,13 +26,12 @@ pub enum UrlGenerationError {
ParseError(UrlParseError), ParseError(UrlParseError),
} }
impl std::error::Error for UrlGenerationError {}
/// `InternalServerError` for `UrlGeneratorError` /// `InternalServerError` for `UrlGeneratorError`
impl ResponseError for UrlGenerationError {} impl ResponseError for UrlGenerationError {}
/// A set of errors that can occur during parsing urlencoded payloads /// A set of errors that can occur during parsing urlencoded payloads
#[derive(Debug, Display, Error, From)] #[derive(Debug, Display, Error, From)]
#[non_exhaustive]
pub enum UrlencodedError { pub enum UrlencodedError {
/// Can not decode chunked transfer encoding. /// Can not decode chunked transfer encoding.
#[display(fmt = "Can not decode chunked transfer encoding.")] #[display(fmt = "Can not decode chunked transfer encoding.")]
@ -52,8 +54,16 @@ pub enum UrlencodedError {
ContentType, ContentType,
/// Parse error. /// Parse error.
#[display(fmt = "Parse error.")] #[display(fmt = "Parse error: {}.", _0)]
Parse, Parse(FormDeError),
/// Encoding error.
#[display(fmt = "Encoding error.")]
Encoding,
/// Serialize error.
#[display(fmt = "Serialize error: {}.", _0)]
Serialize(FormError),
/// Payload error. /// Payload error.
#[display(fmt = "Error that occur during reading payload: {}.", _0)] #[display(fmt = "Error that occur during reading payload: {}.", _0)]
@ -63,52 +73,66 @@ pub enum UrlencodedError {
/// Return `BadRequest` for `UrlencodedError` /// Return `BadRequest` for `UrlencodedError`
impl ResponseError for UrlencodedError { impl ResponseError for UrlencodedError {
fn status_code(&self) -> StatusCode { fn status_code(&self) -> StatusCode {
match *self { match self {
UrlencodedError::Overflow { .. } => StatusCode::PAYLOAD_TOO_LARGE, Self::Overflow { .. } => StatusCode::PAYLOAD_TOO_LARGE,
UrlencodedError::UnknownLength => StatusCode::LENGTH_REQUIRED, Self::UnknownLength => StatusCode::LENGTH_REQUIRED,
Self::Payload(err) => err.status_code(),
_ => StatusCode::BAD_REQUEST, _ => StatusCode::BAD_REQUEST,
} }
} }
} }
/// A set of errors that can occur during parsing json payloads /// A set of errors that can occur during parsing json payloads
#[derive(Debug, Display, From)] #[derive(Debug, Display, Error)]
#[non_exhaustive]
pub enum JsonPayloadError { pub enum JsonPayloadError {
/// Payload size is bigger than allowed. (default: 32kB) /// Payload size is bigger than allowed. (default: 32kB)
#[display(fmt = "Json payload size is bigger than allowed")] #[display(fmt = "Json payload size is bigger than allowed")]
Overflow, Overflow,
/// Content type error /// Content type error
#[display(fmt = "Content type error")] #[display(fmt = "Content type error")]
ContentType, ContentType,
/// Deserialize error /// Deserialize error
#[display(fmt = "Json deserialize error: {}", _0)] #[display(fmt = "Json deserialize error: {}", _0)]
Deserialize(JsonError), Deserialize(JsonError),
/// Serialize error
#[display(fmt = "Json serialize error: {}", _0)]
Serialize(JsonError),
/// Payload error /// Payload error
#[display(fmt = "Error that occur during reading payload: {}", _0)] #[display(fmt = "Error that occur during reading payload: {}", _0)]
Payload(PayloadError), Payload(PayloadError),
} }
impl std::error::Error for JsonPayloadError {} impl From<PayloadError> for JsonPayloadError {
fn from(err: PayloadError) -> Self {
Self::Payload(err)
}
}
impl ResponseError for JsonPayloadError { impl ResponseError for JsonPayloadError {
fn status_code(&self) -> StatusCode { fn status_code(&self) -> StatusCode {
match *self { match self {
JsonPayloadError::Overflow => StatusCode::PAYLOAD_TOO_LARGE, Self::Overflow => StatusCode::PAYLOAD_TOO_LARGE,
Self::Serialize(_) => StatusCode::INTERNAL_SERVER_ERROR,
Self::Payload(err) => err.status_code(),
_ => StatusCode::BAD_REQUEST, _ => StatusCode::BAD_REQUEST,
} }
} }
} }
/// A set of errors that can occur during parsing request paths /// A set of errors that can occur during parsing request paths
#[derive(Debug, Display, From)] #[derive(Debug, Display, Error)]
#[non_exhaustive]
pub enum PathError { pub enum PathError {
/// Deserialize error /// Deserialize error
#[display(fmt = "Path deserialize error: {}", _0)] #[display(fmt = "Path deserialize error: {}", _0)]
Deserialize(serde::de::value::Error), Deserialize(serde::de::value::Error),
} }
impl std::error::Error for PathError {}
/// Return `BadRequest` for `PathError` /// Return `BadRequest` for `PathError`
impl ResponseError for PathError { impl ResponseError for PathError {
fn status_code(&self) -> StatusCode { fn status_code(&self) -> StatusCode {
@ -118,6 +142,7 @@ impl ResponseError for PathError {
/// A set of errors that can occur during parsing query strings. /// A set of errors that can occur during parsing query strings.
#[derive(Debug, Display, Error, From)] #[derive(Debug, Display, Error, From)]
#[non_exhaustive]
pub enum QueryPayloadError { pub enum QueryPayloadError {
/// Query deserialize error. /// Query deserialize error.
#[display(fmt = "Query deserialize error: {}", _0)] #[display(fmt = "Query deserialize error: {}", _0)]
@ -132,25 +157,26 @@ impl ResponseError for QueryPayloadError {
} }
/// Error type returned when reading body as lines. /// Error type returned when reading body as lines.
#[derive(From, Display, Debug)] #[derive(Debug, Display, Error, From)]
#[non_exhaustive]
pub enum ReadlinesError { pub enum ReadlinesError {
/// Error when decoding a line.
#[display(fmt = "Encoding error")] #[display(fmt = "Encoding error")]
/// Payload size is bigger than allowed. (default: 256kB) /// Payload size is bigger than allowed. (default: 256kB)
EncodingError, EncodingError,
/// Payload error. /// Payload error.
#[display(fmt = "Error that occur during reading payload: {}", _0)] #[display(fmt = "Error that occur during reading payload: {}", _0)]
Payload(PayloadError), Payload(PayloadError),
/// Line limit exceeded. /// Line limit exceeded.
#[display(fmt = "Line limit exceeded")] #[display(fmt = "Line limit exceeded")]
LimitOverflow, LimitOverflow,
/// ContentType error. /// ContentType error.
#[display(fmt = "Content-type error")] #[display(fmt = "Content-type error")]
ContentTypeError(ContentTypeError), ContentTypeError(ContentTypeError),
} }
impl std::error::Error for ReadlinesError {}
/// Return `BadRequest` for `ReadlinesError` /// Return `BadRequest` for `ReadlinesError`
impl ResponseError for ReadlinesError { impl ResponseError for ReadlinesError {
fn status_code(&self) -> StatusCode { fn status_code(&self) -> StatusCode {

View File

@ -24,7 +24,7 @@ use actix_http::http::header::HeaderValue;
#[cfg(feature = "cookies")] #[cfg(feature = "cookies")]
use cookie::{Cookie, CookieJar}; use cookie::{Cookie, CookieJar};
use crate::error::Error; use crate::error::{Error, JsonPayloadError};
/// An HTTP Response /// An HTTP Response
pub struct HttpResponse<B = Body> { pub struct HttpResponse<B = Body> {
@ -385,7 +385,10 @@ impl HttpResponseBuilder {
} }
/// Replaced with [`Self::insert_header()`]. /// Replaced with [`Self::insert_header()`].
#[deprecated = "Replaced with `insert_header((key, value))`."] #[deprecated(
since = "4.0.0",
note = "Replaced with `insert_header((key, value))`. Will be removed in v5."
)]
pub fn set_header<K, V>(&mut self, key: K, value: V) -> &mut Self pub fn set_header<K, V>(&mut self, key: K, value: V) -> &mut Self
where where
K: TryInto<HeaderName>, K: TryInto<HeaderName>,
@ -406,7 +409,10 @@ impl HttpResponseBuilder {
} }
/// Replaced with [`Self::append_header()`]. /// Replaced with [`Self::append_header()`].
#[deprecated = "Replaced with `append_header((key, value))`."] #[deprecated(
since = "4.0.0",
note = "Replaced with `append_header((key, value))`. Will be removed in v5."
)]
pub fn header<K, V>(&mut self, key: K, value: V) -> &mut Self pub fn header<K, V>(&mut self, key: K, value: V) -> &mut Self
where where
K: TryInto<HeaderName>, K: TryInto<HeaderName>,
@ -572,7 +578,7 @@ impl HttpResponseBuilder {
/// Set a body and generate `Response`. /// Set a body and generate `Response`.
/// ///
/// `ResponseBuilder` can not be used after this call. /// `HttpResponseBuilder` can not be used after this call.
#[inline] #[inline]
pub fn body<B: Into<Body>>(&mut self, body: B) -> HttpResponse { pub fn body<B: Into<Body>>(&mut self, body: B) -> HttpResponse {
self.message_body(body.into()) self.message_body(body.into())
@ -580,7 +586,7 @@ impl HttpResponseBuilder {
/// Set a body and generate `Response`. /// Set a body and generate `Response`.
/// ///
/// `ResponseBuilder` can not be used after this call. /// `HttpResponseBuilder` can not be used after this call.
pub fn message_body<B>(&mut self, body: B) -> HttpResponse<B> { pub fn message_body<B>(&mut self, body: B) -> HttpResponse<B> {
if let Some(err) = self.err.take() { if let Some(err) = self.err.take() {
return HttpResponse::from_error(Error::from(err)).into_body(); return HttpResponse::from_error(Error::from(err)).into_body();
@ -608,7 +614,7 @@ impl HttpResponseBuilder {
/// Set a streaming body and generate `Response`. /// Set a streaming body and generate `Response`.
/// ///
/// `ResponseBuilder` can not be used after this call. /// `HttpResponseBuilder` can not be used after this call.
#[inline] #[inline]
pub fn streaming<S, E>(&mut self, stream: S) -> HttpResponse pub fn streaming<S, E>(&mut self, stream: S) -> HttpResponse
where where
@ -620,7 +626,7 @@ impl HttpResponseBuilder {
/// Set a json body and generate `Response` /// Set a json body and generate `Response`
/// ///
/// `ResponseBuilder` can not be used after this call. /// `HttpResponseBuilder` can not be used after this call.
pub fn json(&mut self, value: impl Serialize) -> HttpResponse { pub fn json(&mut self, value: impl Serialize) -> HttpResponse {
match serde_json::to_string(&value) { match serde_json::to_string(&value) {
Ok(body) => { Ok(body) => {
@ -636,19 +642,19 @@ impl HttpResponseBuilder {
self.body(Body::from(body)) self.body(Body::from(body))
} }
Err(e) => HttpResponse::from_error(Error::from(e)), Err(err) => HttpResponse::from_error(JsonPayloadError::Serialize(err).into()),
} }
} }
/// Set an empty body and generate `Response` /// Set an empty body and generate `Response`
/// ///
/// `ResponseBuilder` can not be used after this call. /// `HttpResponseBuilder` can not be used after this call.
#[inline] #[inline]
pub fn finish(&mut self) -> HttpResponse { pub fn finish(&mut self) -> HttpResponse {
self.body(Body::Empty) self.body(Body::Empty)
} }
/// This method construct new `ResponseBuilder` /// This method construct new `HttpResponseBuilder`
pub fn take(&mut self) -> Self { pub fn take(&mut self) -> Self {
Self { Self {
head: self.head.take(), head: self.head.take(),
@ -814,32 +820,33 @@ mod http_codes {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use serde_json::json;
use super::{HttpResponse as Response, HttpResponseBuilder as ResponseBuilder}; use super::{HttpResponse, HttpResponseBuilder};
use crate::dev::{Body, MessageBody, ResponseBody}; use crate::dev::{Body, MessageBody, ResponseBody};
use crate::http::header::{self, HeaderValue, CONTENT_TYPE, COOKIE}; use crate::http::header::{self, HeaderValue, CONTENT_TYPE, COOKIE};
use crate::http::StatusCode; use crate::http::StatusCode;
#[test] #[test]
fn test_debug() { fn test_debug() {
let resp = Response::Ok() let resp = HttpResponse::Ok()
.append_header((COOKIE, HeaderValue::from_static("cookie1=value1; "))) .append_header((COOKIE, HeaderValue::from_static("cookie1=value1; ")))
.append_header((COOKIE, HeaderValue::from_static("cookie2=value2; "))) .append_header((COOKIE, HeaderValue::from_static("cookie2=value2; ")))
.finish(); .finish();
let dbg = format!("{:?}", resp); let dbg = format!("{:?}", resp);
assert!(dbg.contains("Response")); assert!(dbg.contains("HttpResponse"));
} }
#[test] #[test]
fn test_basic_builder() { fn test_basic_builder() {
let resp = Response::Ok().insert_header(("X-TEST", "value")).finish(); let resp = HttpResponse::Ok()
.insert_header(("X-TEST", "value"))
.finish();
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
} }
#[test] #[test]
fn test_upgrade() { fn test_upgrade() {
let resp = ResponseBuilder::new(StatusCode::OK) let resp = HttpResponseBuilder::new(StatusCode::OK)
.upgrade("websocket") .upgrade("websocket")
.finish(); .finish();
assert!(resp.upgrade()); assert!(resp.upgrade());
@ -851,13 +858,15 @@ mod tests {
#[test] #[test]
fn test_force_close() { fn test_force_close() {
let resp = ResponseBuilder::new(StatusCode::OK).force_close().finish(); let resp = HttpResponseBuilder::new(StatusCode::OK)
.force_close()
.finish();
assert!(!resp.keep_alive()) assert!(!resp.keep_alive())
} }
#[test] #[test]
fn test_content_type() { fn test_content_type() {
let resp = ResponseBuilder::new(StatusCode::OK) let resp = HttpResponseBuilder::new(StatusCode::OK)
.content_type("text/plain") .content_type("text/plain")
.body(Body::Empty); .body(Body::Empty);
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain") assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain")
@ -878,7 +887,7 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn test_json() { async fn test_json() {
let mut resp = Response::Ok().json(vec!["v1", "v2", "v3"]); let mut resp = HttpResponse::Ok().json(vec!["v1", "v2", "v3"]);
let ct = resp.headers().get(CONTENT_TYPE).unwrap(); let ct = resp.headers().get(CONTENT_TYPE).unwrap();
assert_eq!(ct, HeaderValue::from_static("application/json")); assert_eq!(ct, HeaderValue::from_static("application/json"));
assert_eq!( assert_eq!(
@ -886,7 +895,7 @@ mod tests {
br#"["v1","v2","v3"]"# br#"["v1","v2","v3"]"#
); );
let mut resp = Response::Ok().json(&["v1", "v2", "v3"]); let mut resp = HttpResponse::Ok().json(&["v1", "v2", "v3"]);
let ct = resp.headers().get(CONTENT_TYPE).unwrap(); let ct = resp.headers().get(CONTENT_TYPE).unwrap();
assert_eq!(ct, HeaderValue::from_static("application/json")); assert_eq!(ct, HeaderValue::from_static("application/json"));
assert_eq!( assert_eq!(
@ -895,7 +904,7 @@ mod tests {
); );
// content type override // content type override
let mut resp = Response::Ok() let mut resp = HttpResponse::Ok()
.insert_header((CONTENT_TYPE, "text/json")) .insert_header((CONTENT_TYPE, "text/json"))
.json(&vec!["v1", "v2", "v3"]); .json(&vec!["v1", "v2", "v3"]);
let ct = resp.headers().get(CONTENT_TYPE).unwrap(); let ct = resp.headers().get(CONTENT_TYPE).unwrap();
@ -908,8 +917,10 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn test_serde_json_in_body() { async fn test_serde_json_in_body() {
use serde_json::json; let mut resp = HttpResponse::Ok().body(
let mut resp = Response::Ok().body(json!({"test-key":"test-value"})); serde_json::to_vec(&serde_json::json!({ "test-key": "test-value" })).unwrap(),
);
assert_eq!( assert_eq!(
read_body(resp.take_body()).await.as_ref(), read_body(resp.take_body()).await.as_ref(),
br#"{"test-key":"test-value"}"# br#"{"test-key":"test-value"}"#
@ -918,7 +929,7 @@ mod tests {
#[test] #[test]
fn response_builder_header_insert_kv() { fn response_builder_header_insert_kv() {
let mut res = Response::Ok(); let mut res = HttpResponse::Ok();
res.insert_header(("Content-Type", "application/octet-stream")); res.insert_header(("Content-Type", "application/octet-stream"));
let res = res.finish(); let res = res.finish();
@ -930,7 +941,7 @@ mod tests {
#[test] #[test]
fn response_builder_header_insert_typed() { fn response_builder_header_insert_typed() {
let mut res = Response::Ok(); let mut res = HttpResponse::Ok();
res.insert_header((header::CONTENT_TYPE, mime::APPLICATION_OCTET_STREAM)); res.insert_header((header::CONTENT_TYPE, mime::APPLICATION_OCTET_STREAM));
let res = res.finish(); let res = res.finish();
@ -942,7 +953,7 @@ mod tests {
#[test] #[test]
fn response_builder_header_append_kv() { fn response_builder_header_append_kv() {
let mut res = Response::Ok(); let mut res = HttpResponse::Ok();
res.append_header(("Content-Type", "application/octet-stream")); res.append_header(("Content-Type", "application/octet-stream"));
res.append_header(("Content-Type", "application/json")); res.append_header(("Content-Type", "application/json"));
let res = res.finish(); let res = res.finish();
@ -955,7 +966,7 @@ mod tests {
#[test] #[test]
fn response_builder_header_append_typed() { fn response_builder_header_append_typed() {
let mut res = Response::Ok(); let mut res = HttpResponse::Ok();
res.append_header((header::CONTENT_TYPE, mime::APPLICATION_OCTET_STREAM)); res.append_header((header::CONTENT_TYPE, mime::APPLICATION_OCTET_STREAM));
res.append_header((header::CONTENT_TYPE, mime::APPLICATION_JSON)); res.append_header((header::CONTENT_TYPE, mime::APPLICATION_JSON));
let res = res.finish(); let res = res.finish();

View File

@ -163,7 +163,7 @@ impl<T: Serialize> Responder for Form<T> {
Ok(body) => HttpResponse::Ok() Ok(body) => HttpResponse::Ok()
.content_type(mime::APPLICATION_WWW_FORM_URLENCODED) .content_type(mime::APPLICATION_WWW_FORM_URLENCODED)
.body(body), .body(body),
Err(err) => HttpResponse::from_error(err.into()), Err(err) => HttpResponse::from_error(UrlencodedError::Serialize(err).into()),
} }
} }
} }
@ -346,14 +346,14 @@ where
} }
if encoding == UTF_8 { if encoding == UTF_8 {
serde_urlencoded::from_bytes::<T>(&body).map_err(|_| UrlencodedError::Parse) serde_urlencoded::from_bytes::<T>(&body).map_err(UrlencodedError::Parse)
} else { } else {
let body = encoding let body = encoding
.decode_without_bom_handling_and_without_replacement(&body) .decode_without_bom_handling_and_without_replacement(&body)
.map(|s| s.into_owned()) .map(|s| s.into_owned())
.ok_or(UrlencodedError::Parse)?; .ok_or(UrlencodedError::Encoding)?;
serde_urlencoded::from_str::<T>(&body).map_err(|_| UrlencodedError::Parse) serde_urlencoded::from_str::<T>(&body).map_err(UrlencodedError::Parse)
} }
} }
.boxed_local(), .boxed_local(),

View File

@ -127,7 +127,7 @@ impl<T: Serialize> Responder for Json<T> {
Ok(body) => HttpResponse::Ok() Ok(body) => HttpResponse::Ok()
.content_type(mime::APPLICATION_JSON) .content_type(mime::APPLICATION_JSON)
.body(body), .body(body),
Err(err) => HttpResponse::from_error(err.into()), Err(err) => HttpResponse::from_error(JsonPayloadError::Serialize(err).into()),
} }
} }
} }
@ -412,7 +412,8 @@ where
} }
} }
None => { None => {
let json = serde_json::from_slice::<T>(&buf)?; let json = serde_json::from_slice::<T>(&buf)
.map_err(JsonPayloadError::Deserialize)?;
return Poll::Ready(Ok(json)); return Poll::Ready(Ok(json));
} }
} }

View File

@ -93,7 +93,7 @@ where
T: de::DeserializeOwned, T: de::DeserializeOwned,
{ {
type Error = Error; type Error = Error;
type Future = Ready<Result<Self, Error>>; type Future = Ready<Result<Self, Self::Error>>;
type Config = PathConfig; type Config = PathConfig;
#[inline] #[inline]
@ -106,17 +106,17 @@ where
ready( ready(
de::Deserialize::deserialize(PathDeserializer::new(req.match_info())) de::Deserialize::deserialize(PathDeserializer::new(req.match_info()))
.map(Path) .map(Path)
.map_err(move |e| { .map_err(move |err| {
log::debug!( log::debug!(
"Failed during Path extractor deserialization. \ "Failed during Path extractor deserialization. \
Request path: {:?}", Request path: {:?}",
req.path() req.path()
); );
if let Some(error_handler) = error_handler { if let Some(error_handler) = error_handler {
let e = PathError::Deserialize(e); let e = PathError::Deserialize(err);
(error_handler)(e, req) (error_handler)(e, req)
} else { } else {
ErrorNotFound(e) ErrorNotFound(err)
} }
}), }),
) )
@ -303,7 +303,7 @@ mod tests {
let s = Path::<(usize,)>::from_request(&req, &mut pl) let s = Path::<(usize,)>::from_request(&req, &mut pl)
.await .await
.unwrap_err(); .unwrap_err();
let res = HttpResponse::from_error(s.into()); let res = HttpResponse::from_error(s);
assert_eq!(res.status(), http::StatusCode::CONFLICT); assert_eq!(res.status(), http::StatusCode::CONFLICT);
} }