teo_result/
error.rs

1use std::any::Any;
2use std::borrow::Cow;
3use std::fmt::{Debug, Display, Formatter};
4use std::sync::Arc;
5use indexmap::{IndexMap, indexmap};
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8
9pub struct Error {
10    pub code: u16,
11    pub message: String,
12    pub errors: Option<IndexMap<String, String>>,
13    pub platform_native_object: Option<Arc<dyn Any + Send + Sync>>,
14}
15
16#[derive(Serialize, Deserialize)]
17pub struct ErrorSerializable {
18    pub code: u16,
19    pub message: String,
20    pub errors: Value,
21}
22
23impl ErrorSerializable {
24    pub fn from_error(error: &Error) -> Self {
25        ErrorSerializable {
26            code: error.code,
27            message: error.message().to_string(),
28            errors: if let Some(errors) = error.errors() {
29                Value::Object(errors.iter().map(|(k, v)| (k.to_string(), Value::String(v.to_string()))).collect())
30            } else {
31                Value::Null
32            },
33        }
34    }
35
36    pub fn error_string(error: &Error) -> String {
37        let serializable = Self::from_error(error);
38        serde_json::to_string(&serializable).unwrap()
39    }
40}
41
42impl Error {
43
44    pub fn new(message: impl Into<String>) -> Self {
45        Self {
46            code: 500,
47            message: message.into(),
48            errors: None,
49            platform_native_object: None,
50        }
51    }
52
53    pub fn new_with_code(message: impl Into<String>, code: u16) -> Self {
54        Self {
55            code,
56            message: message.into(),
57            errors: None,
58            platform_native_object: None,
59        }
60    }
61
62    pub fn new_with_code_errors(message: impl Into<String>, code: u16, errors: IndexMap<String, String>) -> Self {
63        Self {
64            code,
65            message: message.into(),
66            errors: Some(errors),
67            platform_native_object: None,
68        }
69    }
70
71    pub fn new_pathed(message: impl Into<String>, code: u16, key: impl Into<String>, value: impl Into<String>) -> Self {
72        Self {
73            code,
74            message: message.into(),
75            errors: Some(indexmap! { key.into() => value.into() }),
76            platform_native_object: None,
77        }
78    }
79
80    pub fn message_prefixed(&self, prefix: impl AsRef<str>) -> Self {
81        Self {
82            code: self.code,
83            message: if self.errors.is_some() {
84                self.message.clone()
85            } else {
86                format!("{}: {}", prefix.as_ref(), self.message())
87            },
88            errors: if let Some(errors) = self.errors.as_ref() {
89                Some(errors.iter().map(|(k, v)| (k.clone(), format!("{}: {}", prefix.as_ref(), v))).collect())
90            } else {
91                None
92            },
93            platform_native_object: self.platform_native_object.clone(),
94        }
95    }
96
97    pub fn path_prefixed(&self, prefix: impl AsRef<str>) -> Self {
98        Self {
99            code: self.code,
100            message: self.message.clone(),
101            errors: if let Some(errors) = self.errors.as_ref() {
102                Some(errors.iter().map(|(k, v)| (format!("{}.{}", prefix.as_ref(), k), v.clone())).collect())
103            } else {
104                None
105            },
106            platform_native_object: self.platform_native_object.clone(),
107        }
108    }
109
110    pub fn pathed(&self, prefix: impl AsRef<str>) -> Self {
111        Self {
112            code: self.code,
113            message: self.message.clone(),
114            errors: if let Some(errors) = self.errors.as_ref() {
115                Some(errors.iter().map(|(k, v)| (k.to_owned(), v.clone())).collect())
116            } else {
117                Some(indexmap! {prefix.as_ref().to_string() => self.message.clone()})
118            },
119            platform_native_object: self.platform_native_object.clone(),
120        }
121    }
122
123    pub fn map_path<F>(&self, mapper: F) -> Self where F: Fn(&str) -> String {
124        Self {
125            code: self.code,
126            message: self.message.clone(),
127            errors: if let Some(errors) = self.errors.as_ref() {
128                Some(errors.iter().map(|(k, v)| (mapper(k.as_str()), v.clone())).collect())
129            } else {
130                None
131            },
132            platform_native_object: self.platform_native_object.clone(),
133        }
134    }
135
136    pub fn code(&self) -> u16 {
137        self.code
138    }
139
140    pub fn message(&self) -> &str {
141        self.message.as_str()
142    }
143
144    pub fn errors(&self) -> Option<&IndexMap<String, String>> {
145        self.errors.as_ref()
146    }
147
148    pub fn assign_platform_native_object<T: 'static + Send + Sync>(&mut self, val: T) {
149        self.platform_native_object = Some(Arc::new(val));
150    }
151
152    pub fn platform_native_object<T: 'static + Send>(&self) -> Option<&T> {
153        self.platform_native_object.as_ref().map(|boxed| boxed.downcast_ref()).flatten()
154    }
155
156    pub fn inferred_title(&self) -> Cow<'static, str> {
157        match self.code {
158            100 => Cow::Borrowed("Continue"),
159            101 => Cow::Borrowed("SwitchingProtocols"),
160            102 => Cow::Borrowed("Processing"),
161            103 => Cow::Borrowed("EarlyHints"),
162            200 => Cow::Borrowed("OK"),
163            201 => Cow::Borrowed("Created"),
164            202 => Cow::Borrowed("Accepted"),
165            203 => Cow::Borrowed("NonAuthoritativeInformation"),
166            204 => Cow::Borrowed("NoContent"),
167            205 => Cow::Borrowed("ResetContent"),
168            206 => Cow::Borrowed("PartialContent"),
169            207 => Cow::Borrowed("MultiStatus"),
170            208 => Cow::Borrowed("AlreadyReported"),
171            226 => Cow::Borrowed("IMUsed"),
172            300 => Cow::Borrowed("MultipleChoices"),
173            301 => Cow::Borrowed("MovedPermanently"),
174            302 => Cow::Borrowed("Found"),
175            303 => Cow::Borrowed("SeeOther"),
176            304 => Cow::Borrowed("NotModified"),
177            307 => Cow::Borrowed("TemporaryRedirect"),
178            308 => Cow::Borrowed("PermanentRedirect"),
179            400 => Cow::Borrowed("BadRequest"),
180            401 => Cow::Borrowed("Unauthorized"),
181            402 => Cow::Borrowed("PaymentRequired"),
182            403 => Cow::Borrowed("Forbidden"),
183            404 => Cow::Borrowed("NotFound"),
184            405 => Cow::Borrowed("MethodNotAllowed"),
185            406 => Cow::Borrowed("NotAcceptable"),
186            407 => Cow::Borrowed("ProxyAuthenticationRequired"),
187            408 => Cow::Borrowed("RequestTimeout"),
188            409 => Cow::Borrowed("Conflict"),
189            410 => Cow::Borrowed("Gone"),
190            411 => Cow::Borrowed("LengthRequired"),
191            412 => Cow::Borrowed("PreconditionFailed"),
192            413 => Cow::Borrowed("PayloadTooLarge"),
193            414 => Cow::Borrowed("URITooLong"),
194            415 => Cow::Borrowed("UnsupportedMediaType"),
195            416 => Cow::Borrowed("RangeNotSatisfiable"),
196            417 => Cow::Borrowed("ExpectationFailed"),
197            418 => Cow::Borrowed("ImATeapot"),
198            421 => Cow::Borrowed("MisdirectedRequest"),
199            422 => Cow::Borrowed("UnprocessableContent"),
200            423 => Cow::Borrowed("Locked"),
201            424 => Cow::Borrowed("FailedDependency"),
202            425 => Cow::Borrowed("TooEarly"),
203            426 => Cow::Borrowed("UpgradeRequired"),
204            428 => Cow::Borrowed("PreconditionRequired"),
205            429 => Cow::Borrowed("TooManyRequests"),
206            431 => Cow::Borrowed("RequestHeaderFieldsTooLarge"),
207            451 => Cow::Borrowed("UnavailableForLegalReasons"),
208            500 => Cow::Borrowed("InternalServerError"),
209            501 => Cow::Borrowed("NotImplemented"),
210            502 => Cow::Borrowed("BadGateway"),
211            503 => Cow::Borrowed("ServiceUnavailable"),
212            504 => Cow::Borrowed("GatewayTimeout"),
213            505 => Cow::Borrowed("HTTPVersionNotSupported"),
214            506 => Cow::Borrowed("VariantAlsoNegotiates"),
215            507 => Cow::Borrowed("InsufficientStorage"),
216            508 => Cow::Borrowed("LoopDetected"),
217            510 => Cow::Borrowed("NotExtended"),
218            511 => Cow::Borrowed("NetworkAuthenticationRequired"),
219            _ => Cow::Owned(format!("ServerError({})", self.code)),
220        }
221    }
222
223    pub fn not_found() -> Self {
224        Self {
225            code: 404,
226            message: "not found".to_owned(),
227            errors: None,
228            platform_native_object: None,
229        }
230    }
231
232    pub fn not_found_message(message: impl Into<String>) -> Self {
233        Self {
234            code: 404,
235            message: message.into(),
236            errors: None,
237            platform_native_object: None,
238        }
239    }
240
241    pub fn not_found_pathed(path: impl Into<String>, message: impl Into<String>) -> Self {
242        Self {
243            code: 404,
244            message: "not found".to_owned(),
245            errors: Some(indexmap! {
246                path.into() => message.into()
247            }),
248            platform_native_object: None,
249        }
250    }
251
252    pub fn invalid_request() -> Self {
253        Self {
254            code: 400,
255            message: "value is invalid".to_owned(),
256            errors: None,
257            platform_native_object: None,
258        }
259    }
260
261    pub fn invalid_request_message(message: impl Into<String>) -> Self {
262        Self {
263            code: 400,
264            message: message.into(),
265            errors: None,
266            platform_native_object: None,
267        }
268    }
269
270    pub fn invalid_request_pathed(path: impl Into<String>, message: impl Into<String>) -> Self {
271        Self {
272            code: 400,
273            message: "value is invalid".to_owned(),
274            errors: Some(indexmap! {
275                path.into() => message.into()
276            }),
277            platform_native_object: None,
278        }
279    }
280
281    pub fn unique_error(path: impl Into<String>, constraint: impl AsRef<str>) -> Self {
282        Self {
283            code: 400,
284            message: "value is invalid".to_owned(),
285            errors: Some(indexmap! {
286                path.into() => format!("value violates '{}' constraint", constraint.as_ref())
287            }),
288            platform_native_object: None,
289        }
290    }
291
292    pub fn internal_server_error() -> Self {
293        Self {
294            code: 500,
295            message: "internal server error".to_owned(),
296            errors: None,
297            platform_native_object: None,
298        }
299    }
300
301    pub fn internal_server_error_message(message: impl Into<String>) -> Self {
302        Self {
303            code: 500,
304            message: message.into(),
305            errors: None,
306            platform_native_object: None,
307        }
308    }
309
310    pub fn internal_server_error_pathed(path: impl Into<String>, message: impl Into<String>) -> Self {
311        Self {
312            code: 500,
313            message: "internal server error".to_owned(),
314            errors: Some(indexmap! {
315                path.into() => message.into()
316            }),
317            platform_native_object: None,
318        }
319    }
320
321    pub fn unauthorized() -> Self {
322        Self {
323            code: 401,
324            message: "unauthorized".to_owned(),
325            errors: None,
326            platform_native_object: None,
327        }
328    }
329
330    pub fn unauthorized_message(message: impl Into<String>) -> Self {
331        Self {
332            code: 401,
333            message: message.into(),
334            errors: None,
335            platform_native_object: None,
336        }
337    }
338
339    pub fn unauthorized_pathed(path: impl Into<String>, message: impl Into<String>) -> Self {
340        Self {
341            code: 401,
342            message: "unauthorized".to_owned(),
343            errors: Some(indexmap! {
344                path.into() => message.into()
345            }),
346            platform_native_object: None,
347        }
348    }
349}
350
351impl Debug for Error {
352    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
353        let serialized = ErrorSerializable::error_string(self);
354        f.write_str(&format!("teo_result::Error: {}", serialized))
355    }
356}
357
358impl Display for Error {
359    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
360        Debug::fmt(self, f)
361    }
362}
363
364impl std::error::Error for Error { }