use std::collections::VecDeque;
use convert_case::Case;
use convert_case::Casing;
use nonempty::NonEmpty;
use crate::concern::Code;
use crate::file::location;
pub mod failure;
pub use failure::Failure;
#[derive(Debug)]
pub enum Error {
Location(location::Error),
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::Location(err) => write!(f, "location error: {err}"),
}
}
}
impl std::error::Error for Error {}
pub type Result = std::result::Result<Option<NonEmpty<Failure>>, Error>;
#[derive(Debug)]
pub struct Validator;
impl Validator {
pub fn validate<'a, E>(tree: &'a E, rules: Vec<Box<dyn Rule<&'a E>>>) -> Result {
let mut failures = rules
.iter()
.map(|rule| rule.validate(tree))
.collect::<std::result::Result<Vec<Option<NonEmpty<Failure>>>, Error>>()?
.into_iter()
.flatten()
.flatten()
.collect::<VecDeque<Failure>>();
match failures.pop_front() {
Some(front) => {
let mut result = NonEmpty::new(front);
result.extend(failures);
Ok(Some(result))
}
None => Ok(None),
}
}
}
pub trait Rule<E>: std::fmt::Debug + Sync {
fn name(&self) -> String {
format!("{:?}", self).to_case(Case::Snake)
}
fn code(&self) -> Code;
fn validate(&self, tree: E) -> Result;
}