mod body;
mod extension;
#[cfg(feature = "grpc")]
mod grpc;
mod header;
mod router;
mod status;
pub use body::*;
pub use extension::*;
#[cfg(feature = "grpc")]
pub use grpc::*;
pub use header::*;
pub use router::*;
pub use status::*;
use core::{any::Any, convert::Infallible, fmt};
use std::{error, io, sync::Mutex};
use crate::{
context::WebContext,
http::WebResponse,
service::{Service, pipeline::PipelineE},
};
use self::service_impl::ErrorService;
pub struct Error(Box<dyn for<'r> ErrorService<WebContext<'r, Request<'r>>>>);
pub struct Request<'a> {
inner: &'a dyn Any,
}
impl Request<'_> {
pub fn request_ref<C>(&self) -> Option<&C>
where
C: 'static,
{
self.inner.downcast_ref()
}
}
impl Error {
pub fn from_service<S>(s: S) -> Self
where
S: for<'r> Service<WebContext<'r, Request<'r>>, Response = WebResponse, Error = Infallible>
+ error::Error
+ Send
+ Sync
+ 'static,
{
Self(Box::new(s))
}
pub fn upcast(&self) -> &(dyn error::Error + 'static) {
let e = self.0.dyn_err();
if let Some(e) = e.downcast_ref::<StdError>() {
return &*e.0;
}
e
}
pub async fn call_dyn(&self, ctx: WebContext<'_, Request<'_>>) -> Result<WebResponse, Infallible> {
crate::service::object::ServiceObject::call(&self.0, ctx).await
}
}
impl fmt::Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&*self.0, f)
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&*self.0, f)
}
}
impl error::Error for Error {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
self.0.source()
}
#[cfg(feature = "nightly")]
fn provide<'a>(&'a self, request: &mut error::Request<'a>) {
self.0.provide(request)
}
}
impl<'r, C> Service<WebContext<'r, C>> for Error
where
C: 'static,
{
type Response = WebResponse;
type Error = Infallible;
async fn call(&self, ctx: WebContext<'r, C>) -> Result<Self::Response, Self::Error> {
let WebContext { req, body, ctx } = ctx;
self.call_dyn(WebContext {
req,
body,
ctx: &Request { inner: ctx as _ },
})
.await
}
}
macro_rules! error_from_service {
($tt: ty) => {
impl From<$tt> for crate::error::Error {
fn from(e: $tt) -> Self {
Self::from_service(e)
}
}
};
}
pub(crate) use error_from_service;
#[allow(unused_macros)]
macro_rules! blank_error_service {
($type: ty, $status: path) => {
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: 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)
}
}
};
}
#[allow(unused_imports)]
pub(crate) use blank_error_service;
macro_rules! forward_blank_internal {
($type: ty) => {
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: crate::WebContext<'r, C, B>) -> Result<Self::Response, Self::Error> {
crate::http::StatusCode::INTERNAL_SERVER_ERROR.call(ctx).await
}
}
};
}
pub(crate) use forward_blank_internal;
macro_rules! forward_blank_bad_request {
($type: ty) => {
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: crate::WebContext<'r, C, B>) -> Result<Self::Response, Self::Error> {
crate::http::StatusCode::BAD_REQUEST.call(ctx).await
}
}
};
}
pub(crate) use forward_blank_bad_request;
impl From<Infallible> for Error {
fn from(e: Infallible) -> Self {
match e {}
}
}
impl<'r, C, B> Service<WebContext<'r, C, B>> for Infallible {
type Response = WebResponse;
type Error = Infallible;
async fn call(&self, _: WebContext<'r, C, B>) -> Result<Self::Response, Self::Error> {
unreachable!()
}
}
error_from_service!(io::Error);
forward_blank_internal!(io::Error);
type StdErr = Box<dyn error::Error + Send + Sync>;
impl From<StdErr> for Error {
fn from(e: StdErr) -> Self {
if let Some(e) = e.downcast_ref::<BodyOverFlow>() {
return Self::from(e.clone());
}
Self(Box::new(StdError(e)))
}
}
forward_blank_internal!(StdErr);
struct StdError(StdErr);
impl fmt::Debug for StdError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.0, f)
}
}
impl fmt::Display for StdError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
impl error::Error for StdError {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
self.0.source()
}
#[cfg(feature = "nightly")]
fn provide<'a>(&'a self, request: &mut error::Request<'a>) {
self.0.provide(request);
}
}
error_from_service!(StdError);
impl<'r, C, B> Service<WebContext<'r, C, B>> for StdError {
type Response = WebResponse;
type Error = Infallible;
async fn call(&self, ctx: WebContext<'r, C, B>) -> Result<Self::Response, Self::Error> {
self.0.call(ctx).await
}
}
pub struct ThreadJoinError(pub Mutex<Box<dyn Any + Send>>);
impl fmt::Debug for ThreadJoinError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ThreadJoinError").finish()
}
}
impl fmt::Display for ThreadJoinError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let any = self.0.lock().unwrap();
any.downcast_ref::<String>()
.map(String::as_str)
.or_else(|| any.downcast_ref::<&str>().copied())
.map(|msg| write!(f, "error joining thread: {msg}"))
.unwrap_or_else(|| f.write_str("error joining thread: unknown. please consider downcast ThreadJoinError.0"))
}
}
impl error::Error for ThreadJoinError {}
impl ThreadJoinError {
pub(crate) fn new(e: Box<dyn Any + Send>) -> Self {
Self(Mutex::new(e))
}
}
error_from_service!(ThreadJoinError);
forward_blank_internal!(ThreadJoinError);
impl<F, S> From<PipelineE<F, S>> for Error
where
F: Into<Error>,
S: Into<Error>,
{
fn from(pipe: PipelineE<F, S>) -> Self {
match pipe {
PipelineE::First(f) => f.into(),
PipelineE::Second(s) => s.into(),
}
}
}
mod service_impl {
use crate::service::object::ServiceObject;
use super::*;
pub trait ErrorService<Req>:
ServiceObject<Req, Response = WebResponse, Error = Infallible> + DynError + Send + Sync
{
}
impl<S, Req> ErrorService<Req> for S where
S: ServiceObject<Req, Response = WebResponse, Error = Infallible> + DynError + Send + Sync
{
}
pub trait DynError: error::Error {
fn dyn_err(&self) -> &(dyn error::Error + 'static);
}
impl<E> DynError for E
where
E: error::Error + 'static,
{
fn dyn_err(&self) -> &(dyn error::Error + 'static) {
self
}
}
}
#[cfg(test)]
mod test {
use xitca_unsafe_collection::futures::NowOrPanic;
use crate::{body::ResponseBody, http::StatusCode};
use super::*;
#[test]
fn cast() {
#[derive(Debug)]
struct Foo;
impl fmt::Display for Foo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Foo")
}
}
impl error::Error for Foo {}
impl<'r, C> Service<WebContext<'r, C>> for Foo {
type Response = WebResponse;
type Error = Infallible;
async fn call(&self, _: WebContext<'r, C>) -> Result<Self::Response, Self::Error> {
Ok(WebResponse::new(ResponseBody::empty()))
}
}
let foo = Error::from_service(Foo);
println!("{foo:?}");
println!("{foo}");
let mut ctx = WebContext::new_test(());
let res = Service::call(&foo, ctx.as_web_ctx()).now_or_panic().unwrap();
assert_eq!(res.status().as_u16(), 200);
let err = Error::from(Box::new(Foo) as Box<dyn std::error::Error + Send + Sync>);
println!("{err:?}");
println!("{err}");
assert!(err.upcast().downcast_ref::<Foo>().is_some());
#[derive(Debug)]
struct Bar;
impl fmt::Display for Bar {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Foo")
}
}
impl error::Error for Bar {}
impl<'r> Service<WebContext<'r, Request<'r>>> for Bar {
type Response = WebResponse;
type Error = Infallible;
async fn call(&self, ctx: WebContext<'r, Request<'r>>) -> Result<Self::Response, Self::Error> {
let status = ctx.state().request_ref::<StatusCode>().unwrap();
Ok(WebResponse::builder().status(*status).body(Default::default()).unwrap())
}
}
let bar = Error::from_service(Bar);
let res = bar
.call(WebContext::new_test(StatusCode::IM_USED).as_web_ctx())
.now_or_panic()
.unwrap();
assert_eq!(res.status(), StatusCode::IM_USED);
}
}