api_response/
error.rs

1use std::{self, collections::HashMap, error::Error, fmt, sync::Arc};
2
3use serde::{Deserialize, Serialize};
4
5use crate::{ApiResponse, MaybeString, utils::OrderedHashMap};
6
7/// Struct to represent an error response
8#[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/// Struct to represent error information
114#[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    /// Insert the setted source error into the detail field.
218    #[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}