[go: up one dir, main page]

garde 0.14.0

Validation library
Documentation
//! Error types used by `garde`.
//!
//! Even though these are primarily meant for usage in derive macros, care was taken to maintain
//! composability of the various error constructors.
//!
//! The entrypoint of this module is the [`Errors`] type.
//!
//! An important highlight is the [`Errors::flatten`] function, which may be used to print readable errors.

use std::borrow::Cow;
use std::collections::BTreeMap;

/// This type encapsulates a single validation error.
#[derive(Clone, Debug)]
pub struct Error {
    /// The error message.
    ///
    /// Care should be taken to ensure this error message makes sense in the following context:
    /// ```text,ignore
    /// field_name: {message}
    /// ```
    pub message: Cow<'static, str>,
}

impl Error {
    /// Create a simple error from an error message.
    pub fn new(message: impl Into<Cow<'static, str>>) -> Self {
        Self {
            message: message.into(),
        }
    }
}

impl std::fmt::Display for Error {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.message)
    }
}

impl std::error::Error for Error {}

/// This type encapsulates a set of (potentially nested) validation errors.
#[derive(Clone, Debug)]
pub enum Errors {
    /// Errors attached directly to a field.
    ///
    /// For example, `#[garde(length(min=1, max=100))]` on a `T: Length` field.
    Simple(Vec<Error>),
    /// Errors which are attached both to a field and its inner value.
    ///
    /// For example, `#[garde(length(min=1, max=100), dive)]` on a `Vec<T: Validate>` field.
    Nested {
        outer: Box<Errors>,
        inner: Box<Errors>,
    },
    /// A list of errors.
    ///
    /// For example,  `#[garde(dive)]` on a `Vec<T: Validate>` field.
    List(Vec<Errors>),
    /// A map of field names to errors.
    ///
    /// For example, `#[garde(dive)]` on a `HashMap<T: Validate>` field.
    Fields(BTreeMap<Cow<'static, str>, Errors>),
}

impl From<Result<(), Errors>> for Errors {
    fn from(value: Result<(), Errors>) -> Self {
        match value {
            Ok(()) => Errors::empty(),
            Err(errors) => errors,
        }
    }
}

impl Errors {
    /// Finish building the error.
    ///
    /// If `!self.is_empty()`, this returns `Err(self)`, otherwise this returns `Ok(())`.
    ///
    /// This exists to make converting the error into a `Result` easier
    /// in manual implementations of `Validate`.
    pub fn finish(self) -> Result<(), Errors> {
        if !self.is_empty() {
            Err(self)
        } else {
            Ok(())
        }
    }

    /// If the error is empty, returns true.
    ///
    /// - For [`Errors::Simple`] and [`Errors::List`] the inner list must be empty.
    /// - For [`Errors::Fields`] the inner map must be empty.
    /// - For [`Errors::Nested`] both the list of errors *and* the nested error must be empty.
    pub fn is_empty(&self) -> bool {
        match self {
            Errors::Simple(v) => v.is_empty(),
            Errors::List(v) => v.is_empty(),
            Errors::Fields(v) => v.is_empty(),
            Errors::Nested { outer, inner } => outer.is_empty() && inner.is_empty(),
        }
    }

    /// Recursively flattens the error, returning a list of `(path, error)`.
    ///
    /// `path` is generated by appending a suffix to the path each time the traversal function recurses:
    /// - For [`Errors::List`], it appends `[{index}]` to each item.
    /// - For [`Errors::Fields`], it appends `.{key}` for each key-value pair.
    /// - For [`Errors::Nested`], it does not append anything.
    /// - For [`Errors::Simple`], it does not append anything.
    ///
    /// For example:
    /// ```text,ignore
    /// let errors = Errors::Fields({
    ///     "a": Errors::List([
    ///         Errors::Simple([
    ///             "length is lower than 15",
    ///             "not alphanumeric"
    ///         ])
    ///     ]),
    ///     "b": Errors::Fields({
    ///         "c": Errors::Simple([
    ///             "not a valid url"
    ///         ])
    ///     })
    /// });
    ///
    /// println!("{errors}");
    /// ```
    /// Would print:
    /// ```text,ignore
    /// value.a[0]: length is lower than 15
    /// value.a[0]: not alphanumeric
    /// value.b.c: not a valid url
    /// ```
    pub fn flatten(&self) -> Vec<(String, Error)> {
        fn flatten_inner(out: &mut Vec<(String, Error)>, current_path: String, errors: &Errors) {
            match errors {
                Errors::Simple(errors) => {
                    for error in errors {
                        out.push((current_path.clone(), error.clone()));
                    }
                }
                Errors::Nested { outer, inner } => {
                    flatten_inner(out, current_path.clone(), inner);
                    flatten_inner(out, current_path, outer);
                }
                Errors::List(errors) => {
                    for (i, errors) in errors.iter().enumerate() {
                        flatten_inner(out, format!("{current_path}[{i}]"), errors);
                    }
                }
                Errors::Fields(errors) => {
                    for (key, errors) in errors.iter() {
                        flatten_inner(out, format!("{current_path}.{key}"), errors);
                    }
                }
            }
        }

        let mut errors = vec![];
        flatten_inner(&mut errors, "value".to_string(), self);
        errors
    }

