1#[cfg(feature = "salvo")]
4#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
5use std::any::Any;
6
7#[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))]
8use std::fmt::Formatter;
9
10#[cfg(feature = "salvo")]
11use salvo::http::StatusCode;
12
13#[cfg(feature = "salvo")]
14use salvo::oapi::{EndpointOutRegister, ToSchema};
15
16#[cfg(feature = "salvo")]
17use salvo::{Depot, Request, Response};
18
19#[cfg(feature = "salvo")]
20use salvo::Writer as ServerResponseWriter;
21
22pub type BoxDynError = Box<dyn std::error::Error + 'static + Send + Sync>;
23
24#[derive(Debug)]
26pub struct ErrorResponse {
27 #[cfg(feature = "salvo")]
28 #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
29 pub status_code: Option<StatusCode>,
30 pub error_text: String,
31 pub original_text: Option<String>,
32 pub public_error: bool,
33}
34
35#[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))]
37#[derive(Debug)]
38pub struct CliError {
39 pub message: String,
40}
41
42#[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))]
43impl std::fmt::Display for CliError {
44 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
45 f.write_str(&format!("{}", self.message))
46 }
47}
48
49#[cfg(feature = "salvo")]
50#[salvo::async_trait]
51impl ServerResponseWriter for ErrorResponse {
52 async fn write(self, _req: &mut Request, _depot: &mut Depot, res: &mut Response) {
54 res.status_code(self.status_code.unwrap_or(StatusCode::INTERNAL_SERVER_ERROR));
55 if !self.public_error {
56 let public_error_desc = match self.status_code {
57 Some(StatusCode::BAD_REQUEST) => "Bad request.",
58 Some(StatusCode::UNAUTHORIZED) => "Unauthorized request.",
59 Some(StatusCode::FORBIDDEN) => "Access denied.",
60 Some(StatusCode::NOT_FOUND) => "Page or method not found.",
61 Some(StatusCode::METHOD_NOT_ALLOWED) => "Method not allowed.",
62 Some(StatusCode::LOCKED) => "Your actions is locked.",
63 Some(StatusCode::INTERNAL_SERVER_ERROR) => {
64 "Internal server error. Contact the administrator."
65 }
66 _ => "Specific error. Check with the administrator for details.",
67 };
68 log::error!(
69 "Error with code {:?}: \"{}\", client will get: \"{}\"",
70 self.status_code,
71 self.error_text,
72 public_error_desc
73 );
74 if self.original_text.is_some() {
75 log::error!("The original error text: {:?}", self.original_text.unwrap());
76 }
77 res.render(public_error_desc);
78 } else {
79 log::error!(
80 "Error with code {:?}: \"{}\"",
81 self.status_code,
82 self.error_text
83 );
84 if self.original_text.is_some() {
85 log::error!("The original error text: {:?}", self.original_text.unwrap());
86 }
87 res.render(&self.error_text);
88 }
89 }
90}
91
92#[cfg(feature = "salvo")]
93impl EndpointOutRegister for ErrorResponse {
94 fn register(components: &mut salvo::oapi::Components, operation: &mut salvo::oapi::Operation) {
96 operation.responses.insert(
97 "400",
98 salvo::oapi::Response::new("Bad request")
99 .add_content("text/plain", String::to_schema(components)),
100 );
101 operation.responses.insert(
102 "401",
103 salvo::oapi::Response::new("Unauthorized")
104 .add_content("text/plain", String::to_schema(components)),
105 );
106 operation.responses.insert(
107 "403",
108 salvo::oapi::Response::new("Forbidden")
109 .add_content("text/plain", String::to_schema(components)),
110 );
111 operation.responses.insert(
112 "404",
113 salvo::oapi::Response::new("Not found")
114 .add_content("text/plain", String::to_schema(components)),
115 );
116 operation.responses.insert(
117 "405",
118 salvo::oapi::Response::new("Method not allowed")
119 .add_content("text/plain", String::to_schema(components)),
120 );
121 operation.responses.insert(
122 "423",
123 salvo::oapi::Response::new("Locked")
124 .add_content("text/plain", String::to_schema(components)),
125 );
126 operation.responses.insert(
127 "500",
128 salvo::oapi::Response::new("Internal server error")
129 .add_content("text/plain", String::to_schema(components)),
130 );
131 }
132}
133
134#[cfg(feature = "salvo")]
135#[allow(dead_code)]
136impl ErrorResponse {
137 pub fn with_400(&mut self) -> &mut Self {
139 self.status_code = Some(StatusCode::BAD_REQUEST);
140 self.public_error = false;
141 self
142 }
143
144 pub fn with_400_pub(&mut self) -> &mut Self {
146 self.status_code = Some(StatusCode::BAD_REQUEST);
147 self.public_error = true;
148 self
149 }
150
151 pub fn with_401(&mut self) -> &mut Self {
153 self.status_code = Some(StatusCode::UNAUTHORIZED);
154 self.public_error = false;
155 self
156 }
157
158 pub fn with_401_pub(&mut self) -> &mut Self {
160 self.status_code = Some(StatusCode::UNAUTHORIZED);
161 self.public_error = true;
162 self
163 }
164
165 pub fn with_403(&mut self) -> &mut Self {
167 self.status_code = Some(StatusCode::FORBIDDEN);
168 self.public_error = false;
169 self
170 }
171
172 pub fn with_403_pub(&mut self) -> &mut Self {
174 self.status_code = Some(StatusCode::FORBIDDEN);
175 self.public_error = true;
176 self
177 }
178
179 pub fn with_404(&mut self) -> &mut Self {
181 self.status_code = Some(StatusCode::NOT_FOUND);
182 self.public_error = false;
183 self
184 }
185
186 pub fn with_404_pub(&mut self) -> &mut Self {
188 self.status_code = Some(StatusCode::NOT_FOUND);
189 self.public_error = true;
190 self
191 }
192
193 pub fn with_405(&mut self) -> &mut Self {
195 self.status_code = Some(StatusCode::METHOD_NOT_ALLOWED);
196 self.public_error = false;
197 self
198 }
199
200 pub fn with_405_pub(&mut self) -> &mut Self {
202 self.status_code = Some(StatusCode::METHOD_NOT_ALLOWED);
203 self.public_error = true;
204 self
205 }
206
207 pub fn with_423(&mut self) -> &mut Self {
209 self.status_code = Some(StatusCode::LOCKED);
210 self.public_error = false;
211 self
212 }
213
214 pub fn with_423_pub(&mut self) -> &mut Self {
216 self.status_code = Some(StatusCode::LOCKED);
217 self.public_error = true;
218 self
219 }
220
221 pub fn with_500(&mut self) -> &mut Self {
223 self.status_code = Some(StatusCode::INTERNAL_SERVER_ERROR);
224 self.public_error = false;
225 self
226 }
227
228 pub fn with_500_pub(&mut self) -> &mut Self {
230 self.status_code = Some(StatusCode::INTERNAL_SERVER_ERROR);
231 self.public_error = true;
232 self
233 }
234
235 pub fn with_text(&mut self, text: String) -> &mut Self {
237 match self.original_text {
238 None => self.original_text = Some(self.error_text.to_owned()),
239 Some(_) => {}
240 }
241 self.error_text = text;
242 self
243 }
244
245 pub fn build(&mut self) -> Self {
247 Self {
248 status_code: self.status_code,
249 error_text: self.error_text.to_owned(),
250 original_text: self.original_text.clone(),
251 public_error: self.public_error,
252 }
253 }
254}
255
256#[cfg(feature = "salvo")]
258pub trait Consider<T> {
259 fn consider(
260 self,
261 status_code: Option<StatusCode>,
262 error_text_replacement: Option<String>,
263 public: bool,
264 ) -> Result<T, ErrorResponse>;
265}
266
267#[cfg(feature = "reqwest")]
268#[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))]
269pub trait ConsiderCli<T> {
270 fn consider_cli(self, error_text_replacement: Option<String>) -> Result<T, CliError>;
271}
272
273#[cfg(feature = "salvo")]
274impl<T> Consider<T> for Result<T, ErrorResponse> {
275 fn consider(
277 self,
278 status_code: Option<StatusCode>,
279 error_text_replacement: Option<String>,
280 public: bool,
281 ) -> Result<T, ErrorResponse> {
282 self.map_err(|e| {
283 let mut new_error = ErrorResponse {
284 status_code,
285 error_text: e.error_text,
286 original_text: e.original_text,
287 public_error: public,
288 };
289 if error_text_replacement.is_some() {
290 new_error.original_text = Some(new_error.error_text.to_owned());
291 new_error.error_text = error_text_replacement.unwrap();
292 }
293 new_error
294 })
295 }
296}
297
298#[cfg(feature = "reqwest")]
299#[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))]
300impl<T> ConsiderCli<T> for Result<T, CliError> {
301 fn consider_cli(self, error_text_replacement: Option<String>) -> Result<T, CliError> {
303 self.map_err(|e| {
304 let mut new_error = CliError { message: e.message };
305 if error_text_replacement.is_some() {
306 new_error.message = error_text_replacement.unwrap();
307 }
308 new_error
309 })
310 }
311}
312
313#[cfg(feature = "salvo")]
314impl<T> Consider<T> for Result<T, String> {
315 fn consider(
317 self,
318 status_code: Option<StatusCode>,
319 error_text_replacement: Option<String>,
320 public: bool,
321 ) -> Result<T, ErrorResponse> {
322 self.map_err(|e| {
323 let mut new_error = ErrorResponse {
324 status_code,
325 error_text: e,
326 original_text: None,
327 public_error: public,
328 };
329 if error_text_replacement.is_some() {
330 new_error.original_text = Some(new_error.error_text.to_owned());
331 new_error.error_text = error_text_replacement.unwrap();
332 }
333 new_error
334 })
335 }
336}
337
338#[cfg(feature = "reqwest")]
339#[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))]
340impl<T> ConsiderCli<T> for Result<T, String> {
341 fn consider_cli(self, error_text_replacement: Option<String>) -> Result<T, CliError> {
343 self.map_err(|e| {
344 let mut new_error = CliError { message: e };
345 if error_text_replacement.is_some() {
346 new_error.message = error_text_replacement.unwrap();
347 }
348 new_error
349 })
350 }
351}
352
353#[cfg(feature = "salvo")]
354impl<T> Consider<T> for Result<T, &str> {
355 fn consider(
357 self,
358 status_code: Option<StatusCode>,
359 error_text_replacement: Option<String>,
360 public: bool,
361 ) -> Result<T, ErrorResponse> {
362 self.map_err(|e| {
363 let mut new_error = ErrorResponse {
364 status_code,
365 error_text: e.to_owned(),
366 original_text: None,
367 public_error: public,
368 };
369 if error_text_replacement.is_some() {
370 new_error.original_text = Some(new_error.error_text.to_owned());
371 new_error.error_text = error_text_replacement.unwrap();
372 }
373 new_error
374 })
375 }
376}
377
378#[cfg(feature = "reqwest")]
379#[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))]
380impl<T> ConsiderCli<T> for Result<T, &str> {
381 fn consider_cli(self, error_text_replacement: Option<String>) -> Result<T, CliError> {
383 self.map_err(|e| {
384 let mut new_error = CliError {
385 message: e.to_owned(),
386 };
387 if error_text_replacement.is_some() {
388 new_error.message = error_text_replacement.unwrap();
389 }
390 new_error
391 })
392 }
393}
394
395#[cfg(feature = "salvo")]
396impl From<String> for ErrorResponse {
397 fn from(value: String) -> Self {
399 Self {
400 status_code: None,
401 error_text: value,
402 original_text: None,
403 public_error: false,
404 }
405 }
406}
407
408#[cfg(feature = "reqwest")]
409#[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))]
410impl From<String> for CliError {
411 fn from(value: String) -> Self {
413 Self { message: value }
414 }
415}
416
417#[cfg(feature = "salvo")]
418impl From<&str> for ErrorResponse {
419 fn from(value: &str) -> Self {
421 Self {
422 status_code: None,
423 error_text: value.to_owned(),
424 original_text: None,
425 public_error: false,
426 }
427 }
428}
429
430#[cfg(feature = "reqwest")]
431#[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))]
432impl From<&str> for CliError {
433 fn from(value: &str) -> Self {
435 Self {
436 message: value.to_owned(),
437 }
438 }
439}
440
441#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
443macro_rules! impl_consider {
444 ($e:ty) => {
445 #[cfg(feature = "salvo")]
446 impl<T> Consider<T> for Result<T, $e> {
447 fn consider(
449 self,
450 status_code: Option<StatusCode>,
451 error_text_replacement: Option<String>,
452 public: bool,
453 ) -> Result<T, ErrorResponse> {
454 self.map_err(|e| {
455 let mut new_error = ErrorResponse {
456 status_code,
457 error_text: e.to_string(),
458 original_text: None,
459 public_error: public,
460 };
461 if error_text_replacement.is_some() {
462 new_error.original_text = Some(new_error.error_text.to_owned());
463 new_error.error_text = error_text_replacement.unwrap();
464 }
465 new_error
466 })
467 }
468 }
469
470 #[cfg(feature = "salvo")]
471 impl From<$e> for ErrorResponse {
472 fn from(value: $e) -> Self {
474 value.to_string().into()
475 }
476 }
477 };
478}
479
480#[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))]
482macro_rules! impl_consider_cli {
483 ($e:ty) => {
484 #[cfg(feature = "reqwest")]
485 impl<T> ConsiderCli<T> for Result<T, $e> {
486 fn consider_cli(self, error_text_replacement: Option<String>) -> Result<T, CliError> {
488 self.map_err(|e| {
489 let mut new_error = CliError {
490 message: e.to_string(),
491 };
492 if error_text_replacement.is_some() {
493 new_error.message = error_text_replacement.unwrap();
494 }
495 new_error
496 })
497 }
498 }
499
500 #[cfg(feature = "reqwest")]
501 impl From<$e> for CliError {
502 fn from(value: $e) -> Self {
504 value.to_string().into()
505 }
506 }
507 };
508}
509
510#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
511impl_consider!(rmp_serde::encode::Error);
512#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
513impl_consider!(rmp_serde::decode::Error);
514#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
515impl_consider!(std::io::Error);
516#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
517impl_consider!(std::string::FromUtf8Error);
518#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
519impl_consider!(std::env::VarError);
520#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
521impl_consider!(std::sync::mpsc::RecvError);
522#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
523impl_consider!(log::SetLoggerError);
524#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
525impl_consider!(serde_json::Error);
526#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
527impl_consider!(BoxDynError);
528
529#[cfg(feature = "salvo")]
530#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
531impl_consider!(salvo::Error);
532
533#[cfg(feature = "salvo")]
534#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
535impl_consider!(salvo::hyper::http::status::InvalidStatusCode);
536
537#[cfg(feature = "salvo")]
538#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
539impl_consider!(salvo::http::ParseError);
540
541#[cfg(feature = "salvo")]
542#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
543impl<T> Consider<T> for Result<T, Option<&Box<dyn Any + Send + Sync>>> {
544 fn consider(
546 self,
547 status_code: Option<StatusCode>,
548 error_text_replacement: Option<String>,
549 public: bool,
550 ) -> Result<T, ErrorResponse> {
551 self.map_err(|_| {
552 let mut new_error = ErrorResponse {
553 status_code,
554 error_text: "Depot obtain failed!".into(),
555 original_text: None,
556 public_error: public,
557 };
558 if error_text_replacement.is_some() {
559 new_error.original_text = Some(new_error.error_text.to_owned());
560 new_error.error_text = error_text_replacement.unwrap();
561 }
562 new_error
563 })
564 }
565}
566
567#[cfg(feature = "salvo")]
568#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
569impl From<Option<&Box<dyn Any + Send + Sync>>> for ErrorResponse {
570 fn from(_value: Option<&Box<(dyn Any + Send + Sync + 'static)>>) -> Self {
572 "Depot obtain failed!".into()
573 }
574}
575
576#[cfg(feature = "salvo")]
577#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
578impl<T, U> Consider<T> for Result<T, std::sync::mpsc::SendError<U>> {
579 fn consider(
581 self,
582 status_code: Option<StatusCode>,
583 error_text_replacement: Option<String>,
584 public: bool,
585 ) -> Result<T, ErrorResponse> {
586 self.map_err(|e| {
587 let mut new_error = ErrorResponse {
588 status_code,
589 error_text: e.to_string(),
590 original_text: None,
591 public_error: public,
592 };
593 if error_text_replacement.is_some() {
594 new_error.original_text = Some(new_error.error_text.to_owned());
595 new_error.error_text = error_text_replacement.unwrap();
596 }
597 new_error
598 })
599 }
600}
601
602#[cfg(feature = "salvo")]
603#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
604impl<U> From<std::sync::mpsc::SendError<U>> for ErrorResponse {
605 fn from(value: std::sync::mpsc::SendError<U>) -> Self {
607 value.to_string().into()
608 }
609}
610
611#[cfg(feature = "salvo")]
612#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
613impl_consider!(salvo::http::header::ToStrError);
614
615#[cfg(feature = "bb8-redis")]
616#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
617impl_consider!(bb8_redis::redis::RedisError);
618
619#[cfg(feature = "bb8-redis")]
620#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
621impl_consider!(bb8::RunError<bb8_redis::redis::RedisError>);
622
623#[cfg(feature = "bb8-mongo")]
624#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
625impl_consider!(bb8_mongodb::Error);
626
627#[cfg(feature = "bb8-mongo")]
628#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
629impl_consider!(mongodb::error::Error);
630
631#[cfg(feature = "dotenv")]
632#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
633impl_consider!(dotenv::Error);
634
635#[cfg(feature = "log4rs")]
636#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
637impl_consider!(log4rs::config::runtime::ConfigErrors);
638
639#[cfg(feature = "sea-orm")]
640#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
641impl_consider!(sea_orm::DbErr);
642
643#[cfg(feature = "serde-yaml")]
644#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
645impl_consider!(serde_yaml::Error);
646
647#[cfg(feature = "reqwest")]
648#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
649impl_consider!(reqwest::Error);
650
651#[cfg(feature = "base64")]
652#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
653impl_consider!(base64::DecodeError);
654
655#[cfg(feature = "uuid")]
656#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
657impl_consider!(uuid::Error);
658
659#[cfg(feature = "sqlx")]
660#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
661impl_consider!(sqlx::Error);
662
663#[cfg(feature = "salvo")]
664#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
665impl_consider!(salvo::http::errors::StatusError);
666
667#[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))]
668impl_consider_cli!(rmp_serde::encode::Error);
669#[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))]
670impl_consider_cli!(rmp_serde::decode::Error);
671#[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))]
672impl_consider_cli!(std::io::Error);
673#[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))]
674impl_consider_cli!(std::string::FromUtf8Error);
675#[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))]
676impl_consider_cli!(log::SetLoggerError);
677#[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))]
678impl_consider_cli!(serde_json::Error);
679#[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))]
680impl_consider_cli!(BoxDynError);
681
682#[cfg(feature = "reqwest")]
683#[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))]
684impl_consider_cli!(reqwest::Error);
685
686#[cfg(feature = "base64")]
687#[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))]
688impl_consider_cli!(base64::DecodeError);
689
690#[cfg(feature = "uuid")]
691#[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))]
692impl_consider_cli!(uuid::Error);
693
694#[cfg(feature = "web-sys")]
695#[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))]
696impl<T> ConsiderCli<T> for Result<T, web_sys::wasm_bindgen::JsValue> {
697 fn consider_cli(self, error_text_replacement: Option<String>) -> Result<T, CliError> {
699 self.map_err(|e| {
700 let mut new_error = CliError {
701 message: e.as_string().unwrap_or_default(),
702 };
703 if error_text_replacement.is_some() {
704 new_error.message = error_text_replacement.unwrap();
705 }
706 new_error
707 })
708 }
709}
710
711#[cfg(feature = "web-sys")]
712#[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))]
713impl From<web_sys::wasm_bindgen::JsValue> for CliError {
714 fn from(value: web_sys::wasm_bindgen::JsValue) -> Self {
716 let s = value.as_string().unwrap_or_default();
717 format!("Ошибка в JavaScript: {}", s).into()
718 }
719}
720
721#[cfg(feature = "web-ws")]
722#[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))]
723impl_consider_cli!(ws_stream_wasm::WsErr);