diff --git a/web/CHANGES.md b/web/CHANGES.md index 3c198708..d7ef73be 100644 --- a/web/CHANGES.md +++ b/web/CHANGES.md @@ -1,5 +1,11 @@ # unreleased 0.4.0 +## Add +- expose `error::HeaderNameNotFound` type for interacting with internal header name error. +- expose `error::InvalidHeaderValue` type for interacting with internal header value error. +- expose `error::ExtensionNotFound` type for interacting with extension typed map error. + ## Change +- `error::InvalidHeaderValue` displays it's associated header name in `Debug` and `Display` format. - update `xitca-http` to `0.4.0` # 0.3.0 diff --git a/web/src/error/extension.rs b/web/src/error/extension.rs new file mode 100644 index 00000000..678b686a --- /dev/null +++ b/web/src/error/extension.rs @@ -0,0 +1,28 @@ +use core::{any::type_name, fmt}; + +use std::error; + +use super::{error_from_service, forward_blank_bad_request}; + +/// error type for typed instance can't be found from [`Extensions`] +/// +/// [`Extensions`]: crate::http::Extensions +#[derive(Debug)] +pub struct ExtensionNotFound(&'static str); + +impl ExtensionNotFound { + pub(crate) fn from_type() -> Self { + Self(type_name::()) + } +} + +impl fmt::Display for ExtensionNotFound { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{} type can't be found from Extensions", self.0) + } +} + +impl error::Error for ExtensionNotFound {} + +error_from_service!(ExtensionNotFound); +forward_blank_bad_request!(ExtensionNotFound); diff --git a/web/src/error/header.rs b/web/src/error/header.rs new file mode 100644 index 00000000..3807a152 --- /dev/null +++ b/web/src/error/header.rs @@ -0,0 +1,37 @@ +use core::fmt; + +use std::error; + +use crate::http::HeaderName; + +use super::{error_from_service, forward_blank_bad_request}; + +/// error type when named header is not found from request. +#[derive(Debug)] +pub struct HeaderNotFound(pub HeaderName); + +impl fmt::Display for HeaderNotFound { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "HeaderName: {} is not found", self.0.as_str()) + } +} + +impl error::Error for HeaderNotFound {} + +error_from_service!(HeaderNotFound); +forward_blank_bad_request!(HeaderNotFound); + +/// error type when named header is not associated with valid header value. +#[derive(Debug)] +pub struct InvalidHeaderValue(pub HeaderName); + +impl fmt::Display for InvalidHeaderValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "HeaderName: {} associated with invalid HeaderValue", self.0.as_str()) + } +} + +impl error::Error for InvalidHeaderValue {} + +error_from_service!(InvalidHeaderValue); +forward_blank_bad_request!(InvalidHeaderValue); diff --git a/web/src/error.rs b/web/src/error/mod.rs similarity index 74% rename from web/src/error.rs rename to web/src/error/mod.rs index 29a6acf6..1e237966 100644 --- a/web/src/error.rs +++ b/web/src/error/mod.rs @@ -70,6 +70,16 @@ //! } //! ``` +mod extension; +mod header; +mod router; +mod status; + +pub use extension::*; +pub use header::*; +pub use router::*; +pub use status::*; + use core::{ any::Any, convert::Infallible, @@ -77,23 +87,13 @@ use core::{ ops::{Deref, DerefMut}, }; -use std::{backtrace::Backtrace, error, io, sync::Mutex}; +use std::{error, io, sync::Mutex}; -pub use xitca_http::{ - error::BodyError, - util::service::{ - route::MethodNotAllowed, - router::{MatchError, RouterError}, - }, -}; +pub use xitca_http::error::BodyError; use crate::{ - body::ResponseBody, context::WebContext, - http::{ - header::{InvalidHeaderValue, ALLOW}, - StatusCode, WebResponse, - }, + http::WebResponse, service::{pipeline::PipelineE, Service}, }; @@ -155,6 +155,7 @@ use self::service_impl::ErrorService; pub struct Error(Box ErrorService>>); impl Error { + // construct an error object from given service type. pub fn from_service(s: S) -> Self where S: for<'r> Service, Response = WebResponse, Error = Infallible> @@ -227,12 +228,12 @@ pub(crate) use error_from_service; macro_rules! blank_error_service { ($type: ty, $status: path) => { - impl<'r, C, B> Service> for $type { - type Response = WebResponse; - type Error = Infallible; + impl<'r, C, B> crate::service::Service> for $type { + type Response = crate::http::WebResponse; + type Error = ::core::convert::Infallible; - async fn call(&self, ctx: WebContext<'r, C, B>) -> Result { - let mut res = ctx.into_response(ResponseBody::empty()); + async fn call(&self, ctx: crate::WebContext<'r, C, B>) -> Result { + let mut res = ctx.into_response(crate::body::ResponseBody::empty()); *res.status_mut() = $status; Ok(res) } @@ -240,11 +241,13 @@ macro_rules! blank_error_service { }; } +pub(crate) use blank_error_service; + macro_rules! forward_blank_internal { ($type: ty) => { impl<'r, C, B> crate::service::Service> for $type { - type Response = WebResponse; - type Error = Infallible; + type Response = crate::http::WebResponse; + type Error = core::convert::Infallible; async fn call(&self, ctx: WebContext<'r, C, B>) -> Result { crate::http::StatusCode::INTERNAL_SERVER_ERROR.call(ctx).await @@ -257,11 +260,11 @@ pub(crate) use forward_blank_internal; macro_rules! forward_blank_bad_request { ($type: ty) => { - impl<'r, C, B> crate::service::Service> for $type { - type Response = WebResponse; + impl<'r, C, B> crate::service::Service> for $type { + type Response = crate::http::WebResponse; type Error = ::core::convert::Infallible; - async fn call(&self, ctx: WebContext<'r, C, B>) -> Result { + async fn call(&self, ctx: crate::WebContext<'r, C, B>) -> Result { crate::http::StatusCode::BAD_REQUEST.call(ctx).await } } @@ -285,143 +288,9 @@ impl<'r, C, B> Service> for Infallible { } } -/// error type derive from http status code. produce minimal "StatusCode Reason" response and stack backtrace -/// of the location status code error occurs. -pub struct ErrorStatus { - status: StatusCode, - _back_trace: Backtrace, -} - -impl ErrorStatus { - /// construct an ErrorStatus type from [`StatusCode::INTERNAL_SERVER_ERROR`] - pub fn internal() -> Self { - // verbosity of constructor is desired here so back trace capture - // can direct capture the call site. - Self { - status: StatusCode::BAD_REQUEST, - _back_trace: Backtrace::capture(), - } - } - - /// construct an ErrorStatus type from [`StatusCode::BAD_REQUEST`] - pub fn bad_request() -> Self { - Self { - status: StatusCode::BAD_REQUEST, - _back_trace: Backtrace::capture(), - } - } -} - -impl fmt::Debug for ErrorStatus { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(&self.status, f) - } -} - -impl fmt::Display for ErrorStatus { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(&self.status, f) - } -} - -impl error::Error for ErrorStatus { - #[cfg(feature = "nightly")] - fn provide<'a>(&'a self, request: &mut error::Request<'a>) { - request.provide_ref(&self._back_trace); - } -} - -impl From for ErrorStatus { - fn from(status: StatusCode) -> Self { - Self { - status, - _back_trace: Backtrace::capture(), - } - } -} - -impl From for Error { - fn from(e: StatusCode) -> Self { - Error::from(ErrorStatus::from(e)) - } -} - -impl From for Error { - fn from(e: ErrorStatus) -> Self { - Error::from_service(e) - } -} - -impl<'r, C, B> Service> for ErrorStatus { - type Response = WebResponse; - type Error = Infallible; - - async fn call(&self, ctx: WebContext<'r, C, B>) -> Result { - self.status.call(ctx).await - } -} - -impl<'r, C, B> Service> for StatusCode { - type Response = WebResponse; - type Error = Infallible; - - async fn call(&self, ctx: WebContext<'r, C, B>) -> Result { - let mut res = ctx.into_response(ResponseBody::empty()); - *res.status_mut() = *self; - Ok(res) - } -} - error_from_service!(io::Error); forward_blank_internal!(io::Error); -error_from_service!(MatchError); -blank_error_service!(MatchError, StatusCode::NOT_FOUND); - -error_from_service!(MethodNotAllowed); - -error_from_service!(InvalidHeaderValue); -forward_blank_bad_request!(InvalidHeaderValue); - -impl<'r, C, B> Service> for MethodNotAllowed { - type Response = WebResponse; - type Error = Infallible; - - async fn call(&self, ctx: WebContext<'r, C, B>) -> Result { - let mut res = ctx.into_response(ResponseBody::empty()); - - let allowed = self.allowed_methods(); - - let len = allowed.iter().fold(0, |a, m| a + m.as_str().len() + 1); - - let mut methods = String::with_capacity(len); - - for method in allowed { - methods.push_str(method.as_str()); - methods.push(','); - } - methods.pop(); - - res.headers_mut().insert(ALLOW, methods.parse().unwrap()); - *res.status_mut() = StatusCode::METHOD_NOT_ALLOWED; - - Ok(res) - } -} - -impl From> for Error -where - E: Into, -{ - fn from(e: RouterError) -> Self { - match e { - RouterError::Match(e) => e.into(), - RouterError::NotAllowed(e) => e.into(), - RouterError::Service(e) => e.into(), - } - } -} - type StdErr = Box; impl From for Error { diff --git a/web/src/error/router.rs b/web/src/error/router.rs new file mode 100644 index 00000000..d8712c48 --- /dev/null +++ b/web/src/error/router.rs @@ -0,0 +1,59 @@ +pub use xitca_http::util::service::{ + route::MethodNotAllowed, + router::{MatchError, RouterError}, +}; + +use core::convert::Infallible; + +use crate::{ + body::ResponseBody, + http::{header::ALLOW, StatusCode, WebResponse}, + service::Service, + WebContext, +}; + +use super::{blank_error_service, error_from_service, Error}; + +error_from_service!(MatchError); +blank_error_service!(MatchError, StatusCode::NOT_FOUND); + +error_from_service!(MethodNotAllowed); + +impl<'r, C, B> Service> for MethodNotAllowed { + type Response = WebResponse; + type Error = Infallible; + + async fn call(&self, ctx: WebContext<'r, C, B>) -> Result { + let mut res = ctx.into_response(ResponseBody::empty()); + + let allowed = self.allowed_methods(); + + let len = allowed.iter().fold(0, |a, m| a + m.as_str().len() + 1); + + let mut methods = String::with_capacity(len); + + for method in allowed { + methods.push_str(method.as_str()); + methods.push(','); + } + methods.pop(); + + res.headers_mut().insert(ALLOW, methods.parse().unwrap()); + *res.status_mut() = StatusCode::METHOD_NOT_ALLOWED; + + Ok(res) + } +} + +impl From> for Error +where + E: Into, +{ + fn from(e: RouterError) -> Self { + match e { + RouterError::Match(e) => e.into(), + RouterError::NotAllowed(e) => e.into(), + RouterError::Service(e) => e.into(), + } + } +} diff --git a/web/src/error/status.rs b/web/src/error/status.rs new file mode 100644 index 00000000..dadab112 --- /dev/null +++ b/web/src/error/status.rs @@ -0,0 +1,101 @@ +use core::{convert::Infallible, fmt}; + +use std::error; + +use std::backtrace::Backtrace; + +use crate::{ + body::ResponseBody, + http::{StatusCode, WebResponse}, + service::Service, + WebContext, +}; + +use super::Error; + +/// error type derive from http status code. produce minimal "StatusCode Reason" response and stack backtrace +/// of the location status code error occurs. +pub struct ErrorStatus { + status: StatusCode, + _back_trace: Backtrace, +} + +impl ErrorStatus { + /// construct an ErrorStatus type from [`StatusCode::INTERNAL_SERVER_ERROR`] + pub fn internal() -> Self { + // verbosity of constructor is desired here so back trace capture + // can direct capture the call site. + Self { + status: StatusCode::BAD_REQUEST, + _back_trace: Backtrace::capture(), + } + } + + /// construct an ErrorStatus type from [`StatusCode::BAD_REQUEST`] + pub fn bad_request() -> Self { + Self { + status: StatusCode::BAD_REQUEST, + _back_trace: Backtrace::capture(), + } + } +} + +impl fmt::Debug for ErrorStatus { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&self.status, f) + } +} + +impl fmt::Display for ErrorStatus { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&self.status, f) + } +} + +impl error::Error for ErrorStatus { + #[cfg(feature = "nightly")] + fn provide<'a>(&'a self, request: &mut error::Request<'a>) { + request.provide_ref(&self._back_trace); + } +} + +impl From for ErrorStatus { + fn from(status: StatusCode) -> Self { + Self { + status, + _back_trace: Backtrace::capture(), + } + } +} + +impl From for Error { + fn from(e: StatusCode) -> Self { + Error::from(ErrorStatus::from(e)) + } +} + +impl From for Error { + fn from(e: ErrorStatus) -> Self { + Error::from_service(e) + } +} + +impl<'r, C, B> Service> for ErrorStatus { + type Response = WebResponse; + type Error = Infallible; + + async fn call(&self, ctx: WebContext<'r, C, B>) -> Result { + self.status.call(ctx).await + } +} + +impl<'r, C, B> Service> for StatusCode { + type Response = WebResponse; + type Error = Infallible; + + async fn call(&self, ctx: WebContext<'r, C, B>) -> Result { + let mut res = ctx.into_response(ResponseBody::empty()); + *res.status_mut() = *self; + Ok(res) + } +} diff --git a/web/src/handler/types/cookie.rs b/web/src/handler/types/cookie.rs index 71438473..d6da7443 100644 --- a/web/src/handler/types/cookie.rs +++ b/web/src/handler/types/cookie.rs @@ -8,7 +8,9 @@ use cookie::CookieJar as _CookieJar; use crate::{ body::{BodyStream, ResponseBody}, - error::{error_from_service, forward_blank_bad_request, Error}, + error::{ + error_from_service, forward_blank_bad_request, Error, ExtensionNotFound, HeaderNotFound, InvalidHeaderValue, + }, handler::{FromRequest, Responder}, http::{ header::ToStrError, @@ -18,8 +20,6 @@ use crate::{ WebContext, }; -use super::{extension::ExtensionNotFound, header::HeaderNotFound}; - macro_rules! key_impl { ($key: tt) => { impl $key { @@ -86,7 +86,7 @@ impl<'a, 'r, C, B> FromRequest<'a, WebContext<'r, C, B>> for ExtensionKey { .extensions() .get::() .cloned() - .ok_or_else(|| Error::from(ExtensionNotFound)) + .ok_or_else(|| Error::from(ExtensionNotFound::from_type::())) } } @@ -272,7 +272,8 @@ impl<'r, C, B, K> Responder> for CookieJar { fn map(self, mut res: Self::Response) -> Result { let headers = res.headers_mut(); for cookie in self.jar.delta() { - let value = HeaderValue::try_from(cookie.encoded().to_string())?; + let value = + HeaderValue::try_from(cookie.encoded().to_string()).map_err(|_| InvalidHeaderValue(SET_COOKIE))?; headers.append(SET_COOKIE, value); } Ok(res) diff --git a/web/src/handler/types/extension.rs b/web/src/handler/types/extension.rs index 65ec6948..63de7db0 100644 --- a/web/src/handler/types/extension.rs +++ b/web/src/handler/types/extension.rs @@ -2,13 +2,11 @@ use core::{fmt, ops::Deref}; -use std::error; - use crate::{ context::WebContext, - error::{error_from_service, forward_blank_bad_request, Error}, + error::{Error, ExtensionNotFound}, handler::FromRequest, - http::{Extensions, WebResponse}, + http::Extensions, }; /// Extract immutable reference of element stored inside [Extensions] @@ -36,7 +34,7 @@ where .extensions() .get::() .map(ExtensionRef) - .ok_or_else(|| Error::from_service(ExtensionNotFound)) + .ok_or_else(|| Error::from_service(ExtensionNotFound::from_type::())) } } @@ -70,7 +68,7 @@ where .extensions() .get::() .map(|ext| ExtensionOwn(ext.clone())) - .ok_or_else(|| Error::from_service(ExtensionNotFound)) + .ok_or_else(|| Error::from_service(ExtensionNotFound::from_type::())) } } @@ -94,18 +92,3 @@ impl<'a, 'r, C, B> FromRequest<'a, WebContext<'r, C, B>> for ExtensionsRef<'a> { Ok(ExtensionsRef(ctx.req().extensions())) } } - -/// Absent type of request's (Extensions)[crate::http::Extensions] type map. -#[derive(Debug)] -pub struct ExtensionNotFound; - -impl fmt::Display for ExtensionNotFound { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("Extension data type can not be found") - } -} - -impl error::Error for ExtensionNotFound {} - -error_from_service!(ExtensionNotFound); -forward_blank_bad_request!(ExtensionNotFound); diff --git a/web/src/handler/types/header.rs b/web/src/handler/types/header.rs index e409c14f..da11a685 100644 --- a/web/src/handler/types/header.rs +++ b/web/src/handler/types/header.rs @@ -2,12 +2,10 @@ use core::{fmt, ops::Deref}; -use std::error; - use crate::{ body::{BodyStream, ResponseBody}, context::WebContext, - error::{error_from_service, forward_blank_bad_request, Error}, + error::{Error, HeaderNotFound}, handler::{FromRequest, Responder}, http::{ header::{self, HeaderMap, HeaderName, HeaderValue}, @@ -104,11 +102,12 @@ where #[inline] async fn from_request(ctx: &'a WebContext<'r, C, B>) -> Result { + let name = map_to_header_name::(); ctx.req() .headers() - .get(&map_to_header_name::()) + .get(&name) .map(HeaderRef) - .ok_or_else(|| Error::from_service(HeaderNotFound(map_to_header_name::()))) + .ok_or_else(|| Error::from_service(HeaderNotFound(name))) } } @@ -175,17 +174,3 @@ mod test { ); } } - -#[derive(Debug)] -pub struct HeaderNotFound(pub(crate) header::HeaderName); - -impl fmt::Display for HeaderNotFound { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "HeaderName: {} not found", self.0.as_str()) - } -} - -impl error::Error for HeaderNotFound {} - -error_from_service!(HeaderNotFound); -forward_blank_bad_request!(HeaderNotFound); diff --git a/web/src/handler/types/multipart.rs b/web/src/handler/types/multipart.rs index 56af2332..fc4a503d 100644 --- a/web/src/handler/types/multipart.rs +++ b/web/src/handler/types/multipart.rs @@ -3,7 +3,6 @@ use crate::{ context::WebContext, error::{forward_blank_bad_request, Error}, handler::FromRequest, - http::WebResponse, }; pub type Multipart = http_multipart::Multipart; diff --git a/web/src/handler/types/websocket.rs b/web/src/handler/types/websocket.rs index 135caf77..a06fcb23 100644 --- a/web/src/handler/types/websocket.rs +++ b/web/src/handler/types/websocket.rs @@ -22,7 +22,7 @@ use crate::{ body::{BodyStream, RequestBody, ResponseBody}, bytes::Bytes, context::WebContext, - error::Error, + error::{Error, HeaderNotFound}, handler::{FromRequest, Responder}, http::{ header::{CONNECTION, SEC_WEBSOCKET_VERSION, UPGRADE}, @@ -31,8 +31,6 @@ use crate::{ service::Service, }; -use super::header::HeaderNotFound; - pub use http_ws::{ResponseSender, ResponseWeakSender}; /// simplified websocket message type.