1extern crate alloc;
2
3use alloc::boxed::Box;
4use alloc::format;
5use alloc::string::{String, ToString};
6use alloc::vec::Vec;
7
8use crate::gerr_box::GErrBox;
9use crate::gerr_source::DataSource;
10use crate::gerr_source::GErrSource;
11use crate::gerr_source::IdSource;
12use crate::types::DefaultConfig;
13use crate::types::NoData;
14
15use core::{
16 error::Error,
17 fmt::{Debug, Display},
18 panic::Location,
19};
20
21#[cfg(feature = "backtrace")]
22use std::backtrace::Backtrace;
23
24use alloc::borrow::Cow;
25
26pub trait Config {
28 type Id;
34
35 #[inline]
39 fn id() -> Option<Self::Id> {
40 None
41 }
42
43 const CODE: Option<&'static str> = None;
45
46 const TAGS: Option<&'static [&'static str]> = None;
48
49 #[inline]
55 fn display<C: Config, D>(gerr: &GErr<C, D>) -> String
56 where
57 C::Id: Display,
58 D: Debug,
59 {
60 match (gerr.id(), gerr.code()) {
61 (Some(id), Some(code)) => format!("[{id}][{code}] {}", gerr.message()),
62 (Some(id), None) => format!("[{id}][-] {}", gerr.message()),
63 (None, Some(code)) => format!("[-][{code}] {}", gerr.message()),
64 (None, None) => format!("[-][-] {}", gerr.message()),
65 }
66 }
67}
68
69pub trait SetField<K, V> {
73 fn set_field(&mut self, key: K, value: V);
74}
75
76pub type Result<T, C = DefaultConfig, D = NoData> = core::result::Result<T, GErr<C, D>>;
78
79pub type GErrDefault = GErr<DefaultConfig, NoData>;
81
82#[derive(Debug)]
84pub enum Source {
85 Err(Box<dyn Error + Send + Sync + 'static>),
87
88 GErr(Box<GErrSource>),
91}
92
93pub struct GErr<C: Config = DefaultConfig, D = NoData> {
127 id: Option<C::Id>,
128
129 code: Option<Cow<'static, str>>,
130
131 message: Cow<'static, str>,
132
133 sources: Option<Vec<Source>>,
134
135 tags: Option<Vec<Cow<'static, str>>>,
136
137 data: Option<D>,
138
139 help: Option<Cow<'static, str>>,
140
141 location: ErrorLocation,
142
143 #[cfg(feature = "backtrace")]
144 backtrace: Backtrace,
145}
146
147#[derive(Debug, PartialEq, Eq)]
149pub struct ErrorLocation {
150 pub file: Cow<'static, str>,
152 pub line: u32,
154 pub column: u32,
156}
157
158impl<C: Config, D> GErr<C, D> {
159 #[track_caller]
161 #[inline]
162 pub fn new<M>(message: M) -> Self
163 where
164 M: Into<Cow<'static, str>>,
165 {
166 Self::new_untracked(message, Location::caller())
167 }
168
169 #[inline]
170 pub(crate) fn new_untracked<M>(message: M, location: &'static Location<'static>) -> Self
171 where
172 M: Into<Cow<'static, str>>,
173 {
174 Self::new_with_id_untracked(C::id(), message.into(), location)
175 }
176
177 #[track_caller]
181 #[inline]
182 pub fn from_error<E>(err: E) -> Self
183 where
184 E: Error + Send + Sync + 'static,
185 {
186 Self::new_untracked(err.to_string(), Location::caller()).add_source(err)
187 }
188
189 #[track_caller]
193 #[inline]
194 pub fn new_with_id<M>(id: C::Id, message: M) -> Self
195 where
196 M: Into<Cow<'static, str>>,
197 {
198 Self::new_with_id_untracked(Some(id), message, Location::caller())
199 }
200
201 #[inline]
202 pub(crate) fn new_with_id_untracked<M>(
203 id: Option<C::Id>,
204 message: M,
205 location: &'static Location<'static>,
206 ) -> Self
207 where
208 M: Into<Cow<'static, str>>,
209 {
210 Self {
211 id,
212
213 code: C::CODE.map(Cow::Borrowed),
214
215 message: message.into(),
216
217 sources: None,
218
219 tags: C::TAGS.map(|tags| {
220 tags.iter()
221 .map(|tag| Cow::Borrowed(*tag))
222 .collect::<Vec<_>>()
223 }),
224
225 data: None,
226
227 help: None,
228
229 location: location.into(),
230
231 #[cfg(feature = "backtrace")]
232 backtrace: Backtrace::capture(),
233 }
234 }
235
236 #[track_caller]
240 #[inline]
241 pub fn from_error_with_id<E>(id: C::Id, err: E) -> Self
242 where
243 E: Error + Send + Sync + 'static,
244 {
245 Self::new_with_id_untracked(Some(id), err.to_string(), Location::caller()).add_source(err)
246 }
247
248 #[track_caller]
249 #[inline]
250 pub fn from_gerr<E>(gerr: E) -> Self
251 where
252 E: Into<GErrSource> + Send + Sync + 'static,
253 {
254 Self::from_gerr_untracked(gerr, Location::caller())
255 }
256
257 #[inline]
258 pub(crate) fn from_gerr_untracked<E>(gerr: E, location: &'static Location<'static>) -> Self
259 where
260 E: Into<GErrSource> + Send + Sync + 'static,
261 {
262 let gerr = gerr.into();
263 Self {
264 id: C::id(),
265 code: C::CODE.map(Cow::Borrowed),
266 message: gerr.message,
267 sources: gerr.sources,
268 tags: C::TAGS.map(|tags| {
269 tags.iter()
270 .map(|tag| Cow::Borrowed(*tag))
271 .collect::<Vec<_>>()
272 }),
273 data: None,
274 help: gerr.help,
275 location: location.into(),
276 #[cfg(feature = "backtrace")]
277 backtrace: Backtrace::capture(),
278 }
279 }
280}
281
282impl<C: Config, D> GErr<C, D> {
283 #[must_use]
285 #[inline]
286 pub fn set_id(mut self, id: C::Id) -> Self {
287 self.id = Some(id);
288 self
289 }
290
291 #[must_use]
293 #[inline]
294 pub fn set_code<T>(mut self, code: T) -> Self
295 where
296 T: Into<Cow<'static, str>>,
297 {
298 self.code = Some(code.into());
299 self
300 }
301
302 #[must_use]
304 #[inline]
305 pub fn set_sources<I>(mut self, sources: I) -> Self
306 where
307 I: IntoIterator<Item = Source>,
308 {
309 self.sources = Some(sources.into_iter().collect());
310 self
311 }
312
313 #[must_use]
315 #[inline]
316 pub fn add_source<E>(mut self, source: E) -> Self
317 where
318 E: Error + Send + Sync + 'static,
319 {
320 self.sources
321 .get_or_insert_default()
322 .push(Source::Err(Box::new(source)));
323 self
324 }
325
326 #[must_use]
330 #[inline]
331 pub fn add_source_gerr<E>(mut self, gerr: E) -> Self
332 where
333 E: Into<GErrSource> + Error + Send + Sync + 'static,
334 {
335 self.sources
336 .get_or_insert_default()
337 .push(Source::GErr(Box::new(gerr.into())));
338 self
339 }
340
341 #[must_use]
343 #[inline]
344 pub fn add_tag<T>(mut self, tag: T) -> Self
345 where
346 T: Into<Cow<'static, str>>,
347 {
348 self.tags.get_or_insert_default().push(tag.into());
349 self
350 }
351
352 #[must_use]
354 #[inline]
355 pub fn add_tags<I, T>(mut self, tags: I) -> Self
356 where
357 I: IntoIterator<Item = T>,
358 T: Into<Cow<'static, str>>,
359 {
360 let mut tags = tags.into_iter().peekable();
361 if tags.peek().is_some() {
362 self.tags
363 .get_or_insert_default()
364 .extend(tags.map(Into::into));
365 }
366 self
367 }
368
369 #[must_use]
371 #[inline]
372 pub fn set_data(mut self, data: D) -> Self {
373 self.data = Some(data);
374 self
375 }
376
377 #[must_use]
379 #[inline]
380 pub fn set_help<H>(mut self, help: H) -> Self
381 where
382 H: Into<Cow<'static, str>>,
383 {
384 self.help = Some(help.into());
385 self
386 }
387
388 #[must_use]
390 #[inline]
391 pub fn with_config<T: Config>(self) -> GErr<T, D> {
392 GErr {
393 id: T::id(),
394 code: T::CODE.map(Cow::Borrowed),
395 message: self.message,
396 sources: self.sources,
397 tags: T::TAGS.map(|tags| {
398 tags.iter()
399 .map(|tag| Cow::Borrowed(*tag))
400 .collect::<Vec<_>>()
401 }),
402 data: self.data,
403 help: self.help,
404 location: self.location,
405 #[cfg(feature = "backtrace")]
406 backtrace: self.backtrace,
407 }
408 }
409
410 #[must_use]
412 #[inline]
413 pub fn with_data_type<T>(self) -> GErr<C, T> {
414 GErr {
415 id: self.id,
416 code: self.code,
417 message: self.message,
418
419 sources: self.sources,
420
421 tags: self.tags,
422
423 data: None,
424
425 help: self.help,
426
427 location: self.location,
428
429 #[cfg(feature = "backtrace")]
430 backtrace: self.backtrace,
431 }
432 }
433
434 #[must_use]
438 #[inline]
439 pub fn with_data<T>(self, data: T) -> GErr<C, T> {
440 GErr {
441 id: self.id,
442 message: self.message,
443
444 code: self.code,
445 sources: self.sources,
446
447 tags: self.tags,
448
449 data: Some(data),
450
451 help: self.help,
452
453 location: self.location,
454
455 #[cfg(feature = "backtrace")]
456 backtrace: self.backtrace,
457 }
458 }
459
460 #[allow(dead_code)]
461 #[inline]
462 pub(crate) fn set_location(mut self, loc: ErrorLocation) -> Self {
463 self.location = loc;
464 self
465 }
466
467 #[inline]
471 pub fn id(&self) -> Option<&C::Id> {
472 self.id.as_ref()
473 }
474
475 #[inline]
477 pub fn message(&self) -> &str {
478 &self.message
479 }
480
481 #[inline]
483 pub fn code(&self) -> Option<&str> {
484 self.code.as_deref().or(C::CODE)
485 }
486
487 #[inline]
489 pub fn tags(&self) -> Option<&[Cow<'static, str>]> {
490 self.tags.as_deref()
491 }
492
493 #[inline]
495 pub fn data(&self) -> Option<&D> {
496 self.data.as_ref()
497 }
498
499 #[inline]
501 pub fn sources(&self) -> Option<&[Source]> {
502 self.sources.as_deref()
503 }
504
505 #[inline]
507 pub fn location(&self) -> &ErrorLocation {
508 &self.location
509 }
510
511 #[inline]
513 pub fn help(&self) -> Option<&str> {
514 self.help.as_deref()
515 }
516
517 #[cfg(feature = "backtrace")]
519 #[inline]
520 pub fn backtrace(&self) -> &std::backtrace::Backtrace {
521 &self.backtrace
522 }
523
524 #[inline]
526 pub fn result<T>(self) -> Result<T, C, D> {
527 Result::Err(self)
528 }
529
530 #[inline]
532 pub fn boxed(self) -> GErrBox<C, D> {
533 Box::new(self)
534 }
535}
536
537impl<C: Config, D> GErr<C, D> {
538 #[must_use]
542 #[inline]
543 pub fn set_field<K, V>(mut self, key: K, value: V) -> Self
544 where
545 D: Default + SetField<K, V>,
546 {
547 let data = self.data.get_or_insert_with(Default::default);
548 data.set_field(key, value);
549 self
550 }
551}
552
553impl<C: Config, D> Display for GErr<C, D>
554where
555 C::Id: Display,
556 D: Debug,
557{
558 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
559 write!(f, "{}", C::display(self))
560 }
561}
562
563impl<C: Config, D: Debug> Debug for GErr<C, D>
564where
565 C::Id: Debug,
566{
567 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
568 let mut debug = f.debug_struct("GErr");
569
570 debug
571 .field("id", &self.id)
572 .field("code", &self.code())
573 .field("message", &self.message)
574 .field("sources", &self.sources)
575 .field("tags", &self.tags)
576 .field("data", &self.data)
577 .field("help", &self.help())
578 .field("location", &self.location);
579
580 #[cfg(feature = "backtrace")]
581 debug.field("backtrace", &self.backtrace);
582
583 debug.finish()
584 }
585}
586
587impl<C: Config, D> Error for GErr<C, D>
588where
589 C::Id: Debug + Display,
590 D: Debug,
591{
592 fn source(&self) -> Option<&(dyn Error + 'static)> {
593 if let Some(ref sources) = self.sources
594 && !sources.is_empty()
595 {
596 return sources.first().map(|s| match s {
597 Source::Err(e) => &**e as &(dyn Error + 'static),
598 Source::GErr(e) => &**e as &(dyn Error + 'static),
599 });
600 }
601 None
602 }
603}
604
605#[cfg(not(feature = "serde"))]
606impl<C: Config, D> From<GErr<C, D>> for GErrSource
607where
608 C::Id: IdSource + 'static,
609 D: DataSource + 'static,
610{
611 fn from(gerr: GErr<C, D>) -> Self {
612 GErrSource {
613 id: gerr.id.map(|id| Box::new(id) as Box<dyn IdSource>),
614 code: gerr.code,
615 message: gerr.message,
616 tags: gerr.tags,
617 data: gerr.data.map(|d| Box::new(d) as Box<dyn DataSource>),
618 help: gerr.help,
619 location: Some(gerr.location),
620 sources: gerr.sources,
621 }
622 }
623}
624
625#[cfg(not(feature = "serde"))]
626impl<C: Config, D> GErr<C, D>
627where
628 C::Id: IdSource + 'static,
629 D: DataSource + 'static,
630{
631 #[inline]
633 pub fn into_gerr_source(self) -> GErrSource {
634 self.into()
635 }
636}
637
638#[cfg(feature = "serde")]
639impl<C: Config, D> From<GErr<C, D>> for GErrSource
640where
641 C::Id: ::serde::Serialize + IdSource + 'static,
642 D: ::serde::Serialize + DataSource + 'static,
643{
644 fn from(gerr: GErr<C, D>) -> Self {
645 GErrSource {
646 id_json: gerr
647 .id()
648 .map(|id| serde_json::to_value(id).unwrap_or_default()),
649 id: gerr.id.map(|id| Box::new(id) as Box<dyn IdSource>),
650 code: gerr.code,
651 message: gerr.message,
652 tags: gerr.tags,
653 data_json: gerr
654 .data
655 .as_ref()
656 .map(|d| serde_json::to_value(d).unwrap_or_default()),
657 data: gerr.data.map(|d| Box::new(d) as Box<dyn DataSource>),
658 help: gerr.help,
659 location: Some(gerr.location),
660 sources: gerr.sources,
661 }
662 }
663}
664
665#[cfg(feature = "serde")]
666impl<C: Config, D> GErr<C, D>
667where
668 C::Id: ::serde::Serialize + IdSource + 'static,
669 D: ::serde::Serialize + DataSource + 'static,
670{
671 #[inline]
673 pub fn into_gerr_source(self) -> GErrSource {
674 self.into()
675 }
676}
677
678impl<T, C: Config, D> From<GErr<C, D>> for core::result::Result<T, GErr<C, D>> {
679 #[inline]
680 fn from(value: GErr<C, D>) -> Self {
681 core::result::Result::Err(value)
682 }
683}
684
685impl From<&'static core::panic::Location<'static>> for ErrorLocation {
686 #[inline]
687 fn from(location: &'static core::panic::Location<'static>) -> Self {
688 Self {
689 file: Cow::Borrowed(location.file()),
690 line: location.line(),
691 column: location.column(),
692 }
693 }
694}