use crate::error::BoxError;
use crate::std::sync::Arc;
pub trait ErrorSink<E = BoxError>: Send + Sync + 'static {
fn sink_error(&self, error: E);
}
impl<E, F> ErrorSink<E> for F
where
F: Fn(E) + Send + Sync + 'static,
{
fn sink_error(&self, error: E) {
(self)(error)
}
}
impl<E, T> ErrorSink<E> for Arc<T>
where
T: ErrorSink<E> + ?Sized,
{
fn sink_error(&self, error: E) {
(**self).sink_error(error)
}
}
#[derive(Debug, Clone)]
pub struct TracingErrorSink {
level: tracing::Level,
}
impl Default for TracingErrorSink {
fn default() -> Self {
Self::new(tracing::Level::DEBUG)
}
}
impl TracingErrorSink {
#[must_use]
pub const fn new(level: tracing::Level) -> Self {
Self { level }
}
#[must_use]
pub const fn trace() -> Self {
Self::new(tracing::Level::TRACE)
}
#[must_use]
pub const fn debug() -> Self {
Self::new(tracing::Level::DEBUG)
}
#[must_use]
pub const fn info() -> Self {
Self::new(tracing::Level::INFO)
}
#[must_use]
pub const fn warn() -> Self {
Self::new(tracing::Level::WARN)
}
#[must_use]
pub const fn error() -> Self {
Self::new(tracing::Level::ERROR)
}
}
impl<E> ErrorSink<E> for TracingErrorSink
where
E: Into<BoxError>,
{
fn sink_error(&self, error: E) {
const MESSAGE: &str = "error sink: unhandled error";
let error = error.into();
match self.level {
tracing::Level::TRACE => tracing::trace!(?error, "{MESSAGE}"),
tracing::Level::DEBUG => tracing::debug!(?error, "{MESSAGE}"),
tracing::Level::INFO => tracing::info!(?error, "{MESSAGE}"),
tracing::Level::WARN => tracing::warn!(?error, "{MESSAGE}"),
tracing::Level::ERROR => tracing::error!(?error, "{MESSAGE}"),
}
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct DropErrorSink;
impl DropErrorSink {
#[must_use]
pub const fn new() -> Self {
Self
}
}
impl<E> ErrorSink<E> for DropErrorSink {
fn sink_error(&self, _error: E) {}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::BoxErrorExt as _;
use crate::std::sync::Arc;
use core::sync::atomic::{AtomicUsize, Ordering};
#[test]
fn closure_is_error_sink() {
let count = Arc::new(AtomicUsize::new(0));
let sink = {
let count = count.clone();
move |_err: BoxError| {
count.fetch_add(1, Ordering::SeqCst);
}
};
sink.sink_error(BoxError::from_static_str("boom"));
sink.sink_error(BoxError::from_static_str("boom again"));
assert_eq!(count.load(Ordering::SeqCst), 2);
}
#[test]
fn tracing_sink_is_object_safe_and_callable() {
let sink: Arc<dyn ErrorSink> = Arc::new(TracingErrorSink::default());
sink.sink_error(BoxError::from_static_str("observed"));
for sink in [
TracingErrorSink::trace(),
TracingErrorSink::debug(),
TracingErrorSink::info(),
TracingErrorSink::warn(),
TracingErrorSink::error(),
] {
sink.sink_error(BoxError::from_static_str("level"));
}
}
#[test]
fn drop_error_sink_ignores_any_error_type() {
struct NotAnError;
let sink = DropErrorSink::new();
sink.sink_error(NotAnError);
sink.sink_error(BoxError::from_static_str("ignored"));
sink.sink_error(42_u32);
let sink: Arc<dyn ErrorSink> = Arc::new(DropErrorSink::new());
sink.sink_error(BoxError::from_static_str("ignored"));
}
#[test]
fn custom_closure_sink_as_trait_object() {
let count = Arc::new(AtomicUsize::new(0));
let sink: Arc<dyn ErrorSink> = {
let count = count.clone();
Arc::new(move |_err: BoxError| {
count.fetch_add(1, Ordering::SeqCst);
})
};
sink.sink_error(BoxError::from_static_str("routed"));
assert_eq!(count.load(Ordering::SeqCst), 1);
}
}