use core::{
error::Error,
fmt::{self, Debug, Display, Formatter},
marker::PhantomData,
};
use crate::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(Clone, Copy, PartialEq, Eq, Hash)]
pub struct WithContext<C, E, F = Colon> {
pub context: C,
pub error: E,
_format: PhantomData<fn() -> F>,
}
impl<C, E, F> WithContext<C, E, F> {
pub const fn new(context: C, error: E) -> Self {
Self {
context,
error,
_format: PhantomData,
}
}
pub fn with_format<G>(self) -> WithContext<C, E, G>
where
G: Format<WithContext<C, E, G>>,
{
WithContext {
context: self.context,
error: self.error,
_format: PhantomData,
}
}
}
impl<C, E, F> From<(C, E)> for WithContext<C, E, F> {
fn from((context, error): (C, E)) -> Self {
Self::new(context, error)
}
}
impl<C, E, F> Display for WithContext<C, E, F>
where
F: Format<Self>,
{
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
F::fmt(self, f)
}
}
impl<C: Debug, E: Debug, F> Debug for WithContext<C, E, F> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("WithContext")
.field("context", &self.context)
.field("error", &self.error)
.finish()
}
}
impl<C, E, F> Error for WithContext<C, E, F>
where
C: Debug,
E: Error + 'static,
F: Format<Self>,
{
fn source(&self) -> Option<&(dyn Error + 'static)> {
self.error.source()
}
}
mod format {
use core::fmt::{self, Display, Formatter};
#[cfg(feature = "std")]
use std::path::Path;
#[allow(unused_imports)] use crate::add::separator::{ColonSpace, WithSep};
use crate::{Format, add::separator::WithColonSpace};
use super::WithContext;
pub type WithContextColon<C, E> = WithContext<C, E, Colon>;
#[cfg(feature = "std")]
pub type WithContextPathColon<C, E> = WithContext<C, E, PathColon>;
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct ContextField;
impl<C: Display, E, F> Format<WithContext<C, E, F>> for ContextField {
fn fmt(w: &WithContext<C, E, F>, f: &mut Formatter<'_>) -> fmt::Result {
Display::fmt(&w.context, f)
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct ErrorField;
impl<C, E: Display, F> Format<WithContext<C, E, F>> for ErrorField {
fn fmt(w: &WithContext<C, E, F>, f: &mut Formatter<'_>) -> fmt::Result {
Display::fmt(&w.error, f)
}
}
#[cfg(feature = "std")]
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct ContextPath;
#[cfg(feature = "std")]
impl<P: AsRef<Path>, E, F> Format<WithContext<P, E, F>> for ContextPath {
fn fmt(w: &WithContext<P, E, F>, f: &mut Formatter<'_>) -> fmt::Result {
w.context.as_ref().display().fmt(f)
}
}
pub type Colon = WithColonSpace<ContextField, ErrorField>;
#[cfg(feature = "std")]
pub type PathColon = WithColonSpace<ContextPath, ErrorField>;
}
#[cfg(test)]
mod tests {
use std::{error::Error as _, io};
use thiserror::Error;
use super::*;
use crate::FormatError;
#[derive(Error, Debug)]
#[error("leaf error")]
struct Leaf;
#[derive(Error, Debug)]
#[error("middle")]
struct Middle(#[source] Leaf);
struct Bracketed;
impl<C: Display, E: Display, F> Format<WithContext<C, E, F>> for Bracketed {
fn fmt(w: &WithContext<C, E, F>, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "[{}] {}", w.context, w.error)
}
}
#[derive(Error, Debug)]
#[error("an error happened")]
pub struct Error(#[from] WithContext<&'static str, Middle, Bracketed>);
fn returning_middle() -> Result<(), Middle> {
Err(Middle(Leaf))
}
fn returning_error() -> Result<(), Error> {
returning_middle().map_err(|e| WithContext::new("context", e))?;
Ok(())
}
#[test]
fn test_new_and_fields() {
let w = WithContextColon::new("ctx", Leaf);
assert_eq!(w.context, "ctx");
}
#[test]
fn test_from_tuple() {
let w: WithContextColon<&str, Leaf> = ("ctx", Leaf).into();
assert_eq!(w.context, "ctx");
}
#[test]
fn test_display_default_format() {
let w = WithContextColon::new("step 3", Leaf);
assert_eq!(w.to_string(), "step 3: leaf error");
}
#[test]
fn test_source_skips_inner_error() {
let w = WithContextColon::new("ctx", Leaf);
assert!(w.source().is_none());
let w = WithContextColon::new("ctx", Middle(Leaf));
let src = w.source().expect("source must be Some");
assert_eq!(src.to_string(), "leaf error");
}
#[test]
fn test_one_line_walks_full_chain() {
let w = WithContextColon::new("ctx", Middle(Leaf));
assert_eq!(w.one_line().to_string(), "ctx: middle: leaf error");
}
#[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() {
struct Arrow;
impl<C: Display, E: Display, F> Format<WithContext<C, E, F>> for Arrow {
fn fmt(w: &WithContext<C, E, F>, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{} -> {}", w.context, w.error)
}
}
let w = WithContext::<_, _, Arrow>::new("step", Leaf);
assert_eq!(w.to_string(), "step -> leaf error");
}
#[test]
fn test_custom_format_affects_one_line() {
let w = WithContext::<_, _, Bracketed>::new("ctx", Middle(Leaf));
assert_eq!(w.one_line().to_string(), "[ctx] middle: leaf error");
}
#[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] middle: leaf error",
);
}
#[cfg(feature = "std")]
#[test]
fn test_path_colon_strategy() {
use std::path::{Path, PathBuf};
let io_err = io::Error::new(io::ErrorKind::NotFound, "file missing");
let w = WithContext::<_, _, PathColon>::new(PathBuf::from("a/b/c.txt"), io_err);
assert_eq!(w.to_string(), "a/b/c.txt: file missing");
let io_err = io::Error::new(io::ErrorKind::NotFound, "file missing");
let path: &Path = Path::new("a/b/c.txt");
let w = WithContext::<_, _, PathColon>::new(path, io_err);
assert_eq!(w.to_string(), "a/b/c.txt: file missing");
}
#[test]
fn test_composed_separator() {
use crate::separator::WithSpace;
type SpacePair = WithSpace<ContextField, ErrorField>;
let w = WithContext::<_, _, SpacePair>::new("ctx", Leaf);
assert_eq!(w.to_string(), "ctx leaf error");
}
}