diff --git a/Cargo.toml b/Cargo.toml index 5dcf236..52159a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,21 +1,25 @@ -[package] -name = "bigerror" -version = "0.8.2" +[workspace] +members = [ + "bigerror", "bigerror_derive", +] +resolver = "2" + +[workspace.package] +version = "0.9.0" edition = "2021" description = "handle big errors ¯\\_(ツ)_/¯" license = "MIT" +documentation = "https://docs.rs/bigerror" +repository = "https://github.com/knox-networks/bigerror" -[features] -default = ["std"] -std = ["error-stack/std", "error-stack/anyhow"] -spantrace = ["error-stack/spantrace"] -eyre = ["error-stack/eyre"] -serde = ["error-stack/serde"] -hooks = ["error-stack/hooks"] - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -error-stack = "0.4.1" -thiserror = "1" +[workspace.dependencies] +derive_more = { version = "1", features = ["display"] } tracing = "0.1" +proc-macro2 = "1.0.74" +quote = "1.0.35" +error-stack = "0.5" +syn = "2.0.46" +bigerror-derive = { path = "./bigerror_derive", version = "0.1.0" } + +[workspace.lints.clippy] +unexpected_cfgs = "allow" diff --git a/bigerror/Cargo.toml b/bigerror/Cargo.toml new file mode 100644 index 0000000..f25de5b --- /dev/null +++ b/bigerror/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "bigerror" +version.workspace = true +edition.workspace = true +description.workspace = true +license.workspace = true +documentation.workspace = true +repository.workspace = true + +[features] +default = ["std"] +std = ["error-stack/std", "error-stack/anyhow"] +spantrace = ["error-stack/spantrace"] +eyre = ["error-stack/eyre"] +serde = ["error-stack/serde"] +hooks = ["error-stack/hooks"] + + +[dependencies] +error-stack.workspace = true +tracing.workspace = true +bigerror-derive.workspace = true +derive_more.workspace = true + diff --git a/src/attachment.rs b/bigerror/src/attachment.rs similarity index 91% rename from src/attachment.rs rename to bigerror/src/attachment.rs index 66dd457..fe05c84 100644 --- a/src/attachment.rs +++ b/bigerror/src/attachment.rs @@ -1,11 +1,7 @@ use std::{fmt, time::Duration}; -use tracing::error; - +use derive_more as dm; pub use error_stack::{self, Context, Report, ResultExt}; -pub use thiserror; - -use crate::reportable; pub trait Display: fmt::Display + fmt::Debug + Send + Sync + 'static {} @@ -32,7 +28,7 @@ impl fmt::Display for Dbg { } // simple key-value pair attachment -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Eq)] pub struct KeyValue(pub K, pub V); impl fmt::Display for KeyValue { @@ -42,7 +38,7 @@ impl fmt::Display for KeyValue { } impl KeyValue> { - pub fn dbg(key: K, value: V) -> Self { + pub const fn dbg(key: K, value: V) -> Self { Self(key, Dbg(value)) } } @@ -76,13 +72,13 @@ impl fmt::Display for Field { } impl Field { - pub fn new(key: Id, status: S) -> Self { + pub const fn new(key: Id, status: S) -> Self { Self { id: key, status } } } /// wrapper attachment that is used to refer to the type of an object /// rather than the value -#[derive(PartialEq)] +#[derive(PartialEq, Eq)] pub struct Type(&'static str); impl Type { @@ -116,30 +112,29 @@ macro_rules! ty { }; } -#[derive(Debug, thiserror::Error)] -#[error("already present")] +#[derive(Debug, dm::Display)] +#[display("already present")] pub struct AlreadyPresent; -reportable!(AlreadyPresent); -#[derive(Debug, thiserror::Error)] -#[error("missing")] +#[derive(Debug, dm::Display)] +#[display("missing")] pub struct Missing; -#[derive(Debug, thiserror::Error)] -#[error("unsupported")] +#[derive(Debug, dm::Display)] +#[display("unsupported")] pub struct Unsupported; -#[derive(Debug, thiserror::Error)] -#[error("invalid")] +#[derive(Debug, dm::Display)] +#[display("invalid")] pub struct Invalid; -#[derive(Debug, thiserror::Error)] +#[derive(Debug)] pub struct Expectation { pub expected: E, pub actual: A, } -#[derive(Debug, thiserror::Error)] +#[derive(Debug)] pub struct FromTo(pub F, pub T); #[allow(dead_code)] @@ -243,6 +238,7 @@ pub fn hms_string(duration: Duration) -> String { hms } +#[must_use] pub fn simple_type_name() -> &'static str { let full_type = std::any::type_name::(); // Option, [T], Vec @@ -256,8 +252,8 @@ pub fn simple_type_name() -> &'static str { // that the underlying `A` is being // used as an index key for getter methods in a collection // such as `HashMap` keys and `Vec` indices -#[derive(Debug, thiserror::Error)] -#[error("idx [{0}: {}]", simple_type_name::())] +#[derive(Debug, dm::Display)] +#[display("idx [{0}: {}]", simple_type_name::())] pub struct Index(pub I); #[cfg(test)] diff --git a/src/context.rs b/bigerror/src/context.rs similarity index 60% rename from src/context.rs rename to bigerror/src/context.rs index 7793fd0..b13b2d6 100644 --- a/src/context.rs +++ b/bigerror/src/context.rs @@ -1,26 +1,21 @@ +use derive_more as dm; use std::{path::Path, time::Duration}; use error_stack::Context; use crate::{ attachment::{self, simple_type_name, Display, FromTo, Unsupported}, - ty, AttachExt, Index, Report, Reportable, + ty, AttachExt, Index, Report, ThinContext, }; -use crate::{attachment::DisplayDuration, reportable, Field}; +use crate::{attachment::DisplayDuration, Field}; -/// Used to enacpsulate opaque `dyn std::error::Error` types -#[derive(Debug, thiserror::Error)] -#[error("{0}")] -pub struct BoxError(Box); +/// Used to enacpsulate opaque `dyn core::error::Error` types +#[derive(Debug, dm::Display)] +#[display("{_0}")] +pub struct BoxError(Box); +impl ::core::error::Error for BoxError {} -/// this is a [`BoxError`] that satistifes [`core::error::Error`] -/// using [`core::fmt::Debug`] and [`core::fmt::Display`] -#[derive(Debug, thiserror::Error)] -#[error("{0}")] -pub struct BoxCoreError(Box); -#[derive(Debug, thiserror::Error)] -#[error("DecodeError")] /// Represents errors emitted during while processing bytes into an object. /// * byte types can can be represented by objects such as `&[u8]`, `bytes::Bytes`, and `Vec` /// * used by codecs/serializers/deserializers @@ -30,99 +25,86 @@ pub struct BoxCoreError(Box); /// * /// * /// * +#[derive(ThinContext)] +#[bigerror(crate)] pub struct DecodeError; -reportable!(DecodeError); /// Emitted while turning an object into bytes. /// See [`DecodeError`] for more details. -#[derive(Debug, thiserror::Error)] -#[error("EncodeError")] +#[derive(ThinContext)] +#[bigerror(crate)] pub struct EncodeError; -reportable!(EncodeError); /// Emitted during an authorization/verification check -#[derive(Debug, thiserror::Error)] -#[error("AuthError")] +#[derive(ThinContext)] +#[bigerror(crate)] pub struct AuthError; -reportable!(AuthError); -#[derive(Debug, thiserror::Error)] -#[error("NetworkError")] +#[derive(ThinContext)] +#[bigerror(crate)] pub struct NetworkError; -reportable!(NetworkError); /// Emitted while processing a string (UTF-8 or otherwise). /// Usually associated with the [`std::str::FromStr`] trait and the `.parse::()` method -#[derive(Debug, thiserror::Error)] -#[error("ParseError")] +#[derive(ThinContext)] +#[bigerror(crate)] pub struct ParseError; -reportable!(ParseError); /// Represents the conversion of an `Option::::None` into a [`Report`] -#[derive(Debug, thiserror::Error)] -#[error("NotFound")] +#[derive(ThinContext)] +#[bigerror(crate)] pub struct NotFound; -reportable!(NotFound); -#[derive(Debug, thiserror::Error)] -#[error("DbError")] +#[derive(ThinContext)] +#[bigerror(crate)] pub struct DbError; -reportable!(DbError); /// An error that is related to filesystem operations such as those in [`std::fs`] -#[derive(Debug, thiserror::Error)] -#[error("FsError")] +#[derive(ThinContext)] +#[bigerror(crate)] pub struct FsError; -reportable!(FsError); /// Emitted during the startup/provisioning phase of a program -#[derive(Debug, thiserror::Error)] -#[error("SetupError")] +#[derive(ThinContext)] +#[bigerror(crate)] pub struct SetupError; -reportable!(SetupError); /// Emitted during transformations between [non scalar](https://en.wikipedia.org/w/index.php?title=Scalar_processor&useskin=vector#Scalar_data_type) /// objects (such as structs, enums, and unions). -#[derive(Debug, thiserror::Error)] -#[error("ConversionError")] +#[derive(ThinContext)] +#[bigerror(crate)] pub struct ConversionError; -reportable!(ConversionError); -#[derive(Debug, thiserror::Error)] -#[error("InvalidInput")] +#[derive(ThinContext)] +#[bigerror(crate)] pub struct InvalidInput; -reportable!(InvalidInput); -#[derive(Debug, thiserror::Error)] -#[error("InvalidStatus")] +#[derive(ThinContext)] +#[bigerror(crate)] pub struct InvalidStatus; -reportable!(InvalidStatus); -#[derive(Debug, thiserror::Error)] -#[error("InvalidState")] +#[derive(ThinContext)] +#[bigerror(crate)] pub struct InvalidState; -reportable!(InvalidState); /// Emitted during runtime, indicates problems with input/default settings -#[derive(Debug, thiserror::Error)] -#[error("ConfigError")] +#[derive(ThinContext)] +#[bigerror(crate)] pub struct ConfigError; -reportable!(ConfigError); /// Typically emitted by a `build.rs` failure -#[derive(Debug, thiserror::Error)] -#[error("BuildError")] +#[derive(ThinContext)] +#[bigerror(crate)] pub struct BuildError; -reportable!(BuildError); -#[derive(Debug, thiserror::Error)] -#[error("{}", Field::new("timeout", DisplayDuration(*.0)))] +#[derive(Debug, dm::Display)] +#[display("{}", Field::new("timeout", DisplayDuration(self.0)))] pub struct Timeout(pub Duration); +impl ::core::error::Error for Timeout {} -#[derive(Debug, thiserror::Error)] -#[error("AssertionError")] +#[derive(ThinContext)] +#[bigerror(crate)] pub struct AssertionError; -reportable!(AssertionError); pub trait CoreError: core::fmt::Debug + core::fmt::Display + Send + Sync + 'static {} @@ -132,24 +114,13 @@ impl BoxError { #[track_caller] pub fn new(err: E) -> Report where - E: std::error::Error + 'static + Send + Sync, + E: core::error::Error + 'static + Send + Sync, { Report::new(Self(Box::new(err))) } #[track_caller] - pub fn from(err: Box) -> Report { - Report::new(Self(err)) - } -} -impl BoxCoreError { - #[track_caller] - pub fn new(err: E) -> Report { - Report::new(Self(Box::new(err))) - } - - #[track_caller] - pub fn from(err: Box) -> Report { + pub fn from(err: Box) -> Report { Report::new(Self(err)) } } @@ -191,7 +162,7 @@ impl NotFound { } pub fn with_index(key: K) -> Report { - Self::with_kv(Index(key), ty!(T)) + Self::attach_kv(Index(key), ty!(T)) } } diff --git a/src/lib.rs b/bigerror/src/lib.rs similarity index 64% rename from src/lib.rs rename to bigerror/src/lib.rs index 3b67795..aab7818 100644 --- a/src/lib.rs +++ b/bigerror/src/lib.rs @@ -2,8 +2,8 @@ use error_stack::fmt::ColorMode; use std::{any::TypeId, fmt, panic::Location}; use tracing::{debug, error, info, trace, warn, Level}; +pub use bigerror_derive::ThinContext; pub use error_stack::{self, bail, ensure, report, Context, Report, ResultExt}; -pub use thiserror; pub mod attachment; pub mod context; @@ -27,15 +27,16 @@ pub fn init_no_ansi() { Report::set_color_mode(ColorMode::None); } -/// `Reportable` behaves as an `error_stack::ContextExt` +/// `ThinContext` behaves as an `error_stack::ContextExt` /// ideally used for zero sized errors or ones that hold a `'static` ref/value -pub trait Reportable +pub trait ThinContext where Self: Sized + Context, { - fn value() -> Self; + const VALUE: Self; + fn report(ctx: C) -> Report { - Report::new(ctx).change_context(Self::value()) + Report::new(ctx).change_context(Self::VALUE) } #[track_caller] @@ -43,7 +44,7 @@ where where A: Display, { - Report::new(Self::value()).attach_printable(attach_fn()) + Report::new(Self::VALUE).attach_printable(attach_fn()) } #[track_caller] @@ -51,7 +52,7 @@ where where A: Display, { - Report::new(Self::value()).attach_printable(value) + Report::new(Self::VALUE).attach_printable(value) } #[track_caller] fn attach_dbg(value: A) -> Report @@ -61,7 +62,7 @@ where Self::attach(Dbg(value)) } #[track_caller] - fn with_kv(key: K, value: V) -> Report + fn attach_kv(key: K, value: V) -> Report where K: Display, V: Display, @@ -69,7 +70,7 @@ where Self::attach(KeyValue(key, value)) } #[track_caller] - fn with_kv_dbg(key: K, value: V) -> Report + fn attach_kv_dbg(key: K, value: V) -> Report where K: Display, V: Debug, @@ -77,7 +78,7 @@ where Self::attach(KeyValue::dbg(key, value)) } #[track_caller] - fn with_field_status(key: &'static str, status: S) -> Report { + fn attach_field(key: &'static str, status: S) -> Report { Self::attach(Field::new(key, status)) } @@ -87,38 +88,38 @@ where } #[track_caller] - fn with_variant(value: A) -> Report { - Self::with_kv(attachment::Type::of::(), value) + fn attach_ty_val(value: A) -> Report { + Self::attach_kv(ty!(A), value) } #[track_caller] - fn with_variant_dbg(value: A) -> Report { - Self::with_kv_dbg(attachment::Type::of::(), value) + fn attach_ty_dbg(value: A) -> Report { + Self::attach_kv_dbg(ty!(A), value) } #[track_caller] - fn with_type() -> Report { - Self::attach(attachment::Type::of::()) + fn attach_ty() -> Report { + Self::attach(ty!(A)) } #[track_caller] - fn with_type_status(status: impl Display) -> Report { - Self::attach(Field::new(attachment::Type::of::(), status)) + fn attach_ty_status(status: impl Display) -> Report { + Self::attach(Field::new(ty!(A), status)) } } /// Extends [`error_stack::IntoReport`] to allow an implicit `E -> Report` inference pub trait ReportAs { - fn report_as(self) -> Result>; + fn report_as(self) -> Result>; } -impl ReportAs for Result { +impl ReportAs for Result { #[inline] #[track_caller] - fn report_as(self) -> Result> { + fn report_as(self) -> Result> { // TODO #[track_caller] on closure // https://github.com/rust-lang/rust/issues/87417 - // self.map_err(|e| Report::new(C::value()).attach_printable(e)) + // self.map_err(|e| Report::new(C::VALUE).attach_printable(e)) match self { Ok(v) => Ok(v), Err(e) => { @@ -143,34 +144,34 @@ impl ReportAs for Result { } pub trait IntoContext { - fn into_ctx(self) -> Report; + fn into_ctx(self) -> Report; } impl IntoContext for Report { #[inline] #[track_caller] - fn into_ctx(self) -> Report { + fn into_ctx(self) -> Report { if TypeId::of::() == TypeId::of::() { // if C and C2 are zero-sized and have the same TypeId then they are covariant unsafe { return std::mem::transmute::>(self.attach(*Location::caller())); } } - self.change_context(C2::value()) + self.change_context(C2::VALUE) } } pub trait ResultIntoContext: ResultExt { - fn into_ctx(self) -> Result>; + fn into_ctx(self) -> Result>; // Result::and_then fn and_then_ctx(self, op: F) -> Result> where - C2: Reportable, + C2: ThinContext, F: FnOnce(Self::Ok) -> Result>; // Result::map fn map_ctx(self, op: F) -> Result> where - C2: Reportable, + C2: ThinContext, F: FnOnce(Self::Ok) -> U; } @@ -180,7 +181,7 @@ where { #[inline] #[track_caller] - fn into_ctx(self) -> Result> { + fn into_ctx(self) -> Result> { // Can't use `map_err` as `#[track_caller]` is unstable on closures match self { Ok(ok) => Ok(ok), @@ -192,12 +193,12 @@ where #[track_caller] fn and_then_ctx(self, op: F) -> Result> where - C2: Reportable, + C2: ThinContext, F: FnOnce(T) -> Result>, { match self { Ok(t) => op(t), - Err(ctx) => Err(ctx.change_context(C2::value())), + Err(ctx) => Err(ctx.change_context(C2::VALUE)), } } @@ -205,56 +206,45 @@ where #[track_caller] fn map_ctx(self, op: F) -> Result> where - C2: Reportable, + C2: ThinContext, F: FnOnce(T) -> U, { match self { Ok(t) => Ok(op(t)), - Err(ctx) => Err(ctx.change_context(C2::value())), + Err(ctx) => Err(ctx.change_context(C2::VALUE)), } } } -// TODO convert to #[derive(Reportable)] -#[macro_export] -macro_rules! reportable { - ($context:ident) => { - impl $crate::Reportable for $context { - #[track_caller] - fn report(ctx: C) -> $crate::Report { - $crate::Report::new(ctx).change_context(Self) - } - - #[track_caller] - fn value() -> Self { - $context - } - } - }; -} - pub trait AttachExt { + #[must_use] fn attach_kv(self, key: K, value: V) -> Self where K: Display, V: Display; + #[must_use] fn attach_kv_dbg(self, key: K, value: V) -> Self where K: Display, V: Debug; + #[must_use] fn attach_field_status(self, name: &'static str, status: S) -> Self where S: Display; + + #[must_use] fn attach_dbg(self, value: A) -> Self where A: Debug; - fn attach_variant(self, value: A) -> Self + + #[must_use] + fn attach_ty_val(self, value: A) -> Self where Self: Sized, A: Display, { - self.attach_kv(attachment::Type::of::(), value) + self.attach_kv(ty!(A), value) } } @@ -447,220 +437,6 @@ impl ClearResult for Result { } } -pub trait ToReport { - #[track_caller] - fn to_report(self) -> Report; - - #[track_caller] - fn change_context(self, context: C) -> Report - where - Self: Sized, - { - self.to_report().change_context(context) - } -} - -pub trait FromReport { - fn to_report(self) -> Report; -} - -/// USAGE: -/// * `impl From $(as $context:path)*> for OurError::Report(OurReportError)` -/// - Implements `From where E: ToReport<_>` for errors that implement [`ToReport`] -/// * `impl From>> for OurError::Report(TransactionError)` -/// - Implements `From>` for `Report` -/// * `impl From for OurError::Report(TransactionError)` -/// - Implements `From` for `Report` -/// - Used to cast an error to `OurReportError`, can use `as` to do multiple `::from` conversion: -/// `impl From` does a concrete implementation of the `impl` below where `E` is `SomeError`: -/// `impl From for OurError where E: Into {}` -#[macro_export] -macro_rules! from_report { - // ------ - // From as ctx> - ({ impl From<$from:path as ToReport<_> $(as $context:path)*> $($tail:tt)* }) => { - from_report!(@t[] @report_t[] @to_report[$from $(as $context)*,] $($tail)*); - }; - // From> - ({ impl From<$from:path as ToReport<_>> $($tail:tt)* }) => { - from_report!(@t[] @report_t[] @to_report[$from,] $($tail)*); - }; - // From> - ({ impl From> $($tail:tt)* }) => { - from_report!(@t[] @report_t[$from] @to_report[] $($tail)*); - }; - // From - ({ impl From<$from:path $(as $context:path)*> $($tail:tt)* }) => { - from_report!(@t[$from $(as $context)*,] @report_t[] @to_report[] $($tail)*); - }; - // ------ - // From> - ( - @t[$($t:path $(as $t_context:path)*,)*] - @report_t[$($report_t:path)*] - @to_report[$($to_report:path $(as $to_context:path)*,)*] - impl From> $($tail:tt)*) => { - from_report!( - @t[$($t $(as $t_context)*,)*] - @report_t[$($report_t)* $from] - @to_report[$($to_report $(as $to_context)*,)*] - $($tail)* - ); - }; - // From as ctx> - ( - @t[$($t:path $(as $t_context:path)*,)*] - @report_t[$($report_t:path)*] - @to_report[$($to_report:path $(as $to_context:path)*,)*] - impl From<$from:path as ToReport<_> $(as $context:path)*> $($tail:tt)*) => { - from_report!( - @t[$($t $(as $t_context)*,)*] - @report_t[$($report_t)*] - @to_report[$($to_report $(as $to_context)*,)* $from $(as $context)*,] - $($tail)* - ); - }; - // From> - ( - @t[$($t:path $(as $t_context:path)*,)*] - @report_t[$($report_t:path)*] - @to_report[$($to_report:path $(as $to_context:path)*,)*] - impl From<$from:path as ToReport<_>> $($tail:tt)*) => { - from_report!( - @t[$($t $(as $t_context)*,)*] - @report_t[$($report_t)*] - @to_report[$($to_report $(as $to_context)*,)* $from,] - $($tail)* - ); - }; - // From - ( - @t[$($t:path $(as $t_context:path)*,)*] - @report_t[$($report_t:path)*] - @to_report[$($to_report:path $(as $to_context:path)*,)*] - impl From<$from:path $(as $context:path)*> $($tail:tt)*) => { - from_report!( - @t[$($t $(as $t_context)*,)* $from $(as $context)*,] - @report_t[$($report_t)*] - @to_report[$($to_report $(as $to_context)*,)*] - $($tail)* - ); - }; - ( - @t[$($t:path $(as $t_context:path)*,)*] - @report_t[$($report_t:path)*] - @to_report[$($to_report:path $(as $to_context:path)*,)*] - for $for:ident::$variant:ident($inner:path)) => { - // T - $(impl From<$t> for $for { - #[track_caller] - fn from(e: $t) -> Self { - let report = $crate::Report::new(e) - $(.change_context($t_context))* - .change_context($inner); - Self::$variant(report) - } - })* - - // Report - $(impl From<$crate::Report<$report_t>> for $for { - #[track_caller] - fn from(r: $crate::Report<$report_t>) -> Self { - Self::$variant(r.change_context($inner)) - } - })* - // ToReport<_> - $(impl From<$to_report> for $for { - #[track_caller] - fn from(e: $to_report) -> Self { - use $crate::ToReport; - let report = e.to_report() - $(.change_context($to_context))* - .change_context($inner); - Self::$variant(report) - } - })* - // original - impl From<$crate::Report<$inner>> for $for { - #[track_caller] - fn from(r: $crate::Report<$inner>) -> Self { - Self::$variant(r) - } - } - }; - (impl From> for $for:ident::$variant:ident) => { - impl From<$crate::Report<$from>> for $for { - #[track_caller] - fn from(r: $crate::Report<$from>) -> Self { - Self::$variant(r) - } - } - }; - (impl From<$from:path as ToReport<$context:path>> for $for:ident::$variant:ident) => { - impl From<$from> for $for { - #[track_caller] - fn from(e: $from) -> Self { - Self::$variant(<$from as $crate::error::ToReport<_>>::change_context( - e, $context, - )) - } - } - }; - (impl From<$from:path $(as $context:path)*> for $for:ident::$variant:ident) => { - impl From<$from> for $for { - #[track_caller] - fn from(e: $from) -> Self { - let report = $crate::Report::new(e) - $(.change_context($context))* ; - Self::$variant(report) - } - } - }; -} - -#[macro_export] -macro_rules! to_report { - (impl ToReport<$reportable:path> for $for:ident::$variant:ident) => { - impl $crate::ToReport<$reportable> for $for { - #[track_caller] - fn to_report(self) -> $crate::Report<$reportable> { - #[allow(unreachable_patterns)] - match self { - Self::$variant(inner) => inner, - _ => $crate::Report::new(self).change_context($reportable), - } - } - } - }; -} - -pub trait ResultToReport { - /// Type of the [`Ok`] value in the [`Result`] - type Ok; - - #[track_caller] - fn to_report(self) -> Result>; -} - -impl ResultToReport for Result -where - E: ToReport, -{ - type Ok = T; - - #[inline] - #[track_caller] - fn to_report(self) -> Result> { - self.map_err(ToReport::to_report) - } -} - -impl ToReport for Report { - fn to_report(self) -> Self { - self - } -} - /// Used to produce [`NotFound`] reports from an [`Option`] pub trait OptionReport where @@ -709,7 +485,7 @@ impl OptionReport for Option { // self.ok_or_else(|| Report::new(NotFound)) match self { Some(v) => Ok(v), - None => Err(NotFound::with_type::()), + None => Err(NotFound::attach_ty::()), } } @@ -722,7 +498,7 @@ impl OptionReport for Option { { match self { Some(v) => Ok(v), - None => Err(NotFound::with_kv(key, value)), + None => Err(NotFound::attach_kv(key, value)), } } @@ -790,16 +566,9 @@ mod test { use super::*; - #[derive(Debug, thiserror::Error)] - #[error("MyError")] + #[derive(ThinContext)] + #[bigerror(crate)] pub struct MyError; - reportable!(MyError); - - #[derive(Debug, thiserror::Error)] - pub enum Error { - #[error("{0:?}")] - Report(Report), - } #[derive(Default)] struct MyStruct { @@ -809,7 +578,7 @@ mod test { impl MyStruct { fn __field(_t: T, _field: &'static str) {} - fn my_field(&self) -> Option<()> { + const fn my_field(&self) -> Option<()> { self.my_field } } @@ -833,15 +602,6 @@ mod test { }; } - from_report!({ - - impl From - - for Error::Report(MyError) - }); - - to_report!(impl ToReport for Error::Report); - #[test] fn report_as() { fn output() -> Result> { @@ -860,7 +620,7 @@ mod test { } #[test] fn box_reportable() { - fn output() -> Result> { + fn output() -> Result> { Ok("NaN".parse::().map_err(Box::new)?) } @@ -943,10 +703,10 @@ mod test { } #[test] - fn with_type_status() { + fn attach_ty_status() { fn try_even(num: usize) -> Result<(), Report> { if num % 2 != 0 { - return Err(InvalidInput::with_type_status::(Invalid).into_ctx()); + return Err(InvalidInput::attach_ty_status::(Invalid).into_ctx()); } Ok(()) } @@ -985,11 +745,11 @@ mod test { } #[test] - fn attach_variant() { + fn attach_ty_val() { fn compare(mine: usize, other: usize) -> Result<(), Report> { if other != mine { bail!(InvalidInput::attach("expected my number!") - .attach_variant(other) + .attach_ty_val(other) .into_ctx()); } Ok(()) @@ -1001,7 +761,7 @@ mod test { assert_err!(compare(my_number, other_number)); } - // should behave the same as `test::attach_variant` + // should behave the same as `test::attach_ty_val` // but displays lazy allocation of attachment #[test] fn attach_kv_macro() { diff --git a/bigerror_derive/Cargo.toml b/bigerror_derive/Cargo.toml new file mode 100644 index 0000000..f6f74fc --- /dev/null +++ b/bigerror_derive/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "bigerror-derive" +version = "0.1.0" +edition.workspace = true +description.workspace = true +license.workspace = true +documentation.workspace = true +repository.workspace = true + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = { workspace = true } +quote = { workspace = true } +syn = { workspace = true } diff --git a/bigerror_derive/src/attributes.rs b/bigerror_derive/src/attributes.rs new file mode 100644 index 0000000..2ccb46f --- /dev/null +++ b/bigerror_derive/src/attributes.rs @@ -0,0 +1,69 @@ +use quote::ToTokens; +use syn::{meta::ParseNestedMeta, parse_quote, DeriveInput, Error, Path, Token}; + +fn try_set_attribute( + attribute: &mut Option, + value: T, + name: &'static str, +) -> Result<(), Error> { + if attribute.is_some() { + return Err(Error::new_spanned( + value, + format!("{name} already specified"), + )); + } + *attribute = Some(value); + Ok(()) +} + +#[derive(Default)] +struct Builder { + pub display: bool, + pub crate_path: Option, +} + +impl Builder { + fn parse_meta(&mut self, meta: &ParseNestedMeta<'_>) -> Result<(), Error> { + if meta.path.is_ident("crate") { + if meta.input.parse::().is_ok() { + let path = meta.input.parse::()?; + try_set_attribute(&mut self.crate_path, path, "crate") + } else if meta.input.is_empty() || meta.input.peek(Token![,]) { + try_set_attribute(&mut self.crate_path, parse_quote! { crate }, "crate") + } else { + Err(meta.error("expected `crate` or `crate = ...`")) + } + } else { + Ok(()) + } + } +} + +pub struct Attributes { + // TODO port #[display(...)] + pub has_display: bool, + pub crate_path: Path, +} +impl Attributes { + pub fn parse(input: &DeriveInput) -> Result { + let mut builder = Builder::default(); + + for attr in &input.attrs { + if attr.path().is_ident("display") { + builder.display = true; + } + if attr.path().is_ident("bigerror") { + attr.parse_nested_meta(|meta| builder.parse_meta(&meta))?; + } + } + let has_display = builder.display; + let crate_path = builder + .crate_path + .take() + .unwrap_or_else(|| parse_quote! { ::bigerror }); + Ok(Self { + has_display, + crate_path, + }) + } +} diff --git a/bigerror_derive/src/context.rs b/bigerror_derive/src/context.rs new file mode 100644 index 0000000..d69978e --- /dev/null +++ b/bigerror_derive/src/context.rs @@ -0,0 +1,57 @@ +use proc_macro2::TokenStream; +use quote::quote; +use syn::{Data, DeriveInput, Error, Fields}; + +use crate::attributes::Attributes; + +pub fn thin(input: &DeriveInput) -> Result { + let ctx = &input.ident; + let attributes = Attributes::parse(input)?; + match &input.data { + Data::Struct(data_struct) => { + if !matches!(data_struct.fields, Fields::Unit) { + return Err(Error::new_spanned( + &input.ident, + "struct must be a zero-sized-type to implement `ThinContext`", + )); + } + } + Data::Enum(_) => { + return Err(Error::new_spanned( + &input.ident, + "enum `ThinContext` derives are currently unsupported", + )); + } + Data::Union(_) => { + return Err(Error::new_spanned( + &input.ident, + "union `ThinContext` derives are currently unsupported", + )); + } + } + + let bigerror = attributes.crate_path; + let impl_display = (!attributes.has_display).then(|| { + quote! { + impl ::core::fmt::Display for #ctx { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + f.write_str(stringify!(#ctx)) + } + } + } + }); + Ok(quote! { + + impl ::core::error::Error for #ctx {} + impl ::core::fmt::Debug for #ctx { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + f.write_str(stringify!(#ctx)) + } + } + + #impl_display + impl #bigerror::ThinContext for #ctx { + const VALUE: Self = #ctx; + } + }) +} diff --git a/bigerror_derive/src/lib.rs b/bigerror_derive/src/lib.rs new file mode 100644 index 0000000..f820a35 --- /dev/null +++ b/bigerror_derive/src/lib.rs @@ -0,0 +1,26 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::{parse_macro_input, DeriveInput}; + +mod attributes; +mod context; + +#[proc_macro_derive(ThinContext, attributes(display, bigerror))] +pub fn derive_thin_context(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + match context::thin(&input) { + Ok(result) => result.into(), + Err(e) => e.to_compile_error().into(), + } +} + +#[proc_macro_attribute] +pub fn derive_ctx(_attr: TokenStream, input: TokenStream) -> TokenStream { + let input: proc_macro2::TokenStream = input.into(); + let output = quote! { + #[derive(crate::ThinContext)] + #[bigerror(crate)] + #input + }; + output.into() +}