yaml-validator 0.2.0

A library for validating YAML against YAML-defined schemas
Documentation
use thiserror::Error;

use crate::breadcrumb::{Breadcrumb, BreadcrumbSegment, BreadcrumbSegmentVec};

use super::GenericError;

#[derive(Error, Debug, PartialEq, Eq)]
pub enum ValidationErrorKind<'a> {
    #[error("wrong type, expected {expected} got {actual}")]
    WrongType {
        expected: &'static str,
        actual: &'a str,
    },
    #[error("special requirements for field not met: {error}")]
    ValidationError { error: &'a str },
    #[error("field '{field}' missing")]
    FieldMissing { field: &'a str },
    #[error("field '{field}' is not specified in the schema")]
    ExtraField { field: &'a str },
    #[error("unknown type specified: {unknown_type}")]
    UnknownType { unknown_type: &'a str },
    #[error("multiple errors were encountered: {errors:?}")]
    Multiple { errors: Vec<ValidationError<'a>> },
    #[error("schema '{uri}' references was not found")]
    UnknownSchema { uri: &'a str },
}

impl<'a> ValidationErrorKind<'a> {
    pub fn with_path(self, path: BreadcrumbSegmentVec<'a>) -> ValidationError<'a> {
        ValidationError {
            kind: self,
            state: Breadcrumb::new(path),
        }
    }

    pub fn with_path_name(self, path: &'a str) -> ValidationError<'a> {
        let mut err: ValidationError = self.into();
        err.state.push(BreadcrumbSegment::Name(path));
        err
    }

    pub fn with_path_index(self, index: usize) -> ValidationError<'a> {
        let mut err: ValidationError = self.into();
        err.state.push(BreadcrumbSegment::Index(index));
        err
    }
}

impl<'a> From<ValidationErrorKind<'a>> for ValidationError<'a> {
    fn from(kind: ValidationErrorKind<'a>) -> ValidationError<'a> {
        ValidationError {
            kind,
            state: Breadcrumb::default(),
        }
    }
}

impl<'a> From<Vec<ValidationError<'a>>> for ValidationError<'a> {
    fn from(errors: Vec<ValidationError<'a>>) -> Self {
        ValidationErrorKind::Multiple { errors }.into()
    }
}

impl<'a> From<GenericError<'a>> for ValidationErrorKind<'a> {
    fn from(e: GenericError<'a>) -> Self {
        match e {
            GenericError::WrongType { expected, actual } => {
                ValidationErrorKind::WrongType { expected, actual }
            }
            GenericError::FieldMissing { field } => ValidationErrorKind::FieldMissing { field },
            GenericError::ExtraField { field } => ValidationErrorKind::ExtraField { field },
            GenericError::Multiple { errors } => ValidationErrorKind::Multiple {
                errors: errors
                    .into_iter()
                    .map(ValidationErrorKind::from)
                    .map(ValidationError::from)
                    .collect(),
            },
        }
    }
}

impl<'a> From<GenericError<'a>> for ValidationError<'a> {
    fn from(e: GenericError<'a>) -> Self {
        ValidationErrorKind::from(e).into()
    }
}

#[derive(Debug, PartialEq, Eq)]
pub struct ValidationError<'a> {
    pub kind: ValidationErrorKind<'a>,
    pub state: Breadcrumb<'a>,
}

impl<'a> ValidationError<'a> {
    fn flatten(&self, fmt: &mut std::fmt::Formatter<'_>, root: String) -> std::fmt::Result {
        match &self.kind {
            ValidationErrorKind::Multiple { errors } => {
                for err in errors {
                    err.flatten(fmt, format!("{}{}", root, self.state))?;
                }
            }
            err => writeln!(fmt, "{}{}: {}", root, self.state, err)?,
        }

        Ok(())
    }

    pub fn add_path_name(path: &'a str) -> impl Fn(ValidationError<'a>) -> ValidationError<'a> {
        move |mut err: ValidationError<'a>| -> ValidationError<'a> {
            err.state.push(BreadcrumbSegment::Name(path));
            err
        }
    }

    pub fn add_path_index(index: usize) -> impl Fn(ValidationError<'a>) -> ValidationError<'a> {
        move |mut err: ValidationError<'a>| -> ValidationError<'a> {
            err.state.push(BreadcrumbSegment::Index(index));
            err
        }
    }
}

impl<'a> std::fmt::Display for ValidationError<'a> {
    fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.flatten(fmt, "#".to_string())
    }
}