Skip to main content

wdl_core/concern/
validation.rs

1//! Validation.
2
3use std::collections::VecDeque;
4
5use convert_case::Case;
6use convert_case::Casing;
7use nonempty::NonEmpty;
8
9use crate::concern::Code;
10use crate::file::location;
11
12pub mod failure;
13
14pub use failure::Failure;
15
16/// An unrecoverable error that occurs during validation.
17#[derive(Debug)]
18pub enum Error {
19    /// A location error.
20    Location(location::Error),
21}
22
23impl std::fmt::Display for Error {
24    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25        match self {
26            Error::Location(err) => write!(f, "location error: {err}"),
27        }
28    }
29}
30
31impl std::error::Error for Error {}
32
33/// A [`Result`](std::result::Result) returned from a validation check.
34pub type Result = std::result::Result<Option<NonEmpty<Failure>>, Error>;
35
36/// A parse tree validator.
37#[derive(Debug)]
38pub struct Validator;
39
40impl Validator {
41    /// Validates a tree according to a set of validation rules.
42    pub fn validate<'a, E>(tree: &'a E, rules: Vec<Box<dyn Rule<&'a E>>>) -> Result {
43        let mut failures = rules
44            .iter()
45            .map(|rule| rule.validate(tree))
46            .collect::<std::result::Result<Vec<Option<NonEmpty<Failure>>>, Error>>()?
47            .into_iter()
48            .flatten()
49            .flatten()
50            .collect::<VecDeque<Failure>>();
51
52        match failures.pop_front() {
53            Some(front) => {
54                let mut result = NonEmpty::new(front);
55                result.extend(failures);
56                Ok(Some(result))
57            }
58            None => Ok(None),
59        }
60    }
61}
62
63/// A validation rule.
64pub trait Rule<E>: std::fmt::Debug + Sync {
65    /// The name of the validation rule.
66    ///
67    /// This is what will show up in style guides, it is required to be snake
68    /// case (even though the rust struct is camel case).
69    fn name(&self) -> String {
70        format!("{:?}", self).to_case(Case::Snake)
71    }
72
73    /// Get the code for this validation rule.
74    fn code(&self) -> Code;
75
76    /// Checks the tree according to the implemented validation rule.
77    fn validate(&self, tree: E) -> Result;
78}