yaml_schema/schemas/
base.rs

1use std::collections::HashMap;
2
3use saphyr::AnnotatedMapping;
4use saphyr::MarkedYaml;
5use saphyr::Scalar;
6use saphyr::YamlData;
7
8use crate::ConstValue;
9use crate::schemas::SchemaMetadata;
10use crate::schemas::r#enum::load_enum_values;
11use crate::utils::format_marker;
12
13/// The `BaseSchema` contains common fields for all schemas.
14#[derive(Debug, Default, PartialEq)]
15pub struct BaseSchema {
16    pub r#enum: Option<Vec<ConstValue>>,
17    pub r#const: Option<ConstValue>,
18    pub description: Option<String>,
19}
20
21impl BaseSchema {
22    pub fn as_hash_map(&self) -> HashMap<String, String> {
23        let mut h = HashMap::new();
24        if let Some(r#enum) = &self.r#enum {
25            h.insert(
26                "enum".to_string(),
27                r#enum
28                    .iter()
29                    .map(|v| v.to_string())
30                    .collect::<Vec<String>>()
31                    .join(", "),
32            );
33        }
34        if let Some(r#const) = &self.r#const {
35            h.insert("const".to_string(), r#const.to_string());
36        }
37        h
38    }
39
40    pub fn handle_key_value(
41        &mut self,
42        key: &str,
43        value: &MarkedYaml,
44    ) -> crate::Result<Option<&Self>> {
45        match key {
46            "enum" => {
47                if let YamlData::Sequence(values) = &value.data {
48                    self.r#enum = Some(load_enum_values(values)?);
49                    Ok(Some(self))
50                } else {
51                    Err(expected_scalar!(
52                        "{} Expected an array for enum:, but got: {:#?}",
53                        format_marker(&value.span.start),
54                        value
55                    ))
56                }
57            }
58            "const" => {
59                if let YamlData::Value(scalar) = &value.data {
60                    let const_value: ConstValue = scalar.try_into()?;
61                    self.r#const = Some(const_value);
62                    Ok(Some(self))
63                } else {
64                    Err(expected_scalar!(
65                        "{} Expecting scalar value for const, got {:?}",
66                        format_marker(&value.span.start),
67                        value
68                    ))
69                }
70            }
71            "description" => {
72                if let YamlData::Value(Scalar::String(value)) = &value.data {
73                    self.description = Some(value.to_string());
74                    Ok(Some(self))
75                } else {
76                    Err(expected_scalar!(
77                        "{} Expected a string value for description, got {:?}",
78                        format_marker(&value.span.start),
79                        value
80                    ))
81                }
82            }
83            _ => Ok(None),
84        }
85    }
86}
87
88impl SchemaMetadata for BaseSchema {
89    fn get_accepted_keys() -> &'static [&'static str] {
90        &["type", "enum", "const", "description"]
91    }
92}
93
94impl TryFrom<&MarkedYaml<'_>> for BaseSchema {
95    type Error = crate::Error;
96
97    fn try_from(value: &MarkedYaml) -> Result<Self, Self::Error> {
98        if let YamlData::Mapping(mapping) = &value.data {
99            Ok(BaseSchema::try_from(mapping)?)
100        } else {
101            Err(expected_mapping!(value))
102        }
103    }
104}
105
106impl TryFrom<&AnnotatedMapping<'_, MarkedYaml<'_>>> for BaseSchema {
107    type Error = crate::Error;
108
109    fn try_from(mapping: &AnnotatedMapping<'_, MarkedYaml<'_>>) -> crate::Result<Self> {
110        let mut base_schema = BaseSchema::default();
111        for (key, value) in mapping.iter() {
112            if let YamlData::Value(Scalar::String(key)) = &key.data {
113                base_schema.handle_key_value(key, value)?;
114            } else {
115                return Err(generic_error!(
116                    "{} Expected string key, got {:?}",
117                    format_marker(&key.span.start),
118                    key
119                ));
120            }
121        }
122        Ok(base_schema)
123    }
124}
125
126#[cfg(test)]
127mod tests {
128    use saphyr::LoadableYamlNode as _;
129
130    use super::*;
131
132    #[test]
133    fn test_base_schema_with_enum() {
134        let yaml = r#"
135        type: string
136        enum:
137            - "foo"
138            - "bar"
139        "#;
140        let doc = MarkedYaml::load_from_str(yaml).unwrap();
141        let marked_yaml = doc.first().unwrap();
142        let base_schema = BaseSchema::try_from(marked_yaml).unwrap();
143        assert_eq!(
144            base_schema.r#enum,
145            Some(vec![
146                ConstValue::String("foo".to_string()),
147                ConstValue::String("bar".to_string())
148            ])
149        );
150    }
151}