Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

SSL lib.rs

  Sprache: Rust
 

//! Allows non-fatal errors in a tree of subfunctions to easily be collected by a caller
//!
//! Provides the [`error_graph::ErrorList<E>`][ErrorList] type to hold a list of non-fatal errors
//! that occurred while a function was running.
//!
//! It has a [`subwriter()`][WriteErrorList::subwriter] method that can be passed as a parameter to
//! a subfunction and allows that subfunction to record all the non-fatal errors it encounters.
//! When the subfunction is done running, its error list will be mapped to the caller's error type
//! and added to the caller's [ErrorList] automatically.
//!
//! Since subfunctions may in-turn also use the [`subwriter()`][WriteErrorList::subwriter]
//! function on the writter given to them by their caller, this creates a tree of non-fatal errors
//! that occurred during the execution of an entire call graph.
//!
//! # Usage
//!
//! ```
//! # use error_graph::{ErrorList, WriteErrorList, strategy::{DontCare, ErrorOccurred}};
//! enum UpperError {
//!     Upper,
//!     Middle(ErrorList<MiddleError>),
//! }
//! enum MiddleError {
//!     Middle,
//!     Lower(ErrorList<LowerError>),
//! }
//! enum LowerError {
//!     Lower,
//! }
//! fn upper() {
//!     let mut errors = ErrorList::default();
//!     errors.push(UpperError::Upper);
//!     // Map the ErrorList<MiddleError> to our UpperError::Middle variant
//!     middle(errors.subwriter(UpperError::Middle));
//!     errors.push(UpperError::Upper);
//!
//!     // Some callers just don't want to know if things went wrong or not
//!     middle(DontCare);
//!
//!     // Some callers are only interested in whether an error occurred or not
//!     let mut error_occurred = ErrorOccurred::default();
//!     middle(&mut error_occurred);
//!     if error_occurred.as_bool() {
//!         errors.push(UpperError::Upper);
//!     }
//! }
//! fn middle(mut errors: impl WriteErrorList<MiddleError>) {
//!     // We can pass a sublist by mutable reference if we need to manipulate it before and after
//!     let mut sublist = errors.sublist(MiddleError::Lower);
//!     lower(&mut sublist);
//!     let num_errors = sublist.len();
//!     sublist.finish();
//!     if num_errors > 10 {
//!         errors.push(MiddleError::Middle);
//!     }
//!     // We can pass a reference directly to our error list for peer functions
//!     middle_2(&mut errors);
//! }
//! fn middle_2(mut errors: impl WriteErrorList<MiddleError>) {
//!     errors.push(MiddleError::Middle);
//! }
//! fn lower(mut errors: impl WriteErrorList<LowerError>) {
//!     errors.push(LowerError::Lower);
//! }
//! ```
//!
//! # Motivation
//!
//! In most call graphs, a function that encounters an error will early-return and pass an
//! error type to its caller. The caller will often respond by passing that error further up the
//! call stack up to its own caller (possibly after wrapping it in its own error type). That
//! continues so-on-and-so-forth until some caller finally handles the error, returns from `main`,
//! or panics. Ultimately, the result is that some interested caller will receive a linear chain of
//! errors that led to the failure.
//!
//! But, not all errors are fatal -- Sometimes, a function might be able to continue working after
//! it encounters an error and still be able to at-least-partially achieve its goals. Calling it
//! again - or calling other functions in the same API - is still permissible and may also result
//! in full or partial functionality.
//!
//! In that case, the function may still choose to return `Result::Ok`; however, that leaves the
//! function with a dilemma -- How can it report the non-fatal errors to the caller?
//!
//! 1.  **Return a tuple in its `Result::Ok` type**: that wouldn't capture the non-fatal errors in
//!     the case that a fatal error occurs, so it would also have to be added to the `Result::Err`
//!     type as well.
//!
//!     That adds a bunch of boilerplate, as the function needs to allocate the list and map it
//!     into the return type for every error return and good return. It also makes the function
//!     signature much more noisy.
//!
//! 2.  **Take a list as a mutable reference?**: Better, but now the caller has to allocate the
//!     list, and there's no way for it to opt out if it doesn't care about the non-fatal errors.
//!
//! 3.  **Maybe add an `Option` to it?** Okay, so a parameter like `errors: Option<&mut Vec<E>>`?
//!     Getting warmer, but now the child has to do a bunch of
//!     `if let Some(v) = errors { v.push(error); }` all over the place.
//!
//! And what about the caller side of it? For a simple caller, the last point isn't too bad: The
//! caller just has to allocate the list, pass `Some(&mut errors)` to the child, and check it upon
//! return.
//!
//! But often, the caller itself is keeping its own list of non-fatal errors and may also be a
//! subfunction to some other caller, and so-on-and-so-forth. In this case, we no longer have
//! a simple chain of errors, but instead we have a tree of errors -- Each level in the tree
//! contains all the non-fatal errors that occurred during execution of a function and all
//! subfunctions in its call graph.
//!
//! # Solution
//!
//! The main behavior we want is captured by the [WriteErrorList] trait in this crate. It can be
//! passed as a parameter to any function that wants to be able to report non-fatal errors to its
//! caller, and it gives the caller flexibility to decide what it wants to do with that
//! information.
//!
//! The main concrete type in this crate is [ErrorList], which stores a list of a single type of
//! error. Any time a list of errors needs to be stored in memory, this is the type to use. It will
//! usually be created by the top-level caller using [ErrorList::default], and any subfunction will
//! give an [ErrorList] of its own error type to the `map_fn` that was passed in by its caller upon
//! return.
//!
//! However, [ErrorList] should rarely be passed as a parameter to a function, as that wouldn't
//! provide the caller with the flexiblity to decide what strategy it actually wants
//! to use when collecting its subfunction's non-fatal errors. The caller may want to pass direct
//! reference to its own error list, it may want to pass a [Sublist] type that automatically
//! pushes the subfunction's error list to its own error list after mapping, or it may want to
//! pass the [DontCare] type if it doesn't want to know anything about the
//! subfunction's non-fatal errors.
//!
//! Instead, subfunctions should take `impl WriteErrorList<E>` as a parameter.
//! This allows any of those types above, as well as mutable references to those types, to be
//! passed in by the caller. This also allows future caller strategies to be implemented, like
//! a caller that only cares how many non-fatal errors occurred but doesn't care about the details.
//!
//! # Serde
//!
//! (This section only applies if the `serde` feature is enabled)
//!
//! [ErrorList] implements the `Serialize` trait if the errors it contains do, and
//! likewise with the `Deserialize` trait. This means that if every error type in the tree
//! implements these traits then the entire tree can be sent over the wire and recreated elsewhere.
//! Very useful if the errors are to be examined remotely!

