valico/json_schema/validators/
properties.rs1use 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 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 _ => (),
131 }
132 }
133
134 state.set_replacement(object);
135 state
136 }
137}