Skip to content

Commit 2ac8ff5

Browse files
BuzzecstegaBOB
andauthored
Added better map support (#452)
* Added better map support * Made map still output properties even with additional enabled. * Removed early return * Handled multiple patterns or properties with the same value. --------- Co-authored-by: Sammy Harris <41593264+stegaBOB@users.noreply.github.com>
1 parent 6009e82 commit 2ac8ff5

File tree

2 files changed

+106
-20
lines changed

2 files changed

+106
-20
lines changed

schemars/src/generate.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -611,7 +611,7 @@ impl SchemaGenerator {
611611

612612
/// Returns `self.settings.definitions_path` as a plain JSON pointer to the definitions object,
613613
/// i.e. without a leading '#' or trailing '/'
614-
fn definitions_path_stripped(&self) -> &str {
614+
pub(crate) fn definitions_path_stripped(&self) -> &str {
615615
let path = &self.settings.definitions_path;
616616
let path = path.strip_prefix('#').unwrap_or(path);
617617
path.strip_suffix('/').unwrap_or(path)

schemars/src/json_schema_impls/maps.rs

Lines changed: 105 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
use crate::_alloc_prelude::*;
22
use crate::{json_schema, JsonSchema, Schema, SchemaGenerator};
33
use alloc::borrow::Cow;
4-
use alloc::collections::BTreeMap;
5-
use serde_json::{json, Value};
4+
use alloc::collections::{BTreeMap, BTreeSet};
5+
use serde_json::{Map, Value};
6+
7+
#[derive(Ord, PartialOrd, Eq, PartialEq, Copy, Clone)]
8+
enum IntegerSupport {
9+
None,
10+
Unsigned,
11+
Signed,
12+
}
613

714
impl<K, V> JsonSchema for BTreeMap<K, V>
815
where
@@ -35,25 +42,104 @@ where
3542
"type": "object",
3643
});
3744

38-
let key_pattern = key_schema.get("pattern").and_then(Value::as_str);
39-
let key_type = key_schema.get("type").and_then(Value::as_str);
40-
let key_minimum = key_schema.get("minimum").and_then(Value::as_u64);
41-
42-
let pattern = match (key_pattern, key_type) {
43-
(Some(pattern), Some("string")) => pattern,
44-
(_, Some("integer")) if key_minimum == Some(0) => r"^\d+$",
45-
(_, Some("integer")) => r"^-?\d+$",
46-
_ => {
47-
map_schema.insert("additionalProperties".to_owned(), value_schema.to_value());
48-
return map_schema;
49-
}
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+
});
5055
};
5156

52-
map_schema.insert(
53-
"patternProperties".to_owned(),
54-
json!({ pattern: value_schema }),
55-
);
56-
map_schema.insert("additionalProperties".to_owned(), Value::Bool(false));
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+
}
57143

58144
map_schema
59145
}

0 commit comments

Comments
 (0)