Skip to main content

valico/json_schema/validators/
properties.rs

1use serde_json::Value;
2use std::borrow::Cow;
3use std::collections;
4
5use super::super::errors;
6use super::super::scope;
7
8#[derive(Debug)]
9pub enum AdditionalKind {
10    Unspecified,
11    Boolean(bool),
12    Schema(url::Url),
13}
14
15#[allow(missing_copy_implementations)]
16pub struct Properties {
17    pub properties: collections::HashMap<String, url::Url>,
18    pub additional: AdditionalKind,
19    pub patterns: Vec<(fancy_regex::Regex, url::Url)>,
20}
21
22impl super::Validator for Properties {
23    fn validate(
24        &self,
25        val: &Value,
26        path: &str,
27        scope: &scope::Scope,
28        _: &super::ValidationState,
29    ) -> super::ValidationState {
30        let mut object = Cow::Borrowed(nonstrict_process!(val.as_object(), path));
31        let mut state = super::ValidationState::new();
32
33        if scope.supply_defaults {
34            for (key, url) in self.properties.iter() {
35                if let Some(schema) = scope.resolve(url) {
36                    if object.get(key).is_none() && schema.has_default() {
37                        object
38                            .to_mut()
39                            .insert(key.clone(), schema.get_default().unwrap());
40                    }
41                }
42            }
43        }
44
45        // necessary due to object being mutated in the loop
46        let keys = object.keys().cloned().collect::<Vec<_>>();
47        'main: for key in keys.iter() {
48            let is_property_passed = if self.properties.contains_key(key) {
49                let url = &self.properties[key];
50                let schema = scope.resolve(url);
51                if let Some(schema) = schema {
52                    let value_path = [path, key.as_ref()].join("/");
53                    let mut result = schema.validate_in(&object[key], value_path.as_ref());
54                    if result.is_valid() {
55                        state.evaluated.insert(value_path);
56                        if result.replacement.is_some() {
57                            object
58                                .to_mut()
59                                .insert(key.to_string(), result.replacement.take().unwrap());
60                        }
61                    }
62                    state.append(result);
63                } else {
64                    state.missing.push(url.clone())
65                }
66
67                true
68            } else {
69                false
70            };
71
72            let mut is_pattern_passed = false;
73            for (regex, url) in self.patterns.iter() {
74                if regex.is_match(key.as_ref()).unwrap_or(false) {
75                    let schema = scope.resolve(url);
76                    if let Some(schema) = schema {
77                        let value_path = [path, key.as_ref()].join("/");
78                        let mut result = schema.validate_in(&object[key], value_path.as_ref());
79                        if result.is_valid() {
80                            state.evaluated.insert(value_path);
81                            if result.replacement.is_some() {
82                                object
83                                    .to_mut()
84                                    .insert(key.to_string(), result.replacement.take().unwrap());
85                            }
86                        }
87                        state.append(result);
88                        is_pattern_passed = true;
89                    } else {
90                        state.missing.push(url.clone())
91                    }
92                }
93            }
94
95            if is_property_passed || is_pattern_passed {
96                continue 'main;
97            }
98
99            match self.additional {
100                AdditionalKind::Boolean(allowed) => {
101                    if !allowed {
102                        state.errors.push(Box::new(errors::Properties {
103                            path: path.to_string(),
104                            detail: format!("Additional property '{key}' is not allowed"),
105                        }))
106                    } else {
107                        state.evaluated.insert([path, key.as_ref()].join("/"));
108                    }
109                }
110                AdditionalKind::Schema(ref url) => {
111                    let schema = scope.resolve(url);
112
113                    if let Some(schema) = schema {
114                        let value_path = [path, key.as_ref()].join("/");
115                        let mut result = schema.validate_in(&object[key], value_path.as_ref());
116                        if result.is_valid() {
117                            state.evaluated.insert(value_path);
118                            if result.replacement.is_some() {
119                                object
120                                    .to_mut()
121                                    .insert(key.to_string(), result.replacement.take().unwrap());
122                            }
123                        }
124                        state.append(result);
125                    } else {
126                        state.missing.push(url.clone())
127                    }
128                }
129                // Additional are allowed here
130                _ => (),
131            }
132        }
133
134        state.set_replacement(object);
135        state
136    }
137}