1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
use crate::{traits::Size, MaxPropertiesError};

/// Max size validation of the object properties.
///
/// See <https://json-schema.org/understanding-json-schema/reference/object.html#size>
///
/// ```rust
/// use std::collections::HashMap;
///
/// use serde_json::json;
/// use serde_valid::{Validate, ValidateMaxProperties};
///
/// struct MyType(HashMap<String, String>);
///
/// impl ValidateMaxProperties for MyType {
///     fn validate_max_properties(
///         &self,
///         max_properties: usize,
///     ) -> Result<(), serde_valid::MaxPropertiesError> {
///         self.0.validate_max_properties(max_properties)
///     }
/// }
///
/// #[derive(Validate)]
/// struct TestStruct {
///     #[validate(max_properties = 2)]
///     val: MyType,
/// }
///
/// let mut map = HashMap::new();
/// map.insert("key1".to_string(), "value1".to_string());
/// map.insert("key2".to_string(), "value2".to_string());
/// map.insert("key3".to_string(), "value3".to_string());
///
/// let s = TestStruct { val: MyType(map) };
///
/// assert_eq!(
///     s.validate().unwrap_err().to_string(),
///     json!({
///         "errors": [],
///         "properties": {
///             "val": {
///                 "errors": ["The size of the properties must be `<= 2`."]
///             }
///         }
///     })
///     .to_string()
/// );
/// ```
pub trait ValidateMaxProperties {
    fn validate_max_properties(&self, max_properties: usize) -> Result<(), MaxPropertiesError>;
}

impl<T> ValidateMaxProperties for T
where
    T: Size,
{
    fn validate_max_properties(&self, max_properties: usize) -> Result<(), MaxPropertiesError> {
        if max_properties >= self.size() {
            Ok(())
        } else {
            Err(MaxPropertiesError::new(max_properties))
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use serde_json::json;
    use std::collections::BTreeMap;
    use std::collections::HashMap;

    #[test]
    fn test_validate_object_max_properties_hash_map_type() {
        let mut map = HashMap::new();
        map.insert("key1".to_string(), "value1".to_string());
        map.insert("key2".to_string(), "value2".to_string());
        map.insert("key3".to_string(), "value3".to_string());
        assert!(ValidateMaxProperties::validate_max_properties(&map, 3).is_ok());
    }

    #[test]
    fn test_validate_object_max_properties_btree_map_type() {
        let mut map = BTreeMap::new();
        map.insert("key1".to_string(), "value1".to_string());
        map.insert("key2".to_string(), "value2".to_string());
        map.insert("key3".to_string(), "value3".to_string());
        assert!(ValidateMaxProperties::validate_max_properties(&map, 3).is_ok());
    }

    #[test]
    fn test_validate_object_max_properties_json_map_type() {
        let value = json!({
            "key1": "value1",
            "key2": "value2",
            "key3": "value3",
        });
        let map = value.as_object().unwrap();

        assert!(ValidateMaxProperties::validate_max_properties(map, 4).is_ok());
        assert!(ValidateMaxProperties::validate_max_properties(map, 3).is_ok());
    }

    #[test]
    fn test_validate_object_max_properties_is_false() {
        let value = json!({
            "key1": "value1",
            "key2": "value2",
            "key3": "value3",
        });
        let map = value.as_object().unwrap();

        assert!(ValidateMaxProperties::validate_max_properties(map, 2).is_err());
    }
}