Skip to main content

sova_core/
error.rs

1use crate::response::Response;
2use thiserror::Error as ThisError;
3
4/// Framework and application errors mapped to HTTP responses.
5#[derive(Debug, ThisError)]
6pub enum Error {
7    #[error("not found")]
8    NotFound,
9
10    #[error("unauthorized")]
11    Unauthorized,
12
13    #[error("forbidden")]
14    Forbidden,
15
16    #[error("bad request: {0}")]
17    BadRequest(String),
18
19    #[error("payload too large")]
20    PayloadTooLarge,
21
22    #[error("method not allowed")]
23    MethodNotAllowed,
24
25    #[error("internal error: {0}")]
26    Internal(String),
27
28    #[error("json error: {0}")]
29    Json(#[from] serde_json::Error),
30
31    #[error("io error: {0}")]
32    Io(#[from] std::io::Error),
33
34    /// Pre-built HTTP response (plugin/domain errors). Skips `error_handler`.
35    #[error("response")]
36    Response(Box<Response>),
37}
38
39pub type Result<T> = std::result::Result<T, Error>;
40
41impl Error {
42    /// Convenience for [`Error::BadRequest`].
43    pub fn bad_request(msg: impl Into<String>) -> Self {
44        Self::BadRequest(msg.into())
45    }
46
47    /// Build [`Error::Response`] from a status and any [`IntoResponse`] body.
48    pub fn custom(status: u16, body: impl IntoResponse) -> Self {
49        Error::Response(Box::new(body.into_response().status(status)))
50    }
51
52    pub fn into_response(self) -> Response {
53        match self {
54            Error::NotFound => Response::text("Not Found").status(404),
55            Error::Unauthorized => Response::text("Unauthorized").status(401),
56            Error::Forbidden => Response::text("Forbidden").status(403),
57            Error::BadRequest(msg) => Response::text(msg).status(400),
58            Error::PayloadTooLarge => Response::text("Payload Too Large").status(413),
59            Error::MethodNotAllowed => Response::text("Method Not Allowed").status(405),
60            Error::Internal(msg) => Response::text(msg).status(500),
61            Error::Json(err) => Response::text(format!("JSON error: {err}")).status(400),
62            Error::Io(err) => Response::text(format!("IO error: {err}")).status(500),
63            Error::Response(res) => *res,
64        }
65    }
66}
67
68/// Convert a value into an HTTP response.
69pub trait IntoResponse {
70    fn into_response(self) -> Response;
71}
72
73impl IntoResponse for Response {
74    fn into_response(self) -> Response {
75        self
76    }
77}
78
79impl IntoResponse for Error {
80    fn into_response(self) -> Response {
81        Error::into_response(self)
82    }
83}
84
85impl IntoResponse for String {
86    fn into_response(self) -> Response {
87        Response::text(self)
88    }
89}
90
91impl IntoResponse for &str {
92    fn into_response(self) -> Response {
93        Response::text(self)
94    }
95}