use std::fmt::{Debug, Display};
use super::naming::{ElementName, PluginName};
#[derive(Debug)]
pub(crate) struct ElementErrorContext(ElementName);
#[derive(Debug)]
pub(crate) struct PluginErrorContext(PluginName);
#[derive(thiserror::Error)]
pub struct PipelineError(#[source] anyhow::Error);
impl Display for ElementErrorContext {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("error in ")?;
Display::fmt(&self.0, f)
}
}
impl Display for PluginErrorContext {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("error in action requested by plugin ")?;
Display::fmt(&self.0 .0, f)
}
}
impl PipelineError {
pub(crate) fn for_element(element: impl Into<ElementName>, error: anyhow::Error) -> Self {
Self(error.context(ElementErrorContext(element.into())))
}
pub(crate) fn for_plugin(plugin: PluginName, error: anyhow::Error) -> Self {
Self(error.context(PluginErrorContext(plugin)))
}
pub(crate) fn internal(error: anyhow::Error) -> Self {
Self(error)
}
pub fn element(&self) -> Option<&ElementName> {
let maybe_ctx = self.0.downcast_ref::<ElementErrorContext>();
match maybe_ctx {
Some(ctx) => Some(&ctx.0),
None => {
for parent in self.0.chain() {
if let Some(p) = parent.downcast_ref::<PipelineError>() {
if let Some(ctx) = p.0.downcast_ref::<ElementErrorContext>() {
return Some(&ctx.0);
}
}
}
None
}
}
}
pub fn is_element(&self) -> bool {
self.element().is_some()
}
pub fn is_internal(&self) -> bool {
self.element().is_none()
}
}
impl From<anyhow::Error> for PipelineError {
fn from(value: anyhow::Error) -> Self {
Self(value.context("error in pipeline"))
}
}
impl Display for PipelineError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Display::fmt(&self.0, f)
}
}
impl Debug for PipelineError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Debug::fmt(&self.0, f)
}
}
#[cfg(test)]
mod tests {
use std::error::Error;
use anyhow::anyhow;
use crate::pipeline::{
error::ElementErrorContext,
naming::{ElementKind, ElementName},
};
use super::PipelineError;
#[test]
fn check_types() {
fn assert_is_error<T: std::error::Error>() {}
assert_is_error::<PipelineError>();
}
#[test]
fn downcasting() {
let name = ElementName {
kind: ElementKind::Source,
plugin: String::from("plugin"),
element: String::from("source-1"),
};
let err = PipelineError(anyhow!("abcd"));
assert_eq!(err.element(), None);
let err = PipelineError(anyhow!("abcd").context(ElementErrorContext(name.clone())));
assert_eq!(err.element(), Some(&name));
let nested = err.0.context("more context");
let nested = PipelineError::from(nested);
println!("nested error: {:#}", nested);
println!("nested error source: {:?}", nested.source());
assert_eq!(nested.element(), Some(&name));
let nested = nested.0.context("more context");
let nested = PipelineError::from(nested);
println!("nested2 error: {:#}", nested);
println!("nested2 error source: {:?}", nested.source());
assert_eq!(nested.element(), Some(&name));
let wrapped = anyhow::Error::new(nested);
let wrapped = PipelineError::from(wrapped);
println!("wrapped error: {:#}", wrapped);
println!("wrapped error source: {:#}", wrapped.0.source().unwrap());
assert_eq!(wrapped.element(), Some(&name));
}
}