roa_core/
err.rs

1use std::fmt::{Display, Formatter};
2use std::result::Result as StdResult;
3
4pub use http::StatusCode;
5
6/// Type alias for `StdResult`.
7pub type Result<R = ()> = StdResult<R, Status>;
8
9/// Construct a `Status`.
10///
11/// - `status!(status_code)` will be expanded to `status!(status_code, "")`
12/// - `status!(status_code, message)` will be expanded to `status!(status_code, message, true)`
13/// - `status!(status_code, message, expose)` will be expanded to `Status::new(status_code, message, expose)`
14///
15/// ### Example
16/// ```rust
17/// use roa_core::{App, Context, Next, Result, status};
18/// use roa_core::http::StatusCode;
19///
20/// let app = App::new()
21///     .gate(gate)
22///     .end(status!(StatusCode::IM_A_TEAPOT, "I'm a teapot!"));
23/// async fn gate(ctx: &mut Context, next: Next<'_>) -> Result {
24///     next.await?; // throw
25///     unreachable!();
26///     ctx.resp.status = StatusCode::OK;
27///     Ok(())
28/// }
29/// ```
30#[macro_export]
31macro_rules! status {
32    ($status_code:expr) => {
33        $crate::status!($status_code, "")
34    };
35    ($status_code:expr, $message:expr) => {
36        $crate::status!($status_code, $message, true)
37    };
38    ($status_code:expr, $message:expr, $expose:expr) => {
39        $crate::Status::new($status_code, $message, $expose)
40    };
41}
42
43/// Throw an `Err(Status)`.
44///
45/// - `throw!(status_code)` will be expanded to `throw!(status_code, "")`
46/// - `throw!(status_code, message)` will be expanded to `throw!(status_code, message, true)`
47/// - `throw!(status_code, message, expose)` will be expanded to `return Err(Status::new(status_code, message, expose));`
48///
49/// ### Example
50/// ```rust
51/// use roa_core::{App, Context, Next, Result, throw};
52/// use roa_core::http::StatusCode;
53///
54/// let app = App::new().gate(gate).end(end);
55/// async fn gate(ctx: &mut Context, next: Next<'_>) -> Result {
56///     next.await?; // throw
57///     unreachable!();
58///     ctx.resp.status = StatusCode::OK;
59///     Ok(())
60/// }
61///
62/// async fn end(ctx: &mut Context) -> Result {
63///     throw!(StatusCode::IM_A_TEAPOT, "I'm a teapot!"); // throw
64///     unreachable!()
65/// }
66/// ```
67#[macro_export]
68macro_rules! throw {
69    ($status_code:expr) => {
70        return core::result::Result::Err($crate::status!($status_code))
71    };
72    ($status_code:expr, $message:expr) => {
73        return core::result::Result::Err($crate::status!($status_code, $message))
74    };
75    ($status_code:expr, $message:expr, $expose:expr) => {
76        return core::result::Result::Err($crate::status!($status_code, $message, $expose))
77    };
78}
79
80/// The `Status` of roa.
81#[derive(Debug, Clone, Eq, PartialEq)]
82pub struct Status {
83    /// StatusCode will be responded to client if Error is thrown by the top middleware.
84    ///
85    /// ### Example
86    /// ```rust
87    /// use roa_core::{App, Context, Next, Result, MiddlewareExt, throw};
88    /// use roa_core::http::StatusCode;
89    ///
90    /// let app = App::new().gate(gate).end(end);
91    /// async fn gate(ctx: &mut Context, next: Next<'_>) -> Result {
92    ///     ctx.resp.status = StatusCode::OK;
93    ///     next.await // not caught
94    /// }
95    ///
96    /// async fn end(ctx: &mut Context) -> Result {
97    ///     throw!(StatusCode::IM_A_TEAPOT, "I'm a teapot!"); // throw
98    ///     unreachable!()
99    /// }
100    /// ```
101    pub status_code: StatusCode,
102
103    /// Data will be written to response body if self.expose is true.
104    /// StatusCode will be responded to client if Error is thrown by the top middleware.
105    ///
106    /// ### Example
107    /// ```rust
108    /// use roa_core::{App, Context, Result, Status};
109    /// use roa_core::http::StatusCode;
110    ///
111    /// let app = App::new().end(end);
112    ///
113    /// async fn end(ctx: &mut Context) -> Result {
114    ///     Err(Status::new(StatusCode::IM_A_TEAPOT, "I'm a teapot!", false)) // message won't be exposed to user.
115    /// }
116    ///
117    /// ```
118    pub message: String,
119
120    /// if message exposed.
121    pub expose: bool,
122}
123
124impl Status {
125    /// Construct an error.
126    #[inline]
127    pub fn new(status_code: StatusCode, message: impl ToString, expose: bool) -> Self {
128        Self {
129            status_code,
130            message: message.to_string(),
131            expose,
132        }
133    }
134}
135
136impl<E> From<E> for Status
137where
138    E: std::error::Error,
139{
140    #[inline]
141    fn from(err: E) -> Self {
142        Self::new(StatusCode::INTERNAL_SERVER_ERROR, err, false)
143    }
144}
145
146impl Display for Status {
147    #[inline]
148    fn fmt(&self, f: &mut Formatter<'_>) -> StdResult<(), std::fmt::Error> {
149        f.write_str(&format!("{}: {}", self.status_code, self.message))
150    }
151}