Skip to content

Commit

Permalink
feat: Improve err_trail
Browse files Browse the repository at this point in the history
  • Loading branch information
mcmah309 committed Dec 23, 2024
1 parent 8d0f158 commit 736c5e3
Show file tree
Hide file tree
Showing 4 changed files with 168 additions and 16 deletions.
6 changes: 4 additions & 2 deletions err_trail/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
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 = []
142 changes: 142 additions & 0 deletions err_trail/src/defmt.rs
Original file line number Diff line number Diff line change
@@ -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<T, E>: sealed::Sealed {
/// If [Err], log context as "error".
fn error_context(self, context: impl Format) -> Result<T, E>;
/// If [Err], log context as "warn".
fn warn_context(self, context: impl Format) -> Result<T, E>;

/// If [Err], lazily log result of [f] as "error".
fn with_error_context<F: FnOnce(&E) -> D, D: Format>(self, f: F) -> Result<T, E>;
/// If [Err], lazily log result of [f] as "warn".
fn with_warn_context<F: FnOnce(&E) -> D, D: Format>(self, f: F) -> Result<T, E>;
}

/// For consuming a [Result]'s [Err] in [Format] when [Err] is encountered.
#[cfg_attr(docsrs, doc(cfg(feature = "defmt")))]
pub trait ErrContextConsume<T, E: Format>: sealed::Sealed {
/// Consume [Err] of a [Result]. Log as "error".
fn consume_as_error(self) -> Option<T>;
/// Consume [Err] of a [Result]. Log as "warn".
fn consume_as_warn(self) -> Option<T>;
}

/// For logging an [Option] when [None] is encountered.
#[cfg_attr(docsrs, doc(cfg(feature = "defmt")))]
pub trait NoneContext<T>: sealed::Sealed {
/// If [None], log context as "error".
fn error_context(self, context: impl Format) -> Option<T>;
/// If [None], log context as "warn".
fn warn_context(self, context: impl Format) -> Option<T>;

/// If [None], lazily log result of [f] as "error".
fn with_error_context<F: FnOnce() -> D, D: Format>(self, f: F) -> Option<T>;
/// If [None], lazily log result of [f] as "warn".
fn with_warn_context<F: FnOnce() -> D, D: Format>(self, f: F) -> Option<T>;
}

//************************************************************************//

impl<T, E> sealed::Sealed for Result<T, E> {}
impl<T, E> ErrContext<T, E> for Result<T, E> {
#[inline]
fn error_context(self, context: impl Format) -> Result<T, E> {
if self.is_err() {
defmt::error!("{}", context);
}
self
}

#[inline]
fn warn_context(self, context: impl Format) -> Result<T, E> {
if self.is_err() {
defmt::warn!("{}", context);
}
self
}

#[inline]
fn with_error_context<F: FnOnce(&E) -> D, D: Format>(self, f: F) -> Result<T, E> {
if let Err(err) = &self {
defmt::error!("{}", f(err));
}
self
}

#[inline]
fn with_warn_context<F: FnOnce(&E) -> D, D: Format>(self, f: F) -> Result<T, E> {
if let Err(err) = &self {
defmt::warn!("{}", f(err));
}
self
}
}

impl<T, E: Format> ErrContextConsume<T, E> for Result<T, E> {
#[inline]
fn consume_as_error(self) -> Option<T> {
match self {
Ok(ok) => Some(ok),
Err(err) => {
defmt::error!("{}", err);
None
}
}
}

#[inline]
fn consume_as_warn(self) -> Option<T> {
match self {
Ok(ok) => Some(ok),
Err(err) => {
defmt::warn!("{}", err);
None
}
}
}
}

//************************************************************************//

