Skip to main content

rango_core/
error.rs

1//! Error type for the request pipeline.
2
3use std::fmt;
4
5use axum::{
6    http::StatusCode,
7    response::{IntoResponse, Response},
8};
9
10/// Request pipeline error, convertible into an HTTP response.
11#[derive(Debug)]
12pub enum Error {
13    /// Invalid request with the reason in the message.
14    BadRequest(String),
15    /// Request denied by a guard.
16    Forbidden,
17    /// No route or resource matched.
18    NotFound,
19    /// Server-side failure with the cause in the message.
20    Server(String),
21    /// Template rendering failure with the cause in the message.
22    Render(String),
23}
24
25impl Error {
26    /// HTTP status for this error.
27    pub fn status(&self) -> StatusCode {
28        match self {
29            Self::BadRequest(_) => StatusCode::BAD_REQUEST,
30            Self::Forbidden => StatusCode::FORBIDDEN,
31            Self::NotFound => StatusCode::NOT_FOUND,
32            Self::Server(_) | Self::Render(_) => StatusCode::INTERNAL_SERVER_ERROR,
33        }
34    }
35}
36
37impl fmt::Display for Error {
38    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39        match self {
40            Self::BadRequest(msg) => write!(f, "{msg}"),
41            Self::Forbidden => write!(f, "Forbidden"),
42            Self::NotFound => write!(f, "Not Found"),
43            Self::Server(msg) | Self::Render(msg) => write!(f, "{msg}"),
44        }
45    }
46}
47
48impl std::error::Error for Error {}
49
50impl From<askama::Error> for Error {
51    fn from(value: askama::Error) -> Self {
52        Self::Render(value.to_string())
53    }
54}
55
56impl From<rango_store::StoreError> for Error {
57    fn from(value: rango_store::StoreError) -> Self {
58        Self::Server(value.to_string())
59    }
60}
61
62impl IntoResponse for Error {
63    fn into_response(self) -> Response {
64        if self.status().is_server_error() {
65            tracing::error!(error = %self);
66        }
67        (self.status(), self.to_string()).into_response()
68    }
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74
75    #[test]
76    fn statuses() {
77        assert_eq!(
78            Error::BadRequest("x".into()).status(),
79            StatusCode::BAD_REQUEST
80        );
81        assert_eq!(Error::Forbidden.status(), StatusCode::FORBIDDEN);
82        assert_eq!(Error::NotFound.status(), StatusCode::NOT_FOUND);
83        assert!(Error::Server("x".into()).status().is_server_error());
84        assert!(Error::Render("x".into()).status().is_server_error());
85    }
86
87    #[test]
88    fn display() {
89        assert_eq!(Error::BadRequest("msg".into()).to_string(), "msg");
90        assert_eq!(Error::Forbidden.to_string(), "Forbidden");
91        assert_eq!(Error::NotFound.to_string(), "Not Found");
92        assert_eq!(Error::Server("msg".into()).to_string(), "msg");
93    }
94}