Skip to main content

yaml_schema/schemas/
enum.rs

1use log::debug;
2
3use saphyr::AnnotatedSequence;
4use saphyr::MarkedYaml;
5use saphyr::YamlData;
6
7use crate::ConstValue;
8use crate::Context;
9use crate::Result;
10use crate::Validator;
11use crate::utils::format_vec;
12use crate::utils::format_yaml_data;
13
14/// An enum schema represents a set of constant values
15#[derive(Debug, Default, PartialEq)]
16pub struct EnumSchema {
17    pub r#enum: Vec<ConstValue>,
18}
19
20impl std::fmt::Display for EnumSchema {
21    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
22        write!(f, "Enum {{ enum: {} }}", format_vec(&self.r#enum))
23    }
24}
25
26impl TryFrom<&MarkedYaml<'_>> for EnumSchema {
27    type Error = crate::Error;
28
29    fn try_from(value: &MarkedYaml<'_>) -> crate::Result<Self> {
30        if let YamlData::Sequence(values) = &value.data {
31            let enum_values = load_enum_values(values)?;
32            Ok(EnumSchema {
33                r#enum: enum_values,
34            })
35        } else {
36            Err(generic_error!(
37                "enum: Expected a sequence, but got: {}",
38                format_yaml_data(&value.data)
39            ))
40        }
41    }
42}
43
44pub fn load_enum_values(values: &AnnotatedSequence<MarkedYaml>) -> Result<Vec<ConstValue>> {
45    values.iter().map(|v| v.try_into()).collect()
46}
47
48impl Validator for EnumSchema {
49    fn validate(&self, context: &Context, value: &saphyr::MarkedYaml) -> Result<()> {
50        debug!("[EnumSchema] self: {self}");
51        let data = &value.data;
52        debug!("[EnumSchema] Validating value: {data:?}");
53        let const_value: ConstValue = match ConstValue::try_from(data) {
54            Ok(const_value) => const_value,
55            Err(_) => {
56                context.add_error(
57                    value,
58                    format!(
59                        "Unable to convert value: {} to ConstValue",
60                        format_yaml_data(data)
61                    ),
62                );
63                return Ok(());
64            }
65        };
66        debug!("[EnumSchema] const_value: {const_value}");
67        for value in &self.r#enum {
68            debug!("[EnumSchema] value: {value}");
69            if value.eq(&const_value) {
70                return Ok(());
71            }
72        }
73        if !self.r#enum.contains(&const_value) {
74            let value_str = format_yaml_data(data);
75            let enum_values = self
76                .r#enum
77                .iter()
78                .map(|v| format!("{v}"))
79                .collect::<Vec<String>>()
80                .join(", ");
81            let error = format!("Value {value_str} is not in the enum: [{enum_values}]");
82            debug!("[EnumSchema] error: {error}");
83            context.add_error(value, error);
84        }
85        Ok(())
86    }
87}
88
89#[cfg(test)]
90mod tests {
91    use crate::loader;
92
93    use super::*;
94    use saphyr::LoadableYamlNode;
95
96    #[test]
97    fn test_enum_schema() {
98        let schema = EnumSchema {
99            r#enum: vec![ConstValue::String("NW".to_string())],
100        };
101        let docs = saphyr::MarkedYaml::load_from_str("NW").unwrap();
102        let value = docs.first().unwrap();
103        let context = Context::default();
104        let result = schema.validate(&context, value);
105        assert!(result.is_ok());
106    }
107
108    #[test]
109    fn test_loading_enum_schema() {
110        let schema = r#"
111        enum:
112          - red
113          - amber
114          - green
115        "#;
116        let schema = loader::load_from_str(schema).expect("Failed to load schema");
117
118        let docs = MarkedYaml::load_from_str(
119            r#"
120        red
121        "#,
122        )
123        .unwrap();
124        let value = docs.first().unwrap();
125        let context = Context::default();
126        let result = schema.validate(&context, value);
127        assert!(result.is_ok());
128        assert!(!context.has_errors());
129
130        let docs = MarkedYaml::load_from_str(
131            r#"
132        blue
133        "#,
134        )
135        .unwrap();
136        let value = docs.first().unwrap();
137        let context = Context::default();
138        let result = schema.validate(&context, value);
139        assert!(result.is_ok());
140        assert!(context.has_errors());
141        let errors = context.errors.borrow();
142        assert_eq!(errors.len(), 1);
143        assert_eq!(
144            errors[0].error,
145            "Value \"blue\" is not in the enum: [\"red\", \"amber\", \"green\"]"
146        );
147    }
148}