1use axum::Json;
17use axum::http::{HeaderValue, StatusCode, header};
18use axum::response::{IntoResponse, Response};
19use serde::{Deserialize, Serialize};
20use snafu::Snafu;
21
22#[derive(Debug, Snafu, Default, Serialize, Deserialize, Clone)]
25#[snafu(display("{message}"))]
26pub struct Error {
27 #[serde(skip)]
29 pub status: u16,
30 pub category: String,
32 pub message: String,
34 pub sub_category: Option<String>,
36 pub code: Option<String>,
38 pub exception: Option<bool>,
40 pub extra: Option<Box<Vec<String>>>,
42}
43
44impl Error {
45 #[must_use]
46 pub fn new(message: impl ToString) -> Self {
47 Self {
48 message: message.to_string(),
49 ..Default::default()
50 }
51 }
52 #[must_use]
54 pub fn with_category(mut self, category: impl ToString) -> Self {
55 self.category = category.to_string();
56 self
57 }
58 #[must_use]
60 pub fn with_sub_category(mut self, sub_category: impl ToString) -> Self {
61 self.sub_category = Some(sub_category.to_string());
62 self
63 }
64 #[must_use]
66 pub fn with_code(mut self, code: impl ToString) -> Self {
67 self.code = Some(code.to_string());
68 self
69 }
70 #[must_use]
72 pub fn with_status(mut self, status: u16) -> Self {
73 self.status = status;
74 self
75 }
76 #[must_use]
78 pub fn with_exception(mut self, exception: bool) -> Self {
79 self.exception = Some(exception);
80 self
81 }
82 #[must_use]
84 pub fn add_extra(mut self, value: impl ToString) -> Self {
85 self.extra
86 .get_or_insert_with(Box::default)
87 .push(value.to_string());
88 self
89 }
90}
91
92impl IntoResponse for Error {
95 fn into_response(self) -> Response {
96 let status = StatusCode::from_u16(self.status).unwrap_or(StatusCode::BAD_REQUEST);
97 let mut res = Json(&self).into_response();
99 res.extensions_mut().insert(self);
100 res.headers_mut()
101 .insert(header::CACHE_CONTROL, HeaderValue::from_static("no-cache"));
102 (status, res).into_response()
103 }
104}