use {
    std::{
        error::Error,
        fmt::{self, Debug, Display, Formatter},
    },
    strategy::*,
};

#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

pub mod strategy;

/// Types that are capable of having errors and sublists of errors pushed onto them
///
/// This is the main trait that allows a function to record a list of non-fatal errors it
/// encounters during its execution. Generally, the [WriteErrorList::push] method will be used when
/// such an error occurs to record the error and any relevant information.
///
/// Often, a function will want to call a subfunction and add any non-fatal errors encountered
/// to its own list of errors. There are 2 strategies it could use:
///
/// 1.  **Let the subfunction directly push onto its error list** For functions that are at the same
///     level of abstraction and use the same error type, it might make the most sense for them
///     to just share an error list. In this case, simply pass a mutable reference to the error
///     list. For any type that implements this trait, a mutable reference to it implements the
///     trait too. This allows a single function to be a composition of a bunch of functions that
///     each share a single flat error list.
///
/// 2.  **Map the subfunction's error list to the caller** For a subfunction that is at a different
///     level of abstraction than the caller and uses its own error type, this makes the most sense;
///     consume the subfunction's entire error list and store it as a single error of the
///     caller's higher-level error type. Of course, those subfunctions may implement this same
///     strategy for subfunctions they call, creating a hierarchy of errors.
///
///     In this case, call the [WriteErrorList::subwriter()] function as a parameter to
///     the subfunction. If you need to manipulate the list after the subfunction has returned,
///     instead call [WriteErrorList::sublist] and pass a mutable reference as a parameter.
///
/// Function parameters should always prefer to take an object by this trait, and should rarely
/// take parameters as concrete types like [ErrorList] or [Sublist].
/// Doing so would prevent callers from being able to decide what strategy they want to use
/// to merge the subfunction's errors with its own, and would also prevent them from using the
/// [DontCare] call if they want to opt out of receiving non-fatal error information.
///
/// Passing by this trait may also help prevent logic errors: Directly passing a [Sublist] allows
/// the subfunction to query the contents of the list it's passed. Functions may incorrectly rely on
/// the fact that they are always passed an empty list, and will suddenly break if that assumption
/// doesn't hold.
pub trait WriteErrorList<E>: Sized + private::Sealed<E> {
    /// Add an error to the list of errors
    fn push(&mut self, error: E);
    /// Create a new mapping error writer with this as its parent
    ///
    /// Creates a error writer for use by a subfunction. When the subfunction is finished,
    /// either by explicitly calling [WriteErrorList::finish] or by letting it drop, the list
    /// of errors it has written using [WriteErrorList::push] will be passed as an
    /// [`ErrorList<SubErr>`][ErrorList] to the given `map_fn`, which is expected to map it to
    /// our error type, `E`.
    ///
    /// Use of this function should always be preferred to [WriteErrorList::sublist] when the
    /// caller does not need to inspect or manipulate the list returned by the subfunction and
    /// simply wants to pass it upward to its own caller, as this function will pass forward
    /// alternate strategies for collecting the errors, like [DontCare] (which turns
    /// [WriteErrorList::push] into a no-op). In constrast, [WriteErrorList::sublist] actually
    /// materializes a list that will collect all the errors of all the lists below it, even
    /// if the caller above it passed in a [DontCare].
    fn subwriter<'sub, SubMapFn, SubErr: 'sub>(
        &'sub mut self,
        map_fn: SubMapFn,
    ) -> impl WriteErrorList<SubErr> + 'sub
    where
        SubMapFn: FnOnce(ErrorList<SubErr>) -> E + 'sub;
    /// Start a new error list with this error list as its parent
    ///
    /// This works in a very similar manner to [WriteErrorList::subwriter], but it materializes
    /// an actual concrete [Sublist] type. This function
    /// should only be used if the function needs to be able to inspect or manipulate the errors
    /// returned by the subfunction, as it always collects all errors written by the subfunction's
    /// call graph. Otherwise, [WriteErrorList::subwriter] should be used.
    fn sublist<SubMapFn, SubErr>(
        &mut self,
        map_fn: SubMapFn,
    ) -> Sublist<'_, SubErr, SubMapFn, Self, E>
    where
        SubMapFn: FnOnce(ErrorList<SubErr>) -> E,
    {
        Sublist::new(map_fn, self)
    }
    /// Finish this error list
    ///
    /// This doesn't normally need to be called, as the [Drop] implementation will take care of
    /// all the details of cleaning up and ensuring that sublists are mapped up to their parent.
    ///
    /// This is mostly useful when a caller maintains a binding to a subfunction's error list
    /// and passes it by mutable reference instead of by value. Before the caller can continue
    /// to use its own error list, the sublist must release its exclusive reference.
    ///
    /// This function simply calls [drop()], but it's just a bit more clear about the intent.
    fn finish(self) {
        drop(self)
    }
}

