Skip to main content

valico/json_schema/keywords/
of.rs

1use 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}