yaml_schema/schemas/
enum.rs1use 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#[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}