rust_web_server/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/// use rust_web_server::error::{AppError, IntoResponse};
35/// use rust_web_server::response::Response;
36///
37/// fn find_user(id: u64) -> Result<Response, AppError> {
38/// if id == 0 {
39/// return Err(AppError::NotFound("user not found".to_string()));
40/// }
41/// Err(AppError::Internal("db error".to_string()))
42/// }
43///
44/// // In your controller:
45/// // let response = find_user(id).unwrap_or_else(|e| e.into_response());
46/// ```
47#[derive(Debug, PartialEq, Eq)]
48pub enum AppError {
49 /// 400 Bad Request — malformed input.
50 BadRequest(String),
51 /// 401 Unauthorized — authentication is required.
52 Unauthorized,
53 /// 403 Forbidden — authenticated but not permitted.
54 Forbidden,
55 /// 404 Not Found — the requested resource does not exist.
56 NotFound(String),
57 /// 409 Conflict — the request conflicts with current state.
58 Conflict(String),
59 /// 422 Unprocessable Entity — input is syntactically valid but semantically wrong.
60 UnprocessableEntity(String),
61 /// 429 Too Many Requests — client has exceeded the rate limit.
62 TooManyRequests,
63 /// 413 Payload Too Large — the request body exceeds a configured limit.
64 PayloadTooLarge(String),
65 /// 500 Internal Server Error — unexpected server-side failure.
66 Internal(String),
67}
68
69impl IntoResponse for AppError {
70 fn into_response(self) -> Response {
71 let (status, body_str) = match &self {
72 AppError::BadRequest(msg) => (STATUS_CODE_REASON_PHRASE.n400_bad_request, msg.as_str()),
73 AppError::Unauthorized => (STATUS_CODE_REASON_PHRASE.n401_unauthorized, "Unauthorized"),
74 AppError::Forbidden => (STATUS_CODE_REASON_PHRASE.n403_forbidden, "Forbidden"),
75 AppError::NotFound(msg) => (STATUS_CODE_REASON_PHRASE.n404_not_found, msg.as_str()),
76 AppError::Conflict(msg) => (STATUS_CODE_REASON_PHRASE.n409_conflict, msg.as_str()),
77 AppError::UnprocessableEntity(msg) => (STATUS_CODE_REASON_PHRASE.n422_unprocessable_entity, msg.as_str()),
78 AppError::TooManyRequests => (STATUS_CODE_REASON_PHRASE.n429_too_many_requests, "Too Many Requests"),
79 AppError::PayloadTooLarge(msg) => (STATUS_CODE_REASON_PHRASE.n413_payload_too_large, msg.as_str()),
80 AppError::Internal(msg) => (STATUS_CODE_REASON_PHRASE.n500_internal_server_error, msg.as_str()),
81 };
82
83 // Problem details (18.17.0); `error_format = "text"` keeps the bare
84 // message as the body.
85 let detail = match &self {
86 AppError::Unauthorized | AppError::Forbidden | AppError::TooManyRequests => "",
87 _ => body_str,
88 };
89 let dummy = Request {
90 method: "GET".to_string(),
91 request_uri: "/".to_string(),
92 http_version: "HTTP/1.1".to_string(),
93 headers: vec![],
94 body: Default::default(),
95 };
96 problem::with_headers(status, detail, body_str, Some(Header::get_header_list(&dummy)))
97 }
98}