impl<T> sealed::Sealed for Option<T> {}
impl<T> NoneContext<T> for Option<T> {
#[inline]
fn error_context(self, context: impl Format) -> Option<T> {
if self.is_none() {
defmt::error!("{}", context);
}
self
}

#[inline]
fn warn_context(self, context: impl Format) -> Option<T> {
if self.is_none() {
defmt::warn!("{}", context);
}
self
}

#[inline]
fn with_error_context<F: FnOnce() -> D, D: Format>(self, f: F) -> Option<T> {
if self.is_none() {
defmt::error!("{}", f());
}
self
}

#[inline]
fn with_warn_context<F: FnOnce() -> D, D: Format>(self, f: F) -> Option<T> {
if self.is_none() {
defmt::warn!("{}", f());
}
self
}
}
10 changes: 9 additions & 1 deletion err_trail/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1 +1,9 @@
pub mod tracing_log_stub;
#[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::*;
26 changes: 13 additions & 13 deletions err_trail/src/tracing_log_stub.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ mod sealed {
feature = "tracing",
feature = "log",
feature = "defmt",
feature = "context_stub"
feature = "stub"
)))
)]
pub trait ResultTrail<T, E>: sealed::Sealed {
pub trait ErrContext<T, E>: sealed::Sealed {
/// If [Err], logging context as an "error".
fn error_context(self, context: impl Display) -> Result<T, E>;
/// If [Err], logging context as an "warn".
Expand All @@ -35,10 +35,10 @@ pub trait ResultTrail<T, E>: sealed::Sealed {
feature = "tracing",
feature = "log",
feature = "defmt",
feature = "context_stub"
feature = "stub"
)))
)]
pub trait OptionTrail<T>: sealed::Sealed {
pub trait NoneContext<T>: sealed::Sealed {
/// If [None], logging context as an "error".
fn error_context(self, context: impl Display) -> Option<T>;
/// If [None], logging context as an "warn".
Expand All @@ -57,22 +57,22 @@ pub trait OptionTrail<T>: sealed::Sealed {
feature = "tracing",
feature = "log",
feature = "defmt",
feature = "context_stub"
feature = "stub"
)))
)]
pub trait ResultTrailDisplay<T, E: Display>: sealed::Sealed {
pub trait ErrContextDisplay<T, E: Display>: 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<T>;
fn consume_as_error(self) -> Option<T>;
/// 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<T>;
fn consume_as_warn(self) -> Option<T>;
}

//************************************************************************//

impl<T, E> sealed::Sealed for Result<T, E> {}
impl<T, E> ResultTrail<T, E> for Result<T, E> {
impl<T, E> ErrContext<T, E> for Result<T, E> {
#[inline]
fn error_context(self, context: impl Display) -> Result<T, E> {
if self.is_err() {
Expand Down Expand Up @@ -122,9 +122,9 @@ impl<T, E> ResultTrail<T, E> for Result<T, E> {

//************************************************************************//

impl<T, E: Display> ResultTrailDisplay<T, E> for Result<T, E> {
impl<T, E: Display> ErrContextDisplay<T, E> for Result<T, E> {
#[inline]
fn error_context_end(self) -> Option<T> {
fn consume_as_error(self) -> Option<T> {
match self {
Ok(value) => Some(value),
Err(err) => {
Expand All @@ -138,7 +138,7 @@ impl<T, E: Display> ResultTrailDisplay<T, E> for Result<T, E> {
}

#[inline]
fn warn_context_end(self) -> Option<T> {
fn consume_as_warn(self) -> Option<T> {
match self {
Ok(value) => Some(value),
Err(err) => {
Expand All @@ -155,7 +155,7 @@ impl<T, E: Display> ResultTrailDisplay<T, E> for Result<T, E> {
//************************************************************************//

impl<T> sealed::Sealed for Option<T> {}
impl<T> OptionTrail<T> for Option<T> {
impl<T> NoneContext<T> for Option<T> {
#[inline]
fn error_context(self, context: impl Display) -> Option<T> {
if self.is_none() {
Expand Down

0 comments on commit 736c5e3

Please sign in to comment.