    /// Creates an empty list of errors.
    ///
    /// This is used as a fallback in case there is nothing to validate (such as when a field is marked `#[garde(skip)]`).
    pub fn empty() -> Self {
        Errors::Simple(Vec::new())
    }

    /// Creates a list of [`Error`] constructed via `f`.
    pub fn simple<F>(f: F) -> Errors
    where
        F: FnMut(&mut SimpleErrorBuilder),
    {
        SimpleErrorBuilder::dive(f)
    }

    /// Creates a nested [`Errors`] from a list of simple errors constructed via `outer`, and an arbitrary `inner` error.
    pub fn nested(outer: Errors, inner: Errors) -> Errors {
        Errors::Nested {
            outer: Box::new(outer),
            inner: Box::new(inner),
        }
    }

    /// Creates a list of [`Errors`] constructed via `f`.
    pub fn list<F>(f: F) -> Errors
    where
        F: FnMut(&mut ListErrorBuilder),
    {
        ListErrorBuilder::dive(f)
    }

    /// Creates a map of field names to [`Errors`] constructed via `f`.
    pub fn fields<F>(f: F) -> Errors
    where
        F: FnMut(&mut FieldsErrorBuilder),
    {
        FieldsErrorBuilder::dive(f)
    }
}

// TODO: remove rename, change rules to not require field_name

pub struct SimpleErrorBuilder {
    inner: Vec<Error>,
}

impl SimpleErrorBuilder {
    fn dive<F>(mut f: F) -> Errors
    where
        F: FnMut(&mut SimpleErrorBuilder),
    {
        let mut builder = SimpleErrorBuilder { inner: Vec::new() };
        f(&mut builder);
        Errors::Simple(builder.inner)
    }

    pub fn push(&mut self, error: Error) {
        self.inner.push(error);
    }
}

pub struct ListErrorBuilder {
    inner: Vec<Errors>,
}

impl ListErrorBuilder {
    fn dive<F>(mut f: F) -> Errors
    where
        F: FnMut(&mut ListErrorBuilder),
    {
        let mut builder = ListErrorBuilder { inner: Vec::new() };
        f(&mut builder);
        Errors::List(builder.inner)
    }

    pub fn push(&mut self, entry: impl Into<Errors>) {
        let entry = entry.into();

        if entry.is_empty() {
            return;
        }

        self.inner.push(entry);
    }
}

pub struct FieldsErrorBuilder {
    inner: BTreeMap<Cow<'static, str>, Errors>,
}

impl FieldsErrorBuilder {
    fn dive<F>(mut f: F) -> Errors
    where
        F: FnMut(&mut FieldsErrorBuilder),
    {
        let mut builder = FieldsErrorBuilder {
            inner: BTreeMap::new(),
        };
        f(&mut builder);
        Errors::Fields(builder.inner)
    }

    pub fn insert(&mut self, field: impl Into<Cow<'static, str>>, entry: impl Into<Errors>) {
        let entry = entry.into();

        if entry.is_empty() {
            return;
        }

        let existing = self.inner.insert(field.into(), entry);
        assert!(
            existing.is_none(),
            "each field should only be dived into once"
        )
    }
}

impl std::fmt::Display for Errors {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let errors = self.flatten();
        let mut iter = errors.iter().peekable();
        while let Some((path, error)) = iter.next() {
            write!(f, "{path}: {error}")?;
            if iter.peek().is_some() {
                writeln!(f)?;
            }
        }
        Ok(())
    }
}

impl std::error::Error for Errors {}