use core::fmt::{self, Debug, Display};
use alloc::{
boxed::Box,
string::{String, ToString},
vec::Vec,
};
use crate::{
err,
error::{self, Location},
Error, StashedResult,
};
pub trait ErrorSink<E, I>
where
E: Into<I>,
{
fn stash(&mut self, error: E) -> &mut StashWithErrors<I>;
}
pub trait ErrorSource<I> {
fn errors(&self) -> &[I];
}
pub trait EnforceErrors<I> {
fn enforce_errors(&mut self) -> &mut StashWithErrors<I>;
}
#[cfg_attr(
feature = "eyre",
doc = r##"
There's also [`IntoEyreResult`](crate::IntoEyreResult)
which performs a (lossy) conversion to
[`eyre::Result`](eyre::Result).
"##
)]
pub enum ErrorStash<F, M, I>
where
F: FnOnce() -> M,
M: Display,
{
Empty(F),
WithErrors(StashWithErrors<I>),
}
#[cfg_attr(
feature = "eyre",
doc = r##"
There's also [`IntoEyreReport`](crate::IntoEyreReport)
which performs a (lossy) conversion to
[`eyre::Report`](eyre::Report).
"##
)]
#[derive(Debug)]
pub struct StashWithErrors<I> {
summary: Box<str>,
errors: Vec<I>,
locations: Vec<Location>,
}
impl<F, M, I> Debug for ErrorStash<F, M, I>
where
F: FnOnce() -> M,
M: Display,
I: Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Empty(_) => write!(f, "ErrorStash(Empty)"),
Self::WithErrors(errs) => {
write!(f, "ErrorStash(")?;
Debug::fmt(errs, f)?;
write!(f, ")")?;
Ok(())
}
}
}
}
impl<F, M, I> Display for ErrorStash<F, M, I>
where
F: FnOnce() -> M,
M: Display,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Empty(_) => display::<I>(f, &[]),
Self::WithErrors(errs) => Display::fmt(errs, f),
}
}
}
impl<I> Display for StashWithErrors<I> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
display(f, self.errors())
}
}
impl<F, M, I> ErrorSource<I> for ErrorStash<F, M, I>
where
F: FnOnce() -> M,
M: Display,
{
fn errors(&self) -> &[I] {
self.errors()
}
}
impl<I> ErrorSource<I> for StashWithErrors<I> {
fn errors(&self) -> &[I] {
self.errors()
}
}
impl<E, F, M, I> ErrorSink<E, I> for ErrorStash<F, M, I>
where
E: Into<I>,
F: FnOnce() -> M,
M: Display,
{
#[track_caller]
fn stash(&mut self, err: E) -> &mut StashWithErrors<I> {
self.push(err)
}
}
impl<E, I> ErrorSink<E, I> for StashWithErrors<I>
where
E: Into<I>,
{
#[track_caller]
fn stash(&mut self, err: E) -> &mut StashWithErrors<I> {
self.push(err)
}
}
impl<F, M, I> EnforceErrors<I> for ErrorStash<F, M, I>
where
F: FnOnce() -> M,
M: Display,
Error<I>: Into<I>,
{
#[track_caller]
fn enforce_errors(&mut self) -> &mut StashWithErrors<I> {
match self {
ErrorStash::Empty(_) => self.stash(err!("INTERNAL ERROR")),
ErrorStash::WithErrors(stash) => stash,
}
}
}
impl<I> EnforceErrors<I> for StashWithErrors<I>
where
Error<I>: Into<I>,
{
fn enforce_errors(&mut self) -> &mut StashWithErrors<I> {
self
}
}
impl<F, M, I> From<ErrorStash<F, M, I>> for Result<(), Error<I>>
where
F: FnOnce() -> M,
M: Display,
{
fn from(stash: ErrorStash<F, M, I>) -> Self {
match stash {
ErrorStash::Empty(_) => Ok(()),
ErrorStash::WithErrors(stash) => Err(stash.into()),
}
}
}
impl<I> From<StashWithErrors<I>> for Error<I> {
fn from(stash: StashWithErrors<I>) -> Self {
Error::from_stash(stash.summary, stash.errors, stash.locations)
}
}
impl<F, M, I> ErrorStash<F, M, I>
where
F: FnOnce() -> M,
M: Display,
{
pub fn new(f: F) -> Self {
Self::Empty(f)
}
#[track_caller]
pub fn push<E>(&mut self, err: E) -> &mut StashWithErrors<I>
where
E: Into<I>,
{
let mut swap = Self::WithErrors(StashWithErrors {
summary: String::new().into_boxed_str(),
errors: vec![],
locations: vec![],
});
core::mem::swap(self, &mut swap);
*self = ErrorStash::WithErrors(swap.push_and_convert(err));
match self {
ErrorStash::Empty(_) => unreachable!(),
ErrorStash::WithErrors(stash_with_errors) => stash_with_errors,
}
}
#[track_caller]
pub fn push_and_convert<E>(self, err: E) -> StashWithErrors<I>
where
E: Into<I>,
{
match self {
ErrorStash::Empty(f) => StashWithErrors::from(f(), err),
ErrorStash::WithErrors(mut stash) => {
stash.push(err);
stash
}
}
}
pub fn is_empty(&self) -> bool {
match self {
ErrorStash::Empty(_) => true,
ErrorStash::WithErrors(_) => false,
}
}
pub fn errors(&self) -> &[I] {
match self {
ErrorStash::Empty(_) => &[],
ErrorStash::WithErrors(stash) => stash.errors(),
}
}
pub fn ok(&mut self) -> StashedResult<(), I> {
match self {
ErrorStash::Empty(_) => StashedResult::Ok(()),
ErrorStash::WithErrors(errs) => StashedResult::Err(errs),
}
}
pub fn into_result(self) -> Result<(), Error<I>> {
self.into()
}
}
impl<I> StashWithErrors<I> {
#[track_caller]
pub fn from<M, E>(summary: M, error: E) -> Self
where
M: Display,
E: Into<I>,
{
Self {
summary: summary.to_string().into(),
errors: vec![error.into()],
locations: vec![error::location()],
}
}
#[track_caller]
pub fn push<E>(&mut self, err: E) -> &mut StashWithErrors<I>
where
E: Into<I>,
{
self.errors.push(err.into());
self.locations.push(error::location());
self
}
pub fn errors(&self) -> &[I] {
&self.errors
}
#[doc(hidden)]
pub fn take(&mut self) -> Self {
const WARNING: &str = "Internal error: Error info cleared by take()";
let mut swap_with = Self {
summary: WARNING.to_string().into_boxed_str(),
errors: vec![],
locations: vec![],
};
core::mem::swap(&mut swap_with, self);
swap_with
}
}
fn display<I>(f: &mut fmt::Formatter<'_>, errors: &[I]) -> fmt::Result {
let count = errors.len();
write!(f, "Stash of {count} errors currently")
}
#[cfg(test)]
mod tests {
#[cfg(any(feature = "rust-v1.81", feature = "std"))]
use crate::prelude::*;
#[cfg(not(any(feature = "rust-v1.81", feature = "std")))]
use crate::surrogate_error_trait::prelude::*;
use crate::stash::EnforceErrors;
#[test]
fn stash_debug_fmt_when_empty() {
let errs = ErrorStash::new(|| "Mock message");
assert_eq!(format!("{errs:?}"), "ErrorStash(Empty)");
}
#[test]
fn stash_debug_fmt_with_errors() {
let mut errs = ErrorStash::new(|| "Mock message");
errs.push("First error");
errs.push(Error::from_message("Second error"));
let msg = format!("{errs:?}");
dbg!(&msg);
assert!(msg.contains("ErrorStash"));
assert!(msg.contains("StashWithErrors"));
assert!(msg.contains("First error"));
assert!(msg.contains("Second error"));
assert!(msg.contains("lazy_errors"));
assert!(msg.contains("stash.rs"));
}
#[cfg(feature = "eyre")]
#[test]
fn stash_debug_fmt_with_errors_eyre() {
let mut errs = ErrorStash::new(|| "Mock message");
errs.push(eyre::eyre!("Eyre error"));
let msg = format!("{errs:?}");
dbg!(&msg);
assert!(msg.contains("Eyre error"));
assert!(msg.contains("lazy_errors"));
assert!(msg.contains("stash.rs"));
}
#[test]
fn error_stash_enforce_errors_modifies_original_stash() {
let mut error_stash = ErrorStash::new(|| "Failure");
assert_eq!(error_stash.errors().len(), 0);
{
let stash_with_errors = error_stash.enforce_errors();
assert_eq!(stash_with_errors.errors().len(), 1);
}
assert_eq!(error_stash.errors().len(), 1);
let err = error_stash.into_result().unwrap_err();
let msg = format!("{err}");
assert_eq!("Failure: INTERNAL ERROR", &msg);
}
#[test]
fn error_stash_enforce_errors_modifies_only_once() {
let mut error_stash = ErrorStash::new(|| "Failure");
assert_eq!(error_stash.errors().len(), 0);
error_stash.enforce_errors();
assert_eq!(error_stash.errors().len(), 1);
{
let stash_with_errors = error_stash.enforce_errors();
assert_eq!(stash_with_errors.errors().len(), 1);
}
assert_eq!(error_stash.errors().len(), 1);
let err = error_stash.into_result().unwrap_err();
let msg = format!("{err}");
assert_eq!("Failure: INTERNAL ERROR", &msg);
}
#[test]
fn error_stash_enforce_errors_does_not_modify_if_nonempty() {
let mut error_stash = ErrorStash::new(|| "Failure");
assert_eq!(error_stash.errors().len(), 0);
error_stash.push("External error");
assert_eq!(error_stash.errors().len(), 1);
error_stash.enforce_errors();
assert_eq!(error_stash.errors().len(), 1);
{
let stash_with_errors = error_stash.enforce_errors();
assert_eq!(stash_with_errors.errors().len(), 1);
}
assert_eq!(error_stash.errors().len(), 1);
let err = error_stash.into_result().unwrap_err();
let msg = format!("{err}");
assert_eq!("Failure: External error", &msg);
}
#[test]
fn stash_with_errors_enforce_errors_modifies_only_once() {
let mut error_stash = ErrorStash::new(|| "Failure");
assert_eq!(error_stash.errors().len(), 0);
error_stash.enforce_errors();
assert_eq!(error_stash.errors().len(), 1);
{
let stash_with_errors = error_stash.enforce_errors();
assert_eq!(stash_with_errors.errors().len(), 1);
stash_with_errors.enforce_errors();
assert_eq!(stash_with_errors.errors().len(), 1);
}
assert_eq!(error_stash.errors().len(), 1);
}
#[test]
fn stash_with_errors_enforce_errors_does_not_modify() {
let mut swe = StashWithErrors::from("Failure", "External error");
assert_eq!(swe.errors().len(), 1);
swe.enforce_errors();
assert_eq!(swe.errors().len(), 1);
let err: Error = swe.into();
let msg = format!("{err}");
assert_eq!("Failure: External error", &msg);
}
}