mirror of
https://github.com/fafhrd91/actix-web
synced 2024-11-30 10:42:55 +01:00
big clean up and docs improvmenet of types mod (#1894)
This commit is contained in:
parent
530d03791d
commit
6575ee93f2
10
CHANGES.md
10
CHANGES.md
@ -1,11 +1,21 @@
|
||||
# Changes
|
||||
|
||||
## Unreleased - 2021-xx-xx
|
||||
### Added
|
||||
* The method `Either<web::Json<T>, web::Form<T>>::into_inner()` which returns the inner type for
|
||||
whichever variant was created. Also works for `Either<web::Form<T>, web::Json<T>>`. [#1894]
|
||||
|
||||
### Changed
|
||||
* Rework `Responder` trait to be sync and returns `Response`/`HttpResponse` directly.
|
||||
Making it more simple and performant. [#1891]
|
||||
* Our `Either` type now uses `Left`/`Right` variants (instead of `A`/`B`) [#1894]
|
||||
|
||||
### Removed
|
||||
* Public field of `web::Path` has been made private. [#1894]
|
||||
* Public field of `web::Query` has been made private. [#1894]
|
||||
|
||||
[#1891]: https://github.com/actix/actix-web/pull/1891
|
||||
[#1894]: https://github.com/actix/actix-web/pull/1894
|
||||
|
||||
## 4.0.0-beta.1 - 2021-01-07
|
||||
### Added
|
||||
|
@ -90,6 +90,7 @@ awc = { version = "3.0.0-beta.1", default-features = false }
|
||||
ahash = "0.6"
|
||||
bytes = "1"
|
||||
derive_more = "0.99.5"
|
||||
either = "1.5.3"
|
||||
encoding_rs = "0.8"
|
||||
futures-core = { version = "0.3.7", default-features = false }
|
||||
futures-util = { version = "0.3.7", default-features = false }
|
||||
|
@ -1,6 +1,9 @@
|
||||
# Changes
|
||||
|
||||
## Unreleased - 2021-xx-xx
|
||||
* `Response::content_type` now takes an `impl IntoHeaderValue` to support `mime` types. [#1894]
|
||||
|
||||
[#1894]: https://github.com/actix/actix-web/pull/1894
|
||||
|
||||
|
||||
## 3.0.0-beta.1 - 2021-01-07
|
||||
|
@ -100,10 +100,6 @@ impl fmt::Debug for Error {
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {
|
||||
fn cause(&self) -> Option<&dyn std::error::Error> {
|
||||
None
|
||||
}
|
||||
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
None
|
||||
}
|
||||
@ -309,28 +305,45 @@ impl From<httparse::Error> for ParseError {
|
||||
pub enum PayloadError {
|
||||
/// A payload reached EOF, but is not complete.
|
||||
#[display(
|
||||
fmt = "A payload reached EOF, but is not complete. With error: {:?}",
|
||||
fmt = "A payload reached EOF, but is not complete. Inner error: {:?}",
|
||||
_0
|
||||
)]
|
||||
Incomplete(Option<io::Error>),
|
||||
/// Content encoding stream corruption
|
||||
|
||||
/// Content encoding stream corruption.
|
||||
#[display(fmt = "Can not decode content-encoding.")]
|
||||
EncodingCorrupted,
|
||||
/// A payload reached size limit.
|
||||
#[display(fmt = "A payload reached size limit.")]
|
||||
|
||||
/// Payload reached size limit.
|
||||
#[display(fmt = "Payload reached size limit.")]
|
||||
Overflow,
|
||||
/// A payload length is unknown.
|
||||
#[display(fmt = "A payload length is unknown.")]
|
||||
|
||||
/// Payload length is unknown.
|
||||
#[display(fmt = "Payload length is unknown.")]
|
||||
UnknownLength,
|
||||
/// Http2 payload error
|
||||
|
||||
/// HTTP/2 payload error.
|
||||
#[display(fmt = "{}", _0)]
|
||||
Http2Payload(h2::Error),
|
||||
/// Io error
|
||||
|
||||
/// Generic I/O error.
|
||||
#[display(fmt = "{}", _0)]
|
||||
Io(io::Error),
|
||||
}
|
||||
|
||||
impl std::error::Error for PayloadError {}
|
||||
impl std::error::Error for PayloadError {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
match self {
|
||||
PayloadError::Incomplete(None) => None,
|
||||
PayloadError::Incomplete(Some(err)) => Some(err as &dyn std::error::Error),
|
||||
PayloadError::EncodingCorrupted => None,
|
||||
PayloadError::Overflow => None,
|
||||
PayloadError::UnknownLength => None,
|
||||
PayloadError::Http2Payload(err) => Some(err as &dyn std::error::Error),
|
||||
PayloadError::Io(err) => Some(err as &dyn std::error::Error),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<h2::Error> for PayloadError {
|
||||
fn from(err: h2::Error) -> Self {
|
||||
@ -1009,22 +1022,22 @@ mod tests {
|
||||
fn test_payload_error() {
|
||||
let err: PayloadError =
|
||||
io::Error::new(io::ErrorKind::Other, "ParseError").into();
|
||||
assert!(format!("{}", err).contains("ParseError"));
|
||||
assert!(err.to_string().contains("ParseError"));
|
||||
|
||||
let err = PayloadError::Incomplete(None);
|
||||
assert_eq!(
|
||||
format!("{}", err),
|
||||
"A payload reached EOF, but is not complete. With error: None"
|
||||
err.to_string(),
|
||||
"A payload reached EOF, but is not complete. Inner error: None"
|
||||
);
|
||||
}
|
||||
|
||||
macro_rules! from {
|
||||
($from:expr => $error:pat) => {
|
||||
match ParseError::from($from) {
|
||||
e @ $error => {
|
||||
assert!(format!("{}", e).len() >= 5);
|
||||
err @ $error => {
|
||||
assert!(err.to_string().len() >= 5);
|
||||
}
|
||||
e => unreachable!("{:?}", e),
|
||||
err => unreachable!("{:?}", err),
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -1067,7 +1080,7 @@ mod tests {
|
||||
let err = PayloadError::Overflow;
|
||||
let resp_err: &dyn ResponseError = &err;
|
||||
let err = resp_err.downcast_ref::<PayloadError>().unwrap();
|
||||
assert_eq!(err.to_string(), "A payload reached size limit.");
|
||||
assert_eq!(err.to_string(), "Payload reached size limit.");
|
||||
let not_err = resp_err.downcast_ref::<ContentTypeError>();
|
||||
assert!(not_err.is_none());
|
||||
}
|
||||
|
@ -481,15 +481,14 @@ impl ResponseBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set response content type
|
||||
/// Set response content type.
|
||||
#[inline]
|
||||
pub fn content_type<V>(&mut self, value: V) -> &mut Self
|
||||
where
|
||||
HeaderValue: TryFrom<V>,
|
||||
<HeaderValue as TryFrom<V>>::Error: Into<HttpError>,
|
||||
V: IntoHeaderValue,
|
||||
{
|
||||
if let Some(parts) = parts(&mut self.head, &self.err) {
|
||||
match HeaderValue::try_from(value) {
|
||||
match value.try_into() {
|
||||
Ok(value) => {
|
||||
parts.headers.insert(header::CONTENT_TYPE, value);
|
||||
}
|
||||
@ -802,7 +801,7 @@ impl From<ResponseBuilder> for Response {
|
||||
impl From<&'static str> for Response {
|
||||
fn from(val: &'static str) -> Self {
|
||||
Response::Ok()
|
||||
.content_type("text/plain; charset=utf-8")
|
||||
.content_type(mime::TEXT_PLAIN_UTF_8)
|
||||
.body(val)
|
||||
}
|
||||
}
|
||||
@ -810,7 +809,7 @@ impl From<&'static str> for Response {
|
||||
impl From<&'static [u8]> for Response {
|
||||
fn from(val: &'static [u8]) -> Self {
|
||||
Response::Ok()
|
||||
.content_type("application/octet-stream")
|
||||
.content_type(mime::APPLICATION_OCTET_STREAM)
|
||||
.body(val)
|
||||
}
|
||||
}
|
||||
@ -818,7 +817,7 @@ impl From<&'static [u8]> for Response {
|
||||
impl From<String> for Response {
|
||||
fn from(val: String) -> Self {
|
||||
Response::Ok()
|
||||
.content_type("text/plain; charset=utf-8")
|
||||
.content_type(mime::TEXT_PLAIN_UTF_8)
|
||||
.body(val)
|
||||
}
|
||||
}
|
||||
@ -826,7 +825,7 @@ impl From<String> for Response {
|
||||
impl<'a> From<&'a String> for Response {
|
||||
fn from(val: &'a String) -> Self {
|
||||
Response::Ok()
|
||||
.content_type("text/plain; charset=utf-8")
|
||||
.content_type(mime::TEXT_PLAIN_UTF_8)
|
||||
.body(val)
|
||||
}
|
||||
}
|
||||
@ -834,7 +833,7 @@ impl<'a> From<&'a String> for Response {
|
||||
impl From<Bytes> for Response {
|
||||
fn from(val: Bytes) -> Self {
|
||||
Response::Ok()
|
||||
.content_type("application/octet-stream")
|
||||
.content_type(mime::APPLICATION_OCTET_STREAM)
|
||||
.body(val)
|
||||
}
|
||||
}
|
||||
@ -842,7 +841,7 @@ impl From<Bytes> for Response {
|
||||
impl From<BytesMut> for Response {
|
||||
fn from(val: BytesMut) -> Self {
|
||||
Response::Ok()
|
||||
.content_type("application/octet-stream")
|
||||
.content_type(mime::APPLICATION_OCTET_STREAM)
|
||||
.body(val)
|
||||
}
|
||||
}
|
||||
|
@ -89,7 +89,7 @@ impl Codec {
|
||||
|
||||
/// Set max frame size.
|
||||
///
|
||||
/// By default max size is set to 64kb.
|
||||
/// By default max size is set to 64kB.
|
||||
pub fn max_size(mut self, size: usize) -> Self {
|
||||
self.max_size = size;
|
||||
self
|
||||
|
@ -184,7 +184,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Change max size of payload. By default max size is 256Kb
|
||||
/// Change max size of payload. By default max size is 256kB
|
||||
pub fn limit(mut self, limit: usize) -> Self {
|
||||
if let Some(ref mut fut) = self.fut {
|
||||
fut.limit = limit;
|
||||
@ -276,7 +276,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Change max size of payload. By default max size is 64Kb
|
||||
/// Change max size of payload. By default max size is 64kB
|
||||
pub fn limit(mut self, limit: usize) -> Self {
|
||||
if let Some(ref mut fut) = self.fut {
|
||||
fut.limit = limit;
|
||||
|
@ -147,7 +147,7 @@ impl WebsocketsRequest {
|
||||
|
||||
/// Set max frame size
|
||||
///
|
||||
/// By default max size is set to 64kb
|
||||
/// By default max size is set to 64kB
|
||||
pub fn max_frame_size(mut self, size: usize) -> Self {
|
||||
self.max_size = size;
|
||||
self
|
||||
|
46
src/error.rs
46
src/error.rs
@ -1,12 +1,11 @@
|
||||
//! Error and Result module
|
||||
|
||||
pub use actix_http::error::*;
|
||||
use derive_more::{Display, From};
|
||||
use derive_more::{Display, Error, From};
|
||||
use serde_json::error::Error as JsonError;
|
||||
use url::ParseError as UrlParseError;
|
||||
|
||||
use crate::http::StatusCode;
|
||||
use crate::HttpResponse;
|
||||
use crate::{http::StatusCode, HttpResponse};
|
||||
|
||||
/// Errors which can occur when attempting to generate resource uri.
|
||||
#[derive(Debug, PartialEq, Display, From)]
|
||||
@ -28,34 +27,37 @@ impl std::error::Error for UrlGenerationError {}
|
||||
impl ResponseError for UrlGenerationError {}
|
||||
|
||||
/// A set of errors that can occur during parsing urlencoded payloads
|
||||
#[derive(Debug, Display, From)]
|
||||
#[derive(Debug, Display, Error, From)]
|
||||
pub enum UrlencodedError {
|
||||
/// Can not decode chunked transfer encoding
|
||||
#[display(fmt = "Can not decode chunked transfer encoding")]
|
||||
/// Can not decode chunked transfer encoding.
|
||||
#[display(fmt = "Can not decode chunked transfer encoding.")]
|
||||
Chunked,
|
||||
/// Payload size is bigger than allowed. (default: 256kB)
|
||||
|
||||
/// Payload size is larger than allowed. (default limit: 256kB).
|
||||
#[display(
|
||||
fmt = "Urlencoded payload size is bigger ({} bytes) than allowed (default: {} bytes)",
|
||||
fmt = "URL encoded payload is larger ({} bytes) than allowed (limit: {} bytes).",
|
||||
size,
|
||||
limit
|
||||
)]
|
||||
Overflow { size: usize, limit: usize },
|
||||
/// Payload size is now known
|
||||
#[display(fmt = "Payload size is now known")]
|
||||
|
||||
/// Payload size is now known.
|
||||
#[display(fmt = "Payload size is now known.")]
|
||||
UnknownLength,
|
||||
/// Content type error
|
||||
#[display(fmt = "Content type error")]
|
||||
|
||||
/// Content type error.
|
||||
#[display(fmt = "Content type error.")]
|
||||
ContentType,
|
||||
/// Parse error
|
||||
#[display(fmt = "Parse error")]
|
||||
|
||||
/// Parse error.
|
||||
#[display(fmt = "Parse error.")]
|
||||
Parse,
|
||||
/// Payload error
|
||||
#[display(fmt = "Error that occur during reading payload: {}", _0)]
|
||||
|
||||
/// Payload error.
|
||||
#[display(fmt = "Error that occur during reading payload: {}.", _0)]
|
||||
Payload(PayloadError),
|
||||
}
|
||||
|
||||
impl std::error::Error for UrlencodedError {}
|
||||
|
||||
/// Return `BadRequest` for `UrlencodedError`
|
||||
impl ResponseError for UrlencodedError {
|
||||
fn status_code(&self) -> StatusCode {
|
||||
@ -115,16 +117,14 @@ impl ResponseError for PathError {
|
||||
}
|
||||
}
|
||||
|
||||
/// A set of errors that can occur during parsing query strings
|
||||
#[derive(Debug, Display, From)]
|
||||
/// A set of errors that can occur during parsing query strings.
|
||||
#[derive(Debug, Display, Error, From)]
|
||||
pub enum QueryPayloadError {
|
||||
/// Deserialize error
|
||||
/// Query deserialize error.
|
||||
#[display(fmt = "Query deserialize error: {}", _0)]
|
||||
Deserialize(serde::de::value::Error),
|
||||
}
|
||||
|
||||
impl std::error::Error for QueryPayloadError {}
|
||||
|
||||
/// Return `BadRequest` for `QueryPayloadError`
|
||||
impl ResponseError for QueryPayloadError {
|
||||
fn status_code(&self) -> StatusCode {
|
||||
|
@ -1,34 +1,37 @@
|
||||
//! Request extractors
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
use actix_http::error::Error;
|
||||
use futures_util::future::{ready, Ready};
|
||||
use futures_util::ready;
|
||||
use std::{
|
||||
future::Future,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use crate::dev::Payload;
|
||||
use crate::request::HttpRequest;
|
||||
use futures_util::{
|
||||
future::{ready, Ready},
|
||||
ready,
|
||||
};
|
||||
|
||||
use crate::{dev::Payload, Error, HttpRequest};
|
||||
|
||||
/// Trait implemented by types that can be extracted from request.
|
||||
///
|
||||
/// Types that implement this trait can be used with `Route` handlers.
|
||||
pub trait FromRequest: Sized {
|
||||
/// Configuration for this extractor.
|
||||
type Config: Default + 'static;
|
||||
|
||||
/// The associated error which can be returned.
|
||||
type Error: Into<Error>;
|
||||
|
||||
/// Future that resolves to a Self
|
||||
/// Future that resolves to a Self.
|
||||
type Future: Future<Output = Result<Self, Self::Error>>;
|
||||
|
||||
/// Configuration for this extractor
|
||||
type Config: Default + 'static;
|
||||
|
||||
/// Convert request to a Self
|
||||
/// Create a Self from request parts asynchronously.
|
||||
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future;
|
||||
|
||||
/// Convert request to a Self
|
||||
/// Create a Self from request head asynchronously.
|
||||
///
|
||||
/// This method uses `Payload::None` as payload stream.
|
||||
/// This method is short for `T::from_request(req, &mut Payload::None)`.
|
||||
fn extract(req: &HttpRequest) -> Self::Future {
|
||||
Self::from_request(req, &mut Payload::None)
|
||||
}
|
||||
|
@ -6,7 +6,8 @@
|
||||
//! use actix_web::{get, web, App, HttpServer, Responder};
|
||||
//!
|
||||
//! #[get("/{id}/{name}/index.html")]
|
||||
//! async fn index(web::Path((id, name)): web::Path<(u32, String)>) -> impl Responder {
|
||||
//! async fn index(path: web::Path<(u32, String)>) -> impl Responder {
|
||||
//! let (id, name) = path.into_inner();
|
||||
//! format!("Hello {}! id:{}", name, id)
|
||||
//! }
|
||||
//!
|
||||
@ -90,7 +91,7 @@ mod scope;
|
||||
mod server;
|
||||
mod service;
|
||||
pub mod test;
|
||||
mod types;
|
||||
pub(crate) mod types;
|
||||
pub mod web;
|
||||
|
||||
pub use actix_http::Response as HttpResponse;
|
||||
@ -106,6 +107,7 @@ pub use crate::responder::Responder;
|
||||
pub use crate::route::Route;
|
||||
pub use crate::scope::Scope;
|
||||
pub use crate::server::HttpServer;
|
||||
// TODO: is exposing the error directly really needed
|
||||
pub use crate::types::{Either, EitherExtractError};
|
||||
|
||||
pub mod dev {
|
||||
|
@ -15,7 +15,7 @@ use std::{
|
||||
use actix_service::{Service, Transform};
|
||||
use bytes::Bytes;
|
||||
use futures_util::future::{ok, Ready};
|
||||
use log::debug;
|
||||
use log::{debug, warn};
|
||||
use regex::{Regex, RegexSet};
|
||||
use time::OffsetDateTime;
|
||||
|
||||
@ -188,9 +188,8 @@ where
|
||||
for unit in &self.0.format.0 {
|
||||
// missing request replacement function diagnostic
|
||||
if let FormatText::CustomRequest(label, None) = unit {
|
||||
debug!(
|
||||
"No custom request replacement function was registered for label {} in\
|
||||
logger format.",
|
||||
warn!(
|
||||
"No custom request replacement function was registered for label \"{}\".",
|
||||
label
|
||||
);
|
||||
}
|
||||
|
@ -17,9 +17,10 @@ use crate::rmap::ResourceMap;
|
||||
#[derive(Clone)]
|
||||
/// An HTTP Request
|
||||
pub struct HttpRequest {
|
||||
// *. Rc<HttpRequestInner> is used exclusively and NO Weak<HttpRequestInner>
|
||||
// is allowed anywhere in the code. Weak pointer is purposely ignored when
|
||||
// doing Rc's ref counter check.
|
||||
/// # Panics
|
||||
/// `Rc<HttpRequestInner>` is used exclusively and NO `Weak<HttpRequestInner>`
|
||||
/// is allowed anywhere in the code. Weak pointer is purposely ignored when
|
||||
/// doing `Rc`'s ref counter check. Expect panics if this invariant is violated.
|
||||
pub(crate) inner: Rc<HttpRequestInner>,
|
||||
}
|
||||
|
||||
|
@ -4,17 +4,17 @@ use actix_http::error::InternalError;
|
||||
use actix_http::http::{
|
||||
header::IntoHeaderValue, Error as HttpError, HeaderMap, HeaderName, StatusCode,
|
||||
};
|
||||
use actix_http::{Error, Response, ResponseBuilder};
|
||||
use actix_http::ResponseBuilder;
|
||||
use bytes::{Bytes, BytesMut};
|
||||
|
||||
use crate::request::HttpRequest;
|
||||
use crate::{Error, HttpRequest, HttpResponse};
|
||||
|
||||
/// Trait implemented by types that can be converted to a http response.
|
||||
///
|
||||
/// Types that implement this trait can be used as the return type of a handler.
|
||||
pub trait Responder {
|
||||
/// Convert self to `Response`.
|
||||
fn respond_to(self, req: &HttpRequest) -> Response;
|
||||
/// Convert self to `HttpResponse`.
|
||||
fn respond_to(self, req: &HttpRequest) -> HttpResponse;
|
||||
|
||||
/// Override a status code for a Responder.
|
||||
///
|
||||
@ -63,18 +63,18 @@ pub trait Responder {
|
||||
}
|
||||
}
|
||||
|
||||
impl Responder for Response {
|
||||
impl Responder for HttpResponse {
|
||||
#[inline]
|
||||
fn respond_to(self, _: &HttpRequest) -> Response {
|
||||
fn respond_to(self, _: &HttpRequest) -> HttpResponse {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Responder> Responder for Option<T> {
|
||||
fn respond_to(self, req: &HttpRequest) -> Response {
|
||||
fn respond_to(self, req: &HttpRequest) -> HttpResponse {
|
||||
match self {
|
||||
Some(t) => t.respond_to(req),
|
||||
None => Response::build(StatusCode::NOT_FOUND).finish(),
|
||||
None => HttpResponse::build(StatusCode::NOT_FOUND).finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -84,23 +84,23 @@ where
|
||||
T: Responder,
|
||||
E: Into<Error>,
|
||||
{
|
||||
fn respond_to(self, req: &HttpRequest) -> Response {
|
||||
fn respond_to(self, req: &HttpRequest) -> HttpResponse {
|
||||
match self {
|
||||
Ok(val) => val.respond_to(req),
|
||||
Err(e) => Response::from_error(e.into()),
|
||||
Err(e) => HttpResponse::from_error(e.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Responder for ResponseBuilder {
|
||||
#[inline]
|
||||
fn respond_to(mut self, _: &HttpRequest) -> Response {
|
||||
fn respond_to(mut self, _: &HttpRequest) -> HttpResponse {
|
||||
self.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Responder> Responder for (T, StatusCode) {
|
||||
fn respond_to(self, req: &HttpRequest) -> Response {
|
||||
fn respond_to(self, req: &HttpRequest) -> HttpResponse {
|
||||
let mut res = self.0.respond_to(req);
|
||||
*res.status_mut() = self.1;
|
||||
res
|
||||
@ -108,49 +108,49 @@ impl<T: Responder> Responder for (T, StatusCode) {
|
||||
}
|
||||
|
||||
impl Responder for &'static str {
|
||||
fn respond_to(self, _: &HttpRequest) -> Response {
|
||||
Response::build(StatusCode::OK)
|
||||
.content_type("text/plain; charset=utf-8")
|
||||
fn respond_to(self, _: &HttpRequest) -> HttpResponse {
|
||||
HttpResponse::Ok()
|
||||
.content_type(mime::TEXT_PLAIN_UTF_8)
|
||||
.body(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Responder for &'static [u8] {
|
||||
fn respond_to(self, _: &HttpRequest) -> Response {
|
||||
Response::build(StatusCode::OK)
|
||||
.content_type("application/octet-stream")
|
||||
fn respond_to(self, _: &HttpRequest) -> HttpResponse {
|
||||
HttpResponse::Ok()
|
||||
.content_type(mime::APPLICATION_OCTET_STREAM)
|
||||
.body(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Responder for String {
|
||||
fn respond_to(self, _: &HttpRequest) -> Response {
|
||||
Response::build(StatusCode::OK)
|
||||
.content_type("text/plain; charset=utf-8")
|
||||
fn respond_to(self, _: &HttpRequest) -> HttpResponse {
|
||||
HttpResponse::Ok()
|
||||
.content_type(mime::TEXT_PLAIN_UTF_8)
|
||||
.body(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Responder for &'a String {
|
||||
fn respond_to(self, _: &HttpRequest) -> Response {
|
||||
Response::build(StatusCode::OK)
|
||||
.content_type("text/plain; charset=utf-8")
|
||||
fn respond_to(self, _: &HttpRequest) -> HttpResponse {
|
||||
HttpResponse::Ok()
|
||||
.content_type(mime::TEXT_PLAIN_UTF_8)
|
||||
.body(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Responder for Bytes {
|
||||
fn respond_to(self, _: &HttpRequest) -> Response {
|
||||
Response::build(StatusCode::OK)
|
||||
.content_type("application/octet-stream")
|
||||
fn respond_to(self, _: &HttpRequest) -> HttpResponse {
|
||||
HttpResponse::Ok()
|
||||
.content_type(mime::APPLICATION_OCTET_STREAM)
|
||||
.body(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Responder for BytesMut {
|
||||
fn respond_to(self, _: &HttpRequest) -> Response {
|
||||
Response::build(StatusCode::OK)
|
||||
.content_type("application/octet-stream")
|
||||
fn respond_to(self, _: &HttpRequest) -> HttpResponse {
|
||||
HttpResponse::Ok()
|
||||
.content_type(mime::APPLICATION_OCTET_STREAM)
|
||||
.body(self)
|
||||
}
|
||||
}
|
||||
@ -231,7 +231,7 @@ impl<T: Responder> CustomResponder<T> {
|
||||
}
|
||||
|
||||
impl<T: Responder> Responder for CustomResponder<T> {
|
||||
fn respond_to(self, req: &HttpRequest) -> Response {
|
||||
fn respond_to(self, req: &HttpRequest) -> HttpResponse {
|
||||
let mut res = self.responder.respond_to(req);
|
||||
|
||||
if let Some(status) = self.status {
|
||||
@ -252,8 +252,8 @@ impl<T> Responder for InternalError<T>
|
||||
where
|
||||
T: std::fmt::Debug + std::fmt::Display + 'static,
|
||||
{
|
||||
fn respond_to(self, _: &HttpRequest) -> Response {
|
||||
Response::from_error(self.into())
|
||||
fn respond_to(self, _: &HttpRequest) -> HttpResponse {
|
||||
HttpResponse::from_error(self.into())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,93 +1,170 @@
|
||||
use actix_http::{Error, Response};
|
||||
//! For either helper, see [`Either`].
|
||||
|
||||
use bytes::Bytes;
|
||||
use futures_util::{future::LocalBoxFuture, FutureExt, TryFutureExt};
|
||||
|
||||
use crate::{dev, request::HttpRequest, FromRequest, Responder};
|
||||
use crate::{
|
||||
dev,
|
||||
web::{Form, Json},
|
||||
Error, FromRequest, HttpRequest, HttpResponse, Responder,
|
||||
};
|
||||
|
||||
/// Combines two different responder types into a single type
|
||||
/// Combines two extractor or responder types into a single type.
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_web::{Either, Error, HttpResponse};
|
||||
/// Can be converted to and from an [`either::Either`].
|
||||
///
|
||||
/// type RegisterResult = Either<HttpResponse, Result<HttpResponse, Error>>;
|
||||
/// # Extractor
|
||||
/// Provides a mechanism for trying two extractors, a primary and a fallback. Useful for
|
||||
/// "polymorphic payloads" where, for example, a form might be JSON or URL encoded.
|
||||
///
|
||||
/// fn index() -> RegisterResult {
|
||||
/// if is_a_variant() {
|
||||
/// // <- choose left variant
|
||||
/// Either::A(HttpResponse::BadRequest().body("Bad data"))
|
||||
/// It is important to note that this extractor, by necessity, buffers the entire request payload
|
||||
/// as part of its implementation. Though, it does respect any `PayloadConfig` maximum size limits.
|
||||
///
|
||||
/// ```
|
||||
/// use actix_web::{post, web, Either};
|
||||
/// use serde::Deserialize;
|
||||
///
|
||||
/// #[derive(Deserialize)]
|
||||
/// struct Info {
|
||||
/// name: String,
|
||||
/// }
|
||||
///
|
||||
/// // handler that accepts form as JSON or form-urlencoded.
|
||||
/// #[post("/")]
|
||||
/// async fn index(form: Either<web::Json<Info>, web::Form<Info>>) -> String {
|
||||
/// let name: String = match form {
|
||||
/// Either::Left(json) => json.name.to_owned(),
|
||||
/// Either::Right(form) => form.name.to_owned(),
|
||||
/// };
|
||||
///
|
||||
/// format!("Welcome {}!", name)
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # Responder
|
||||
/// It may be desireable to use a concrete type for a response with multiple branches. As long as
|
||||
/// both types implement `Responder`, so will the `Either` type, enabling it to be used as a
|
||||
/// handler's return type.
|
||||
///
|
||||
/// All properties of a response are determined by the Responder branch returned.
|
||||
///
|
||||
/// ```
|
||||
/// use actix_web::{get, Either, Error, HttpResponse};
|
||||
///
|
||||
/// #[get("/")]
|
||||
/// async fn index() -> Either<&'static str, Result<HttpResponse, Error>> {
|
||||
/// if 1 == 2 {
|
||||
/// // respond with Left variant
|
||||
/// Either::Left("Bad data")
|
||||
/// } else {
|
||||
/// Either::B(
|
||||
/// // <- Right variant
|
||||
/// // respond with Right variant
|
||||
/// Either::Right(
|
||||
/// Ok(HttpResponse::Ok()
|
||||
/// .content_type("text/html")
|
||||
/// .body("Hello!"))
|
||||
/// .content_type(mime::TEXT_HTML)
|
||||
/// .body("<p>Hello!</p>"))
|
||||
/// )
|
||||
/// }
|
||||
/// }
|
||||
/// # fn is_a_variant() -> bool { true }
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Either<A, B> {
|
||||
/// First branch of the type
|
||||
A(A),
|
||||
/// Second branch of the type
|
||||
B(B),
|
||||
pub enum Either<L, R> {
|
||||
/// A value of type `L`.
|
||||
Left(L),
|
||||
|
||||
/// A value of type `R`.
|
||||
Right(R),
|
||||
}
|
||||
|
||||
impl<T> Either<Form<T>, Json<T>> {
|
||||
pub fn into_inner(self) -> T {
|
||||
match self {
|
||||
Either::Left(form) => form.into_inner(),
|
||||
Either::Right(form) => form.into_inner(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Either<Json<T>, Form<T>> {
|
||||
pub fn into_inner(self) -> T {
|
||||
match self {
|
||||
Either::Left(form) => form.into_inner(),
|
||||
Either::Right(form) => form.into_inner(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<L, R> From<either::Either<L, R>> for Either<L, R> {
|
||||
fn from(val: either::Either<L, R>) -> Self {
|
||||
match val {
|
||||
either::Either::Left(l) => Either::Left(l),
|
||||
either::Either::Right(r) => Either::Right(r),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<L, R> From<Either<L, R>> for either::Either<L, R> {
|
||||
fn from(val: Either<L, R>) -> Self {
|
||||
match val {
|
||||
Either::Left(l) => either::Either::Left(l),
|
||||
Either::Right(r) => either::Either::Right(r),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl<A, B> Either<A, B> {
|
||||
pub(self) fn unwrap_left(self) -> A {
|
||||
impl<L, R> Either<L, R> {
|
||||
pub(self) fn unwrap_left(self) -> L {
|
||||
match self {
|
||||
Either::A(data) => data,
|
||||
Either::B(_) => {
|
||||
panic!("Cannot unwrap left branch. Either contains a right branch.")
|
||||
Either::Left(data) => data,
|
||||
Either::Right(_) => {
|
||||
panic!("Cannot unwrap Left branch. Either contains an `R` type.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(self) fn unwrap_right(self) -> B {
|
||||
pub(self) fn unwrap_right(self) -> R {
|
||||
match self {
|
||||
Either::A(_) => {
|
||||
panic!("Cannot unwrap right branch. Either contains a left branch.")
|
||||
Either::Left(_) => {
|
||||
panic!("Cannot unwrap Right branch. Either contains an `L` type.")
|
||||
}
|
||||
Either::B(data) => data,
|
||||
Either::Right(data) => data,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, B> Responder for Either<A, B>
|
||||
/// See [here](#responder) for example of usage as a handler return type.
|
||||
impl<L, R> Responder for Either<L, R>
|
||||
where
|
||||
A: Responder,
|
||||
B: Responder,
|
||||
L: Responder,
|
||||
R: Responder,
|
||||
{
|
||||
fn respond_to(self, req: &HttpRequest) -> Response {
|
||||
fn respond_to(self, req: &HttpRequest) -> HttpResponse {
|
||||
match self {
|
||||
Either::A(a) => a.respond_to(req),
|
||||
Either::B(b) => b.respond_to(req),
|
||||
Either::Left(a) => a.respond_to(req),
|
||||
Either::Right(b) => b.respond_to(req),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A composite error resulting from failure to extract an `Either<A, B>`.
|
||||
/// A composite error resulting from failure to extract an `Either<L, R>`.
|
||||
///
|
||||
/// The implementation of `Into<actix_web::Error>` will return the payload buffering error or the
|
||||
/// error from the primary extractor. To access the fallback error, use a match clause.
|
||||
#[derive(Debug)]
|
||||
pub enum EitherExtractError<A, B> {
|
||||
pub enum EitherExtractError<L, R> {
|
||||
/// Error from payload buffering, such as exceeding payload max size limit.
|
||||
Bytes(Error),
|
||||
|
||||
/// Error from primary extractor.
|
||||
Extract(A, B),
|
||||
Extract(L, R),
|
||||
}
|
||||
|
||||
impl<A, B> From<EitherExtractError<A, B>> for Error
|
||||
impl<L, R> From<EitherExtractError<L, R>> for Error
|
||||
where
|
||||
A: Into<Error>,
|
||||
B: Into<Error>,
|
||||
L: Into<Error>,
|
||||
R: Into<Error>,
|
||||
{
|
||||
fn from(err: EitherExtractError<A, B>) -> Error {
|
||||
fn from(err: EitherExtractError<L, R>) -> Error {
|
||||
match err {
|
||||
EitherExtractError::Bytes(err) => err,
|
||||
EitherExtractError::Extract(a_err, _b_err) => a_err.into(),
|
||||
@ -95,17 +172,13 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides a mechanism for trying two extractors, a primary and a fallback. Useful for
|
||||
/// "polymorphic payloads" where, for example, a form might be JSON or URL encoded.
|
||||
///
|
||||
/// It is important to note that this extractor, by necessity, buffers the entire request payload
|
||||
/// as part of its implementation. Though, it does respect a `PayloadConfig`'s maximum size limit.
|
||||
impl<A, B> FromRequest for Either<A, B>
|
||||
/// See [here](#extractor) for example of usage as an extractor.
|
||||
impl<L, R> FromRequest for Either<L, R>
|
||||
where
|
||||
A: FromRequest + 'static,
|
||||
B: FromRequest + 'static,
|
||||
L: FromRequest + 'static,
|
||||
R: FromRequest + 'static,
|
||||
{
|
||||
type Error = EitherExtractError<A::Error, B::Error>;
|
||||
type Error = EitherExtractError<L::Error, R::Error>;
|
||||
type Future = LocalBoxFuture<'static, Result<Self, Self::Error>>;
|
||||
type Config = ();
|
||||
|
||||
@ -114,32 +187,32 @@ where
|
||||
|
||||
Bytes::from_request(req, payload)
|
||||
.map_err(EitherExtractError::Bytes)
|
||||
.and_then(|bytes| bytes_to_a_or_b(req2, bytes))
|
||||
.and_then(|bytes| bytes_to_l_or_r(req2, bytes))
|
||||
.boxed_local()
|
||||
}
|
||||
}
|
||||
|
||||
async fn bytes_to_a_or_b<A, B>(
|
||||
async fn bytes_to_l_or_r<L, R>(
|
||||
req: HttpRequest,
|
||||
bytes: Bytes,
|
||||
) -> Result<Either<A, B>, EitherExtractError<A::Error, B::Error>>
|
||||
) -> Result<Either<L, R>, EitherExtractError<L::Error, R::Error>>
|
||||
where
|
||||
A: FromRequest + 'static,
|
||||
B: FromRequest + 'static,
|
||||
L: FromRequest + 'static,
|
||||
R: FromRequest + 'static,
|
||||
{
|
||||
let fallback = bytes.clone();
|
||||
let a_err;
|
||||
|
||||
let mut pl = payload_from_bytes(bytes);
|
||||
match A::from_request(&req, &mut pl).await {
|
||||
Ok(a_data) => return Ok(Either::A(a_data)),
|
||||
match L::from_request(&req, &mut pl).await {
|
||||
Ok(a_data) => return Ok(Either::Left(a_data)),
|
||||
// store A's error for returning if B also fails
|
||||
Err(err) => a_err = err,
|
||||
};
|
||||
|
||||
let mut pl = payload_from_bytes(fallback);
|
||||
match B::from_request(&req, &mut pl).await {
|
||||
Ok(b_data) => return Ok(Either::B(b_data)),
|
||||
match R::from_request(&req, &mut pl).await {
|
||||
Ok(b_data) => return Ok(Either::Right(b_data)),
|
||||
Err(b_err) => Err(EitherExtractError::Extract(a_err, b_err)),
|
||||
}
|
||||
}
|
||||
|
@ -1,72 +1,68 @@
|
||||
//! Form extractor
|
||||
//! For URL encoded form helper documentation, see [`Form`].
|
||||
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::rc::Rc;
|
||||
use std::task::{Context, Poll};
|
||||
use std::{fmt, ops};
|
||||
use std::{
|
||||
fmt,
|
||||
future::Future,
|
||||
ops,
|
||||
pin::Pin,
|
||||
rc::Rc,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use actix_http::{Error, HttpMessage, Payload, Response};
|
||||
use actix_http::Payload;
|
||||
use bytes::BytesMut;
|
||||
use encoding_rs::{Encoding, UTF_8};
|
||||
use futures_util::future::{FutureExt, LocalBoxFuture};
|
||||
use futures_util::StreamExt;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
use futures_util::{
|
||||
future::{FutureExt, LocalBoxFuture},
|
||||
StreamExt,
|
||||
};
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
|
||||
#[cfg(feature = "compress")]
|
||||
use crate::dev::Decompress;
|
||||
use crate::error::UrlencodedError;
|
||||
use crate::extract::FromRequest;
|
||||
use crate::http::{
|
||||
header::{ContentType, CONTENT_LENGTH},
|
||||
StatusCode,
|
||||
use crate::{
|
||||
error::UrlencodedError, extract::FromRequest, http::header::CONTENT_LENGTH, web,
|
||||
Error, HttpMessage, HttpRequest, HttpResponse, Responder,
|
||||
};
|
||||
use crate::request::HttpRequest;
|
||||
use crate::{responder::Responder, web};
|
||||
|
||||
/// Form data helper (`application/x-www-form-urlencoded`)
|
||||
/// URL encoded payload extractor and responder.
|
||||
///
|
||||
/// Can be use to extract url-encoded data from the request body,
|
||||
/// or send url-encoded data as the response.
|
||||
/// `Form` has two uses: URL encoded responses, and extracting typed data from URL request payloads.
|
||||
///
|
||||
/// ## Extract
|
||||
/// # Extractor
|
||||
/// To extract typed data from a request body, the inner type `T` must implement the
|
||||
/// [`serde::Deserialize`] trait.
|
||||
///
|
||||
/// To extract typed information from request's body, the type `T` must
|
||||
/// implement the `Deserialize` trait from *serde*.
|
||||
/// Use [`FormConfig`] to configure extraction process.
|
||||
///
|
||||
/// [**FormConfig**](FormConfig) allows to configure extraction
|
||||
/// process.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// use actix_web::web;
|
||||
/// use serde_derive::Deserialize;
|
||||
/// ```
|
||||
/// use actix_web::{post, web};
|
||||
/// use serde::Deserialize;
|
||||
///
|
||||
/// #[derive(Deserialize)]
|
||||
/// struct FormData {
|
||||
/// username: String,
|
||||
/// struct Info {
|
||||
/// name: String,
|
||||
/// }
|
||||
///
|
||||
/// /// Extract form data using serde.
|
||||
/// /// This handler get called only if content type is *x-www-form-urlencoded*
|
||||
/// /// and content of the request could be deserialized to a `FormData` struct
|
||||
/// fn index(form: web::Form<FormData>) -> String {
|
||||
/// format!("Welcome {}!", form.username)
|
||||
/// // This handler is only called if:
|
||||
/// // - request headers declare the content type as `application/x-www-form-urlencoded`
|
||||
/// // - request payload is deserialized into a `Info` struct from the URL encoded format
|
||||
/// #[post("/")]
|
||||
/// async fn index(form: web::Form<Info>) -> String {
|
||||
/// format!("Welcome {}!", form.name)
|
||||
/// }
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
///
|
||||
/// ## Respond
|
||||
/// # Responder
|
||||
/// The `Form` type also allows you to create URL encoded responses:
|
||||
/// simply return a value of type Form<T> where T is the type to be URL encoded.
|
||||
/// The type must implement [`serde::Serialize`].
|
||||
///
|
||||
/// The `Form` type also allows you to respond with well-formed url-encoded data:
|
||||
/// simply return a value of type Form<T> where T is the type to be url-encoded.
|
||||
/// The type must implement `serde::Serialize`;
|
||||
/// Responses use
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// use actix_web::*;
|
||||
/// use serde_derive::Serialize;
|
||||
/// ```
|
||||
/// use actix_web::{get, web};
|
||||
/// use serde::Serialize;
|
||||
///
|
||||
/// #[derive(Serialize)]
|
||||
/// struct SomeForm {
|
||||
@ -74,22 +70,23 @@ use crate::{responder::Responder, web};
|
||||
/// age: u8
|
||||
/// }
|
||||
///
|
||||
/// // Will return a 200 response with header
|
||||
/// // `Content-Type: application/x-www-form-urlencoded`
|
||||
/// // and body "name=actix&age=123"
|
||||
/// fn index() -> web::Form<SomeForm> {
|
||||
/// // Response will have:
|
||||
/// // - status: 200 OK
|
||||
/// // - header: `Content-Type: application/x-www-form-urlencoded`
|
||||
/// // - body: `name=actix&age=123`
|
||||
/// #[get("/")]
|
||||
/// async fn index() -> web::Form<SomeForm> {
|
||||
/// web::Form(SomeForm {
|
||||
/// name: "actix".into(),
|
||||
/// age: 123
|
||||
/// })
|
||||
/// }
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct Form<T>(pub T);
|
||||
|
||||
impl<T> Form<T> {
|
||||
/// Deconstruct to an inner value
|
||||
/// Unwrap into inner `T` value.
|
||||
pub fn into_inner(self) -> T {
|
||||
self.0
|
||||
}
|
||||
@ -109,6 +106,7 @@ impl<T> ops::DerefMut for Form<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// See [here](#extractor) for example of usage as an extractor.
|
||||
impl<T> FromRequest for Form<T>
|
||||
where
|
||||
T: DeserializeOwned + 'static,
|
||||
@ -120,7 +118,7 @@ where
|
||||
#[inline]
|
||||
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
|
||||
let req2 = req.clone();
|
||||
let (limit, err) = req
|
||||
let (limit, err_handler) = req
|
||||
.app_data::<Self::Config>()
|
||||
.or_else(|| {
|
||||
req.app_data::<web::Data<Self::Config>>()
|
||||
@ -132,13 +130,10 @@ where
|
||||
UrlEncoded::new(req, payload)
|
||||
.limit(limit)
|
||||
.map(move |res| match res {
|
||||
Err(e) => {
|
||||
if let Some(err) = err {
|
||||
Err((*err)(e, &req2))
|
||||
} else {
|
||||
Err(e.into())
|
||||
}
|
||||
}
|
||||
Err(err) => match err_handler {
|
||||
Some(err_handler) => Err((err_handler)(err, &req2)),
|
||||
None => Err(err.into()),
|
||||
},
|
||||
Ok(item) => Ok(Form(item)),
|
||||
})
|
||||
.boxed_local()
|
||||
@ -157,44 +152,39 @@ impl<T: fmt::Display> fmt::Display for Form<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// See [here](#responder) for example of usage as a handler return type.
|
||||
impl<T: Serialize> Responder for Form<T> {
|
||||
fn respond_to(self, _: &HttpRequest) -> Response {
|
||||
fn respond_to(self, _: &HttpRequest) -> HttpResponse {
|
||||
match serde_urlencoded::to_string(&self.0) {
|
||||
Ok(body) => Response::build(StatusCode::OK)
|
||||
.set(ContentType::form_url_encoded())
|
||||
Ok(body) => HttpResponse::Ok()
|
||||
.content_type(mime::APPLICATION_WWW_FORM_URLENCODED)
|
||||
.body(body),
|
||||
Err(e) => Response::from_error(e.into()),
|
||||
Err(err) => HttpResponse::from_error(err.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Form extractor configuration
|
||||
/// [`Form`] extractor configuration.
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_web::{web, App, FromRequest, Result};
|
||||
/// use serde_derive::Deserialize;
|
||||
/// ```
|
||||
/// use actix_web::{post, web, App, FromRequest, Result};
|
||||
/// use serde::Deserialize;
|
||||
///
|
||||
/// #[derive(Deserialize)]
|
||||
/// struct FormData {
|
||||
/// struct Info {
|
||||
/// username: String,
|
||||
/// }
|
||||
///
|
||||
/// /// Extract form data using serde.
|
||||
/// /// Custom configuration is used for this handler, max payload size is 4k
|
||||
/// async fn index(form: web::Form<FormData>) -> Result<String> {
|
||||
/// // Custom `FormConfig` is applied to App.
|
||||
/// // Max payload size for URL encoded forms is set to 4kB.
|
||||
/// #[post("/")]
|
||||
/// async fn index(form: web::Form<Info>) -> Result<String> {
|
||||
/// Ok(format!("Welcome {}!", form.username))
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new().service(
|
||||
/// web::resource("/index.html")
|
||||
/// // change `Form` extractor configuration
|
||||
/// .app_data(
|
||||
/// web::FormConfig::default().limit(4097)
|
||||
/// )
|
||||
/// .route(web::get().to(index))
|
||||
/// );
|
||||
/// }
|
||||
/// App::new()
|
||||
/// .app_data(web::FormConfig::default().limit(4096))
|
||||
/// .service(index);
|
||||
/// ```
|
||||
#[derive(Clone)]
|
||||
pub struct FormConfig {
|
||||
@ -203,7 +193,7 @@ pub struct FormConfig {
|
||||
}
|
||||
|
||||
impl FormConfig {
|
||||
/// Change max size of payload. By default max size is 16Kb
|
||||
/// Set maximum accepted payload size. By default this limit is 16kB.
|
||||
pub fn limit(mut self, limit: usize) -> Self {
|
||||
self.limit = limit;
|
||||
self
|
||||
@ -228,33 +218,30 @@ impl Default for FormConfig {
|
||||
}
|
||||
}
|
||||
|
||||
/// Future that resolves to a parsed urlencoded values.
|
||||
/// Future that resolves to some `T` when parsed from a URL encoded payload.
|
||||
///
|
||||
/// Parse `application/x-www-form-urlencoded` encoded request's body.
|
||||
/// Return `UrlEncoded` future. Form can be deserialized to any type that
|
||||
/// implements `Deserialize` trait from *serde*.
|
||||
/// Form can be deserialized from any type `T` that implements [`serde::Deserialize`].
|
||||
///
|
||||
/// Returns error:
|
||||
///
|
||||
/// * content type is not `application/x-www-form-urlencoded`
|
||||
/// * content-length is greater than 32k
|
||||
///
|
||||
pub struct UrlEncoded<U> {
|
||||
/// Returns error if:
|
||||
/// - content type is not `application/x-www-form-urlencoded`
|
||||
/// - content length is greater than [limit](UrlEncoded::limit())
|
||||
pub struct UrlEncoded<T> {
|
||||
#[cfg(feature = "compress")]
|
||||
stream: Option<Decompress<Payload>>,
|
||||
#[cfg(not(feature = "compress"))]
|
||||
stream: Option<Payload>,
|
||||
|
||||
limit: usize,
|
||||
length: Option<usize>,
|
||||
encoding: &'static Encoding,
|
||||
err: Option<UrlencodedError>,
|
||||
fut: Option<LocalBoxFuture<'static, Result<U, UrlencodedError>>>,
|
||||
fut: Option<LocalBoxFuture<'static, Result<T, UrlencodedError>>>,
|
||||
}
|
||||
|
||||
#[allow(clippy::borrow_interior_mutable_const)]
|
||||
impl<U> UrlEncoded<U> {
|
||||
/// Create a new future to URL encode a request
|
||||
pub fn new(req: &HttpRequest, payload: &mut Payload) -> UrlEncoded<U> {
|
||||
impl<T> UrlEncoded<T> {
|
||||
/// Create a new future to decode a URL encoded request payload.
|
||||
pub fn new(req: &HttpRequest, payload: &mut Payload) -> Self {
|
||||
// check content type
|
||||
if req.content_type().to_lowercase() != "application/x-www-form-urlencoded" {
|
||||
return Self::err(UrlencodedError::ContentType);
|
||||
@ -292,29 +279,29 @@ impl<U> UrlEncoded<U> {
|
||||
}
|
||||
}
|
||||
|
||||
fn err(e: UrlencodedError) -> Self {
|
||||
fn err(err: UrlencodedError) -> Self {
|
||||
UrlEncoded {
|
||||
stream: None,
|
||||
limit: 32_768,
|
||||
fut: None,
|
||||
err: Some(e),
|
||||
err: Some(err),
|
||||
length: None,
|
||||
encoding: UTF_8,
|
||||
}
|
||||
}
|
||||
|
||||
/// Change max size of payload. By default max size is 256Kb
|
||||
/// Set maximum accepted payload size. The default limit is 256kB.
|
||||
pub fn limit(mut self, limit: usize) -> Self {
|
||||
self.limit = limit;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<U> Future for UrlEncoded<U>
|
||||
impl<T> Future for UrlEncoded<T>
|
||||
where
|
||||
U: DeserializeOwned + 'static,
|
||||
T: DeserializeOwned + 'static,
|
||||
{
|
||||
type Output = Result<U, UrlencodedError>;
|
||||
type Output = Result<T, UrlencodedError>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
if let Some(ref mut fut) = self.fut {
|
||||
@ -343,6 +330,7 @@ where
|
||||
|
||||
while let Some(item) = stream.next().await {
|
||||
let chunk = item?;
|
||||
|
||||
if (body.len() + chunk.len()) > limit {
|
||||
return Err(UrlencodedError::Overflow {
|
||||
size: body.len() + chunk.len(),
|
||||
@ -354,19 +342,21 @@ where
|
||||
}
|
||||
|
||||
if encoding == UTF_8 {
|
||||
serde_urlencoded::from_bytes::<U>(&body)
|
||||
serde_urlencoded::from_bytes::<T>(&body)
|
||||
.map_err(|_| UrlencodedError::Parse)
|
||||
} else {
|
||||
let body = encoding
|
||||
.decode_without_bom_handling_and_without_replacement(&body)
|
||||
.map(|s| s.into_owned())
|
||||
.ok_or(UrlencodedError::Parse)?;
|
||||
serde_urlencoded::from_str::<U>(&body)
|
||||
|
||||
serde_urlencoded::from_str::<T>(&body)
|
||||
.map_err(|_| UrlencodedError::Parse)
|
||||
}
|
||||
}
|
||||
.boxed_local(),
|
||||
);
|
||||
|
||||
self.poll(cx)
|
||||
}
|
||||
}
|
||||
@ -377,7 +367,10 @@ mod tests {
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::*;
|
||||
use crate::http::header::{HeaderValue, CONTENT_LENGTH, CONTENT_TYPE};
|
||||
use crate::http::{
|
||||
header::{HeaderValue, CONTENT_LENGTH, CONTENT_TYPE},
|
||||
StatusCode,
|
||||
};
|
||||
use crate::test::TestRequest;
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, PartialEq)]
|
||||
@ -514,6 +507,6 @@ mod tests {
|
||||
assert!(s.is_err());
|
||||
|
||||
let err_str = s.err().unwrap().to_string();
|
||||
assert!(err_str.contains("Urlencoded payload size is bigger"));
|
||||
assert!(err_str.starts_with("URL encoded payload is larger"));
|
||||
}
|
||||
}
|
||||
|
@ -1,45 +1,44 @@
|
||||
//! Json extractor/responder
|
||||
//! For JSON helper documentation, see [`Json`].
|
||||
|
||||
use std::future::Future;
|
||||
use std::marker::PhantomData;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use std::task::{Context, Poll};
|
||||
use std::{fmt, ops};
|
||||
use std::{
|
||||
fmt,
|
||||
future::Future,
|
||||
marker::PhantomData,
|
||||
ops,
|
||||
pin::Pin,
|
||||
sync::Arc,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use bytes::BytesMut;
|
||||
use futures_util::ready;
|
||||
use futures_util::stream::Stream;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
use futures_util::{ready, stream::Stream};
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
|
||||
use actix_http::http::{header::CONTENT_LENGTH, StatusCode};
|
||||
use actix_http::{HttpMessage, Payload, Response};
|
||||
use actix_http::Payload;
|
||||
|
||||
#[cfg(feature = "compress")]
|
||||
use crate::dev::Decompress;
|
||||
use crate::error::{Error, JsonPayloadError};
|
||||
use crate::extract::FromRequest;
|
||||
use crate::request::HttpRequest;
|
||||
use crate::{responder::Responder, web};
|
||||
use crate::{
|
||||
error::{Error, JsonPayloadError},
|
||||
extract::FromRequest,
|
||||
http::header::CONTENT_LENGTH,
|
||||
request::HttpRequest,
|
||||
web, HttpMessage, HttpResponse, Responder,
|
||||
};
|
||||
|
||||
/// Json helper
|
||||
/// JSON extractor and responder.
|
||||
///
|
||||
/// Json can be used for two different purpose. First is for json response
|
||||
/// generation and second is for extracting typed information from request's
|
||||
/// payload.
|
||||
/// `Json` has two uses: JSON responses, and extracting typed data from JSON request payloads.
|
||||
///
|
||||
/// To extract typed information from request's body, the type `T` must
|
||||
/// implement the `Deserialize` trait from *serde*.
|
||||
/// # Extractor
|
||||
/// To extract typed data from a request body, the inner type `T` must implement the
|
||||
/// [`serde::Deserialize`] trait.
|
||||
///
|
||||
/// [**JsonConfig**](JsonConfig) allows to configure extraction
|
||||
/// process.
|
||||
/// Use [`JsonConfig`] to configure extraction process.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_web::{web, App};
|
||||
/// use serde_derive::Deserialize;
|
||||
/// ```
|
||||
/// use actix_web::{post, web, App};
|
||||
/// use serde::Deserialize;
|
||||
///
|
||||
/// #[derive(Deserialize)]
|
||||
/// struct Info {
|
||||
@ -47,43 +46,37 @@ use crate::{responder::Responder, web};
|
||||
/// }
|
||||
///
|
||||
/// /// deserialize `Info` from request's body
|
||||
/// #[post("/")]
|
||||
/// async fn index(info: web::Json<Info>) -> String {
|
||||
/// format!("Welcome {}!", info.username)
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new().service(
|
||||
/// web::resource("/index.html").route(
|
||||
/// web::post().to(index))
|
||||
/// );
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// The `Json` type allows you to respond with well-formed JSON data: simply
|
||||
/// return a value of type Json<T> where T is the type of a structure
|
||||
/// to serialize into *JSON*. The type `T` must implement the `Serialize`
|
||||
/// trait from *serde*.
|
||||
/// # Responder
|
||||
/// The `Json` type JSON formatted responses. A handler may return a value of type
|
||||
/// `Json<T>` where `T` is the type of a structure to serialize into JSON. The type `T` must
|
||||
/// implement [`serde::Serialize`].
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_web::*;
|
||||
/// use serde_derive::Serialize;
|
||||
/// ```
|
||||
/// use actix_web::{post, web, HttpRequest};
|
||||
/// use serde::Serialize;
|
||||
///
|
||||
/// #[derive(Serialize)]
|
||||
/// struct MyObj {
|
||||
/// struct Info {
|
||||
/// name: String,
|
||||
/// }
|
||||
///
|
||||
/// fn index(req: HttpRequest) -> Result<web::Json<MyObj>> {
|
||||
/// Ok(web::Json(MyObj {
|
||||
/// name: req.match_info().get("name").unwrap().to_string(),
|
||||
/// }))
|
||||
/// #[post("/{name}")]
|
||||
/// async fn index(req: HttpRequest) -> web::Json<Info> {
|
||||
/// web::Json(Info {
|
||||
/// name: req.match_info().get("name").unwrap().to_owned(),
|
||||
/// })
|
||||
/// }
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
pub struct Json<T>(pub T);
|
||||
|
||||
impl<T> Json<T> {
|
||||
/// Deconstruct to an inner value
|
||||
/// Unwrap into inner `T` value.
|
||||
pub fn into_inner(self) -> T {
|
||||
self.0
|
||||
}
|
||||
@ -121,49 +114,21 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates response with OK status code, correct content type header, and serialized JSON payload.
|
||||
///
|
||||
/// If serialization failed
|
||||
impl<T: Serialize> Responder for Json<T> {
|
||||
fn respond_to(self, _: &HttpRequest) -> Response {
|
||||
fn respond_to(self, _: &HttpRequest) -> HttpResponse {
|
||||
match serde_json::to_string(&self.0) {
|
||||
Ok(body) => Response::build(StatusCode::OK)
|
||||
.content_type("application/json")
|
||||
Ok(body) => HttpResponse::Ok()
|
||||
.content_type(mime::APPLICATION_JSON)
|
||||
.body(body),
|
||||
Err(e) => Response::from_error(e.into()),
|
||||
Err(err) => HttpResponse::from_error(err.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Json extractor. Allow to extract typed information from request's
|
||||
/// payload.
|
||||
///
|
||||
/// To extract typed information from request's body, the type `T` must
|
||||
/// implement the `Deserialize` trait from *serde*.
|
||||
///
|
||||
/// [**JsonConfig**](JsonConfig) allows to configure extraction
|
||||
/// process.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_web::{web, App};
|
||||
/// use serde_derive::Deserialize;
|
||||
///
|
||||
/// #[derive(Deserialize)]
|
||||
/// struct Info {
|
||||
/// username: String,
|
||||
/// }
|
||||
///
|
||||
/// /// deserialize `Info` from request's body
|
||||
/// async fn index(info: web::Json<Info>) -> String {
|
||||
/// format!("Welcome {}!", info.username)
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new().service(
|
||||
/// web::resource("/index.html").route(
|
||||
/// web::post().to(index))
|
||||
/// );
|
||||
/// }
|
||||
/// ```
|
||||
/// See [here](#extractor) for example of usage as an extractor.
|
||||
impl<T> FromRequest for Json<T>
|
||||
where
|
||||
T: DeserializeOwned + 'static,
|
||||
@ -209,7 +174,7 @@ where
|
||||
let res = ready!(Pin::new(&mut this.fut).poll(cx));
|
||||
|
||||
let res = match res {
|
||||
Err(e) => {
|
||||
Err(err) => {
|
||||
let req = this.req.take().unwrap();
|
||||
log::debug!(
|
||||
"Failed to deserialize Json from payload. \
|
||||
@ -217,10 +182,10 @@ where
|
||||
req.path()
|
||||
);
|
||||
|
||||
if let Some(err) = this.err_handler.as_ref() {
|
||||
Err((*err)(e, &req))
|
||||
if let Some(err_handler) = this.err_handler.as_ref() {
|
||||
Err((*err_handler)(err, &req))
|
||||
} else {
|
||||
Err(e.into())
|
||||
Err(err.into())
|
||||
}
|
||||
}
|
||||
Ok(data) => Ok(Json(data)),
|
||||
@ -230,44 +195,39 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Json extractor configuration
|
||||
/// `Json` extractor configuration.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_web::{error, web, App, FromRequest, HttpResponse};
|
||||
/// use serde_derive::Deserialize;
|
||||
/// # Usage
|
||||
/// ```
|
||||
/// use actix_web::{error, post, web, App, FromRequest, HttpResponse};
|
||||
/// use serde::Deserialize;
|
||||
///
|
||||
/// #[derive(Deserialize)]
|
||||
/// struct Info {
|
||||
/// username: String,
|
||||
/// name: String,
|
||||
/// }
|
||||
///
|
||||
/// /// deserialize `Info` from request's body, max payload size is 4kb
|
||||
/// // `Json` extraction is bound by custom `JsonConfig` applied to App.
|
||||
/// #[post("/")]
|
||||
/// async fn index(info: web::Json<Info>) -> String {
|
||||
/// format!("Welcome {}!", info.username)
|
||||
/// format!("Welcome {}!", info.name)
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new().service(
|
||||
/// web::resource("/index.html")
|
||||
/// .app_data(
|
||||
/// // Json extractor configuration for this resource.
|
||||
/// web::JsonConfig::default()
|
||||
/// .limit(4096) // Limit request payload size
|
||||
/// .content_type(|mime| { // <- accept text/plain content type
|
||||
/// mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN
|
||||
/// })
|
||||
/// .error_handler(|err, req| { // <- create custom error response
|
||||
/// error::InternalError::from_response(
|
||||
/// err, HttpResponse::Conflict().finish()).into()
|
||||
/// })
|
||||
/// )
|
||||
/// .route(web::post().to(index))
|
||||
/// );
|
||||
/// }
|
||||
/// // custom `Json` extractor configuration
|
||||
/// let json_cfg = web::JsonConfig::default()
|
||||
/// // limit request payload size
|
||||
/// .limit(4096)
|
||||
/// // only accept text/plain content type
|
||||
/// .content_type(|mime| mime == mime::TEXT_PLAIN)
|
||||
/// // use custom error handler
|
||||
/// .error_handler(|err, req| {
|
||||
/// error::InternalError::from_response(err, HttpResponse::Conflict().finish()).into()
|
||||
/// });
|
||||
///
|
||||
/// App::new()
|
||||
/// .app_data(json_cfg)
|
||||
/// .service(index);
|
||||
/// ```
|
||||
///
|
||||
#[derive(Clone)]
|
||||
pub struct JsonConfig {
|
||||
limit: usize,
|
||||
@ -276,13 +236,13 @@ pub struct JsonConfig {
|
||||
}
|
||||
|
||||
impl JsonConfig {
|
||||
/// Change max size of payload. By default max size is 32Kb
|
||||
/// Set maximum accepted payload size. By default this limit is 32kB.
|
||||
pub fn limit(mut self, limit: usize) -> Self {
|
||||
self.limit = limit;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set custom error handler
|
||||
/// Set custom error handler.
|
||||
pub fn error_handler<F>(mut self, f: F) -> Self
|
||||
where
|
||||
F: Fn(JsonPayloadError, &HttpRequest) -> Error + Send + Sync + 'static,
|
||||
@ -291,7 +251,7 @@ impl JsonConfig {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set predicate for allowed content types
|
||||
/// Set predicate for allowed content types.
|
||||
pub fn content_type<F>(mut self, predicate: F) -> Self
|
||||
where
|
||||
F: Fn(mime::Mime) -> bool + Send + Sync + 'static,
|
||||
@ -322,15 +282,14 @@ impl Default for JsonConfig {
|
||||
}
|
||||
}
|
||||
|
||||
/// Request's payload json parser, it resolves to a deserialized `T` value.
|
||||
/// This future could be used with `ServiceRequest` and `ServiceFromRequest`.
|
||||
/// Future that resolves to some `T` when parsed from a JSON payload.
|
||||
///
|
||||
/// Returns error:
|
||||
/// Form can be deserialized from any type `T` that implements [`serde::Deserialize`].
|
||||
///
|
||||
/// * content type is not `application/json`
|
||||
/// (unless specified in [`JsonConfig`])
|
||||
/// * content length is greater than 256k
|
||||
pub enum JsonBody<U> {
|
||||
/// Returns error if:
|
||||
/// - content type is not `application/json`
|
||||
/// - content length is greater than [limit](JsonBody::limit())
|
||||
pub enum JsonBody<T> {
|
||||
Error(Option<JsonPayloadError>),
|
||||
Body {
|
||||
limit: usize,
|
||||
@ -340,17 +299,17 @@ pub enum JsonBody<U> {
|
||||
#[cfg(not(feature = "compress"))]
|
||||
payload: Payload,
|
||||
buf: BytesMut,
|
||||
_res: PhantomData<U>,
|
||||
_res: PhantomData<T>,
|
||||
},
|
||||
}
|
||||
|
||||
impl<U> Unpin for JsonBody<U> {}
|
||||
impl<T> Unpin for JsonBody<T> {}
|
||||
|
||||
impl<U> JsonBody<U>
|
||||
impl<T> JsonBody<T>
|
||||
where
|
||||
U: DeserializeOwned + 'static,
|
||||
T: DeserializeOwned + 'static,
|
||||
{
|
||||
/// Create `JsonBody` for request.
|
||||
/// Create a new future to decode a JSON request payload.
|
||||
#[allow(clippy::borrow_interior_mutable_const)]
|
||||
pub fn new(
|
||||
req: &HttpRequest,
|
||||
@ -394,7 +353,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Change max size of payload. By default max size is 256Kb
|
||||
/// Set maximum accepted payload size. The default limit is 256kB.
|
||||
pub fn limit(self, limit: usize) -> Self {
|
||||
match self {
|
||||
JsonBody::Body {
|
||||
@ -422,11 +381,11 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<U> Future for JsonBody<U>
|
||||
impl<T> Future for JsonBody<T>
|
||||
where
|
||||
U: DeserializeOwned + 'static,
|
||||
T: DeserializeOwned + 'static,
|
||||
{
|
||||
type Output = Result<U, JsonPayloadError>;
|
||||
type Output = Result<T, JsonPayloadError>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = self.get_mut();
|
||||
@ -449,7 +408,7 @@ where
|
||||
}
|
||||
}
|
||||
None => {
|
||||
let json = serde_json::from_slice::<U>(&buf)?;
|
||||
let json = serde_json::from_slice::<T>(&buf)?;
|
||||
return Poll::Ready(Ok(json));
|
||||
}
|
||||
}
|
||||
@ -462,13 +421,17 @@ where
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use bytes::Bytes;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::*;
|
||||
use crate::error::InternalError;
|
||||
use crate::http::header::{self, HeaderValue, CONTENT_LENGTH, CONTENT_TYPE};
|
||||
use crate::test::{load_stream, TestRequest};
|
||||
use crate::HttpResponse;
|
||||
use crate::{
|
||||
error::InternalError,
|
||||
http::{
|
||||
header::{self, HeaderValue, CONTENT_LENGTH, CONTENT_TYPE},
|
||||
StatusCode,
|
||||
},
|
||||
test::{load_stream, TestRequest},
|
||||
};
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||
struct MyObject {
|
||||
@ -526,7 +489,7 @@ mod tests {
|
||||
.to_http_parts();
|
||||
|
||||
let s = Json::<MyObject>::from_request(&req, &mut pl).await;
|
||||
let mut resp = Response::from_error(s.err().unwrap());
|
||||
let mut resp = HttpResponse::from_error(s.err().unwrap());
|
||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||
|
||||
let body = load_stream(resp.take_body()).await.unwrap();
|
||||
|
@ -1,5 +1,6 @@
|
||||
//! Helper types
|
||||
//! Common extractors and responders.
|
||||
|
||||
// TODO: review visibility
|
||||
mod either;
|
||||
pub(crate) mod form;
|
||||
pub(crate) mod json;
|
||||
|
@ -1,70 +1,55 @@
|
||||
//! Path extractor
|
||||
use std::sync::Arc;
|
||||
use std::{fmt, ops};
|
||||
//! For path segment extractor documentation, see [`Path`].
|
||||
|
||||
use std::{fmt, ops, sync::Arc};
|
||||
|
||||
use actix_http::error::{Error, ErrorNotFound};
|
||||
use actix_router::PathDeserializer;
|
||||
use futures_util::future::{ready, Ready};
|
||||
use serde::de;
|
||||
|
||||
use crate::dev::Payload;
|
||||
use crate::error::PathError;
|
||||
use crate::request::HttpRequest;
|
||||
use crate::FromRequest;
|
||||
use crate::{dev::Payload, error::PathError, FromRequest, HttpRequest};
|
||||
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
||||
/// Extract typed information from the request's path.
|
||||
/// Extract typed data from request path segments.
|
||||
///
|
||||
/// [**PathConfig**](PathConfig) allows to configure extraction process.
|
||||
/// Use [`PathConfig`] to configure extraction process.
|
||||
///
|
||||
/// ## Example
|
||||
/// # Usage
|
||||
/// ```
|
||||
/// use actix_web::{get, web};
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_web::{web, App};
|
||||
///
|
||||
/// /// extract path info from "/{username}/{count}/index.html" url
|
||||
/// /// {username} - deserializes to a String
|
||||
/// /// {count} - - deserializes to a u32
|
||||
/// async fn index(web::Path((username, count)): web::Path<(String, u32)>) -> String {
|
||||
/// format!("Welcome {}! {}", username, count)
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new().service(
|
||||
/// web::resource("/{username}/{count}/index.html") // <- define path parameters
|
||||
/// .route(web::get().to(index)) // <- register handler with `Path` extractor
|
||||
/// );
|
||||
/// // extract path info from "/{name}/{count}/index.html" into tuple
|
||||
/// // {name} - deserialize a String
|
||||
/// // {count} - deserialize a u32
|
||||
/// #[get("/")]
|
||||
/// async fn index(path: web::Path<(String, u32)>) -> String {
|
||||
/// let (name, count) = path.into_inner();
|
||||
/// format!("Welcome {}! {}", name, count)
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// It is possible to extract path information to a specific type that
|
||||
/// implements `Deserialize` trait from *serde*.
|
||||
/// Path segments also can be deserialized into any type that implements [`serde::Deserialize`].
|
||||
/// Path segment labels will be matched with struct field names.
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_web::{web, App, Error};
|
||||
/// use serde_derive::Deserialize;
|
||||
/// ```
|
||||
/// use actix_web::{get, web};
|
||||
/// use serde::Deserialize;
|
||||
///
|
||||
/// #[derive(Deserialize)]
|
||||
/// struct Info {
|
||||
/// username: String,
|
||||
/// name: String,
|
||||
/// }
|
||||
///
|
||||
/// /// extract `Info` from a path using serde
|
||||
/// async fn index(info: web::Path<Info>) -> Result<String, Error> {
|
||||
/// Ok(format!("Welcome {}!", info.username))
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new().service(
|
||||
/// web::resource("/{username}/index.html") // <- define path parameters
|
||||
/// .route(web::get().to(index)) // <- use handler with Path` extractor
|
||||
/// );
|
||||
/// // extract `Info` from a path using serde
|
||||
/// #[get("/")]
|
||||
/// async fn index(info: web::Path<Info>) -> String {
|
||||
/// format!("Welcome {}!", info.name)
|
||||
/// }
|
||||
/// ```
|
||||
pub struct Path<T>(pub T);
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct Path<T>(T);
|
||||
|
||||
impl<T> Path<T> {
|
||||
/// Deconstruct to an inner value
|
||||
/// Unwrap into inner `T` value.
|
||||
pub fn into_inner(self) -> T {
|
||||
self.0
|
||||
}
|
||||
@ -108,52 +93,7 @@ impl<T: fmt::Display> fmt::Display for Path<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract typed information from the request's path.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_web::{web, App};
|
||||
///
|
||||
/// /// extract path info from "/{username}/{count}/index.html" url
|
||||
/// /// {username} - deserializes to a String
|
||||
/// /// {count} - - deserializes to a u32
|
||||
/// async fn index(web::Path((username, count)): web::Path<(String, u32)>) -> String {
|
||||
/// format!("Welcome {}! {}", username, count)
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new().service(
|
||||
/// web::resource("/{username}/{count}/index.html") // <- define path parameters
|
||||
/// .route(web::get().to(index)) // <- register handler with `Path` extractor
|
||||
/// );
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// It is possible to extract path information to a specific type that
|
||||
/// implements `Deserialize` trait from *serde*.
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_web::{web, App, Error};
|
||||
/// use serde_derive::Deserialize;
|
||||
///
|
||||
/// #[derive(Deserialize)]
|
||||
/// struct Info {
|
||||
/// username: String,
|
||||
/// }
|
||||
///
|
||||
/// /// extract `Info` from a path using serde
|
||||
/// async fn index(info: web::Path<Info>) -> Result<String, Error> {
|
||||
/// Ok(format!("Welcome {}!", info.username))
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new().service(
|
||||
/// web::resource("/{username}/index.html") // <- define path parameters
|
||||
/// .route(web::get().to(index)) // <- use handler with Path` extractor
|
||||
/// );
|
||||
/// }
|
||||
/// ```
|
||||
/// See [here](#usage) for example of usage as an extractor.
|
||||
impl<T> FromRequest for Path<T>
|
||||
where
|
||||
T: de::DeserializeOwned,
|
||||
@ -191,10 +131,10 @@ where
|
||||
|
||||
/// Path extractor configuration
|
||||
///
|
||||
/// ```rust
|
||||
/// ```
|
||||
/// use actix_web::web::PathConfig;
|
||||
/// use actix_web::{error, web, App, FromRequest, HttpResponse};
|
||||
/// use serde_derive::Deserialize;
|
||||
/// use serde::Deserialize;
|
||||
///
|
||||
/// #[derive(Deserialize, Debug)]
|
||||
/// enum Folder {
|
||||
@ -249,7 +189,7 @@ impl Default for PathConfig {
|
||||
mod tests {
|
||||
use actix_router::ResourceDef;
|
||||
use derive_more::Display;
|
||||
use serde_derive::Deserialize;
|
||||
use serde::Deserialize;
|
||||
|
||||
use super::*;
|
||||
use crate::test::TestRequest;
|
||||
|
@ -1,57 +1,51 @@
|
||||
//! Payload/Bytes/String extractors
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::str;
|
||||
use std::task::{Context, Poll};
|
||||
//! Basic binary and string payload extractors.
|
||||
|
||||
use actix_http::error::{Error, ErrorBadRequest, PayloadError};
|
||||
use actix_http::HttpMessage;
|
||||
use std::{
|
||||
future::Future,
|
||||
pin::Pin,
|
||||
str,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use actix_http::error::{ErrorBadRequest, PayloadError};
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use encoding_rs::{Encoding, UTF_8};
|
||||
use futures_core::stream::Stream;
|
||||
use futures_util::{
|
||||
future::{err, ok, Either, ErrInto, Ready, TryFutureExt as _},
|
||||
future::{ready, Either, ErrInto, Ready, TryFutureExt as _},
|
||||
ready,
|
||||
};
|
||||
use mime::Mime;
|
||||
|
||||
use crate::extract::FromRequest;
|
||||
use crate::http::header;
|
||||
use crate::request::HttpRequest;
|
||||
use crate::{dev, web};
|
||||
use crate::{dev, http::header, web, Error, FromRequest, HttpMessage, HttpRequest};
|
||||
|
||||
/// Payload extractor returns request 's payload stream.
|
||||
/// Extract a request's raw payload stream.
|
||||
///
|
||||
/// ## Example
|
||||
/// See [`PayloadConfig`] for important notes when using this advanced extractor.
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_web::{web, error, App, Error, HttpResponse};
|
||||
/// # Usage
|
||||
/// ```
|
||||
/// use std::future::Future;
|
||||
/// use futures_core::stream::Stream;
|
||||
/// use futures_util::StreamExt;
|
||||
/// /// extract binary data from request
|
||||
/// async fn index(mut body: web::Payload) -> Result<HttpResponse, Error>
|
||||
/// {
|
||||
/// use futures_util::stream::{Stream, StreamExt};
|
||||
/// use actix_web::{post, web};
|
||||
///
|
||||
/// // `body: web::Payload` parameter extracts raw payload stream from request
|
||||
/// #[post("/")]
|
||||
/// async fn index(mut body: web::Payload) -> actix_web::Result<String> {
|
||||
/// // for demonstration only; in a normal case use the `Bytes` extractor
|
||||
/// // collect payload stream into a bytes object
|
||||
/// let mut bytes = web::BytesMut::new();
|
||||
/// while let Some(item) = body.next().await {
|
||||
/// bytes.extend_from_slice(&item?);
|
||||
/// }
|
||||
///
|
||||
/// format!("Body {:?}!", bytes);
|
||||
/// Ok(HttpResponse::Ok().finish())
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new().service(
|
||||
/// web::resource("/index.html").route(
|
||||
/// web::get().to(index))
|
||||
/// );
|
||||
/// Ok(format!("Request Body Bytes:\n{:?}", bytes))
|
||||
/// }
|
||||
/// ```
|
||||
pub struct Payload(pub crate::dev::Payload);
|
||||
|
||||
impl Payload {
|
||||
/// Deconstruct to a inner value
|
||||
/// Unwrap to inner Payload type.
|
||||
pub fn into_inner(self) -> crate::dev::Payload {
|
||||
self.0
|
||||
}
|
||||
@ -69,35 +63,7 @@ impl Stream for Payload {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get request's payload stream
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_web::{web, error, App, Error, HttpResponse};
|
||||
/// use std::future::Future;
|
||||
/// use futures_core::stream::Stream;
|
||||
/// use futures_util::StreamExt;
|
||||
///
|
||||
/// /// extract binary data from request
|
||||
/// async fn index(mut body: web::Payload) -> Result<HttpResponse, Error>
|
||||
/// {
|
||||
/// let mut bytes = web::BytesMut::new();
|
||||
/// while let Some(item) = body.next().await {
|
||||
/// bytes.extend_from_slice(&item?);
|
||||
/// }
|
||||
///
|
||||
/// format!("Body {:?}!", bytes);
|
||||
/// Ok(HttpResponse::Ok().finish())
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new().service(
|
||||
/// web::resource("/index.html").route(
|
||||
/// web::get().to(index))
|
||||
/// );
|
||||
/// }
|
||||
/// ```
|
||||
/// See [here](#usage) for example of usage as an extractor.
|
||||
impl FromRequest for Payload {
|
||||
type Config = PayloadConfig;
|
||||
type Error = Error;
|
||||
@ -105,34 +71,25 @@ impl FromRequest for Payload {
|
||||
|
||||
#[inline]
|
||||
fn from_request(_: &HttpRequest, payload: &mut dev::Payload) -> Self::Future {
|
||||
ok(Payload(payload.take()))
|
||||
ready(Ok(Payload(payload.take())))
|
||||
}
|
||||
}
|
||||
|
||||
/// Request binary data from a request's payload.
|
||||
/// Extract binary data from a request's payload.
|
||||
///
|
||||
/// Loads request's payload and construct Bytes instance.
|
||||
/// Collects request payload stream into a [Bytes] instance.
|
||||
///
|
||||
/// [**PayloadConfig**](PayloadConfig) allows to configure
|
||||
/// extraction process.
|
||||
/// Use [`PayloadConfig`] to configure extraction process.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use bytes::Bytes;
|
||||
/// use actix_web::{web, App};
|
||||
/// # Usage
|
||||
/// ```
|
||||
/// use actix_web::{post, web};
|
||||
///
|
||||
/// /// extract binary data from request
|
||||
/// async fn index(body: Bytes) -> String {
|
||||
/// #[post("/")]
|
||||
/// async fn index(body: web::Bytes) -> String {
|
||||
/// format!("Body {:?}!", body)
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new().service(
|
||||
/// web::resource("/index.html").route(
|
||||
/// web::get().to(index))
|
||||
/// );
|
||||
/// }
|
||||
/// ```
|
||||
impl FromRequest for Bytes {
|
||||
type Config = PayloadConfig;
|
||||
@ -144,8 +101,8 @@ impl FromRequest for Bytes {
|
||||
// allow both Config and Data<Config>
|
||||
let cfg = PayloadConfig::from_req(req);
|
||||
|
||||
if let Err(e) = cfg.check_mimetype(req) {
|
||||
return Either::Right(err(e));
|
||||
if let Err(err) = cfg.check_mimetype(req) {
|
||||
return Either::Right(ready(Err(err)));
|
||||
}
|
||||
|
||||
let limit = cfg.limit;
|
||||
@ -161,26 +118,15 @@ impl FromRequest for Bytes {
|
||||
/// [**PayloadConfig**](PayloadConfig) allows to configure
|
||||
/// extraction process.
|
||||
///
|
||||
/// ## Example
|
||||
/// # Usage
|
||||
/// ```
|
||||
/// use actix_web::{post, web, FromRequest};
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_web::{web, App, FromRequest};
|
||||
///
|
||||
/// /// extract text data from request
|
||||
/// // extract text data from request
|
||||
/// #[post("/")]
|
||||
/// async fn index(text: String) -> String {
|
||||
/// format!("Body {}!", text)
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new().service(
|
||||
/// web::resource("/index.html")
|
||||
/// .app_data(String::configure(|cfg| { // <- limit size of the payload
|
||||
/// cfg.limit(4096)
|
||||
/// }))
|
||||
/// .route(web::get().to(index)) // <- register handler with extractor params
|
||||
/// );
|
||||
/// }
|
||||
/// ```
|
||||
impl FromRequest for String {
|
||||
type Config = PayloadConfig;
|
||||
type Error = Error;
|
||||
@ -191,14 +137,14 @@ impl FromRequest for String {
|
||||
let cfg = PayloadConfig::from_req(req);
|
||||
|
||||
// check content-type
|
||||
if let Err(e) = cfg.check_mimetype(req) {
|
||||
return Either::Right(err(e));
|
||||
if let Err(err) = cfg.check_mimetype(req) {
|
||||
return Either::Right(ready(Err(err)));
|
||||
}
|
||||
|
||||
// check charset
|
||||
let encoding = match req.encoding() {
|
||||
Ok(enc) => enc,
|
||||
Err(e) => return Either::Right(err(e.into())),
|
||||
Err(err) => return Either::Right(ready(Err(err.into()))),
|
||||
};
|
||||
let limit = cfg.limit;
|
||||
let body_fut = HttpMessageBody::new(req, payload).limit(limit);
|
||||
@ -238,11 +184,13 @@ fn bytes_to_string(body: Bytes, encoding: &'static Encoding) -> Result<String, E
|
||||
}
|
||||
}
|
||||
|
||||
/// Configuration for request's payload.
|
||||
/// Configuration for request payloads.
|
||||
///
|
||||
/// Applies to the built-in `Bytes` and `String` extractors. Note that the Payload extractor does
|
||||
/// Applies to the built-in `Bytes` and `String` extractors. Note that the `Payload` extractor does
|
||||
/// not automatically check conformance with this configuration to allow more flexibility when
|
||||
/// building extractors on top of `Payload`.
|
||||
///
|
||||
/// By default, the payload size limit is 256kB and there is no mime type condition.
|
||||
#[derive(Clone)]
|
||||
pub struct PayloadConfig {
|
||||
limit: usize,
|
||||
@ -250,7 +198,7 @@ pub struct PayloadConfig {
|
||||
}
|
||||
|
||||
impl PayloadConfig {
|
||||
/// Create `PayloadConfig` instance and set max size of payload.
|
||||
/// Create new instance with a size limit and no mime type condition.
|
||||
pub fn new(limit: usize) -> Self {
|
||||
Self {
|
||||
limit,
|
||||
@ -258,14 +206,13 @@ impl PayloadConfig {
|
||||
}
|
||||
}
|
||||
|
||||
/// Change max size of payload. By default max size is 256Kb
|
||||
/// Set maximum accepted payload size. The default limit is 256kB.
|
||||
pub fn limit(mut self, limit: usize) -> Self {
|
||||
self.limit = limit;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set required mime-type of the request. By default mime type is not
|
||||
/// enforced.
|
||||
/// Set required mime type of the request. By default mime type is not enforced.
|
||||
pub fn mimetype(mut self, mt: Mime) -> Self {
|
||||
self.mimetype = Some(mt);
|
||||
self
|
||||
@ -292,7 +239,7 @@ impl PayloadConfig {
|
||||
}
|
||||
|
||||
/// Extract payload config from app data. Check both `T` and `Data<T>`, in that order, and fall
|
||||
/// back to the default payload config.
|
||||
/// back to the default payload config if neither is found.
|
||||
fn from_req(req: &HttpRequest) -> &Self {
|
||||
req.app_data::<Self>()
|
||||
.or_else(|| req.app_data::<web::Data<Self>>().map(|d| d.as_ref()))
|
||||
@ -314,13 +261,10 @@ impl Default for PayloadConfig {
|
||||
}
|
||||
}
|
||||
|
||||
/// Future that resolves to a complete http message body.
|
||||
/// Future that resolves to a complete HTTP body payload.
|
||||
///
|
||||
/// Load http message body.
|
||||
///
|
||||
/// By default only 256Kb payload reads to a memory, then
|
||||
/// `PayloadError::Overflow` get returned. Use `MessageBody::limit()`
|
||||
/// method to change upper limit.
|
||||
/// By default only 256kB payload is accepted before `PayloadError::Overflow` is returned.
|
||||
/// Use `MessageBody::limit()` method to change upper limit.
|
||||
pub struct HttpMessageBody {
|
||||
limit: usize,
|
||||
length: Option<usize>,
|
||||
@ -366,7 +310,7 @@ impl HttpMessageBody {
|
||||
}
|
||||
}
|
||||
|
||||
/// Change max size of payload. By default max size is 256Kb
|
||||
/// Change max size of payload. By default max size is 256kB
|
||||
pub fn limit(mut self, limit: usize) -> Self {
|
||||
if let Some(l) = self.length {
|
||||
if l > limit {
|
||||
@ -384,8 +328,8 @@ impl Future for HttpMessageBody {
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = self.get_mut();
|
||||
|
||||
if let Some(e) = this.err.take() {
|
||||
return Poll::Ready(Err(e));
|
||||
if let Some(err) = this.err.take() {
|
||||
return Poll::Ready(Err(err));
|
||||
}
|
||||
|
||||
loop {
|
||||
|
@ -1,30 +1,27 @@
|
||||
//! Query extractor
|
||||
//! For query parameter extractor documentation, see [`Query`].
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::{fmt, ops};
|
||||
use std::{fmt, ops, sync::Arc};
|
||||
|
||||
use actix_http::error::Error;
|
||||
use futures_util::future::{err, ok, Ready};
|
||||
use serde::de;
|
||||
|
||||
use crate::dev::Payload;
|
||||
use crate::error::QueryPayloadError;
|
||||
use crate::extract::FromRequest;
|
||||
use crate::request::HttpRequest;
|
||||
use crate::{dev::Payload, error::QueryPayloadError, Error, FromRequest, HttpRequest};
|
||||
|
||||
/// Extract typed information from the request's query.
|
||||
///
|
||||
/// **Note**: A query string consists of unordered `key=value` pairs, therefore it cannot
|
||||
/// be decoded into any type which depends upon data ordering e.g. tuples or tuple-structs.
|
||||
/// Attempts to do so will *fail at runtime*.
|
||||
/// To extract typed data from the URL query string, the inner type `T` must implement the
|
||||
/// [`serde::Deserialize`] trait.
|
||||
///
|
||||
/// [**QueryConfig**](QueryConfig) allows to configure extraction process.
|
||||
/// Use [`QueryConfig`] to configure extraction process.
|
||||
///
|
||||
/// ## Example
|
||||
/// # Panics
|
||||
/// A query string consists of unordered `key=value` pairs, therefore it cannot be decoded into any
|
||||
/// type which depends upon data ordering (eg. tuples). Trying to do so will result in a panic.
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_web::{web, App};
|
||||
/// use serde_derive::Deserialize;
|
||||
/// # Usage
|
||||
/// ```
|
||||
/// use actix_web::{get, web};
|
||||
/// use serde::Deserialize;
|
||||
///
|
||||
/// #[derive(Debug, Deserialize)]
|
||||
/// pub enum ResponseType {
|
||||
@ -38,35 +35,40 @@ use crate::request::HttpRequest;
|
||||
/// response_type: ResponseType,
|
||||
/// }
|
||||
///
|
||||
/// // Use `Query` extractor for query information (and destructure it within the signature).
|
||||
/// // This handler gets called only if the request's query string contains `id` and `response_type` fields.
|
||||
/// // The correct request for this handler would be `/index.html?id=64&response_type=Code"`.
|
||||
/// async fn index(web::Query(info): web::Query<AuthRequest>) -> String {
|
||||
/// format!("Authorization request for client with id={} and type={:?}!", info.id, info.response_type)
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new().service(
|
||||
/// web::resource("/index.html").route(web::get().to(index))); // <- use `Query` extractor
|
||||
/// // Deserialize `AuthRequest` struct from query string.
|
||||
/// // This handler gets called only if the request's query parameters contain both fields.
|
||||
/// // A valid request path for this handler would be `/?id=64&response_type=Code"`.
|
||||
/// #[get("/")]
|
||||
/// async fn index(info: web::Query<AuthRequest>) -> String {
|
||||
/// format!("Authorization request for id={} and type={:?}!", info.id, info.response_type)
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct Query<T>(pub T);
|
||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct Query<T>(T);
|
||||
|
||||
impl<T> Query<T> {
|
||||
/// Deconstruct to a inner value
|
||||
/// Unwrap into inner `T` value.
|
||||
pub fn into_inner(self) -> T {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Get query parameters from the path
|
||||
/// Deserialize `T` from a URL encoded query parameter string.
|
||||
///
|
||||
/// ```
|
||||
/// # use std::collections::HashMap;
|
||||
/// # use actix_web::web::Query;
|
||||
/// let numbers = Query::<HashMap<String, u32>>::from_query("one=1&two=2").unwrap();
|
||||
/// assert_eq!(numbers.get("one"), Some(&1));
|
||||
/// assert_eq!(numbers.get("two"), Some(&2));
|
||||
/// assert!(numbers.get("three").is_none());
|
||||
/// ```
|
||||
pub fn from_query(query_str: &str) -> Result<Self, QueryPayloadError>
|
||||
where
|
||||
T: de::DeserializeOwned,
|
||||
{
|
||||
serde_urlencoded::from_str::<T>(query_str)
|
||||
.map(|val| Ok(Query(val)))
|
||||
.unwrap_or_else(move |e| Err(QueryPayloadError::Deserialize(e)))
|
||||
.map(Self)
|
||||
.map_err(QueryPayloadError::Deserialize)
|
||||
}
|
||||
}
|
||||
|
||||
@ -96,39 +98,7 @@ impl<T: fmt::Display> fmt::Display for Query<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract typed information from the request's query.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_web::{web, App};
|
||||
/// use serde_derive::Deserialize;
|
||||
///
|
||||
/// #[derive(Debug, Deserialize)]
|
||||
/// pub enum ResponseType {
|
||||
/// Token,
|
||||
/// Code
|
||||
/// }
|
||||
///
|
||||
/// #[derive(Deserialize)]
|
||||
/// pub struct AuthRequest {
|
||||
/// id: u64,
|
||||
/// response_type: ResponseType,
|
||||
/// }
|
||||
///
|
||||
/// // Use `Query` extractor for query information.
|
||||
/// // This handler get called only if request's query contains `id` and `response_type` fields.
|
||||
/// // The correct request for this handler would be `/index.html?id=64&response_type=Code"`
|
||||
/// async fn index(info: web::Query<AuthRequest>) -> String {
|
||||
/// format!("Authorization request for client with id={} and type={:?}!", info.id, info.response_type)
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new().service(
|
||||
/// web::resource("/index.html")
|
||||
/// .route(web::get().to(index))); // <- use `Query` extractor
|
||||
/// }
|
||||
/// ```
|
||||
/// See [here](#usage) for example of usage as an extractor.
|
||||
impl<T> FromRequest for Query<T>
|
||||
where
|
||||
T: de::DeserializeOwned,
|
||||
@ -141,7 +111,7 @@ where
|
||||
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
||||
let error_handler = req
|
||||
.app_data::<Self::Config>()
|
||||
.map(|c| c.ehandler.clone())
|
||||
.map(|c| c.err_handler.clone())
|
||||
.unwrap_or(None);
|
||||
|
||||
serde_urlencoded::from_str::<T>(req.query_string())
|
||||
@ -166,13 +136,12 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Query extractor configuration
|
||||
/// Query extractor configuration.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_web::{error, web, App, FromRequest, HttpResponse};
|
||||
/// use serde_derive::Deserialize;
|
||||
/// # Usage
|
||||
/// ```
|
||||
/// use actix_web::{error, get, web, App, FromRequest, HttpResponse};
|
||||
/// use serde::Deserialize;
|
||||
///
|
||||
/// #[derive(Deserialize)]
|
||||
/// struct Info {
|
||||
@ -180,27 +149,25 @@ where
|
||||
/// }
|
||||
///
|
||||
/// /// deserialize `Info` from request's querystring
|
||||
/// #[get("/")]
|
||||
/// async fn index(info: web::Query<Info>) -> String {
|
||||
/// format!("Welcome {}!", info.username)
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new().service(
|
||||
/// web::resource("/index.html").app_data(
|
||||
/// // change query extractor configuration
|
||||
/// web::QueryConfig::default()
|
||||
/// .error_handler(|err, req| { // <- create custom error response
|
||||
/// error::InternalError::from_response(
|
||||
/// err, HttpResponse::Conflict().finish()).into()
|
||||
/// })
|
||||
/// )
|
||||
/// .route(web::post().to(index))
|
||||
/// );
|
||||
/// }
|
||||
/// // custom `Query` extractor configuration
|
||||
/// let query_cfg = web::QueryConfig::default()
|
||||
/// // use custom error handler
|
||||
/// .error_handler(|err, req| {
|
||||
/// error::InternalError::from_response(err, HttpResponse::Conflict().finish()).into()
|
||||
/// });
|
||||
///
|
||||
/// App::new()
|
||||
/// .app_data(query_cfg)
|
||||
/// .service(index);
|
||||
/// ```
|
||||
#[derive(Clone)]
|
||||
pub struct QueryConfig {
|
||||
ehandler:
|
||||
err_handler:
|
||||
Option<Arc<dyn Fn(QueryPayloadError, &HttpRequest) -> Error + Send + Sync>>,
|
||||
}
|
||||
|
||||
@ -210,14 +177,14 @@ impl QueryConfig {
|
||||
where
|
||||
F: Fn(QueryPayloadError, &HttpRequest) -> Error + Send + Sync + 'static,
|
||||
{
|
||||
self.ehandler = Some(Arc::new(f));
|
||||
self.err_handler = Some(Arc::new(f));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for QueryConfig {
|
||||
fn default() -> Self {
|
||||
QueryConfig { ehandler: None }
|
||||
QueryConfig { err_handler: None }
|
||||
}
|
||||
}
|
||||
|
||||
@ -225,7 +192,7 @@ impl Default for QueryConfig {
|
||||
mod tests {
|
||||
use actix_http::http::StatusCode;
|
||||
use derive_more::Display;
|
||||
use serde_derive::Deserialize;
|
||||
use serde::Deserialize;
|
||||
|
||||
use super::*;
|
||||
use crate::error::InternalError;
|
||||
@ -271,6 +238,17 @@ mod tests {
|
||||
assert_eq!(s.id, "test1");
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
#[should_panic]
|
||||
async fn test_tuple_panic() {
|
||||
let req = TestRequest::with_uri("/?one=1&two=2").to_srv_request();
|
||||
let (req, mut pl) = req.into_parts();
|
||||
|
||||
Query::<(u32, u32)>::from_request(&req, &mut pl)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_custom_error_responder() {
|
||||
let req = TestRequest::with_uri("/name/user1/")
|
||||
|
@ -1,17 +1,23 @@
|
||||
use std::borrow::Cow;
|
||||
use std::pin::Pin;
|
||||
use std::str;
|
||||
use std::task::{Context, Poll};
|
||||
//! For request line reader documentation, see [`Readlines`].
|
||||
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
pin::Pin,
|
||||
str,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use encoding_rs::{Encoding, UTF_8};
|
||||
use futures_util::stream::Stream;
|
||||
use futures_core::{ready, stream::Stream};
|
||||
|
||||
use crate::dev::Payload;
|
||||
use crate::error::{PayloadError, ReadlinesError};
|
||||
use crate::HttpMessage;
|
||||
use crate::{
|
||||
dev::Payload,
|
||||
error::{PayloadError, ReadlinesError},
|
||||
HttpMessage,
|
||||
};
|
||||
|
||||
/// Stream to read request line by line.
|
||||
/// Stream that reads request line by line.
|
||||
pub struct Readlines<T: HttpMessage> {
|
||||
stream: Payload<T::Stream>,
|
||||
buff: BytesMut,
|
||||
@ -43,7 +49,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Change max line size. By default max size is 256Kb
|
||||
/// Set maximum accepted payload size. The default limit is 256kB.
|
||||
pub fn limit(mut self, limit: usize) -> Self {
|
||||
self.limit = limit;
|
||||
self
|
||||
@ -108,9 +114,10 @@ where
|
||||
}
|
||||
this.checked_buff = true;
|
||||
}
|
||||
|
||||
// poll req for more bytes
|
||||
match Pin::new(&mut this.stream).poll_next(cx) {
|
||||
Poll::Ready(Some(Ok(mut bytes))) => {
|
||||
match ready!(Pin::new(&mut this.stream).poll_next(cx)) {
|
||||
Some(Ok(mut bytes)) => {
|
||||
// check if there is a newline in bytes
|
||||
let mut found: Option<usize> = None;
|
||||
for (ind, b) in bytes.iter().enumerate() {
|
||||
@ -144,8 +151,8 @@ where
|
||||
this.buff.extend_from_slice(&bytes);
|
||||
Poll::Pending
|
||||
}
|
||||
Poll::Pending => Poll::Pending,
|
||||
Poll::Ready(None) => {
|
||||
|
||||
None => {
|
||||
if this.buff.is_empty() {
|
||||
return Poll::Ready(None);
|
||||
}
|
||||
@ -165,7 +172,8 @@ where
|
||||
this.buff.clear();
|
||||
Poll::Ready(Some(Ok(line)))
|
||||
}
|
||||
Poll::Ready(Some(Err(e))) => Poll::Ready(Some(Err(ReadlinesError::from(e)))),
|
||||
|
||||
Some(Err(err)) => Poll::Ready(Some(Err(ReadlinesError::from(err)))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user