use alloc::boxed::Box;
use core::fmt;
use crate::StageId;
pub type Result<T> = core::result::Result<T, Error>;
pub trait StageError: fmt::Debug + fmt::Display + Send + Sync + 'static {}
impl<T> StageError for T where T: fmt::Debug + fmt::Display + Send + Sync + 'static {}
pub type BoxError = Box<dyn StageError>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BufferErrorKind {
Full,
Closed,
}
impl fmt::Display for BufferErrorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Full => f.write_str("buffer full"),
Self::Closed => f.write_str("buffer closed"),
}
}
}
#[non_exhaustive]
pub enum Error {
Source {
stage: StageId,
source: BoxError,
},
Stage {
stage: StageId,
source: BoxError,
},
Sink {
stage: StageId,
source: BoxError,
},
Buffer {
stage: StageId,
kind: BufferErrorKind,
},
Cancelled,
Closed,
}
impl fmt::Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Source { stage, source } => f
.debug_struct("Source")
.field("stage", stage)
.field("source", &format_args!("{source}"))
.finish(),
Self::Stage { stage, source } => f
.debug_struct("Stage")
.field("stage", stage)
.field("source", &format_args!("{source}"))
.finish(),
Self::Sink { stage, source } => f
.debug_struct("Sink")
.field("stage", stage)
.field("source", &format_args!("{source}"))
.finish(),
Self::Buffer { stage, kind } => f
.debug_struct("Buffer")
.field("stage", stage)
.field("kind", kind)
.finish(),
Self::Cancelled => f.write_str("Cancelled"),
Self::Closed => f.write_str("Closed"),
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Source { stage, source } => write!(f, "source `{stage}`: {source}"),
Self::Stage { stage, source } => write!(f, "stage `{stage}`: {source}"),
Self::Sink { stage, source } => write!(f, "sink `{stage}`: {source}"),
Self::Buffer { stage, kind } => write!(f, "buffer at `{stage}`: {kind}"),
Self::Cancelled => f.write_str("pipeline cancelled"),
Self::Closed => f.write_str("pipeline closed"),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for Error {}
impl Error {
#[must_use]
pub fn stage(&self) -> Option<StageId> {
match self {
Self::Source { stage, .. }
| Self::Stage { stage, .. }
| Self::Sink { stage, .. }
| Self::Buffer { stage, .. } => Some(*stage),
Self::Cancelled | Self::Closed => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum ErrorPolicy {
#[default]
FailFast,
Continue,
DeadLetter,
}
#[non_exhaustive]
pub struct StageFailure {
pub stage: StageId,
pub source: BoxError,
}
impl StageFailure {
#[must_use]
pub fn new(stage: StageId, source: BoxError) -> Self {
Self { stage, source }
}
}
impl fmt::Debug for StageFailure {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("StageFailure")
.field("stage", &self.stage)
.field("source", &format_args!("{}", self.source))
.finish()
}
}
impl fmt::Display for StageFailure {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "stage `{}`: {}", self.stage, self.source)
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::format;
use alloc::string::ToString;
#[derive(Debug)]
struct Boom;
impl fmt::Display for Boom {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("boom")
}
}
#[test]
fn display_source_error() {
let e = Error::Source {
stage: StageId::new("ingest"),
source: Box::new(Boom),
};
assert_eq!(e.to_string(), "source `ingest`: boom");
}
#[test]
fn display_stage_error() {
let e = Error::Stage {
stage: StageId::new("parse"),
source: Box::new(Boom),
};
assert_eq!(e.to_string(), "stage `parse`: boom");
}
#[test]
fn display_sink_error() {
let e = Error::Sink {
stage: StageId::new("writer"),
source: Box::new(Boom),
};
assert_eq!(e.to_string(), "sink `writer`: boom");
}
#[test]
fn display_buffer_error_kinds() {
let full = Error::Buffer {
stage: StageId::new("edge"),
kind: BufferErrorKind::Full,
};
let closed = Error::Buffer {
stage: StageId::new("edge"),
kind: BufferErrorKind::Closed,
};
assert_eq!(full.to_string(), "buffer at `edge`: buffer full");
assert_eq!(closed.to_string(), "buffer at `edge`: buffer closed");
}
#[test]
fn display_terminal_variants() {
assert_eq!(Error::Cancelled.to_string(), "pipeline cancelled");
assert_eq!(Error::Closed.to_string(), "pipeline closed");
}
#[test]
fn error_stage_extraction() {
let e = Error::Stage {
stage: StageId::new("middle"),
source: Box::new(Boom),
};
assert_eq!(e.stage(), Some(StageId::new("middle")));
assert_eq!(Error::Cancelled.stage(), None);
}
#[test]
fn error_policy_default_is_fail_fast() {
assert_eq!(ErrorPolicy::default(), ErrorPolicy::FailFast);
}
#[test]
fn stage_failure_display() {
let f = StageFailure {
stage: StageId::new("parse"),
source: Box::new(Boom),
};
assert_eq!(format!("{f}"), "stage `parse`: boom");
}
}