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