valico/json_schema/keywords/
of.rs1use serde_json::Value;
2
3use super::super::helpers;
4use super::super::schema;
5use super::super::validators;
6
7macro_rules! of_keyword {
8 ($name:ident, $kw:expr) => {
9 #[allow(missing_copy_implementations)]
10 pub struct $name;
11 impl super::Keyword for $name {
12 fn compile(&self, def: &Value, ctx: &schema::WalkContext<'_>) -> super::KeywordResult {
13 let of = keyword_key_exists!(def, $kw);
14
15 if of.is_array() {
16 let of = of.as_array().unwrap();
17
18 if of.len() == 0 {
19 return Err(schema::SchemaError::Malformed {
20 path: ctx.fragment.join("/"),
21 detail: "This array MUST have at least one element.".to_string(),
22 });
23 }
24
25 let mut schemes = vec![];
26 for (idx, scheme) in of.iter().enumerate() {
27 if scheme.is_object() || scheme.is_boolean() {
28 schemes.push(helpers::alter_fragment_path(
29 ctx.url.clone(),
30 [
31 ctx.escaped_fragment().as_ref(),
32 $kw,
33 idx.to_string().as_ref(),
34 ]
35 .join("/"),
36 ))
37 } else {
38 return Err(schema::SchemaError::Malformed {
39 path: ctx.fragment.join("/"),
40 detail: "Elements of the array MUST be objects or booleans."
41 .to_string(),
42 });
43 }
44 }
45
46 Ok(Some(Box::new(validators::$name { schemes })))
47 } else {
48 Err(schema::SchemaError::Malformed {
49 path: ctx.fragment.join("/"),
50 detail: "The value of this keyword MUST be an array.".to_string(),
51 })
52 }
53 }
54 }
55 };
56}
57
58of_keyword!(AllOf, "allOf");
59of_keyword!(AnyOf, "anyOf");
60of_keyword!(OneOf, "oneOf");
61
62#[cfg(test)]
63use super::super::builder;
64#[cfg(test)]
65use super::super::scope;
66#[cfg(test)]
67use serde_json::to_value;
68
69#[cfg(test)]
70fn mk_schema() -> Value {
71 json!({
72 "properties": {
73 "a": {
74 "oneOf": [
75 { "type": "array", "items": [{"type":"boolean"},{"default":42}] },
76 { "type": "object", "properties": {"x": {"default": "buh"}} }
77 ]
78 },
79 "b": {
80 "anyOf": [
81 { "type": "array", "items": [{"type":"boolean"},{"default":42}] },
82 { "type": "object", "properties": {"x": {"default": "buh"}} }
83 ]
84 },
85 "c": {
86 "allOf": [
87 { "properties": {"x": {"default": false}} },
88 { "properties": {"y": {"default": true}} }
89 ]
90 }
91 }
92 })
93}
94
95#[test]
96fn no_default_for_schema() {
97 let mut scope = scope::Scope::new().supply_defaults();
98 let schema = scope.compile_and_return(mk_schema(), true).unwrap();
99 assert_eq!(schema.get_default(), None);
100}
101
102#[test]
103fn default_when_needed() {
104 let mut scope = scope::Scope::new().supply_defaults();
105 let schema = scope.compile_and_return(mk_schema(), true).unwrap();
106 let result = schema.validate(&json!({"a":[true],"b":[true],"c":{}}));
107 assert!(result.is_strictly_valid());
108 assert_eq!(
109 result.replacement,
110 Some(json!({"a":[true,42],"b":[true,42],"c":{"x":false,"y":true}}))
111 );
112}
113
114#[test]
115fn default_when_needed2() {
116 let mut scope = scope::Scope::new().supply_defaults();
117 let schema = scope.compile_and_return(mk_schema(), true).unwrap();
118 let result = schema.validate(&json!({"a":{},"b":{}}));
119 assert!(result.is_strictly_valid());
120 assert_eq!(
121 result.replacement,
122 Some(json!({"a":{"x":"buh"},"b":{"x":"buh"}}))
123 );
124}
125
126#[test]
127fn no_default_otherwise() {
128 let mut scope = scope::Scope::new().supply_defaults();
129 let schema = scope.compile_and_return(mk_schema(), true).unwrap();
130 let result = schema.validate(&json!({"a":{"x":"x"},"b":[true,0],"c":{"x":1,"y":2}}));
131 assert!(result.is_strictly_valid());
132 assert_eq!(result.replacement, None);
133}
134
135#[test]
136fn conflicting_defaults() {
137 let mut scope = scope::Scope::new().supply_defaults();
138 let schema = scope
139 .compile_and_return(
140 json!({
141 "allOf": [
142 {
143 "properties": {
144 "a": { "type": "number" }
145 },
146 },
147 {
148 "properties": {
149 "a": { "default": "hello" }
150 }
151 }
152 ]
153 }),
154 true,
155 )
156 .unwrap();
157 let result = schema.validate(&json!({}));
158 assert!(!result.is_valid());
159 assert_eq!(&*format!("{result:?}"),
160 "ValidationState { errors: [WrongType { path: \"/a\", detail: \"The value must be number\" }], missing: [], replacement: None, evaluated: {\"/a\"} }");
161}
162
163#[test]
164fn divergent_defaults() {
165 let mut scope = scope::Scope::new().supply_defaults();
166 let schema = scope
167 .compile_and_return(
168 json!({
169 "allOf": [
170 {
171 "properties": {
172 "a": {
173 "anyOf": [{
174 "properties": {
175 "b": { "default": 42 }
176 }
177 }]
178 }
179 },
180 },
181 {
182 "properties": {
183 "a": { "default": {} }
184 }
185 }
186 ]
187 }),
188 true,
189 )
190 .unwrap();
191 let mut result = schema.validate(&json!({}));
192 assert!(!result.is_valid());
193 result.evaluated.clear();
194 assert_eq!(&*format!("{result:?}"),
195 "ValidationState { errors: [DivergentDefaults { path: \"\" }], missing: [], replacement: None, evaluated: {} }");
196}
197
198#[test]
199fn validate_all_of() {
200 let mut scope = scope::Scope::new();
201 let schema = scope
202 .compile_and_return(
203 builder::schema(|s| {
204 s.all_of(|all_of| {
205 all_of.push(|schema| {
206 schema.minimum(5f64);
207 });
208 all_of.push(|schema| {
209 schema.maximum(10f64);
210 });
211 });
212 })
213 .into_json(),
214 true,
215 )
216 .ok()
217 .unwrap();
218
219 assert_eq!(schema.validate(&to_value(7).unwrap()).is_valid(), true);
220 assert_eq!(schema.validate(&to_value(4).unwrap()).is_valid(), false);
221 assert_eq!(schema.validate(&to_value(11).unwrap()).is_valid(), false);
222}
223
224#[test]
225fn validate_any_of() {
226 let mut scope = scope::Scope::new();
227 let schema = scope
228 .compile_and_return(
229 builder::schema(|s| {
230 s.any_of(|all_of| {
231 all_of.push(|schema| {
232 schema.maximum(5f64);
233 });
234 all_of.push(|schema| {
235 schema.maximum(10f64);
236 });
237 });
238 })
239 .into_json(),
240 true,
241 )
242 .ok()
243 .unwrap();
244
245 assert_eq!(schema.validate(&to_value(5).unwrap()).is_valid(), true);
246 assert_eq!(schema.validate(&to_value(10).unwrap()).is_valid(), true);
247 assert_eq!(schema.validate(&to_value(11).unwrap()).is_valid(), false);
248}
249
250#[test]
251fn validate_one_of() {
252 let mut scope = scope::Scope::new();
253 let schema = scope
254 .compile_and_return(
255 builder::schema(|s| {
256 s.one_of(|all_of| {
257 all_of.push(|schema| {
258 schema.maximum(5f64);
259 });
260 all_of.push(|schema| {
261 schema.maximum(10f64);
262 });
263 });
264 })
265 .into_json(),
266 true,
267 )
268 .ok()
269 .unwrap();
270
271 assert_eq!(schema.validate(&to_value(5).unwrap()).is_valid(), false);
272 assert_eq!(schema.validate(&to_value(6).unwrap()).is_valid(), true);
273 assert_eq!(schema.validate(&to_value(11).unwrap()).is_valid(), false);
274}