xitca_web/error/mod.rs
1//! web error types.
2//!
3//! In xitca-web error is treated as high level type and handled lazily.
4//!
5//! - high level:
6//! An error type is represented firstly and mostly as a Rust type with useful trait bounds.It doesn't
7//! necessarily mapped and/or converted into http response immediately. User is encouraged to pass the
8//! error value around and convert it to http response on condition they prefer.
9//!
10//! - lazy:
11//! Since an error is passed as value mostly the error is handled lazily when the value is needed.
12//! Including but not limiting to: formatting, logging, generating http response.
13//!
14//! # Example
15//! ```rust
16//! # use xitca_web::{
17//! # error::Error,
18//! # handler::{handler_service, html::Html, Responder},
19//! # http::{StatusCode, WebResponse},
20//! # service::Service,
21//! # App, WebContext};
22//! // a handler function always produce error.
23//! async fn handler() -> Error {
24//! Error::from(StatusCode::BAD_REQUEST)
25//! }
26//!
27//! // construct application with handler function and middleware.
28//! App::new()
29//! .at("/", handler_service(handler))
30//! .enclosed_fn(error_handler);
31//!
32//! // a handler middleware observe route services output.
33//! async fn error_handler<S>(service: &S, mut ctx: WebContext<'_>) -> Result<WebResponse, Error>
34//! where
35//! S: for<'r> Service<WebContext<'r>, Response = WebResponse, Error = Error>
36//! {
37//! // unlike WebResponse which is already a valid http response. the error is treated as it's
38//! // onw type on the other branch of the Result enum.
39//!
40//! // since the handler function at the start of example always produce error. our middleware
41//! // will always observe the Error type value so let's unwrap it.
42//! let err = service.call(ctx.reborrow()).await.err().unwrap();
43//!
44//! // now we have the error value we can start to interact with it and add our logic of
45//! // handling it.
46//!
47//! // we can print the error.
48//! println!("{err}");
49//!
50//! // we can log the error.
51//! tracing::error!("{err}");
52//!
53//! // we can render the error to html and convert it to http response.
54//! let html = format!("<!DOCTYPE html>\
55//! <html>\
56//! <body>\
57//! <h1>{err}</h1>\
58//! </body>\
59//! </html>");
60//! return (Html(html), StatusCode::BAD_REQUEST).respond(ctx).await;
61//!
62//! // or by default the error value is returned in Result::Err and passed to parent services
63//! // of App or other middlewares where eventually it would be converted to WebResponse.
64//!
65//! // "eventually" can either mean a downstream user provided error handler middleware/service
66//! // or the implicit catch all error middleware xitca-web offers. In the latter case a default
67//! // WebResponse is generated with minimal information describing the reason of error.
68//!
69//! Err(err)
70//! }
71//! ```
72
73mod body;
74mod extension;
75mod header;
76mod router;
77mod status;
78
79pub use body::*;
80pub use extension::*;
81pub use header::*;
82pub use router::*;
83pub use status::*;
84
85use core::{any::Any, convert::Infallible, fmt};
86
87use std::{error, io, sync::Mutex};
88
89use crate::{
90 context::WebContext,
91 http::WebResponse,
92 service::{Service, pipeline::PipelineE},
93};
94
95use self::service_impl::ErrorService;
96
97/// type erased error object. can be used for dynamic access to error's debug/display info.
98/// it also support upcasting and downcasting.
99///
100/// # Examples:
101/// ```rust
102/// use std::{convert::Infallible, error, fmt};
103///
104/// use xitca_web::{error::Error, http::WebResponse, service::Service, WebContext};
105///
106/// // concrete error type
107/// #[derive(Debug)]
108/// struct Foo;
109///
110/// // implement debug and display format.
111/// impl fmt::Display for Foo {
112/// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
113/// f.write_str("Foo")
114/// }
115/// }
116///
117/// // implement Error trait
118/// impl error::Error for Foo {}
119///
120/// // implement Service trait for http response generating.
121/// impl<'r, C> Service<WebContext<'r, C>> for Foo {
122/// type Response = WebResponse;
123/// type Error = Infallible;
124///
125/// async fn call(&self, _: WebContext<'r, C>) -> Result<Self::Response, Self::Error> {
126/// Ok(WebResponse::default())
127/// }
128/// }
129///
130/// async fn handle_error<C>(ctx: WebContext<'_, C>) where C: 'static {
131/// // construct error object.
132/// let e = Error::from_service(Foo);
133///
134/// // format and display error
135/// println!("{e:?}");
136/// println!("{e}");
137///
138/// // generate http response.
139/// let res = Service::call(&e, ctx).await.unwrap();
140/// assert_eq!(res.status().as_u16(), 200);
141///
142/// // upcast error to trait object of std::error::Error
143/// let e = e.upcast();
144///
145/// // downcast error object to concrete type again
146/// assert!(e.downcast_ref::<Foo>().is_some());
147/// }
148/// ```
149pub struct Error(Box<dyn for<'r> ErrorService<WebContext<'r, Request<'r>>>>);
150
151// TODO: this is a temporary type to mirror std::error::request_ref API and latter should
152// be used when it's stabled.
153/// container for dynamic type provided by Error's default Service impl
154pub struct Request<'a> {
155 inner: &'a dyn Any,
156}
157
158impl Request<'_> {
159 /// request a reference of concrete type from dynamic container.
160 /// [Error] would provide your application's global state to it.
161 ///
162 /// # Examples
163 /// ```rust
164 /// use std::{convert::Infallible, fmt};
165 ///
166 /// use xitca_web::{
167 /// error::{Error, Request},
168 /// handler::handler_service,
169 /// http::WebResponse,
170 /// service::Service,
171 /// App, WebContext
172 /// };
173 ///
174 /// let app = App::new()
175 /// .at("/", handler_service(handler))
176 /// .with_state(String::from("996")); // application has a root state of String type.
177 ///
178 /// // handler function returns custom error type
179 /// async fn handler(_: &WebContext<'_, String>) -> Result<&'static str, MyError> {
180 /// Err(MyError)
181 /// }
182 ///
183 /// // a self defined error type and necessary error implements.
184 /// #[derive(Debug)]
185 /// struct MyError;
186 ///
187 /// impl fmt::Display for MyError {
188 /// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
189 /// f.write_str("my error")
190 /// }
191 /// }
192 ///
193 /// impl std::error::Error for MyError {}
194 ///
195 /// impl From<MyError> for Error {
196 /// fn from(e: MyError) -> Self {
197 /// Self::from_service(e)
198 /// }
199 /// }
200 ///
201 /// // Assuming application state is needed in error handler then this is the Service impl
202 /// // you want to write
203 /// impl<'r> Service<WebContext<'r, Request<'r>>> for MyError {
204 /// type Response = WebResponse;
205 /// type Error = Infallible;
206 ///
207 /// async fn call(&self, ctx: WebContext<'r, Request<'r>>) -> Result<Self::Response, Self::Error> {
208 /// // error::Request is able to provide application's state reference with runtime type casting.
209 /// if let Some(state) = ctx.state().request_ref::<String>() {
210 /// assert_eq!(state, "996");
211 /// }
212 /// todo!()
213 /// }
214 /// }
215 /// ```
216 pub fn request_ref<C>(&self) -> Option<&C>
217 where
218 C: 'static,
219 {
220 self.inner.downcast_ref()
221 }
222}
223
224impl Error {
225 // construct an error object from given service type.
226 pub fn from_service<S>(s: S) -> Self
227 where
228 S: for<'r> Service<WebContext<'r, Request<'r>>, Response = WebResponse, Error = Infallible>
229 + error::Error
230 + Send
231 + Sync
232 + 'static,
233 {
234 Self(Box::new(s))
235 }
236
237 /// upcast Error to trait object for advanced error handling.
238 /// See [std::error::Error] for usage
239 pub fn upcast(&self) -> &(dyn error::Error + 'static) {
240 let e = self.0.dyn_err();
241 // due to Rust's specialization limitation Box<dyn std::error::Error> can impl neither
242 // std::error::Error nor service_impl::DynError traits. Therefore a StdError new type
243 // wrapper is introduced to work around it. When upcasting the error this new type is manually
244 // removed so the trait object have correct std::error::Error trait impl for the inner
245 // type
246 if let Some(e) = e.downcast_ref::<StdError>() {
247 return &*e.0;
248 }
249 e
250 }
251}
252
253impl fmt::Debug for Error {
254 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
255 fmt::Debug::fmt(&*self.0, f)
256 }
257}
258
259impl fmt::Display for Error {
260 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
261 fmt::Display::fmt(&*self.0, f)
262 }
263}
264
265impl error::Error for Error {
266 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
267 self.0.source()
268 }
269
270 #[cfg(feature = "nightly")]
271 fn provide<'a>(&'a self, request: &mut error::Request<'a>) {
272 self.0.provide(request)
273 }
274}
275
276impl<'r, C> Service<WebContext<'r, C>> for Error
277where
278 C: 'static,
279{
280 type Response = WebResponse;
281 type Error = Infallible;
282
283 async fn call(&self, ctx: WebContext<'r, C>) -> Result<Self::Response, Self::Error> {
284 let WebContext { req, body, ctx } = ctx;
285 crate::service::object::ServiceObject::call(
286 &self.0,
287 WebContext {
288 req,
289 body,
290 ctx: &Request { inner: ctx as _ },
291 },
292 )
293 .await
294 }
295}
296
297macro_rules! error_from_service {
298 ($tt: ty) => {
299 impl From<$tt> for crate::error::Error {
300 fn from(e: $tt) -> Self {
301 Self::from_service(e)
302 }
303 }
304 };
305}
306
307pub(crate) use error_from_service;
308
309macro_rules! blank_error_service {
310 ($type: ty, $status: path) => {
311 impl<'r, C, B> crate::service::Service<crate::WebContext<'r, C, B>> for $type {
312 type Response = crate::http::WebResponse;
313 type Error = ::core::convert::Infallible;
314
315 async fn call(&self, ctx: crate::WebContext<'r, C, B>) -> Result<Self::Response, Self::Error> {
316 let mut res = ctx.into_response(crate::body::ResponseBody::empty());
317 *res.status_mut() = $status;
318 Ok(res)
319 }
320 }
321 };
322}
323
324pub(crate) use blank_error_service;
325
326macro_rules! forward_blank_internal {
327 ($type: ty) => {
328 impl<'r, C, B> crate::service::Service<crate::WebContext<'r, C, B>> for $type {
329 type Response = crate::http::WebResponse;
330 type Error = ::core::convert::Infallible;
331
332 async fn call(&self, ctx: crate::WebContext<'r, C, B>) -> Result<Self::Response, Self::Error> {
333 crate::http::StatusCode::INTERNAL_SERVER_ERROR.call(ctx).await
334 }
335 }
336 };
337}
338
339pub(crate) use forward_blank_internal;
340
341macro_rules! forward_blank_bad_request {
342 ($type: ty) => {
343 impl<'r, C, B> crate::service::Service<crate::WebContext<'r, C, B>> for $type {
344 type Response = crate::http::WebResponse;
345 type Error = ::core::convert::Infallible;
346
347 async fn call(&self, ctx: crate::WebContext<'r, C, B>) -> Result<Self::Response, Self::Error> {
348 crate::http::StatusCode::BAD_REQUEST.call(ctx).await
349 }
350 }
351 };
352}
353
354pub(crate) use forward_blank_bad_request;
355
356impl From<Infallible> for Error {
357 fn from(e: Infallible) -> Self {
358 match e {}
359 }
360}
361
362impl<'r, C, B> Service<WebContext<'r, C, B>> for Infallible {
363 type Response = WebResponse;
364 type Error = Infallible;
365
366 async fn call(&self, _: WebContext<'r, C, B>) -> Result<Self::Response, Self::Error> {
367 unreachable!()
368 }
369}
370
371error_from_service!(io::Error);
372forward_blank_internal!(io::Error);
373
374type StdErr = Box<dyn error::Error + Send + Sync>;
375
376impl From<StdErr> for Error {
377 fn from(e: StdErr) -> Self {
378 // this is a hack for middleware::Limit where it wraps around request stream body
379 // and produce BodyOverFlow error and return it as BodyError. In the mean time
380 // BodyError is another type alias share the same real type of StdErr and both share
381 // the same conversion path when converting into Error.
382 //
383 // currently the downcast and clone is to restore BodyOverFlow's original Service impl
384 // where it will produce 400 bad request http response while StdErr will be producing
385 // 500 internal server error http response. As well as restoring downstream Error
386 // consumer's chance to downcast BodyOverFlow type.
387 //
388 // TODO: BodyError type should be replaced with Error in streaming interface.
389 if let Some(e) = e.downcast_ref::<BodyOverFlow>() {
390 return Self::from(e.clone());
391 }
392
393 Self(Box::new(StdError(e)))
394 }
395}
396
397forward_blank_internal!(StdErr);
398
399/*
400 new type for `Box<dyn std::error::Error + Send + Sync>`. produce minimal
401 "500 InternalServerError" response and forward formatting, error handling
402 to inner type.
403 In other words it's an error type keep it's original formatting and error
404 handling methods without a specific `Service` impl for generating custom
405 http response.
406*/
407struct StdError(StdErr);
408
409impl fmt::Debug for StdError {
410 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
411 fmt::Debug::fmt(&self.0, f)
412 }
413}
414
415impl fmt::Display for StdError {
416 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
417 fmt::Display::fmt(&self.0, f)
418 }
419}
420
421impl error::Error for StdError {
422 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
423 self.0.source()
424 }
425
426 #[cfg(feature = "nightly")]
427 fn provide<'a>(&'a self, request: &mut error::Request<'a>) {
428 self.0.provide(request);
429 }
430}
431
432error_from_service!(StdError);
433
434impl<'r, C, B> Service<WebContext<'r, C, B>> for StdError {
435 type Response = WebResponse;
436 type Error = Infallible;
437
438 async fn call(&self, ctx: WebContext<'r, C, B>) -> Result<Self::Response, Self::Error> {
439 self.0.call(ctx).await
440 }
441}
442
443/// error happens when joining a thread. typically caused by code panic inside thread.
444/// [`CatchUnwind`] middleware is able to produce this error type.
445///
446/// # Examples:
447/// ```rust
448/// # use xitca_web::error::ThreadJoinError;
449/// fn handle_error(e: &ThreadJoinError) {
450/// // debug and display format thread join error. can only provide basic error message if the error
451/// // source is typical string.(for example generated by panic! macro or unwrap/expect methods)
452/// println!("{e:?}");
453/// println!("{e}");
454///
455/// // for arbitrary thread join error manual type downcast is needed.(for example generated by std::panic::panic_any)
456/// // the mutex lock inside is to satisfy xitca-web's error type's thread safe guarantee: Send and Sync auto traits.
457/// //
458/// // rust's std library only offers Send bound for thread join error and the mutex is solely for the purpose of making
459/// // the error bound to Send + Sync.
460/// let any = e.0.lock().unwrap();
461///
462/// // an arbitrary type we assume possibly being used as panic message.
463/// struct Foo;
464///
465/// if let Some(_foo) = any.downcast_ref::<Foo>() {
466/// // if downcast is succeed it's possible to handle the typed panic message.
467/// }
468///
469/// // otherwise there is basically no way to retrieve any meaningful information and it's best to just ignore the error.
470/// // xitca-web is able to generate minimal http response from it anyway.
471/// }
472/// ```
473///
474/// [`CatchUnwind`]: crate::middleware::CatchUnwind
475pub struct ThreadJoinError(pub Mutex<Box<dyn Any + Send>>);
476
477impl fmt::Debug for ThreadJoinError {
478 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
479 f.debug_struct("ThreadJoinError").finish()
480 }
481}
482
483impl fmt::Display for ThreadJoinError {
484 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
485 let any = self.0.lock().unwrap();
486
487 // only try to catch typical panic message. currently the cases covered are
488 // format string and string reference generated by panic! macro.
489 any.downcast_ref::<String>()
490 .map(String::as_str)
491 .or_else(|| any.downcast_ref::<&str>().copied())
492 .map(|msg| write!(f, "error joining thread: {msg}"))
493 // arbitrary panic message type has to be handled by user manually.
494 .unwrap_or_else(|| f.write_str("error joining thread: unknown. please consider downcast ThreadJoinError.0"))
495 }
496}
497
498impl error::Error for ThreadJoinError {}
499
500impl ThreadJoinError {
501 pub(crate) fn new(e: Box<dyn Any + Send>) -> Self {
502 Self(Mutex::new(e))
503 }
504}
505
506error_from_service!(ThreadJoinError);
507forward_blank_internal!(ThreadJoinError);
508
509impl<F, S> From<PipelineE<F, S>> for Error
510where
511 F: Into<Error>,
512 S: Into<Error>,
513{
514 fn from(pipe: PipelineE<F, S>) -> Self {
515 match pipe {
516 PipelineE::First(f) => f.into(),
517 PipelineE::Second(s) => s.into(),
518 }
519 }
520}
521
522mod service_impl {
523 use crate::service::object::ServiceObject;
524
525 use super::*;
526
527 /// helper trait for constraint error object to multiple bounds
528 pub trait ErrorService<Req>:
529 ServiceObject<Req, Response = WebResponse, Error = Infallible> + DynError + Send + Sync
530 {
531 }
532
533 impl<S, Req> ErrorService<Req> for S where
534 S: ServiceObject<Req, Response = WebResponse, Error = Infallible> + DynError + Send + Sync
535 {
536 }
537
538 /// helper trait for enabling error trait upcast without depending on nightly rust feature
539 /// (written when project MSRV is 1.79)
540 pub trait DynError: error::Error {
541 fn dyn_err(&self) -> &(dyn error::Error + 'static);
542 }
543
544 impl<E> DynError for E
545 where
546 E: error::Error + 'static,
547 {
548 fn dyn_err(&self) -> &(dyn error::Error + 'static) {
549 self
550 }
551 }
552}
553
554#[cfg(test)]
555mod test {
556 use xitca_unsafe_collection::futures::NowOrPanic;
557
558 use crate::{body::ResponseBody, http::StatusCode};
559
560 use super::*;
561
562 #[test]
563 fn cast() {
564 #[derive(Debug)]
565 struct Foo;
566
567 impl fmt::Display for Foo {
568 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
569 f.write_str("Foo")
570 }
571 }
572
573 impl error::Error for Foo {}
574
575 impl<'r, C> Service<WebContext<'r, C>> for Foo {
576 type Response = WebResponse;
577 type Error = Infallible;
578
579 async fn call(&self, _: WebContext<'r, C>) -> Result<Self::Response, Self::Error> {
580 Ok(WebResponse::new(ResponseBody::none()))
581 }
582 }
583
584 let foo = Error::from_service(Foo);
585
586 println!("{foo:?}");
587 println!("{foo}");
588
589 let mut ctx = WebContext::new_test(());
590 let res = Service::call(&foo, ctx.as_web_ctx()).now_or_panic().unwrap();
591 assert_eq!(res.status().as_u16(), 200);
592
593 let err = Error::from(Box::new(Foo) as Box<dyn std::error::Error + Send + Sync>);
594
595 println!("{err:?}");
596 println!("{err}");
597
598 assert!(err.upcast().downcast_ref::<Foo>().is_some());
599
600 #[derive(Debug)]
601 struct Bar;
602
603 impl fmt::Display for Bar {
604 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
605 f.write_str("Foo")
606 }
607 }
608
609 impl error::Error for Bar {}
610
611 impl<'r> Service<WebContext<'r, Request<'r>>> for Bar {
612 type Response = WebResponse;
613 type Error = Infallible;
614
615 async fn call(&self, ctx: WebContext<'r, Request<'r>>) -> Result<Self::Response, Self::Error> {
616 let status = ctx.state().request_ref::<StatusCode>().unwrap();
617 Ok(WebResponse::builder().status(*status).body(Default::default()).unwrap())
618 }
619 }
620
621 let bar = Error::from_service(Bar);
622
623 let res = bar
624 .call(WebContext::new_test(StatusCode::IM_USED).as_web_ctx())
625 .now_or_panic()
626 .unwrap();
627
628 assert_eq!(res.status(), StatusCode::IM_USED);
629 }
630}