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 const CODE: Option<&'static str> = None;
30
31 type Id;
37
38 #[inline]
40 fn id() -> Option<Self::Id> {
41 None
42 }
43
44 #[inline]
48 fn display<C: Config, D>(gerr: &GErr<C, D>) -> String
49 where
50 C::Id: Display,
51 D: Debug,
52 {
53 match (gerr.id(), gerr.code()) {
54 (Some(id), Some(code)) => format!("[{id}][{code}] {}", gerr.message()),
55 (Some(id), None) => format!("[{id}][-] {}", gerr.message()),
56 (None, Some(code)) => format!("[-][{code}] {}", gerr.message()),
57 (None, None) => format!("[-][-] {}", gerr.message()),
58 }
59 }
60}
61
62pub trait SetField<K, V> {
66 fn set_field(&mut self, key: K, value: V);
67}
68
69pub type Result<T, C = DefaultConfig, D = NoData> = core::result::Result<T, GErr<C, D>>;
71
72pub type GErrDefault = GErr<DefaultConfig, NoData>;
74
75#[derive(Debug)]
77pub enum Source {
78 Err(Box<dyn Error + Send + Sync + 'static>),
80
81 GErr(Box<GErrSource>),
84}
85
86pub struct GErr<C: Config = DefaultConfig, D = NoData> {
124 id: Option<C::Id>,
125
126 code: Option<Cow<'static, str>>,
127
128 message: Cow<'static, str>,
129
130 sources: Option<Vec<Source>>,
131
132 tags: Option<Vec<Cow<'static, str>>>,
133
134 data: Option<D>,
135
136 help: Option<Cow<'static, str>>,
137
138 location: ErrorLocation,
139
140 #[cfg(feature = "backtrace")]
141 backtrace: Backtrace,
142}
143
144#[derive(Debug, PartialEq, Eq)]
146pub struct ErrorLocation {
147 pub file: Cow<'static, str>,
149 pub line: u32,
151 pub column: u32,
153}
154
155impl<C: Config, D> GErr<C, D> {
156 #[track_caller]
158 #[inline]
159 pub fn new<M>(message: M) -> Self
160 where
161 M: Into<Cow<'static, str>>,
162 {
163 Self::new_untracked(message, Location::caller())
164 }
165
166 #[inline]
167 pub(crate) fn new_untracked<M>(message: M, location: &'static Location<'static>) -> Self
168 where
169 M: Into<Cow<'static, str>>,
170 {
171 Self::new_with_id_untracked(C::id(), message.into(), location)
172 }
173
174 #[track_caller]
178 #[inline]
179 pub fn from_error<E>(err: E) -> Self
180 where
181 E: Error + Send + Sync + 'static,
182 {
183 Self::new_untracked(err.to_string(), Location::caller()).add_source(err)
184 }
185
186 #[track_caller]
190 #[inline]
191 pub fn new_with_id<M>(id: C::Id, message: M) -> Self
192 where
193 M: Into<Cow<'static, str>>,
194 {
195 Self::new_with_id_untracked(Some(id), message, Location::caller())
196 }
197
198 #[inline]
199 pub(crate) fn new_with_id_untracked<M>(
200 id: Option<C::Id>,
201 message: M,
202 location: &'static Location<'static>,
203 ) -> Self
204 where
205 M: Into<Cow<'static, str>>,
206 {
207 Self {
208 id,
209
210 code: C::CODE.map(Cow::Borrowed),
211
212 message: message.into(),
213
214 sources: None,
215
216 tags: None,
217
218 data: None,
219
220 help: None,
221
222 location: location.into(),
223
224 #[cfg(feature = "backtrace")]
225 backtrace: Backtrace::capture(),
226 }
227 }
228
229 #[track_caller]
233 #[inline]
234 pub fn from_error_with_id<E>(id: C::Id, err: E) -> Self
235 where
236 E: Error + Send + Sync + 'static,
237 {
238 Self::new_with_id_untracked(Some(id), err.to_string(), Location::caller()).add_source(err)
239 }
240
241 #[allow(dead_code)]
242 #[inline]
243 pub(crate) fn set_location(mut self, loc: ErrorLocation) -> Self {
244 self.location = loc;
245 self
246 }
247}
248
249impl<C: Config, D> GErr<C, D> {
250 #[must_use]
252 #[inline]
253 pub fn set_id(mut self, id: C::Id) -> Self {
254 self.id = Some(id);
255 self
256 }
257
258 #[must_use]
260 #[inline]
261 pub fn set_code<T>(mut self, code: T) -> Self
262 where
263 T: Into<Cow<'static, str>>,
264 {
265 self.code = Some(code.into());
266 self
267 }
268
269 #[must_use]
271 #[inline]
272 pub fn set_sources<I>(mut self, sources: I) -> Self
273 where
274 I: IntoIterator<Item = Source>,
275 {
276 self.sources = Some(sources.into_iter().collect());
277 self
278 }
279
280 #[must_use]
282 #[inline]
283 pub fn add_source<E>(mut self, source: E) -> Self
284 where
285 E: Error + Send + Sync + 'static,
286 {
287 self.sources
288 .get_or_insert_default()
289 .push(Source::Err(Box::new(source)));
290 self
291 }
292
293 #[inline]
297 pub fn add_source_gerr<E>(mut self, gerr: E) -> Self
298 where
299 E: Into<GErrSource> + Error + Send + Sync + 'static,
300 {
301 self.sources
302 .get_or_insert_default()
303 .push(Source::GErr(Box::new(gerr.into())));
304 self
305 }
306
307 #[must_use]
309 pub fn add_tag<T>(mut self, tag: T) -> Self
310 where
311 T: Into<Cow<'static, str>>,
312 {
313 self.tags.get_or_insert_default().push(tag.into());
314 self
315 }
316
317 #[must_use]
319 pub fn add_tags<I, T>(mut self, tags: I) -> Self
320 where
321 I: IntoIterator<Item = T>,
322 T: Into<Cow<'static, str>>,
323 {
324 let mut tags = tags.into_iter().peekable();
325 if tags.peek().is_some() {
326 self.tags
327 .get_or_insert_default()
328 .extend(tags.map(Into::into));
329 }
330 self
331 }
332
333 #[must_use]
335 #[inline]
336 pub fn set_data(mut self, data: D) -> Self {
337 self.data = Some(data);
338 self
339 }
340
341 #[must_use]
343 #[inline]
344 pub fn set_help<H>(mut self, help: H) -> Self
345 where
346 H: Into<Cow<'static, str>>,
347 {
348 self.help = Some(help.into());
349 self
350 }
351
352 #[must_use]
356 #[inline]
357 pub fn with_config<T: Config>(self) -> GErr<T, D> {
358 GErr {
359 id: T::id(),
360 code: T::CODE.map(Cow::Borrowed),
361 message: self.message,
362 sources: self.sources,
363 tags: self.tags,
364 data: self.data,
365 help: self.help,
366 location: self.location,
367 #[cfg(feature = "backtrace")]
368 backtrace: self.backtrace,
369 }
370 }
371
372 #[must_use]
374 #[inline]
375 pub fn with_data_type<T>(self) -> GErr<C, T> {
376 GErr {
377 id: self.id,
378 code: self.code,
379 message: self.message,
380
381 sources: self.sources,
382
383 tags: self.tags,
384
385 data: None,
386
387 help: self.help,
388
389 location: self.location,
390
391 #[cfg(feature = "backtrace")]
392 backtrace: self.backtrace,
393 }
394 }
395
396 #[must_use]
400 #[inline]
401 pub fn with_data<T>(self, data: T) -> GErr<C, T> {
402 GErr {
403 id: self.id,
404 message: self.message,
405
406 code: self.code,
407 sources: self.sources,
408
409 tags: self.tags,
410
411 data: Some(data),
412
413 help: self.help,
414
415 location: self.location,
416
417 #[cfg(feature = "backtrace")]
418 backtrace: self.backtrace,
419 }
420 }
421
422 #[inline]
426 pub fn id(&self) -> Option<&C::Id> {
427 self.id.as_ref()
428 }
429
430 #[inline]
432 pub fn message(&self) -> &str {
433 &self.message
434 }
435
436 #[inline]
438 pub fn code(&self) -> Option<&str> {
439 self.code.as_deref().or(C::CODE)
440 }
441
442 #[inline]
444 pub fn tags(&self) -> Option<&[Cow<'static, str>]> {
445 self.tags.as_deref()
446 }
447
448 #[inline]
450 pub fn iter_tags(&self) -> impl Iterator<Item = &str> {
451 self.tags.iter().flatten().map(Cow::as_ref)
452 }
453
454 #[inline]
456 pub fn data(&self) -> Option<&D> {
457 self.data.as_ref()
458 }
459
460 #[inline]
462 pub fn sources(&self) -> Option<&[Source]> {
463 self.sources.as_deref()
464 }
465
466 #[inline]
468 pub fn location(&self) -> &ErrorLocation {
469 &self.location
470 }
471
472 #[inline]
474 pub fn help(&self) -> Option<&str> {
475 self.help.as_deref()
476 }
477
478 #[cfg(feature = "backtrace")]
480 #[inline]
481 pub fn backtrace(&self) -> &std::backtrace::Backtrace {
482 &self.backtrace
483 }
484
485 #[inline]
487 pub fn result<T>(self) -> Result<T, C, D> {
488 Result::Err(self)
489 }
490
491 #[inline]
493 pub fn boxed(self) -> GErrBox<C, D> {
494 Box::new(self)
495 }
496}
497
498impl<C: Config, D> GErr<C, D> {
499 #[must_use]
503 #[inline]
504 pub fn set_field<K, V>(mut self, key: K, value: V) -> Self
505 where
506 D: Default + SetField<K, V>,
507 {
508 let data = self.data.get_or_insert_with(Default::default);
509 data.set_field(key, value);
510 self
511 }
512}
513
514impl<C: Config, D> Display for GErr<C, D>
515where
516 C::Id: Display,
517 D: Debug,
518{
519 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
520 write!(f, "{}", C::display(self))
521 }
522}
523
524impl<C: Config, D: Debug> Debug for GErr<C, D>
525where
526 C::Id: Debug,
527{
528 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
529 let mut debug = f.debug_struct("GErr");
530
531 debug
532 .field("id", &self.id)
533 .field("code", &self.code())
534 .field("message", &self.message)
535 .field("sources", &self.sources)
536 .field("tags", &self.tags)
537 .field("data", &self.data)
538 .field("help", &self.help())
539 .field("location", &self.location);
540
541 #[cfg(feature = "backtrace")]
542 debug.field("backtrace", &self.backtrace);
543
544 debug.finish()
545 }
546}
547
548impl<C: Config, D> Error for GErr<C, D>
549where
550 C::Id: Debug + Display,
551 D: Debug,
552{
553 fn source(&self) -> Option<&(dyn Error + 'static)> {
554 if let Some(ref sources) = self.sources
555 && !sources.is_empty()
556 {
557 return sources.first().map(|s| match s {
558 Source::Err(e) => &**e as &(dyn Error + 'static),
559 Source::GErr(e) => &**e as &(dyn Error + 'static),
560 });
561 }
562 None
563 }
564}
565
566#[cfg(not(feature = "serde"))]
567impl<C: Config, D> From<GErr<C, D>> for GErrSource
568where
569 C::Id: IdSource + 'static,
570 D: DataSource + 'static,
571{
572 fn from(gerr: GErr<C, D>) -> Self {
573 GErrSource {
574 id: gerr.id.map(|id| Box::new(id) as Box<dyn IdSource>),
575 code: gerr.code,
576 message: gerr.message,
577 tags: gerr.tags,
578 data: gerr.data.map(|d| Box::new(d) as Box<dyn DataSource>),
579 help: gerr.help,
580 location: Some(gerr.location),
581 sources: gerr.sources,
582 }
583 }
584}
585
586#[cfg(not(feature = "serde"))]
587impl<C: Config, D> GErr<C, D>
588where
589 C::Id: IdSource + 'static,
590 D: DataSource + 'static,
591{
592 #[inline]
594 pub fn into_gerr_source(self) -> GErrSource {
595 self.into()
596 }
597}
598
599#[cfg(feature = "serde")]
600impl<C: Config, D> From<GErr<C, D>> for GErrSource
601where
602 C::Id: ::serde::Serialize + IdSource + 'static,
603 D: ::serde::Serialize + DataSource + 'static,
604{
605 fn from(gerr: GErr<C, D>) -> Self {
606 GErrSource {
607 id_json: gerr
608 .id()
609 .map(|id| serde_json::to_value(id).unwrap_or_default()),
610 id: gerr.id.map(|id| Box::new(id) as Box<dyn IdSource>),
611 code: gerr.code,
612 message: gerr.message,
613 tags: gerr.tags,
614 data_json: gerr
615 .data
616 .as_ref()
617 .map(|d| serde_json::to_value(d).unwrap_or_default()),
618 data: gerr.data.map(|d| Box::new(d) as Box<dyn DataSource>),
619 help: gerr.help,
620 location: Some(gerr.location),
621 sources: gerr.sources,
622 }
623 }
624}
625
626#[cfg(feature = "serde")]
627impl<C: Config, D> GErr<C, D>
628where
629 C::Id: ::serde::Serialize + IdSource + 'static,
630 D: ::serde::Serialize + DataSource + 'static,
631{
632 #[inline]
634 pub fn into_gerr_source(self) -> GErrSource {
635 self.into()
636 }
637}
638
639impl<T, C: Config, D> From<GErr<C, D>> for core::result::Result<T, GErr<C, D>> {
640 #[inline]
641 fn from(value: GErr<C, D>) -> Self {
642 core::result::Result::Err(value)
643 }
644}
645
646impl From<&'static core::panic::Location<'static>> for ErrorLocation {
647 #[inline]
648 fn from(location: &'static core::panic::Location<'static>) -> Self {
649 Self {
650 file: Cow::Borrowed(location.file()),
651 line: location.line(),
652 column: location.column(),
653 }
654 }
655}