Skip to main content

rust_web_server/error/
mod.rs

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