|
1 | 1 | use crate::_alloc_prelude::*; |
2 | 2 | use crate::{json_schema, JsonSchema, Schema, SchemaGenerator}; |
3 | 3 | 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 | +} |
6 | 13 |
|
7 | 14 | impl<K, V> JsonSchema for BTreeMap<K, V> |
8 | 15 | where |
@@ -35,25 +42,104 @@ where |
35 | 42 | "type": "object", |
36 | 43 | }); |
37 | 44 |
|
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 | + }); |
50 | 55 | }; |
51 | 56 |
|
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 | + } |
57 | 143 |
|
58 | 144 | map_schema |
59 | 145 | } |
|
0 commit comments