From 736c5e3aafaf8030bc93bc453de99900fefc5c5c Mon Sep 17 00:00:00 2001 From: mcmah309 Date: Mon, 23 Dec 2024 21:56:17 +0000 Subject: [PATCH] feat: Improve err_trail --- err_trail/Cargo.toml | 6 +- err_trail/src/defmt.rs | 142 ++++++++++++++++++++++++++++++ err_trail/src/lib.rs | 10 ++- err_trail/src/tracing_log_stub.rs | 26 +++--- 4 files changed, 168 insertions(+), 16 deletions(-) create mode 100644 err_trail/src/defmt.rs diff --git a/err_trail/Cargo.toml b/err_trail/Cargo.toml index b2eb52d..e527cac 100644 --- a/err_trail/Cargo.toml +++ b/err_trail/Cargo.toml @@ -9,10 +9,12 @@ log = { version = "0.4", optional = true } defmt = { version = "0.3", optional = true } [features] -default = [] +default = ["stub"] # Enables support for the tracing crate. Adds methods to `Result` that are applied on `Err` - e.g. `result.warn(...)`. tracing = ["dep:tracing"] # Enables support for the log crate. Adds methods to `Result` that are applied on `Err` - e.g. `result.warn(...)`. log = ["dep:log"] # Enables support for the defmt crate, which works with no_std. Adds methods to `Result` that are applied on `Err` - e.g. `result.warn(...)`. -defmt = ["dep:defmt"] \ No newline at end of file +defmt = ["dep:defmt"] +# Enables support for the log/tracing/defmt api, without pulling in any crates. Allowing a downstream to choose the appropriate crate. +stub = [] \ No newline at end of file diff --git a/err_trail/src/defmt.rs b/err_trail/src/defmt.rs new file mode 100644 index 0000000..2692ef9 --- /dev/null +++ b/err_trail/src/defmt.rs @@ -0,0 +1,142 @@ +#![allow(unused_variables)] + +use defmt::Format; + +mod sealed { + pub trait Sealed {} +} + +/// For logging a [Result] when [Err] is encountered. +#[cfg_attr(docsrs, doc(cfg(feature = "defmt")))] +pub trait ErrContext: sealed::Sealed { + /// If [Err], log context as "error". + fn error_context(self, context: impl Format) -> Result; + /// If [Err], log context as "warn". + fn warn_context(self, context: impl Format) -> Result; + + /// If [Err], lazily log result of [f] as "error". + fn with_error_context D, D: Format>(self, f: F) -> Result; + /// If [Err], lazily log result of [f] as "warn". + fn with_warn_context D, D: Format>(self, f: F) -> Result; +} + +/// For consuming a [Result]'s [Err] in [Format] when [Err] is encountered. +#[cfg_attr(docsrs, doc(cfg(feature = "defmt")))] +pub trait ErrContextConsume: sealed::Sealed { + /// Consume [Err] of a [Result]. Log as "error". + fn consume_as_error(self) -> Option; + /// Consume [Err] of a [Result]. Log as "warn". + fn consume_as_warn(self) -> Option; +} + +/// For logging an [Option] when [None] is encountered. +#[cfg_attr(docsrs, doc(cfg(feature = "defmt")))] +pub trait NoneContext: sealed::Sealed { + /// If [None], log context as "error". + fn error_context(self, context: impl Format) -> Option; + /// If [None], log context as "warn". + fn warn_context(self, context: impl Format) -> Option; + + /// If [None], lazily log result of [f] as "error". + fn with_error_context D, D: Format>(self, f: F) -> Option; + /// If [None], lazily log result of [f] as "warn". + fn with_warn_context D, D: Format>(self, f: F) -> Option; +} + +//************************************************************************// + +impl sealed::Sealed for Result {} +impl ErrContext for Result { + #[inline] + fn error_context(self, context: impl Format) -> Result { + if self.is_err() { + defmt::error!("{}", context); + } + self + } + + #[inline] + fn warn_context(self, context: impl Format) -> Result { + if self.is_err() { + defmt::warn!("{}", context); + } + self + } + + #[inline] + fn with_error_context D, D: Format>(self, f: F) -> Result { + if let Err(err) = &self { + defmt::error!("{}", f(err)); + } + self + } + + #[inline] + fn with_warn_context D, D: Format>(self, f: F) -> Result { + if let Err(err) = &self { + defmt::warn!("{}", f(err)); + } + self + } +} + +impl ErrContextConsume for Result { + #[inline] + fn consume_as_error(self) -> Option { + match self { + Ok(ok) => Some(ok), + Err(err) => { + defmt::error!("{}", err); + None + } + } + } + + #[inline] + fn consume_as_warn(self) -> Option { + match self { + Ok(ok) => Some(ok), + Err(err) => { + defmt::warn!("{}", err); + None + } + } + } +} + +//************************************************************************// + +impl sealed::Sealed for Option {} +impl NoneContext for Option { + #[inline] + fn error_context(self, context: impl Format) -> Option { + if self.is_none() { + defmt::error!("{}", context); + } + self + } + + #[inline] + fn warn_context(self, context: impl Format) -> Option { + if self.is_none() { + defmt::warn!("{}", context); + } + self + } + + #[inline] + fn with_error_context D, D: Format>(self, f: F) -> Option { + if self.is_none() { + defmt::error!("{}", f()); + } + self + } + + #[inline] + fn with_warn_context D, D: Format>(self, f: F) -> Option { + if self.is_none() { + defmt::warn!("{}", f()); + } + self + } +} \ No newline at end of file diff --git a/err_trail/src/lib.rs b/err_trail/src/lib.rs index 5fcc643..c5c2c6d 100644 --- a/err_trail/src/lib.rs +++ b/err_trail/src/lib.rs @@ -1 +1,9 @@ -pub mod tracing_log_stub; \ No newline at end of file +#[cfg(any(feature = "tracing", feature = "log", feature = "stub"))] +mod tracing_log_stub; +#[cfg(any(feature = "tracing", feature = "log", feature = "stub"))] +pub use tracing_log_stub::*; + +#[cfg(feature = "defmt")] +mod defmt; +#[cfg(feature = "defmt")] +pub use defmt::*; \ No newline at end of file diff --git a/err_trail/src/tracing_log_stub.rs b/err_trail/src/tracing_log_stub.rs index 6372ffd..e1f6535 100644 --- a/err_trail/src/tracing_log_stub.rs +++ b/err_trail/src/tracing_log_stub.rs @@ -13,10 +13,10 @@ mod sealed { feature = "tracing", feature = "log", feature = "defmt", - feature = "context_stub" + feature = "stub" ))) )] -pub trait ResultTrail: sealed::Sealed { +pub trait ErrContext: sealed::Sealed { /// If [Err], logging context as an "error". fn error_context(self, context: impl Display) -> Result; /// If [Err], logging context as an "warn". @@ -35,10 +35,10 @@ pub trait ResultTrail: sealed::Sealed { feature = "tracing", feature = "log", feature = "defmt", - feature = "context_stub" + feature = "stub" ))) )] -pub trait OptionTrail: sealed::Sealed { +pub trait NoneContext: sealed::Sealed { /// If [None], logging context as an "error". fn error_context(self, context: impl Display) -> Option; /// If [None], logging context as an "warn". @@ -57,22 +57,22 @@ pub trait OptionTrail: sealed::Sealed { feature = "tracing", feature = "log", feature = "defmt", - feature = "context_stub" + feature = "stub" ))) )] -pub trait ResultTrailDisplay: sealed::Sealed { +pub trait ErrContextDisplay: sealed::Sealed { /// Consumes the [Err] of a Result. If [Err], logging the display of the error as an "error". /// Represents a bad state in which the current process cannot continue. - fn error_context_end(self) -> Option; + fn consume_as_error(self) -> Option; /// Consumes the [Err] of a Result. If [Err], logging the display of the error as an "warn". /// Represents a bad state in which the current process can continue. - fn warn_context_end(self) -> Option; + fn consume_as_warn(self) -> Option; } //************************************************************************// impl sealed::Sealed for Result {} -impl ResultTrail for Result { +impl ErrContext for Result { #[inline] fn error_context(self, context: impl Display) -> Result { if self.is_err() { @@ -122,9 +122,9 @@ impl ResultTrail for Result { //************************************************************************// -impl ResultTrailDisplay for Result { +impl ErrContextDisplay for Result { #[inline] - fn error_context_end(self) -> Option { + fn consume_as_error(self) -> Option { match self { Ok(value) => Some(value), Err(err) => { @@ -138,7 +138,7 @@ impl ResultTrailDisplay for Result { } #[inline] - fn warn_context_end(self) -> Option { + fn consume_as_warn(self) -> Option { match self { Ok(value) => Some(value), Err(err) => { @@ -155,7 +155,7 @@ impl ResultTrailDisplay for Result { //************************************************************************// impl sealed::Sealed for Option {} -impl OptionTrail for Option { +impl NoneContext for Option { #[inline] fn error_context(self, context: impl Display) -> Option { if self.is_none() {