use log::debug;
use saphyr::AnnotatedSequence;
use saphyr::MarkedYaml;
use saphyr::YamlData;
use crate::ConstValue;
use crate::Context;
use crate::Result;
use crate::Validator;
use crate::utils::format_vec;
use crate::utils::format_yaml_data;
#[derive(Debug, Default, PartialEq)]
pub struct EnumSchema {
pub r#enum: Vec<ConstValue>,
}
impl std::fmt::Display for EnumSchema {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Enum {{ enum: {} }}", format_vec(&self.r#enum))
}
}
impl TryFrom<&MarkedYaml<'_>> for EnumSchema {
type Error = crate::Error;
fn try_from(value: &MarkedYaml<'_>) -> crate::Result<Self> {
if let YamlData::Sequence(values) = &value.data {
let enum_values = load_enum_values(values)?;
Ok(EnumSchema {
r#enum: enum_values,
})
} else {
Err(generic_error!(
"enum: Expected a sequence, but got: {}",
format_yaml_data(&value.data)
))
}
}
}
pub fn load_enum_values(values: &AnnotatedSequence<MarkedYaml>) -> Result<Vec<ConstValue>> {
values.iter().map(|v| v.try_into()).collect()
}
impl Validator for EnumSchema {
fn validate(&self, context: &Context, value: &saphyr::MarkedYaml) -> Result<()> {
debug!("[EnumSchema] self: {self}");
let data = &value.data;
debug!("[EnumSchema] Validating value: {data:?}");
let const_value: ConstValue = match ConstValue::try_from(data) {
Ok(const_value) => const_value,
Err(_) => {
context.add_error(
value,
format!(
"Unable to convert value: {} to ConstValue",
format_yaml_data(data)
),
);
return Ok(());
}
};
debug!("[EnumSchema] const_value: {const_value}");
for value in &self.r#enum {
debug!("[EnumSchema] value: {value}");
if value.eq(&const_value) {
return Ok(());
}
}
if !self.r#enum.contains(&const_value) {
let value_str = format_yaml_data(data);
let enum_values = self
.r#enum
.iter()
.map(|v| format!("{v}"))
.collect::<Vec<String>>()
.join(", ");
let error = format!("Value {value_str} is not in the enum: [{enum_values}]");
debug!("[EnumSchema] error: {error}");
context.add_error(value, error);
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::loader;
use super::*;
use saphyr::LoadableYamlNode;
#[test]
fn test_enum_schema() {
let schema = EnumSchema {
r#enum: vec![ConstValue::String("NW".to_string())],
};
let docs = saphyr::MarkedYaml::load_from_str("NW").unwrap();
let value = docs.first().unwrap();
let context = Context::default();
let result = schema.validate(&context, value);
assert!(result.is_ok());
}
#[test]
fn test_loading_enum_schema() {
let schema = r#"
enum:
- red
- amber
- green
"#;
let schema = loader::load_from_str(schema).expect("Failed to load schema");
let docs = MarkedYaml::load_from_str(
r#"
red
"#,
)
.unwrap();
let value = docs.first().unwrap();
let context = Context::default();
let result = schema.validate(&context, value);
assert!(result.is_ok());
assert!(!context.has_errors());
let docs = MarkedYaml::load_from_str(
r#"
blue
"#,
)
.unwrap();
let value = docs.first().unwrap();
let context = Context::default();
let result = schema.validate(&context, value);
assert!(result.is_ok());
assert!(context.has_errors());
let errors = context.errors.borrow();
assert_eq!(errors.len(), 1);
assert_eq!(
errors[0].error,
"Value \"blue\" is not in the enum: [\"red\", \"amber\", \"green\"]"
);
}
}