impl<E, T: WriteErrorList<E>> private::Sealed<E> for &mut T {}

impl<E, T: WriteErrorList<E>> WriteErrorList<E> for &mut T {
    fn push(&mut self, error: E) {
        WriteErrorList::push(*self, error)
    }
    fn subwriter<'sub, SubMapFn, SubErr: 'sub>(
        &'sub mut self,
        map_fn: SubMapFn,
    ) -> impl WriteErrorList<SubErr> + 'sub
    where
        SubMapFn: FnOnce(ErrorList<SubErr>) -> E + 'sub,
    {
        WriteErrorList::subwriter(*self, map_fn)
    }
}

/// The main type that holds a list of errors.
///
/// See the module-level docs and the docs for [WriteErrorList].
#[derive(Debug, Eq, Hash, PartialEq)]
pub struct ErrorList<E> {
    errors: Vec<E>,
}

#[cfg(feature = "serde")]
impl<E: Serialize> Serialize for ErrorList<E> {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        Serialize::serialize(&self.errors, serializer)
    }
}

#[cfg(feature = "serde")]
impl<'de, E: Deserialize<'de>> Deserialize<'de> for ErrorList<E> {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        Ok(ErrorList {
            errors: Deserialize::deserialize(deserializer)?,
        })
    }
}

