Skip to main content

ullun/
error.rs

1//! Error types and Result alias
2
3use std::fmt;
4
5pub type Result<T> = std::result::Result<T, Error>;
6
7/// One invalid field of a request, reported by [`Error::Validation`]
8#[derive(Debug, Clone, PartialEq, Eq)]
9pub struct FieldError {
10    pub field: String,
11    pub message: String,
12}
13
14/// Framework error type that maps to HTTP status codes
15#[derive(Debug)]
16#[non_exhaustive]
17pub enum Error {
18    /// 400 Bad Request
19    BadRequest(String),
20    /// 401 Unauthorized
21    Unauthorized(String),
22    /// 403 Forbidden
23    Forbidden(String),
24    /// 404 Not Found
25    NotFound(String),
26    /// 500 Internal Server Error
27    Internal(String),
28    /// 422 Unprocessable Content with one entry per invalid field, rendered as
29    /// `{"error": "Validation failed", "status": 422, "fields": {"title": ["..."]}}`.
30    /// See [`Validator`](crate::validate::Validator).
31    Validation(Vec<FieldError>),
32    /// Custom status code + message
33    Custom(u16, String),
34}
35
36impl Error {
37    pub fn status_code(&self) -> u16 {
38        match self {
39            Error::BadRequest(_) => 400,
40            Error::Unauthorized(_) => 401,
41            Error::Forbidden(_) => 403,
42            Error::NotFound(_) => 404,
43            Error::Internal(_) => 500,
44            Error::Validation(_) => 422,
45            Error::Custom(code, _) => *code,
46        }
47    }
48
49    /// The status as an [`http::StatusCode`] (invalid custom codes become 500)
50    pub fn status(&self) -> http::StatusCode {
51        http::StatusCode::from_u16(self.status_code())
52            .unwrap_or(http::StatusCode::INTERNAL_SERVER_ERROR)
53    }
54
55    pub fn message(&self) -> &str {
56        match self {
57            Error::Validation(_) => "Validation failed",
58            Error::BadRequest(msg)
59            | Error::Unauthorized(msg)
60            | Error::Forbidden(msg)
61            | Error::NotFound(msg)
62            | Error::Internal(msg)
63            | Error::Custom(_, msg) => msg,
64        }
65    }
66}
67
68impl fmt::Display for Error {
69    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70        write!(f, "{}", self.message())
71    }
72}
73
74impl std::error::Error for Error {}
75
76// Convenience constructors
77impl Error {
78    pub fn bad_request(msg: impl Into<String>) -> Self {
79        Error::BadRequest(msg.into())
80    }
81
82    pub fn unauthorized(msg: impl Into<String>) -> Self {
83        Error::Unauthorized(msg.into())
84    }
85
86    pub fn forbidden(msg: impl Into<String>) -> Self {
87        Error::Forbidden(msg.into())
88    }
89
90    pub fn not_found(msg: impl Into<String>) -> Self {
91        Error::NotFound(msg.into())
92    }
93
94    pub fn internal(msg: impl Into<String>) -> Self {
95        Error::Internal(msg.into())
96    }
97
98    /// 422 with the given invalid fields
99    pub fn validation(fields: Vec<FieldError>) -> Self {
100        Error::Validation(fields)
101    }
102
103    /// The invalid fields of a [`Error::Validation`], empty for other errors
104    pub fn fields(&self) -> &[FieldError] {
105        match self {
106            Error::Validation(fields) => fields,
107            _ => &[],
108        }
109    }
110
111    pub fn custom(status: u16, msg: impl Into<String>) -> Self {
112        Error::Custom(status, msg.into())
113    }
114}