use core::{
error::Error,
fmt::{self, Debug, Display, Formatter},
marker::PhantomData,
};
use derive_where::derive_where;
use crate::Format;
mod format;
pub use crate::with_context::format::{Colon, ContextField, ErrorField, WithContextColon};
#[cfg(feature = "std")]
pub use crate::with_context::format::{ContextPath, PathColon, WithContextPathColon};
#[cfg(feature = "std")]
pub type WithPath<C, E> = WithContext<C, E, PathColon>;
#[derive_where(Clone, Copy, PartialEq, Eq, Hash, Debug; C, E)]
pub struct WithContext<C, E, WithContextFormat = Colon> {
pub context: C,
pub error: E,
#[derive_where(skip(Debug))]
_format: PhantomData<fn() -> WithContextFormat>,
}
impl<C, E, WithContextFormat> WithContext<C, E, WithContextFormat> {
pub const fn new(context: C, error: E) -> Self {
Self {
context,
error,
_format: PhantomData,
}
}
pub fn with_format<NewSelfFormat>(self) -> WithContext<C, E, NewSelfFormat>
where
NewSelfFormat: Format<WithContext<C, E, NewSelfFormat>>,
{
WithContext {
context: self.context,
error: self.error,
_format: PhantomData,
}
}
}
impl<C, E, WithContextFormat> From<(C, E)> for WithContext<C, E, WithContextFormat> {
fn from((context, error): (C, E)) -> Self {
Self::new(context, error)
}
}
impl<C, E, WithContextFormat> Display for WithContext<C, E, WithContextFormat>
where
WithContextFormat: Format<Self>,
{
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
WithContextFormat::fmt(self, f)
}
}
impl<C, E, WithContextFormat> Error for WithContext<C, E, WithContextFormat>
where
C: Debug,
E: Error + 'static,
WithContextFormat: Format<Self>,
{
fn source(&self) -> Option<&(dyn Error + 'static)> {
self.error.source()
}
}
#[cfg(test)]
mod tests {
use std::io;
use thiserror::Error;
use super::*;
use crate::{
FormatError,
tests::{Bracketed, Inner, Mid, WcArrow},
};
#[derive(Error, Debug)]
#[error("an error happened")]
pub struct PropError(#[from] WithContext<&'static str, Mid, Bracketed>);
fn returning_middle() -> Result<(), Mid> {
Err(Mid::Inner(Inner::A))
}
fn returning_error() -> Result<(), PropError> {
returning_middle().map_err(|e| WithContext::new("context", e))?;
Ok(())
}
#[test]
fn test_new_and_fields() {
let w = WithContextColon::new("ctx", Inner::A);
assert_eq!(w.context, "ctx");
}
#[test]
fn test_from_tuple() {
let w: WithContextColon<&str, Inner> = ("ctx", Inner::A).into();
assert_eq!(w.context, "ctx");
}
#[test]
fn test_display_default_format() {
let w = WithContextColon::new("step 3", Inner::A);
assert_eq!(w.to_string(), "step 3: InnerA");
}
#[test]
fn test_source_skips_inner_error() {
let w = WithContextColon::new("ctx", Inner::A);
assert!(w.source().is_none());
let w = WithContextColon::new("ctx", Mid::Inner(Inner::A));
let src = w.source().expect("source must be Some");
assert_eq!(src.to_string(), "InnerA");
}
#[test]
fn test_one_line_walks_full_chain() {
let w = WithContextColon::new("ctx", Mid::Inner(Inner::A));
assert_eq!(w.one_line().to_string(), "ctx: mid: InnerA");
}
#[test]
fn test_io_error_chain() {
let io = io::Error::new(io::ErrorKind::NotFound, "file missing");
let w = WithContextColon::new("config", io);
assert_eq!(w.one_line().to_string(), "config: file missing");
}
#[test]
fn test_custom_format_strategy() {
let w = WithContext::<_, _, WcArrow>::new("step", Inner::A);
assert_eq!(w.to_string(), "step -> InnerA");
}
#[test]
fn test_custom_format_affects_one_line() {
let w = WithContext::<_, _, Bracketed>::new("ctx", Mid::Inner(Inner::A));
assert_eq!(w.one_line().to_string(), "[ctx] mid: InnerA");
}
#[test]
fn test_propagation_via_question_mark() {
let err = returning_error().expect_err("returning_error must error");
assert_eq!(err.to_string(), "an error happened");
assert_eq!(
err.one_line().to_string(),
"an error happened: [context] mid: InnerA",
);
}
}