Skip to main content

rws_http/error/
mod.rs

1#[cfg(test)]
2mod tests;
3
4pub mod problem;
5pub use problem::problem;
6
7use crate::header::Header;
8use crate::request::Request;
9use crate::response::{Response, STATUS_CODE_REASON_PHRASE};
10
11/// Implemented by any type that can be turned into an HTTP [`Response`].
12///
13/// Implement this on your application error enum so handlers can return
14/// `Result<Response, MyError>` and the framework maps the error to the
15/// correct HTTP status automatically.
16///
17/// [`Response`] itself implements `IntoResponse` as the identity conversion.
18pub trait IntoResponse {
19    fn into_response(self) -> Response;
20}
21
22impl IntoResponse for Response {
23    fn into_response(self) -> Response {
24        self
25    }
26}
27
28/// A built-in typed error that maps common failure cases to standard HTTP
29/// status codes. Use it directly or as a model for your own error type.
30///
31/// # Example
32///
33/// ```rust,no_run
34/// # extern crate rws_http as rust_web_server;
35/// use rust_web_server::error::{AppError, IntoResponse};
36/// use rust_web_server::response::Response;
37///
38/// fn find_user(id: u64) -> Result<Response, AppError> {
39///     if id == 0 {
40///         return Err(AppError::NotFound("user not found".to_string()));
41///     }
42///     Err(AppError::Internal("db error".to_string()))
43/// }
44///
45/// // In your controller:
46/// // let response = find_user(id).unwrap_or_else(|e| e.into_response());
47/// ```
48#[derive(Debug, PartialEq, Eq)]
49pub enum AppError {
50    /// 400 Bad Request — malformed input.
51    BadRequest(String),
52    /// 401 Unauthorized — authentication is required.
53    Unauthorized,
54    /// 403 Forbidden — authenticated but not permitted.
55    Forbidden,
56    /// 404 Not Found — the requested resource does not exist.
57    NotFound(String),
58    /// 409 Conflict — the request conflicts with current state.
59    Conflict(String),
60    /// 422 Unprocessable Entity — input is syntactically valid but semantically wrong.
61    UnprocessableEntity(String),
62    /// 429 Too Many Requests — client has exceeded the rate limit.
63    TooManyRequests,
64    /// 413 Payload Too Large — the request body exceeds a configured limit.
65    PayloadTooLarge(String),
66    /// 500 Internal Server Error — unexpected server-side failure.
67    Internal(String),
68}
69
70impl IntoResponse for AppError {
71    fn into_response(self) -> Response {
72        let (status, body_str) = match &self {
73            AppError::BadRequest(msg)           => (STATUS_CODE_REASON_PHRASE.n400_bad_request, msg.as_str()),
74            AppError::Unauthorized              => (STATUS_CODE_REASON_PHRASE.n401_unauthorized, "Unauthorized"),
75            AppError::Forbidden                 => (STATUS_CODE_REASON_PHRASE.n403_forbidden, "Forbidden"),
76            AppError::NotFound(msg)             => (STATUS_CODE_REASON_PHRASE.n404_not_found, msg.as_str()),
77            AppError::Conflict(msg)             => (STATUS_CODE_REASON_PHRASE.n409_conflict, msg.as_str()),
78            AppError::UnprocessableEntity(msg)  => (STATUS_CODE_REASON_PHRASE.n422_unprocessable_entity, msg.as_str()),
79            AppError::TooManyRequests           => (STATUS_CODE_REASON_PHRASE.n429_too_many_requests, "Too Many Requests"),
80            AppError::PayloadTooLarge(msg)       => (STATUS_CODE_REASON_PHRASE.n413_payload_too_large, msg.as_str()),
81            AppError::Internal(msg)             => (STATUS_CODE_REASON_PHRASE.n500_internal_server_error, msg.as_str()),
82        };
83
84        // Problem details (18.17.0); `error_format = "text"` keeps the bare
85        // message as the body.
86        let detail = match &self {
87            AppError::Unauthorized | AppError::Forbidden | AppError::TooManyRequests => "",
88            _ => body_str,
89        };
90        let dummy = Request {
91            method: "GET".to_string(),
92            request_uri: "/".to_string(),
93            http_version: "HTTP/1.1".to_string(),
94            headers: vec![],
95            body: Default::default(),
96        };
97        problem::with_headers(status, detail, body_str, Some(Header::get_header_list(&dummy)))
98    }
99}