Skip to main content

valico/json_schema/keywords/
contains.rs

1use serde_json::Value;
2
3use crate::json_schema::SchemaVersion;
4
5use super::super::helpers;
6use super::super::schema;
7use super::super::validators;
8
9#[allow(missing_copy_implementations)]
10pub struct Contains;
11impl super::Keyword for Contains {
12    fn compile(&self, def: &Value, ctx: &schema::WalkContext) -> super::KeywordResult {
13        let contains = keyword_key_exists!(def, "contains");
14        let (max_contains, min_contains) = if ctx.version >= SchemaVersion::Draft2019_09 {
15            let max_contains = def
16                .get("maxContains")
17                .map(|v| {
18                    v.as_u64().ok_or_else(|| schema::SchemaError::Malformed {
19                        path: ctx.fragment.join("/"),
20                        detail: "The value of maxContains MUST be a non-negative integer"
21                            .to_string(),
22                    })
23                })
24                .transpose()?;
25            let min_contains = def
26                .get("minContains")
27                .map(|v| {
28                    v.as_u64().ok_or_else(|| schema::SchemaError::Malformed {
29                        path: ctx.fragment.join("/"),
30                        detail: "The value of minContains MUST be a non-negative integer"
31                            .to_string(),
32                    })
33                })
34                .transpose()?;
35            (max_contains, min_contains)
36        } else {
37            (None, None)
38        };
39
40        if contains.is_object() || contains.is_boolean() {
41            Ok(Some(Box::new(validators::Contains {
42                url: helpers::alter_fragment_path(
43                    ctx.url.clone(),
44                    [ctx.escaped_fragment().as_ref(), "contains"].join("/"),
45                ),
46                max_contains,
47                min_contains,
48            })))
49        } else {
50            Err(schema::SchemaError::Malformed {
51                path: ctx.fragment.join("/"),
52                detail: "The value of contains MUST be an object or a boolean".to_string(),
53            })
54        }
55    }
56}
57
58#[cfg(test)]
59pub mod tests {
60    use crate::json_schema::scope;
61    use serde_json::Value;
62
63    fn schema() -> Value {
64        json!({
65            "contains": {
66                "properties": {
67                    "x": {
68                        "type": "string",
69                        "default": "buh"
70                    },
71                }
72            }
73        })
74    }
75
76    #[test]
77    fn no_default_for_schema() {
78        let mut scope = scope::Scope::new().supply_defaults();
79        let schema = scope.compile_and_return(schema(), true).unwrap();
80        assert_eq!(schema.get_default(), None);
81    }
82
83    #[test]
84    fn default_for_first() {
85        let mut scope = scope::Scope::new().supply_defaults();
86        let schema = scope.compile_and_return(schema(), true).unwrap();
87        let result = schema.validate(&json!([{}, {}]));
88        assert!(result.is_strictly_valid());
89        assert_eq!(result.replacement, Some(json!([{"x": "buh"}, {}])));
90    }
91
92    #[test]
93    fn no_default_when_not_needed() {
94        let mut scope = scope::Scope::new().supply_defaults();
95        let schema = scope.compile_and_return(schema(), true).unwrap();
96        let result = schema.validate(&json!([{"x": "y"}, {}]));
97        assert!(result.is_strictly_valid());
98        assert_eq!(result.replacement, None);
99    }
100}