Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

export more internal error types. #949

Merged
merged 1 commit into from
Feb 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions web/CHANGES.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
28 changes: 28 additions & 0 deletions web/src/error/extension.rs
Original file line number Diff line number Diff line change
@@ -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<T>() -> Self {
Self(type_name::<T>())
}
}

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);
37 changes: 37 additions & 0 deletions web/src/error/header.rs
Original file line number Diff line number Diff line change
@@ -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);
183 changes: 26 additions & 157 deletions web/src/error.rs → web/src/error/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,30 +70,30 @@
//! }
//! ```
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,
fmt,
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},
};

Expand Down Expand Up @@ -155,6 +155,7 @@ use self::service_impl::ErrorService;
pub struct Error<C = ()>(Box<dyn for<'r> ErrorService<WebContext<'r, C>>>);

impl<C> Error<C> {
// construct an error object from given service type.
pub fn from_service<S>(s: S) -> Self
where
S: for<'r> Service<WebContext<'r, C>, Response = WebResponse, Error = Infallible>
Expand Down Expand Up @@ -227,24 +228,26 @@ pub(crate) use error_from_service;

macro_rules! blank_error_service {
($type: ty, $status: path) => {
impl<'r, C, B> Service<WebContext<'r, C, B>> for $type {
type Response = WebResponse;
type Error = Infallible;
impl<'r, C, B> crate::service::Service<crate::WebContext<'r, C, B>> for $type {
type Response = crate::http::WebResponse;
type Error = ::core::convert::Infallible;

async fn call(&self, ctx: WebContext<'r, C, B>) -> Result<Self::Response, Self::Error> {
let mut res = ctx.into_response(ResponseBody::empty());
async fn call(&self, ctx: crate::WebContext<'r, C, B>) -> Result<Self::Response, Self::Error> {
let mut res = ctx.into_response(crate::body::ResponseBody::empty());
*res.status_mut() = $status;
Ok(res)
}
}
};
}

pub(crate) use blank_error_service;

macro_rules! forward_blank_internal {
($type: ty) => {
impl<'r, C, B> crate::service::Service<WebContext<'r, C, B>> 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<Self::Response, Self::Error> {
crate::http::StatusCode::INTERNAL_SERVER_ERROR.call(ctx).await
Expand All @@ -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<WebContext<'r, C, B>> for $type {
type Response = WebResponse;
impl<'r, C, B> crate::service::Service<crate::WebContext<'r, C, B>> for $type {
type Response = crate::http::WebResponse;
type Error = ::core::convert::Infallible;

async fn call(&self, ctx: WebContext<'r, C, B>) -> Result<Self::Response, Self::Error> {
async fn call(&self, ctx: crate::WebContext<'r, C, B>) -> Result<Self::Response, Self::Error> {
crate::http::StatusCode::BAD_REQUEST.call(ctx).await
}
}
Expand All @@ -285,143 +288,9 @@ impl<'r, C, B> Service<WebContext<'r, C, B>> 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<StatusCode> for ErrorStatus {
fn from(status: StatusCode) -> Self {
Self {
status,
_back_trace: Backtrace::capture(),
}
}
}

impl<C> From<StatusCode> for Error<C> {
fn from(e: StatusCode) -> Self {
Error::from(ErrorStatus::from(e))
}
}

impl<C> From<ErrorStatus> for Error<C> {
fn from(e: ErrorStatus) -> Self {
Error::from_service(e)
}
}

impl<'r, C, B> Service<WebContext<'r, C, B>> for ErrorStatus {
type Response = WebResponse;
type Error = Infallible;

async fn call(&self, ctx: WebContext<'r, C, B>) -> Result<Self::Response, Self::Error> {
self.status.call(ctx).await
}
}

impl<'r, C, B> Service<WebContext<'r, C, B>> for StatusCode {
type Response = WebResponse;
type Error = Infallible;

async fn call(&self, ctx: WebContext<'r, C, B>) -> Result<Self::Response, Self::Error> {
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<WebContext<'r, C, B>> for MethodNotAllowed {
type Response = WebResponse;
type Error = Infallible;

async fn call(&self, ctx: WebContext<'r, C, B>) -> Result<Self::Response, Self::Error> {
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<E, C> From<RouterError<E>> for Error<C>
where
E: Into<Self>,
{
fn from(e: RouterError<E>) -> Self {
match e {
RouterError::Match(e) => e.into(),
RouterError::NotAllowed(e) => e.into(),
RouterError::Service(e) => e.into(),
}
}
}

type StdErr = Box<dyn error::Error + Send + Sync>;

impl<C> From<StdErr> for Error<C> {
Expand Down
59 changes: 59 additions & 0 deletions web/src/error/router.rs
Original file line number Diff line number Diff line change
@@ -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<WebContext<'r, C, B>> for MethodNotAllowed {
type Response = WebResponse;
type Error = Infallible;

async fn call(&self, ctx: WebContext<'r, C, B>) -> Result<Self::Response, Self::Error> {
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<E, C> From<RouterError<E>> for Error<C>
where
E: Into<Self>,
{
fn from(e: RouterError<E>) -> Self {
match e {
RouterError::Match(e) => e.into(),
RouterError::NotAllowed(e) => e.into(),
RouterError::Service(e) => e.into(),
}
}
}
Loading
Loading