diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/hyper_body.rs | 64 | ||||
-rw-r--r-- | src/lib.rs | 10 | ||||
-rw-r--r-- | src/request.rs | 66 | ||||
-rw-r--r-- | src/response.rs | 10 |
4 files changed, 80 insertions, 70 deletions
diff --git a/src/hyper_body.rs b/src/hyper_body.rs new file mode 100644 index 0000000..c6824fb --- /dev/null +++ b/src/hyper_body.rs @@ -0,0 +1,64 @@ +//! Extends `hyper::Body` with [`SputnikBody`]. +use hyper::http::{self, response::Builder}; +use async_trait::async_trait; +use serde::de::DeserializeOwned; + +use crate::response::EmptyBuilder; + +impl EmptyBuilder<hyper::Body> for Builder { + fn empty(self) -> http::Result<http::response::Response<hyper::Body>> { + self.body(hyper::Body::empty()) + } +} + +/// Adds deserialization methods to [`hyper::Body`]. +#[async_trait] +pub trait SputnikBody { + /// Parses a `application/x-www-form-urlencoded` request body into a given struct. + async fn into_form<T: DeserializeOwned>(self) -> Result<T, FormError>; + + /// Attempts to deserialize the request body as JSON. + #[cfg(feature = "hyper_body_json")] + #[cfg_attr(docsrs, doc(cfg(feature = "hyper_body_json")))] + async fn into_json<T: DeserializeOwned>(self) -> Result<T, JsonError>; +} + +#[async_trait] +impl SputnikBody for hyper::Body { + + async fn into_form<T: DeserializeOwned>(self) -> Result<T, FormError> { + let full_body = hyper::body::to_bytes(self).await.map_err(BodyError)?; + Ok(serde_urlencoded::from_bytes::<T>(&full_body)?) + } + + #[cfg(feature = "hyper_body_json")] + #[cfg_attr(docsrs, doc(cfg(feature = "hyper_body_json")))] + async fn into_json<T: DeserializeOwned>(self) -> Result<T, JsonError> { + let full_body = hyper::body::to_bytes(self).await.map_err(BodyError)?; + Ok(serde_json::from_slice::<T>(&full_body)?) + } +} + +#[derive(thiserror::Error, Debug)] +#[error("failed to read body")] +pub struct BodyError(pub hyper::Error); + +#[derive(thiserror::Error, Debug)] +pub enum FormError { + #[error("{0}")] + Body(#[from] BodyError), + + #[error("form deserialize error: {0}")] + Deserialize(#[from] serde_urlencoded::de::Error), +} + +#[cfg(feature = "hyper_body_json")] +#[cfg_attr(docsrs, doc(cfg(feature = "hyper_body_json")))] +#[derive(thiserror::Error, Debug)] +pub enum JsonError { + #[error("{0}")] + Body(#[from] BodyError), + + #[error("json deserialize error: {0}")] + Deserialize(#[from] serde_json::Error), +}
\ No newline at end of file @@ -9,4 +9,12 @@ pub use httpdate; pub mod security; pub mod request; pub mod response; -mod signed;
\ No newline at end of file +mod signed; +#[cfg(feature="hyper_body")] +#[cfg_attr(docsrs, doc(cfg(feature = "hyper_body")))] +pub mod hyper_body; + +#[cfg(not(feature="hyper_body"))] +use http; +#[cfg(feature="hyper_body")] +use hyper::http;
\ No newline at end of file diff --git a/src/request.rs b/src/request.rs index 95f5a32..6efacc9 100644 --- a/src/request.rs +++ b/src/request.rs @@ -1,14 +1,13 @@ -//! Provides the [`SputnikParts`] and [`SputnikBody`] traits. +//! Provides the [`SputnikParts`] trait. use cookie::Cookie; use mime::Mime; use serde::de::DeserializeOwned; -use hyper::{HeaderMap, body::Bytes, header, http::request::Parts}; use time::Duration; use std::{collections::HashMap, sync::Arc}; -use async_trait::async_trait; use crate::response::{SputnikHeaders, delete_cookie}; +use crate::http::{HeaderMap, header, request::Parts}; /// Adds convenience methods to [`http::request::Parts`](Parts). pub trait SputnikParts { @@ -22,7 +21,7 @@ pub trait SputnikParts { fn enforce_content_type(&self, mime: Mime) -> Result<(), WrongContentTypeError>; /// A map of response headers to allow methods of this trait to set response - /// headers without needing to take a [`Response`](hyper::http::response::Response) as an argument. + /// headers without needing to take a [`Response`](crate::http::response::Response) as an argument. /// /// You need to take care to append these headers to the response yourself. /// This is intended to be done after your routing logic so that your @@ -137,85 +136,30 @@ impl Flash { } } -/// Adds deserialization methods to [`hyper::Body`]. -#[async_trait] -pub trait SputnikBody { - async fn into_bytes(self) -> Result<Bytes, BodyError>; - - /// Parses a `application/x-www-form-urlencoded` request body into a given struct. - async fn into_form<T: DeserializeOwned>(self) -> Result<T, FormError>; - - /// Attempts to deserialize the request body as JSON. - #[cfg(feature = "json")] - #[cfg_attr(docsrs, doc(cfg(feature = "json")))] - async fn into_json<T: DeserializeOwned>(self) -> Result<T, JsonError>; -} - -#[async_trait] -impl SputnikBody for hyper::Body { - async fn into_bytes(self) -> Result<Bytes, BodyError> { - hyper::body::to_bytes(self).await.map_err(BodyError) - } - - async fn into_form<T: DeserializeOwned>(self) -> Result<T, FormError> { - let full_body = self.into_bytes().await?; - Ok(serde_urlencoded::from_bytes::<T>(&full_body)?) - } - - #[cfg(feature = "json")] - #[cfg_attr(docsrs, doc(cfg(feature = "json")))] - async fn into_json<T: DeserializeOwned>(self) -> Result<T, JsonError> { - let full_body = self.into_bytes().await?; - Ok(serde_json::from_slice::<T>(&full_body)?) - } -} #[derive(thiserror::Error, Debug)] #[error("query deserialize error: {0}")] pub struct QueryError(pub serde_urlencoded::de::Error); #[derive(thiserror::Error, Debug)] -#[error("failed to read body")] -pub struct BodyError(pub hyper::Error); - -#[derive(thiserror::Error, Debug)] #[error("expected Content-Type {expected} but received {}", received.as_ref().unwrap_or(&"nothing".to_owned()))] pub struct WrongContentTypeError { pub expected: Mime, pub received: Option<String>, } -#[derive(thiserror::Error, Debug)] -pub enum FormError { - #[error("{0}")] - Body(#[from] BodyError), - - #[error("form deserialize error: {0}")] - Deserialize(#[from] serde_urlencoded::de::Error), -} - -#[cfg(feature = "json")] -#[cfg_attr(docsrs, doc(cfg(feature = "json")))] -#[derive(thiserror::Error, Debug)] -pub enum JsonError { - #[error("{0}")] - Body(#[from] BodyError), - - #[error("json deserialize error: {0}")] - Deserialize(#[from] serde_json::Error), -} #[cfg(test)] mod tests { use std::convert::TryInto; - use hyper::{Request, header}; + use crate::http::{Request, header}; use super::SputnikParts; #[test] fn test_enforce_content_type() { - let (mut parts, _body) = Request::new(hyper::Body::empty()).into_parts(); + let (mut parts, _body) = Request::new("").into_parts(); assert!(parts.enforce_content_type(mime::APPLICATION_JSON).is_err()); parts.headers.append(header::CONTENT_TYPE, "application/json".try_into().unwrap()); diff --git a/src/response.rs b/src/response.rs index 2d5d469..cb87a80 100644 --- a/src/response.rs +++ b/src/response.rs @@ -3,9 +3,9 @@ use std::convert::TryInto; use cookie::Cookie; -use hyper::{HeaderMap, StatusCode, header, http}; use time::{Duration, OffsetDateTime}; -use hyper::http::response::Builder; + +use crate::http::{self, HeaderMap, StatusCode, header, response::Builder}; /// Adds convenience methods to [`Builder`]. pub trait SputnikBuilder { @@ -66,12 +66,6 @@ pub trait EmptyBuilder<B> { fn empty(self) -> http::Result<http::response::Response<B>>; } -impl EmptyBuilder<hyper::Body> for Builder { - fn empty(self) -> http::Result<http::response::Response<hyper::Body>> { - self.body(hyper::Body::empty()) - } -} - #[cfg(test)] mod tests { use super::*; |