extern crate alloc;
use alloc::boxed::Box;
use alloc::format;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use crate::gerr_box::GErrBox;
use crate::gerr_source::DataSource;
use crate::gerr_source::GErrSource;
use crate::gerr_source::IdSource;
use crate::types::DefaultConfig;
use crate::types::NoData;
use core::{
error::Error,
fmt::{Debug, Display},
panic::Location,
};
#[cfg(feature = "backtrace")]
use std::backtrace::Backtrace;
use alloc::borrow::Cow;
pub trait Config {
const CODE: Option<&'static str> = None;
type Id;
#[inline]
fn id() -> Option<Self::Id> {
None
}
#[inline]
fn display<C: Config, D>(gerr: &GErr<C, D>) -> String
where
C::Id: Display,
D: Debug,
{
match (gerr.id(), gerr.code()) {
(Some(id), Some(code)) => format!("[{id}][{code}] {}", gerr.message()),
(Some(id), None) => format!("[{id}][-] {}", gerr.message()),
(None, Some(code)) => format!("[-][{code}] {}", gerr.message()),
(None, None) => format!("[-][-] {}", gerr.message()),
}
}
}
pub trait SetField<K, V> {
fn set_field(&mut self, key: K, value: V);
}
pub type Result<T, C = DefaultConfig, D = NoData> = core::result::Result<T, GErr<C, D>>;
pub type GErrDefault = GErr<DefaultConfig, NoData>;
#[derive(Debug)]
pub enum Source {
Err(Box<dyn Error + Send + Sync + 'static>),
GErr(Box<GErrSource>),
}
pub struct GErr<C: Config = DefaultConfig, D = NoData> {
id: Option<C::Id>,
code: Option<Cow<'static, str>>,
message: Cow<'static, str>,
sources: Option<Vec<Source>>,
tags: Option<Vec<Cow<'static, str>>>,
data: Option<D>,
help: Option<Cow<'static, str>>,
location: ErrorLocation,
#[cfg(feature = "backtrace")]
backtrace: Backtrace,
}
#[derive(Debug, PartialEq, Eq)]
pub struct ErrorLocation {
pub file: Cow<'static, str>,
pub line: u32,
pub column: u32,
}
impl<C: Config, D> GErr<C, D> {
#[track_caller]
#[inline]
pub fn new<M>(message: M) -> Self
where
M: Into<Cow<'static, str>>,
{
Self::new_untracked(message, Location::caller())
}
#[inline]
pub(crate) fn new_untracked<M>(message: M, location: &'static Location<'static>) -> Self
where
M: Into<Cow<'static, str>>,
{
Self::new_with_id_untracked(C::id(), message.into(), location)
}
#[track_caller]
#[inline]
pub fn from_error<E>(err: E) -> Self
where
E: Error + Send + Sync + 'static,
{
Self::new_untracked(err.to_string(), Location::caller()).add_source(err)
}
#[track_caller]
#[inline]
pub fn new_with_id<M>(id: C::Id, message: M) -> Self
where
M: Into<Cow<'static, str>>,
{
Self::new_with_id_untracked(Some(id), message, Location::caller())
}
#[inline]
pub(crate) fn new_with_id_untracked<M>(
id: Option<C::Id>,
message: M,
location: &'static Location<'static>,
) -> Self
where
M: Into<Cow<'static, str>>,
{
Self {
id,
code: C::CODE.map(Cow::Borrowed),
message: message.into(),
sources: None,
tags: None,
data: None,
help: None,
location: location.into(),
#[cfg(feature = "backtrace")]
backtrace: Backtrace::capture(),
}
}
#[track_caller]
#[inline]
pub fn from_error_with_id<E>(id: C::Id, err: E) -> Self
where
E: Error + Send + Sync + 'static,
{
Self::new_with_id_untracked(Some(id), err.to_string(), Location::caller()).add_source(err)
}
#[allow(dead_code)]
#[inline]
pub(crate) fn set_location(mut self, loc: ErrorLocation) -> Self {
self.location = loc;
self
}
}
impl<C: Config, D> GErr<C, D> {
#[must_use]
#[inline]
pub fn set_id(mut self, id: C::Id) -> Self {
self.id = Some(id);
self
}
#[must_use]
#[inline]
pub fn set_code<T>(mut self, code: T) -> Self
where
T: Into<Cow<'static, str>>,
{
self.code = Some(code.into());
self
}
#[must_use]
#[inline]
pub fn set_sources<I>(mut self, sources: I) -> Self
where
I: IntoIterator<Item = Source>,
{
self.sources = Some(sources.into_iter().collect());
self
}
#[must_use]
#[inline]
pub fn add_source<E>(mut self, source: E) -> Self
where
E: Error + Send + Sync + 'static,
{
self.sources
.get_or_insert_default()
.push(Source::Err(Box::new(source)));
self
}
#[inline]
pub fn add_source_gerr<E>(mut self, gerr: E) -> Self
where
E: Into<GErrSource> + Error + Send + Sync + 'static,
{
self.sources
.get_or_insert_default()
.push(Source::GErr(Box::new(gerr.into())));
self
}
#[must_use]
pub fn add_tag<T>(mut self, tag: T) -> Self
where
T: Into<Cow<'static, str>>,
{
self.tags.get_or_insert_default().push(tag.into());
self
}
#[must_use]
pub fn add_tags<I, T>(mut self, tags: I) -> Self
where
I: IntoIterator<Item = T>,
T: Into<Cow<'static, str>>,
{
let mut tags = tags.into_iter().peekable();
if tags.peek().is_some() {
self.tags
.get_or_insert_default()
.extend(tags.map(Into::into));
}
self
}
#[must_use]
#[inline]
pub fn set_data(mut self, data: D) -> Self {
self.data = Some(data);
self
}
#[must_use]
#[inline]
pub fn set_help<H>(mut self, help: H) -> Self
where
H: Into<Cow<'static, str>>,
{
self.help = Some(help.into());
self
}
#[must_use]
#[inline]
pub fn with_config<T: Config>(self) -> GErr<T, D> {
GErr {
id: T::id(),
code: T::CODE.map(Cow::Borrowed),
message: self.message,
sources: self.sources,
tags: self.tags,
data: self.data,
help: self.help,
location: self.location,
#[cfg(feature = "backtrace")]
backtrace: self.backtrace,
}
}
#[must_use]
#[inline]
pub fn with_data_type<T>(self) -> GErr<C, T> {
GErr {
id: self.id,
code: self.code,
message: self.message,
sources: self.sources,
tags: self.tags,
data: None,
help: self.help,
location: self.location,
#[cfg(feature = "backtrace")]
backtrace: self.backtrace,
}
}
#[must_use]
#[inline]
pub fn with_data<T>(self, data: T) -> GErr<C, T> {
GErr {
id: self.id,
message: self.message,
code: self.code,
sources: self.sources,
tags: self.tags,
data: Some(data),
help: self.help,
location: self.location,
#[cfg(feature = "backtrace")]
backtrace: self.backtrace,
}
}
#[inline]
pub fn id(&self) -> Option<&C::Id> {
self.id.as_ref()
}
#[inline]
pub fn message(&self) -> &str {
&self.message
}
#[inline]
pub fn code(&self) -> Option<&str> {
self.code.as_deref().or(C::CODE)
}
#[inline]
pub fn tags(&self) -> Option<&[Cow<'static, str>]> {
self.tags.as_deref()
}
#[inline]
pub fn iter_tags(&self) -> impl Iterator<Item = &str> {
self.tags.iter().flatten().map(Cow::as_ref)
}
#[inline]
pub fn data(&self) -> Option<&D> {
self.data.as_ref()
}
#[inline]
pub fn sources(&self) -> Option<&[Source]> {
self.sources.as_deref()
}
#[inline]
pub fn location(&self) -> &ErrorLocation {
&self.location
}
#[inline]
pub fn help(&self) -> Option<&str> {
self.help.as_deref()
}
#[cfg(feature = "backtrace")]
#[inline]
pub fn backtrace(&self) -> &std::backtrace::Backtrace {
&self.backtrace
}
#[inline]
pub fn result<T>(self) -> Result<T, C, D> {
Result::Err(self)
}
#[inline]
pub fn boxed(self) -> GErrBox<C, D> {
Box::new(self)
}
}
impl<C: Config, D> GErr<C, D> {
#[must_use]
#[inline]
pub fn set_field<K, V>(mut self, key: K, value: V) -> Self
where
D: Default + SetField<K, V>,
{
let data = self.data.get_or_insert_with(Default::default);
data.set_field(key, value);
self
}
}
impl<C: Config, D> Display for GErr<C, D>
where
C::Id: Display,
D: Debug,
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", C::display(self))
}
}
impl<C: Config, D: Debug> Debug for GErr<C, D>
where
C::Id: Debug,
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let mut debug = f.debug_struct("GErr");
debug
.field("id", &self.id)
.field("code", &self.code())
.field("message", &self.message)
.field("sources", &self.sources)
.field("tags", &self.tags)
.field("data", &self.data)
.field("help", &self.help())
.field("location", &self.location);
#[cfg(feature = "backtrace")]
debug.field("backtrace", &self.backtrace);
debug.finish()
}
}
impl<C: Config, D> Error for GErr<C, D>
where
C::Id: Debug + Display,
D: Debug,
{
fn source(&self) -> Option<&(dyn Error + 'static)> {
if let Some(ref sources) = self.sources
&& !sources.is_empty()
{
return sources.first().map(|s| match s {
Source::Err(e) => &**e as &(dyn Error + 'static),
Source::GErr(e) => &**e as &(dyn Error + 'static),
});
}
None
}
}
#[cfg(not(feature = "serde"))]
impl<C: Config, D> From<GErr<C, D>> for GErrSource
where
C::Id: IdSource + 'static,
D: DataSource + 'static,
{
fn from(gerr: GErr<C, D>) -> Self {
GErrSource {
id: gerr.id.map(|id| Box::new(id) as Box<dyn IdSource>),
code: gerr.code,
message: gerr.message,
tags: gerr.tags,
data: gerr.data.map(|d| Box::new(d) as Box<dyn DataSource>),
help: gerr.help,
location: Some(gerr.location),
sources: gerr.sources,
}
}
}
#[cfg(not(feature = "serde"))]
impl<C: Config, D> GErr<C, D>
where
C::Id: IdSource + 'static,
D: DataSource + 'static,
{
#[inline]
pub fn into_gerr_source(self) -> GErrSource {
self.into()
}
}
#[cfg(feature = "serde")]
impl<C: Config, D> From<GErr<C, D>> for GErrSource
where
C::Id: ::serde::Serialize + IdSource + 'static,
D: ::serde::Serialize + DataSource + 'static,
{
fn from(gerr: GErr<C, D>) -> Self {
GErrSource {
id_json: gerr
.id()
.map(|id| serde_json::to_value(id).unwrap_or_default()),
id: gerr.id.map(|id| Box::new(id) as Box<dyn IdSource>),
code: gerr.code,
message: gerr.message,
tags: gerr.tags,
data_json: gerr
.data
.as_ref()
.map(|d| serde_json::to_value(d).unwrap_or_default()),
data: gerr.data.map(|d| Box::new(d) as Box<dyn DataSource>),
help: gerr.help,
location: Some(gerr.location),
sources: gerr.sources,
}
}
}
#[cfg(feature = "serde")]
impl<C: Config, D> GErr<C, D>
where
C::Id: ::serde::Serialize + IdSource + 'static,
D: ::serde::Serialize + DataSource + 'static,
{
#[inline]
pub fn into_gerr_source(self) -> GErrSource {
self.into()
}
}
impl<T, C: Config, D> From<GErr<C, D>> for core::result::Result<T, GErr<C, D>> {
#[inline]
fn from(value: GErr<C, D>) -> Self {
core::result::Result::Err(value)
}
}
impl From<&'static core::panic::Location<'static>> for ErrorLocation {
#[inline]
fn from(location: &'static core::panic::Location<'static>) -> Self {
Self {
file: Cow::Borrowed(location.file()),
line: location.line(),
column: location.column(),
}
}
}