Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error collector #164

Merged
merged 17 commits into from Mar 31, 2022
Merged
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
115 changes: 111 additions & 4 deletions core/src/error/mod.rs
@@ -1,4 +1,4 @@
//! The `darling::Error` type and its internals.
//! The `darling::Error` type, the multiple error `Collector`, and their internals.
//!
//! Error handling is one of the core values of `darling`; creating great errors is hard and
//! never the reason that a proc-macro author started writing their crate. As a result, the
Expand Down Expand Up @@ -47,9 +47,9 @@ pub type Result<T> = ::std::result::Result<T, Error>;
/// This preserves all span information, suggestions, etc. Wrapping a `darling::Error` in
/// a custom error enum works as-expected and does not force any loss of fidelity.
/// 2. Do not use early return (e.g. the `?` operator) for custom validations. Instead,
/// create a local `Vec` to collect errors as they are encountered and then use
/// `darling::Error::multiple` to create an error containing all those issues if the list
/// is non-empty after validation. This can create very complex custom validation functions;
/// create an [`error::Collector`](Collector) to collect errors as they are encountered. Then use
ijackson marked this conversation as resolved.
Show resolved Hide resolved
/// [`Collector::conclude`] to return your validated result; it will give `Ok` iff
/// no errors were encountered. This can create very complex custom validation functions;
/// in those cases, split independent "validation chains" out into their own functions to
/// keep the main validator manageable.
/// 3. Use `darling::Error::custom` to create additional errors as-needed, then call `with_span`
Expand Down Expand Up @@ -194,6 +194,8 @@ impl Error {

/// Bundle a set of multiple errors into a single `Error` instance.
///
/// Usually it will be more convenient to use an [`error::Collector`](Collector).
///
/// # Panics
/// This function will panic if `errors.is_empty() == true`.
pub fn multiple(mut errors: Vec<Error>) -> Self {
Expand All @@ -205,6 +207,13 @@ impl Error {
_ => Error::new(ErrorKind::Multiple(errors)),
}
}

/// Creates an error collector, for aggregating multiple errors
///
/// See [`Collector`] for details.
pub fn collector() -> Collector {
Default::default()
}
}

impl Error {
Expand Down Expand Up @@ -491,6 +500,104 @@ impl Iterator for IntoIter {
}
}

/// Collector for errors, for helping call [`Error::multiple`].
///
/// See the docs for [`darling::error::Error`](Error) for more discussion of error handling with darling.
///
/// ```
/// # extern crate darling_core as darling;
/// # struct Thing;
/// # struct Output;
/// # impl Thing { fn validate(self) -> darling::Result<Output> { Ok(Output) } }
/// fn validate_things(inputs: Vec<Thing>) -> darling::Result<Vec<Output>> {
/// let mut errors = darling::error::Error::collector();
/// let mut outputs = vec![];
///
/// for thing in inputs {
/// let _: Option<()> = errors.run(||{
/// let validateed = thing.validate()?;
/// outputs.push(validateed);
/// Ok(())
/// });
/// }
///
/// errors.conclude(outputs)
/// }
/// ```
#[derive(Default, Debug)]
pub struct Collector {
errors: Vec<Error>,
}

impl Collector {
/// Runs a closure, returning the successful value as `Some`, or collecting the error
///
/// The closure is one which return `Result`, so inside it one can use `?`.
pub fn run<T, F: FnOnce() -> Result<T>>(&mut self, f: F) -> Option<T> {
self.ok_of(f())
}

/// Returns a successful value as `Some`, or collects the error and returns `None`
///
/// If you need an actual value `T` for further processing even in error cases,
/// rather than a `None`,
/// use
/// [`Option::unwrap_or_default`],
/// [`unwrap_or_else`](Option::unwrap_or_else) or
/// [`unwrap_or`](Option::unwrap_or)
/// on the return value from `ok_of` or `run`.
pub fn ok_of<T>(&mut self, result: Result<T>) -> Option<T> {
match result {
Ok(y) => {
TedDriggs marked this conversation as resolved.
Show resolved Hide resolved
Some(y)
}
Err(e) => {
self.errors.push(e);
None
}
}
}

/// Bundles the collected errors if there were any, or returns the success value
///
/// Call this at the end of your input processing.
///
/// If there were no errors recorded, returns `Ok(success)`.
/// Otherwise calls [`Error::multiple`] and returns the result as an `Err`.
pub fn conclude<T>(self, success: T) -> Result<T> {
if self.errors.is_empty() {
Ok(success)
} else {
Err(Error::multiple(self.errors))
}
}

/// Bundles the collected errors if there were any, or returns the success value
ijackson marked this conversation as resolved.
Show resolved Hide resolved
pub fn into_inner(self) -> Vec<Error> {
self.errors
}

/// Add one error
pub fn push(&mut self, item: Error) {
self.errors.push(item)
}

/// Check if we have collected errors, and if so produce an aggregate error right away
pub fn checkpoint(&mut self) -> Result<()> {
std::mem::take(self).conclude(())
TedDriggs marked this conversation as resolved.
Show resolved Hide resolved
}
}

impl Extend<Error> for Collector {
fn extend<I>(&mut self, iter: I)
where
I: IntoIterator<Item = Error>
{
self.errors.extend(iter)
}
}


#[cfg(test)]
mod tests {
use super::Error;
Expand Down