yaml_schema/
validation.rs

1use log::debug;
2
3use saphyr::Marker;
4
5pub mod any_of;
6mod context;
7mod objects;
8mod one_of;
9mod strings;
10
11use crate::Result;
12use crate::Schema;
13use crate::utils::format_yaml_data;
14
15pub use context::Context;
16
17/// A trait for validating a sahpyr::Yaml value against a schema
18pub trait Validator {
19    fn validate(&self, context: &Context, value: &saphyr::MarkedYaml) -> Result<()>;
20}
21
22/// A validation error simply contains a path and an error message
23#[derive(Debug)]
24pub struct ValidationError {
25    /// The path to the value that caused the error
26    pub path: String,
27    /// The line and column of the value that caused the error
28    pub marker: Option<Marker>,
29    /// The error message
30    pub error: String,
31}
32
33/// Display these ValidationErrors as "{path}: {error}"
34impl std::fmt::Display for ValidationError {
35    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36        if let Some(marker) = &self.marker {
37            write!(
38                f,
39                "[{}:{}] .{}: {}",
40                marker.line(),
41                marker.col() + 1, // contrary to the documentation, columns are 0-indexed
42                self.path,
43                self.error
44            )
45        } else {
46            write!(f, ".{}: {}", self.path, self.error)
47        }
48    }
49}
50
51impl Validator for Schema {
52    fn validate(&self, context: &Context, value: &saphyr::MarkedYaml) -> Result<()> {
53        debug!("[Schema] self: {self}");
54        debug!(
55            "[Schema] Validating value: {}",
56            format_yaml_data(&value.data)
57        );
58        match self {
59            Schema::Empty => Ok(()),
60            Schema::BooleanLiteral(boolean) => {
61                if !*boolean {
62                    context.add_error(value, "Schema is `false`!".to_string());
63                }
64                Ok(())
65            }
66            Schema::Const(const_schema) => const_schema.validate(context, value),
67            Schema::Enum(enum_schema) => enum_schema.validate(context, value),
68            Schema::AllOf(all_of_schema) => all_of_schema.validate(context, value),
69            Schema::AnyOf(any_of_schema) => any_of_schema.validate(context, value),
70            Schema::OneOf(one_of_schema) => one_of_schema.validate(context, value),
71            Schema::Not(not_schema) => not_schema.validate(context, value),
72            Schema::Typed(typed_schema) => typed_schema.validate(context, value),
73        }
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80    use crate::YamlSchema;
81    use saphyr::LoadableYamlNode;
82
83    #[test]
84    fn test_validate_empty_schema() {
85        let schema = YamlSchema::empty();
86        let context = Context::default();
87        let docs = saphyr::MarkedYaml::load_from_str("value").unwrap();
88        let value = docs.first().unwrap();
89        let result = schema.validate(&context, value);
90        assert!(result.is_ok());
91        assert!(!context.has_errors());
92    }
93
94    #[test]
95    fn test_validate_type_null() {
96        let schema = YamlSchema::null();
97        let context = Context::default();
98        let docs = saphyr::MarkedYaml::load_from_str("value").unwrap();
99        let value = docs.first().unwrap();
100        let result = schema.validate(&context, value);
101        assert!(result.is_ok());
102        assert!(context.has_errors());
103        let errors = context.errors.borrow();
104        let error = errors.first().unwrap();
105        assert_eq!(
106            error.error,
107            "Expected null, but got: Value(String(\"value\"))"
108        );
109    }
110}