1#[cfg(feature = "mresult")]
4use http::StatusCode;
5
6#[cfg(feature = "salvo")]
7#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
8use salvo::oapi::{EndpointOutRegister, ToSchema};
9
10#[cfg(feature = "salvo")]
11#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
12use salvo::{Depot, Request, Response};
13
14#[cfg(feature = "salvo")]
15#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
16use salvo::Writer as ServerResponseWriter;
17
18#[cfg(feature = "mresult")]
20#[derive(Clone)]
21pub struct ServerError {
22 pub status_code: Option<StatusCode>,
24 pub public_msg: Option<String>,
26 pub private_msg: Option<Vec<String>>,
28}
29
30#[cfg(feature = "mresult")]
31impl std::fmt::Debug for ServerError {
32 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33 f.write_str(&format!(
34 "Error: `{}` status code\n Error message: \"{}\"{}",
35 self.status_code.unwrap_or(StatusCode::INTERNAL_SERVER_ERROR).as_str(),
36 self.decide_public_msg(),
37 if let Some(privates) = self.private_msg.as_ref() {
38 format!(
39 "\n{}",
40 privates
41 .iter()
42 .map(|e| format!(" Caused by: {e}"))
43 .collect::<Vec<_>>()
44 .join("\n")
45 )
46 } else {
47 String::new()
48 }
49 ))
50 }
51}
52
53#[derive(Debug, serde::Serialize, serde::Deserialize)]
55#[cfg_attr(
56 all(feature = "salvo", not(any(target_arch = "wasm32", target_arch = "wasm64"))),
57 derive(salvo::oapi::ToSchema)
58)]
59pub struct ErrorResponse {
60 pub err: String,
62}
63
64#[cfg(feature = "mresult")]
65impl std::fmt::Display for ServerError {
66 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67 f.write_str(&format!(
68 "Error: `{}` status code\n Error message: \"{}\"{}",
69 self.status_code.unwrap_or(StatusCode::INTERNAL_SERVER_ERROR).as_str(),
70 self.decide_public_msg(),
71 if let Some(privates) = self.private_msg.as_ref() {
72 format!(
73 "\n{}",
74 privates
75 .iter()
76 .map(|e| format!(" Caused by: {e}"))
77 .collect::<Vec<_>>()
78 .join("\n")
79 )
80 } else {
81 String::new()
82 }
83 ))
84 }
85}
86
87#[cfg(feature = "mresult")]
88impl std::error::Error for ServerError {}
89
90impl std::fmt::Display for ErrorResponse {
91 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92 f.write_str(&format!("Error: {}", self.err))
93 }
94}
95
96#[cfg(feature = "mresult")]
97impl std::error::Error for ErrorResponse {}
98
99#[cfg(feature = "cresult")]
101#[derive(Clone)]
102pub struct ClientError {
103 pub message: String,
105}
106
107#[cfg(feature = "cresult")]
108impl std::fmt::Display for ClientError {
109 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
110 f.write_str(self.message.as_str())
111 }
112}
113
114#[cfg(feature = "cresult")]
115impl std::fmt::Debug for ClientError {
116 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
117 f.write_str(self.message.as_str())
118 }
119}
120
121#[cfg(feature = "cresult")]
122impl std::error::Error for ClientError {}
123
124#[cfg(all(feature = "salvo", feature = "mresult"))]
125#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
126impl crate::responses::ExplicitServerWrite for ServerError {
127 async fn explicit_write(self, res: &mut Response) {
128 res.status_code(self.status_code.unwrap_or(StatusCode::INTERNAL_SERVER_ERROR));
129 tracing::error!("{}", self);
130
131 res
132 .add_header(salvo::http::header::CONTENT_TYPE, "application/json", true)
133 .ok();
134 res
135 .write_body(
136 sonic_rs::to_string(&ErrorResponse {
137 err: self.decide_public_msg(),
138 })
139 .unwrap_or(r#"{"err":"Unknown server error"}"#.to_string()),
140 )
141 .ok();
142 }
143}
144
145#[cfg(all(feature = "salvo", feature = "mresult"))]
146#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
147#[salvo::async_trait]
148impl ServerResponseWriter for ServerError {
149 async fn write(self, _req: &mut Request, _depot: &mut Depot, res: &mut Response) {
151 crate::responses::ExplicitServerWrite::explicit_write(self, res).await
152 }
153}
154
155#[cfg(all(feature = "salvo", feature = "mresult"))]
156#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
157impl EndpointOutRegister for ServerError {
158 fn register(components: &mut salvo::oapi::Components, operation: &mut salvo::oapi::Operation) {
160 operation.responses.insert(
161 "400",
162 salvo::oapi::Response::new("Bad request").add_content("application/json", ErrorResponse::to_schema(components)),
163 );
164 operation.responses.insert(
165 "401",
166 salvo::oapi::Response::new("Unauthorized").add_content("application/json", ErrorResponse::to_schema(components)),
167 );
168 operation.responses.insert(
169 "403",
170 salvo::oapi::Response::new("Forbidden").add_content("application/json", ErrorResponse::to_schema(components)),
171 );
172 operation.responses.insert(
173 "404",
174 salvo::oapi::Response::new("Not found").add_content("application/json", ErrorResponse::to_schema(components)),
175 );
176 operation.responses.insert(
177 "405",
178 salvo::oapi::Response::new("Method not allowed")
179 .add_content("application/json", ErrorResponse::to_schema(components)),
180 );
181 operation.responses.insert(
182 "500",
183 salvo::oapi::Response::new("Internal server error")
184 .add_content("application/json", ErrorResponse::to_schema(components)),
185 );
186 }
187}
188
189#[cfg(any(feature = "mresult", all(feature = "reqwest", feature = "cresult")))]
190pub(crate) fn public_msg_from(status_code: &Option<u16>) -> &'static str {
191 match status_code {
192 Some(400) => "Bad request.",
193 Some(401) => "Unauthorized request.",
194 Some(403) => "Access denied.",
195 Some(404) => "Page or method not found.",
196 Some(405) => "Method not allowed.",
197 Some(500) => "Internal server error. Contact the administrator.",
198 _ => "Specific error. Check with the administrator for details.",
199 }
200}
201
202#[cfg(feature = "mresult")]
203impl ServerError {
204 fn decide_public_msg(&self) -> String {
205 if let Some(public_msg) = self.public_msg.as_ref() {
206 public_msg.to_owned()
207 } else {
208 public_msg_from(&self.status_code.as_ref().map(|v| v.as_u16())).to_string()
209 }
210 }
211
212 fn format_error(error: &(dyn std::error::Error + 'static)) -> Vec<String> {
213 let mut result = vec![];
214 let mut current_error: Option<&(dyn std::error::Error + 'static)> = Some(error);
215
216 while let Some(err) = current_error {
217 result.push(err.to_string());
218 current_error = err.source();
219 }
220
221 result
222 }
223
224 pub fn from_private(err: impl std::error::Error + 'static) -> Self {
226 let err_data = Self::format_error(&err);
227
228 Self {
229 status_code: None,
230 public_msg: None,
231 private_msg: Some(err_data),
232 }
233 }
234
235 pub fn from_private_str(err: impl Into<String>) -> Self {
237 Self {
238 status_code: None,
239 public_msg: None,
240 private_msg: Some(vec![err.into()]),
241 }
242 }
243
244 pub fn with_private_str(mut self, new_private_msg: impl Into<String>) -> Self {
246 if let Some(privates) = self.private_msg.as_mut() {
247 privates.insert(0, new_private_msg.into());
248 } else {
249 self.private_msg = Some(vec![new_private_msg.into()]);
250 }
251
252 self
253 }
254
255 pub fn with_public(mut self, new_public_msg: impl Into<String>) -> Self {
259 if let Some(old_public_msg) = self.public_msg.take() {
260 if let Some(privates) = self.private_msg.as_mut() {
261 privates.insert(0, old_public_msg);
262 } else {
263 self.private_msg = Some(vec![old_public_msg]);
264 }
265 }
266
267 self.public_msg = Some(new_public_msg.into());
268 self
269 }
270
271 pub fn from_public(public_msg: impl Into<String>) -> Self {
273 Self {
274 status_code: None,
275 public_msg: Some(public_msg.into()),
276 private_msg: None,
277 }
278 }
279
280 pub fn with_400(mut self) -> Self {
282 self.status_code = Some(StatusCode::BAD_REQUEST);
283 self
284 }
285
286 pub fn with_401(mut self) -> Self {
288 self.status_code = Some(StatusCode::UNAUTHORIZED);
289 self
290 }
291
292 pub fn with_403(mut self) -> Self {
294 self.status_code = Some(StatusCode::FORBIDDEN);
295 self
296 }
297
298 pub fn with_404(mut self) -> Self {
300 self.status_code = Some(StatusCode::NOT_FOUND);
301 self
302 }
303
304 pub fn with_405(mut self) -> Self {
306 self.status_code = Some(StatusCode::METHOD_NOT_ALLOWED);
307 self
308 }
309
310 pub fn with_500(mut self) -> Self {
312 self.status_code = Some(StatusCode::INTERNAL_SERVER_ERROR);
313 self
314 }
315
316 pub fn with_code(mut self, code: StatusCode) -> Self {
318 self.status_code = Some(code);
319 self
320 }
321
322 pub fn bail<T>(self) -> Result<T, Self> {
324 Err(self)
325 }
326}
327
328#[cfg(feature = "cresult")]
329impl ClientError {
330 pub fn from<T: std::error::Error>(value: T) -> Self {
332 Self {
333 message: value.to_string(),
334 }
335 }
336
337 #[allow(clippy::should_implement_trait)]
339 pub fn from_str(value: impl Into<String>) -> Self {
340 Self { message: value.into() }
341 }
342
343 pub fn context(mut self, value: impl Into<String>) -> Self {
345 self.message = value.into() + "\n Caused by: " + &self.message;
346 self
347 }
348}
349
350#[cfg(feature = "cresult")]
351impl AsRef<str> for ClientError {
352 fn as_ref(&self) -> &str {
353 self.message.as_str()
354 }
355}