jsona_schema_validator/
lib.rs

1mod validates;
2
3use std::convert::TryFrom;
4
5use indexmap::IndexMap;
6use jsona::dom::{visit_annotations, Key, KeyOrIndex, Keys, Node};
7use jsona_schema::{SchemaError, SchemaResult, SchemaType};
8pub use validates::Error as JSONASchemaValidationError;
9
10pub use jsona_schema::Schema;
11
12pub const VALUE_KEY: &str = "_";
13
14#[derive(Debug)]
15pub struct JSONASchemaValidator {
16    schema: Schema,
17    annotations: Schema,
18}
19
20impl JSONASchemaValidator {
21    pub fn validate(&self, node: &Node) -> Vec<JSONASchemaValidationError> {
22        let mut collect_errors = vec![];
23        let default_defs = Default::default();
24        let defs = self.schema.defs.as_ref().unwrap_or(&default_defs);
25        if let Some(value_schema) = self.get_entry_schema(VALUE_KEY) {
26            collect_errors.extend(validates::validate(
27                defs,
28                value_schema,
29                &Keys::default(),
30                node,
31            ));
32        }
33        for (keys, value) in visit_annotations(node).into_iter() {
34            if let Some(key) = keys.last_annotation_key() {
35                if let Some(schema) = self.get_entry_schema(key.value()) {
36                    collect_errors.extend(validates::validate(defs, schema, &keys, &value));
37                }
38            }
39        }
40        collect_errors
41    }
42
43    pub fn pointer(&self, keys: &Keys) -> Vec<&Schema> {
44        let (annotation_key, keys) = keys.shift_annotation();
45        let new_keys = match annotation_key {
46            Some(key) => {
47                if !self.contains_annotation_key(key.value()) {
48                    return vec![&self.annotations];
49                }
50                let new_keys = Keys::new(
51                    [
52                        KeyOrIndex::property(key.annotation_name().unwrap()),
53                        KeyOrIndex::property("value"),
54                    ]
55                    .into_iter(),
56                );
57                new_keys.extend(keys)
58            }
59            None => {
60                let new_keys = Keys::new(
61                    [
62                        KeyOrIndex::property(VALUE_KEY),
63                        KeyOrIndex::property("value"),
64                    ]
65                    .into_iter(),
66                );
67                new_keys.extend(keys)
68            }
69        };
70        self.schema.pointer(&new_keys)
71    }
72
73    pub fn get_entry_schema(&self, key: &str) -> Option<&Schema> {
74        let properties = self.schema.properties.as_ref()?;
75        let schema = properties.get(key)?;
76        let properties = schema.properties.as_ref()?;
77        properties.get("value")
78    }
79
80    pub fn contains_annotation_key(&self, key: &str) -> bool {
81        match self.annotations.properties.as_ref() {
82            Some(properties) => properties.contains_key(key),
83            None => false,
84        }
85    }
86}
87
88impl TryFrom<&Node> for JSONASchemaValidator {
89    type Error = Vec<SchemaError>;
90    fn try_from(value: &Node) -> Result<Self, Self::Error> {
91        let object = match value.as_object() {
92            Some(v) => v,
93            None => {
94                return Err(vec![SchemaError::InvalidSchemaValue {
95                    keys: Keys::default(),
96                    error: "must be object".into(),
97                }])
98            }
99        };
100        let mut annotation_schemas = IndexMap::default();
101        let mut errors = vec![];
102        for (key, value) in object.value().read().iter() {
103            let keys = Keys::single(key.clone());
104            if key.is_quote() {
105                errors.push(SchemaError::InvalidSchemaValue {
106                    keys,
107                    error: "invalid name".into(),
108                });
109                continue;
110            }
111            let object = match value.as_object() {
112                Some(v) => v,
113                None => {
114                    errors.push(SchemaError::InvalidSchemaValue {
115                        keys,
116                        error: "must be object".into(),
117                    });
118                    continue;
119                }
120            };
121            let value_key = Key::property("value");
122            let keys = keys.join(value_key.clone());
123            match object.get(&value_key) {
124                Some(value) => {
125                    if key.value() != VALUE_KEY {
126                        let schema = Schema {
127                            description: parse_string_annotation(&keys, &value, "@describe")
128                                .ok()
129                                .flatten(),
130                            schema_type: SchemaType::from_node(&value).map(|v| v.into()),
131                            ..Default::default()
132                        };
133                        annotation_schemas.insert(format!("@{}", key.value()), schema);
134                    }
135                }
136                None => {
137                    errors.push(SchemaError::InvalidSchemaValue {
138                        keys,
139                        error: "must be object".into(),
140                    });
141                    continue;
142                }
143            }
144        }
145        if !errors.is_empty() {
146            return Err(errors);
147        }
148        let schema = Schema::try_from(value)?;
149        let annotations = Schema {
150            schema_type: Some(SchemaType::Object.into()),
151            properties: if annotation_schemas.is_empty() {
152                None
153            } else {
154                Some(annotation_schemas)
155            },
156            ..Default::default()
157        };
158        Ok(JSONASchemaValidator {
159            schema,
160            annotations,
161        })
162    }
163}
164
165fn parse_string_annotation(keys: &Keys, node: &Node, name: &str) -> SchemaResult<Option<String>> {
166    match node.get_as_string(name) {
167        Some((_, Some(value))) => Ok(Some(value.value().to_string())),
168        Some((key, None)) => Err(vec![SchemaError::UnexpectedType {
169            keys: keys.clone().join(key),
170        }]),
171        None => Ok(None),
172    }
173}