1use std::error::Error;
2
3use axum_core::response::{IntoResponse, Response};
4
5#[derive(Debug, Clone, PartialEq, Eq)]
6pub enum ApiError {
7 ServerError {
9 status: u16,
10 message: String,
11 },
12 BadRequestError {
14 field: Option<String>,
15 message: String,
16 },
17 NotFoundError {
19 resource: String,
20 },
21 BusinessError {
23 status: u16,
24 message: String,
25 },
26
27 UnauthorizedError {
29 message: String,
30 },
31 ForbiddenError {
33 message: String,
34 },
35 TimeoutError {
37 operation: String,
38 },
39 ValidationError {
41 details: Vec<String>,
42 },
43 ExternalServiceError {
45 service: String,
46 message: String,
47 },
48
49 Created(String),
50}
51
52impl std::fmt::Display for ApiError {
53 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54 match self {
55 ApiError::ServerError { status, message } => {
56 write!(f, "Server Error [Code {}]: {}", status, message)
57 }
58 ApiError::BadRequestError { field, message } => {
59 if let Some(field) = field {
60 write!(f, "Bad Request Error [Field '{}']: {}", field, message)
61 } else {
62 write!(f, "Bad Request Error: {}", message)
63 }
64 }
65 ApiError::NotFoundError { resource } => write!(f, "Resource '{}' not found", resource),
66 ApiError::BusinessError { status, message } => {
67 write!(f, "Business Error [Code {}]: {}", status, message)
68 }
69 ApiError::UnauthorizedError { message } => write!(f, "Unauthorized: {}", message),
70 ApiError::ForbiddenError { message } => write!(f, "Forbidden: {}", message),
71 ApiError::TimeoutError { operation } => {
72 write!(f, "Operation '{}' timed out", operation)
73 }
74
75 ApiError::ValidationError { details } => {
76 write!(f, "Validation failed: {}", details.join(", "))
77 }
78 ApiError::ExternalServiceError { service, message } => {
79 write!(f, "External Service Error [{}]: {}", service, message)
80 }
81
82 ApiError::Created(message) => {
83 write!(f, "Created Error: {}", message)
84 }
85 }
86 }
87}
88
89impl std::error::Error for ApiError {}
90
91impl ApiError {
92 pub fn get_msg(&self) -> String {
93 match self {
94 ApiError::ServerError { message, .. } => message.into(),
95 ApiError::BadRequestError { message, .. } => message.into(),
96 ApiError::NotFoundError { resource } => resource.into(),
97 ApiError::BusinessError { message, .. } => message.into(),
98 ApiError::UnauthorizedError { message } => message.into(),
99 ApiError::ForbiddenError { message } => message.into(),
100 ApiError::TimeoutError { operation } => operation.into(),
101 ApiError::ValidationError { details } => details.join(","),
102 ApiError::ExternalServiceError { message, .. } => message.into(),
103 ApiError::Created(message) => message.into(),
104 }
105 }
106
107 pub fn with_context<T>(self, f: T) -> Self
108 where
109 T: FnOnce() -> String,
110 {
111 match self {
112 ApiError::ServerError { status, message } => ApiError::ServerError {
113 status,
114 message: format!("{}; [with_context] {}", message, f()),
115 },
116 ApiError::BadRequestError { field, message } => ApiError::BadRequestError {
117 field,
118 message: format!("{}; [with_context] {}", message, f()),
119 },
120 ApiError::NotFoundError { resource } => ApiError::NotFoundError {
121 resource: format!("{}; [with_context] {}", resource, f()),
122 },
123 ApiError::BusinessError { status, message } => ApiError::BusinessError {
124 status,
125 message: format!("{}; [with_context] {}", message, f()),
126 },
127 ApiError::UnauthorizedError { message } => ApiError::UnauthorizedError {
128 message: format!("{}; [with_context] {}", message, f()),
129 },
130 ApiError::ForbiddenError { message } => ApiError::ForbiddenError {
131 message: format!("{}; [with_context] {}", message, f()),
132 },
133 ApiError::TimeoutError { operation } => ApiError::TimeoutError {
134 operation: format!("{}; [with_context] {}", operation, f()),
135 },
136 ApiError::ValidationError { mut details } => {
137 details.push(format!("[with_context] {}", f()));
138 ApiError::ValidationError { details }
139 }
140 ApiError::ExternalServiceError { service, message } => ApiError::ExternalServiceError {
141 service,
142 message: format!("{}; [with_context] {}", message, f()),
143 },
144 ApiError::Created(message) => {
145 ApiError::Created(format!("{}; [with_context] {}", message, f()))
146 }
147 }
148 }
149}
150
151impl From<Box<dyn Error>> for ApiError {
152 fn from(err: Box<dyn Error>) -> Self {
153 ApiError::Created(err.to_string())
154 }
155}
156
157impl IntoResponse for ApiError {
158 fn into_response(self) -> Response {
159 let (http_code, status, error_message) = match self {
160 ApiError::ServerError { status, message } => (500, status, message),
161 ApiError::BadRequestError { field, message } => {
162 if let Some(field) = field {
163 (
164 400,
165 400,
166 format!("Bad Request Error [Field '{}']: {}", field, message),
167 )
168 } else {
169 (400, 400, format!("Bad Request Error: {}", message))
170 }
171 }
172 ApiError::NotFoundError { resource } => {
173 (404, 404, format!("Resource '{}' not found", resource))
174 }
175 ApiError::BusinessError { status, message } => (
176 400,
177 status,
178 format!("Business Error [Code {}]: {}", status, message),
179 ),
180 ApiError::UnauthorizedError { message } => {
181 (401, 401, format!("Unauthorized: {}", message))
182 }
183 ApiError::ForbiddenError { message } => (403, 403, format!("Forbidden: {}", message)),
184 ApiError::TimeoutError { operation } => {
185 (408, 408, format!("Operation '{}' timed out", operation))
186 }
187 ApiError::ValidationError { details } => (
188 422,
189 422,
190 format!("Validation failed: {}", details.join(", ")),
191 ),
192 ApiError::ExternalServiceError { service, message } => (
193 503,
194 503,
195 format!("External Service Error [{}]: {}", service, message),
196 ),
197 ApiError::Created(message) => (500, 500, message),
198 };
199
200 Response::builder()
201 .status(http_code)
202 .header("Content-Type", "application/json")
203 .body(
204 format!("{{\"status\": {status},\"message\": {error_message},\"data\": null}}")
205 .into(),
206 )
207 .unwrap()
208 }
209}