1use std::{self, collections::HashMap, error::Error, fmt, sync::Arc};
2
3use serde::{Deserialize, Serialize};
4
5use crate::{ApiResponse, MaybeString, utils::OrderedHashMap};
6
7#[cfg_attr(feature = "salvo", derive(salvo::prelude::ToSchema))]
9#[derive(Debug, Serialize, Deserialize)]
10#[non_exhaustive]
11pub struct ErrorResponse<Meta> {
12 pub error: ApiError,
13 #[serde(skip_serializing_if = "Option::is_none")]
14 pub meta: Option<Meta>,
15}
16
17impl<Meta> ErrorResponse<Meta> {
18 #[inline(always)]
19 pub const fn new(error: ApiError, meta: Meta) -> Self {
20 ErrorResponse {
21 error,
22 meta: Some(meta),
23 }
24 }
25 #[inline(always)]
26 pub const fn from_error(error: ApiError) -> Self {
27 ErrorResponse { error, meta: None }
28 }
29 #[inline(always)]
30 pub fn from_error_msg(code: impl Into<u32>, message: impl Into<String>) -> Self {
31 Self::from_error(ApiError::new(code, message))
32 }
33 #[inline(always)]
34 pub fn from_error_source(
35 code: impl Into<u32>,
36 source: impl Error + Send + Sync + 'static,
37 set_source_detail: bool,
38 message: impl Into<MaybeString>,
39 ) -> Self {
40 Self::from_error(ApiError::from_source(code, source, set_source_detail, message))
41 }
42 #[inline(always)]
43 pub fn with_meta(mut self, meta: Meta) -> Self {
44 self.set_meta(meta);
45 self
46 }
47 #[inline(always)]
48 pub fn set_meta(&mut self, meta: Meta) -> &mut Self {
49 self.meta = Some(meta);
50 self
51 }
52 #[inline(always)]
53 pub fn with_detail(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
54 self.set_detail(key, value);
55 self
56 }
57 #[inline(always)]
58 pub fn set_detail(&mut self, key: impl Into<String>, value: impl Into<String>) -> &mut Self {
59 self.error.set_detail(key, value);
60 self
61 }
62 #[inline(always)]
63 pub fn with_source(mut self, source: impl Error + Send + Sync + 'static, set_source_detail: bool) -> Self {
64 self.set_source(source, set_source_detail);
65 self
66 }
67 #[inline(always)]
68 pub fn set_source(&mut self, source: impl Error + Send + Sync + 'static, set_source_detail: bool) -> &mut Self {
69 self.error.set_source(source, set_source_detail);
70 self
71 }
72 #[inline]
73 pub const fn code(&self) -> u32 {
74 self.error.code()
75 }
76 #[inline]
77 pub const fn message(&self) -> &String {
78 self.error.message()
79 }
80 #[inline]
81 pub fn details(&self) -> Option<&HashMap<String, String>> {
82 self.error.details()
83 }
84 #[inline]
85 pub fn detail(&self, key: impl AsRef<str>) -> Option<&String> {
86 self.error.detail(key)
87 }
88 #[inline(always)]
89 pub fn is<E: Error + 'static>(&self) -> bool {
90 self.error.is::<E>()
91 }
92 #[inline(always)]
93 pub fn downcast_ref<E: Error + 'static>(&self) -> Option<&E> {
94 self.error.downcast_ref::<E>()
95 }
96}
97impl<Meta> From<ApiError> for ErrorResponse<Meta> {
98 fn from(value: ApiError) -> Self {
99 Self::from_error(value)
100 }
101}
102impl<Meta> fmt::Display for ErrorResponse<Meta> {
103 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
104 write!(f, "{}", self.error)
105 }
106}
107impl<Meta: fmt::Debug> Error for ErrorResponse<Meta> {
108 fn source(&self) -> Option<&(dyn Error + 'static)> {
109 self.error.source()
110 }
111}
112
113#[cfg_attr(feature = "salvo", derive(salvo::prelude::ToSchema))]
115#[derive(Serialize, Deserialize)]
116pub struct ApiError {
117 pub(crate) code: u32,
118 pub(crate) message: String,
119 #[serde(skip_serializing_if = "Option::is_none")]
120 pub(crate) details: Option<OrderedHashMap<String, String>>,
121 #[serde(skip)]
122 pub(crate) source: Option<Arc<dyn Error + Send + Sync + 'static>>,
123}
124
125impl fmt::Debug for ApiError {
126 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127 f.debug_struct("ApiError")
128 .field("code", &self.code)
129 .field("message", &self.message)
130 .field("details", &self.details)
131 .finish()
132 }
133}
134
135impl fmt::Display for ApiError {
136 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
137 write!(f, "{} ErrCode({})", self.message, self.code)
138 }
139}
140
141impl Error for ApiError {
142 #[allow(clippy::as_conversions)]
143 fn source(&self) -> Option<&(dyn Error + 'static)> {
144 self.source
145 .as_ref()
146 .map(|source| source.as_ref() as &(dyn Error + 'static))
147 }
148}
149
150impl ApiError {
151 #[inline(always)]
152 pub fn new(code: impl Into<u32>, message: impl Into<String>) -> Self {
153 ApiError {
154 code: code.into(),
155 message: message.into(),
156 details: None,
157 source: None,
158 }
159 }
160 #[inline(always)]
161 pub fn from_source(
162 code: impl Into<u32>,
163 source: impl Error + Send + Sync + 'static,
164 set_source_detail: bool,
165 message: impl Into<MaybeString>,
166 ) -> Self {
167 let mut e = ApiError {
168 code: code.into(),
169 message: message
170 .into()
171 .option_string()
172 .map_or_else(|| source.to_string(), Into::into),
173 details: None,
174 source: Some(Arc::new(source)),
175 };
176 if set_source_detail {
177 e.set_source_detail();
178 }
179 e
180 }
181 pub fn with_code(mut self, code: impl Into<u32>) -> Self {
182 self.code = code.into();
183 self
184 }
185 pub fn with_message(mut self, message: impl Into<String>) -> Self {
186 self.message = message.into();
187 self
188 }
189 #[inline(always)]
190 pub fn with_details(mut self, details: HashMap<String, String>) -> Self {
191 self.details = Some(OrderedHashMap(details));
192 self
193 }
194 #[inline]
195 pub fn with_detail(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
196 self.set_detail(key, value);
197 self
198 }
199 #[inline]
200 pub fn set_detail(&mut self, key: impl Into<String>, value: impl Into<String>) -> &mut Self {
201 self.details.get_or_insert_default().insert(key.into(), value.into());
202 self
203 }
204 #[inline(always)]
205 pub fn with_source(mut self, source: impl Error + Send + Sync + 'static, set_source_detail: bool) -> Self {
206 self.set_source(source, set_source_detail);
207 self
208 }
209 #[inline(always)]
210 pub fn set_source(&mut self, source: impl Error + Send + Sync + 'static, set_source_detail: bool) -> &mut Self {
211 self.source = Some(Arc::new(source));
212 if set_source_detail {
213 self.set_source_detail();
214 }
215 self
216 }
217 #[inline(always)]
219 fn set_source_detail(&mut self) -> &mut Self {
220 if let Some(source) = &self.source {
221 self.set_detail("source", source.to_string());
222 }
223 self
224 }
225 #[inline]
226 pub const fn code(&self) -> u32 {
227 self.code
228 }
229 #[inline]
230 pub const fn message(&self) -> &String {
231 &self.message
232 }
233 #[inline]
234 pub fn details(&self) -> Option<&HashMap<String, String>> {
235 self.details.as_deref()
236 }
237 #[inline]
238 pub fn detail(&self, key: impl AsRef<str>) -> Option<&String> {
239 self.details.as_ref()?.get(key.as_ref())
240 }
241 pub fn is<E: Error + 'static>(&self) -> bool {
242 match &self.source {
243 Some(source) => source.is::<E>(),
244 None => false,
245 }
246 }
247 pub fn downcast_ref<E: Error + 'static>(&self) -> Option<&E> {
248 match &self.source {
249 Some(source) => source.downcast_ref(),
250 None => None,
251 }
252 }
253 pub const fn api_response<Data, Meta>(self, meta: Option<Meta>) -> ApiResponse<Data, Meta> {
254 ApiResponse::Error(ErrorResponse { error: self, meta })
255 }
256 #[inline(always)]
257 pub const fn api_response_without_meta<Data, Meta>(self) -> ApiResponse<Data, Meta>
258 where
259 Self: Sized,
260 {
261 self.api_response(None)
262 }
263 #[inline(always)]
264 pub const fn api_response_with_meta<Data, Meta>(self, meta: Meta) -> ApiResponse<Data, Meta>
265 where
266 Self: Sized,
267 {
268 self.api_response(Some(meta))
269 }
270}
271
272#[cfg(test)]
273mod tests {
274 use crate::{
275 ApiError, ErrorResponse,
276 error_code::{ErrBrief, ErrDecl, ErrPath, ErrPathParent, ErrPathRoot, ErrType},
277 };
278 #[test]
279 fn display() {
280 const ET: ErrType = ErrType::T1100("The operation was cancelled.");
281
282 const EP0: ErrPathRoot = ErrPathRoot::X00("module 0");
283 const EP1: ErrPathParent = EP0.Y01("module 01");
284 const EP2: ErrPath = EP1.Z20("module 20");
285
286 const ED: ErrDecl = ET.declare(EP2);
287 const EB: ErrBrief = ED.extract();
288
289 let api_error: ApiError = ET | &EP2;
290 assert_eq!(EB.api_error().code(), api_error.code());
291 assert_eq!(
292 "The operation was cancelled. ErrCode(1100000120), X00(module 0)/Y01(module 01)/Z20(module 20)",
293 ED.to_string()
294 );
295 let api_error: ApiError = ED.api_error();
296 assert_eq!(
297 "The operation was cancelled. ErrCode(1100000120)",
298 api_error.to_string()
299 );
300 let err_resp: ErrorResponse<()> = ErrorResponse::from_error(api_error);
301 assert_eq!("The operation was cancelled. ErrCode(1100000120)", err_resp.to_string());
302
303 assert_eq!("The operation was cancelled.", ET.text());
304 let et2: ErrType = ET | "The request was cancelled.";
305 assert_eq!("The request was cancelled.", et2.text());
306 }
307}