schemars/json_schema_impls/
maps.rs

1use crate::_alloc_prelude::*;
2use crate::{json_schema, JsonSchema, Schema, SchemaGenerator};
3use alloc::borrow::Cow;
4use alloc::collections::{BTreeMap, BTreeSet};
5use serde_json::{Map, Value};
6
7#[derive(Ord, PartialOrd, Eq, PartialEq, Copy, Clone)]
8enum IntegerSupport {
9    None,
10    Unsigned,
11    Signed,
12}
13
14impl<K, V> JsonSchema for BTreeMap<K, V>
15where
16    K: JsonSchema,
17    V: JsonSchema,
18{
19    inline_schema!();
20
21    fn schema_name() -> Cow<'static, str> {
22        Cow::Owned(if K::schema_id() == <str>::schema_id() {
23            format!("Map_of_{}", V::schema_name())
24        } else {
25            format!("Map_from_{}_to_{}", K::schema_name(), V::schema_name())
26        })
27    }
28
29    fn schema_id() -> Cow<'static, str> {
30        Cow::Owned(if K::schema_id() == <str>::schema_id() {
31            format!("Map<{}>", V::schema_id())
32        } else {
33            format!("Map<{}, {}>", K::schema_id(), V::schema_id())
34        })
35    }
36
37    fn json_schema(generator: &mut SchemaGenerator) -> Schema {
38        let key_schema = K::json_schema(generator);
39        let value_schema = generator.subschema_for::<V>();
40
41        let mut map_schema = json_schema!({
42            "type": "object",
43        });
44
45        let Some(mut options) = key_schema
46            .get("anyOf")
47            .and_then(Value::as_array)
48            .and_then(|a| a.iter().map(Value::as_object).collect::<Option<Vec<_>>>())
49            .or_else(|| Some(vec![key_schema.as_object()?]))
50        else {
51            return json_schema!({
52                "additionalProperties": value_schema,
53                "type": "object",
54            });
55        };
56
57        // Handle refs
58        let prefix = format!("#{}/", generator.definitions_path_stripped());
59        for option in &mut options {
60            if let Some(d) = option
61                .get("$ref")
62                .and_then(Value::as_str)
63                .and_then(|r| r.strip_prefix(&prefix))
64                .and_then(|r| generator.definitions().get(r))
65                .and_then(Value::as_object)
66            {
67                *option = d;
68            }
69        }
70
71        let mut additional_properties = false;
72        let mut support_integers = IntegerSupport::None;
73        let mut patterns = BTreeSet::new();
74        let mut properties = BTreeSet::new();
75        for option in options {
76            let key_pattern = option.get("pattern").and_then(Value::as_str);
77            let key_enum = option
78                .get("enum")
79                .and_then(Value::as_array)
80                .and_then(|a| a.iter().map(Value::as_str).collect::<Option<Vec<_>>>());
81            let key_type = option.get("type").and_then(Value::as_str);
82            let key_minimum = option.get("minimum").and_then(Value::as_u64);
83
84            match (key_pattern, key_enum, key_type) {
85                (Some(pattern), _, Some("string")) => {
86                    patterns.insert(pattern);
87                }
88                (None, Some(enum_values), Some("string")) => {
89                    for value in enum_values {
90                        properties.insert(value);
91                    }
92                }
93                (_, _, Some("integer")) if key_minimum == Some(0) => {
94                    support_integers = support_integers.max(IntegerSupport::Unsigned);
95                }
96                (_, _, Some("integer")) => {
97                    support_integers = support_integers.max(IntegerSupport::Signed);
98                }
99                _ => {
100                    additional_properties = true;
101                }
102            }
103        }
104
105        if additional_properties {
106            map_schema.insert(
107                "additionalProperties".to_owned(),
108                value_schema.clone().to_value(),
109            );
110        } else {
111            map_schema.insert("additionalProperties".to_owned(), Value::Bool(false));
112        }
113
114        match support_integers {
115            IntegerSupport::None => {}
116            IntegerSupport::Unsigned => {
117                patterns.insert(r"^\d+$");
118            }
119            IntegerSupport::Signed => {
120                patterns.insert(r"^-?\d+$");
121            }
122        }
123
124        if !patterns.is_empty() {
125            let mut patterns_map = Map::new();
126
127            for pattern in patterns {
128                patterns_map.insert(pattern.to_owned(), value_schema.clone().to_value());
129            }
130
131            map_schema.insert("patternProperties".to_owned(), Value::Object(patterns_map));
132        }
133
134        if !properties.is_empty() {
135            let mut properties_map = Map::new();
136
137            for property in properties {
138                properties_map.insert(property.to_owned(), value_schema.clone().to_value());
139            }
140
141            map_schema.insert("properties".to_owned(), Value::Object(properties_map));
142        }
143
144        map_schema
145    }
146}
147
148#[cfg(feature = "std")]
149forward_impl!((<K: JsonSchema, V: JsonSchema, H> JsonSchema for std::collections::HashMap<K, V, H>) => BTreeMap<K, V>);