impl<E> ErrorList<E> {
    /// Returns whether the error list is empty
    pub fn is_empty(&self) -> bool {
        self.errors.is_empty()
    }
    /// Return the length of the error list
    pub fn len(&self) -> usize {
        self.errors.len()
    }
    /// Iterate the error list, returning immutable references
    pub fn iter<'a>(&'self) -> impl Iterator<Item = &'a E>
    where
        E: 'a,
    {
        self.errors.iter()
    }
    /// Iterate the error list, returning mutable references
    pub fn iter_mut<'a>(&'mut self) -> impl Iterator<Item = &'a mut E>
    where
        E: 'a,
    {
        self.errors.iter_mut()
    }
}

impl<E> private::Sealed<E> for ErrorList<E> {}

impl<E> WriteErrorList<E> for ErrorList<E> {
    fn push(&mut self, error: E) {
        self.errors.push(error);
    }
    fn subwriter<'sub, SubMapFn, SubErr: 'sub>(
        &'sub mut self,
        map_fn: SubMapFn,
    ) -> impl WriteErrorList<SubErr> + 'sub
    where
        SubMapFn: FnOnce(ErrorList<SubErr>) -> E + 'sub,
    {
        self.sublist(map_fn)
    }
}

impl<E> Default for ErrorList<E> {
    fn default() -> Self {
        Self { errors: Vec::new() }
    }
}

impl<E: Error> Display for ErrorList<E> {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        writeln!(f, "one or more errors occurred:")?;
        writeln!(f)?;
        for (i, e) in self.errors.iter().enumerate() {
            writeln!(f, "  {i}:")?;

            for line in e.to_string().lines() {
                writeln!(f, "    {line}")?;
            }

            writeln!(f)?;

            let mut source = e.source();
            while let Some(e) = source {
                writeln!(f, "    caused by:")?;

                for line in e.to_string().lines() {
                    writeln!(f, "      {line}")?;
                }

                writeln!(f)?;

                source = e.source();
            }
        }
        Ok(())
    }
}

impl<E: Error> Error for ErrorList<E> {}

impl<E> IntoIterator for ErrorList<E> {
    type Item = <Vec<E> as IntoIterator>::Item;
    type IntoIter = <Vec<E> as IntoIterator>::IntoIter;
    fn into_iter(self) -> Self::IntoIter {
        self.errors.into_iter()
    }
}

mod private {
    /// Prevent users of this crate from implementing traits for their own types
    pub trait Sealed<E> {}
}

Messung V0.5 in Prozent
C=44 H=99 G=76

¤ Dauer der Verarbeitung: 0.4 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Statistik
#Sources=141584
#Domains=752002