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    /// Build [`Error::Response`] from a status and any [`IntoResponse`] body.
40    pub fn custom(status: u16, body: impl IntoResponse) -> Self {
41        Error::Response(Box::new(body.into_response().status(status)))
42    }
43
44    pub fn into_response(self) -> Response {
45        match self {
46            Error::NotFound => Response::text("Not Found").status(404),
47            Error::Unauthorized => Response::text("Unauthorized").status(401),
48            Error::BadRequest(msg) => Response::text(msg).status(400),
49            Error::PayloadTooLarge => Response::text("Payload Too Large").status(413),
50            Error::MethodNotAllowed => Response::text("Method Not Allowed").status(405),
51            Error::Internal(msg) => Response::text(msg).status(500),
52            Error::Json(err) => Response::text(format!("JSON error: {err}")).status(400),
53            Error::Io(err) => Response::text(format!("IO error: {err}")).status(500),
54            Error::Response(res) => *res,
55        }
56    }
57}
58
59/// Convert a value into an HTTP response.
60pub trait IntoResponse {
61    fn into_response(self) -> Response;
62}
63
64impl IntoResponse for Response {
65    fn into_response(self) -> Response {
66        self
67    }
68}
69
70impl IntoResponse for Error {
71    fn into_response(self) -> Response {
72        Error::into_response(self)
73    }
74}
75
76impl IntoResponse for String {
77    fn into_response(self) -> Response {
78        Response::text(self)
79    }
80}
81
82impl IntoResponse for &str {
83    fn into_response(self) -> Response {
84        Response::text(self)
85    }
86}