schema2000/renderer/
json_schema_renderer.rs1use std::collections::{BTreeMap, BTreeSet};
2
3use crate::model::{ArrayNode, NodeType, ObjectNode, ObjectProperty};
4use crate::SchemaHypothesis;
5use serde_json::json;
6use serde_json::value::Value;
7use serde_json::Map;
8
9#[must_use]
10#[allow(clippy::missing_panics_doc)]
11pub fn render_schema(schema: &SchemaHypothesis) -> String {
12 serde_json::to_string_pretty(&render_json_schema(schema)).unwrap()
13}
14
15fn render_json_schema(schema: &SchemaHypothesis) -> Value {
16 render_node(&schema.root)
17}
18
19fn render_node(node_type: &NodeType) -> Value {
20 match node_type {
21 NodeType::String(_) => json!({"type": "string"}),
22 NodeType::Integer(_) => json!({"type": "integer"}),
23 NodeType::Number(_) => json!({"type": "number"}),
24 NodeType::Boolean => json!({"type": "boolean"}),
25 NodeType::Null => json!({"type": "null"}),
26 NodeType::Array(node_types) => Value::Object(generate_array_map(node_types)),
27 NodeType::Object(ObjectNode { properties }) => {
28 Value::Object(generate_object_map(properties))
29 }
30 NodeType::Any(node_types) => Value::Object(generate_any_map(&node_types.nodes)),
31 }
32}
33
34fn generate_any_map(node_types: &BTreeSet<NodeType>) -> Map<String, Value> {
35 let mut map = Map::new();
36 map.insert(
37 "anyOf".to_string(),
38 node_types.iter().map(render_node).collect(),
39 );
40
41 map
42}
43
44fn generate_array_map(node_type: &ArrayNode) -> Map<String, Value> {
45 let mut map = Map::new();
46 map.insert("type".to_string(), Value::String("array".to_string()));
47 node_type
48 .items
49 .as_ref()
50 .map(|node_type| map.insert("items".to_string(), render_node(node_type)));
51 map
52}
53
54fn generate_object_map(properties: &BTreeMap<String, ObjectProperty>) -> Map<String, Value> {
55 let required_props: Vec<Value> = properties
56 .iter()
57 .filter_map(|(key, value)| {
58 if value.required {
59 Option::Some(Value::String(key.to_string()))
60 } else {
61 Option::None
62 }
63 })
64 .collect();
65
66 let object_properties: Map<String, Value> = properties
67 .iter()
68 .map(|(key, value)| (key.to_string(), render_node(&value.node_type)))
69 .collect();
70
71 let mut map = Map::new();
72
73 map.insert("type".to_string(), Value::String("object".to_string()));
74 map.insert("required".to_string(), Value::Array(required_props));
75 map.insert("properties".to_string(), Value::Object(object_properties));
76
77 map
78}
79
80#[cfg(test)]
81mod test {
82 use maplit::{btreemap, btreeset};
83 use serde_json::json;
84
85 use crate::model::{
86 AnyNode, ArrayNode, IntegerNode, NodeType, ObjectNode, ObjectProperty, SchemaHypothesis,
87 StringNode,
88 };
89 use crate::renderer::json_schema_renderer::{render_json_schema, render_node};
90
91 #[test]
92 fn test_object() {
93 let hypothesis = SchemaHypothesis::new(ObjectNode::new(btreemap! {
94 "name".to_string() => ObjectProperty::new(StringNode::new()),
95 }));
96
97 let actual = render_json_schema(&hypothesis);
98
99 assert_eq!(
100 actual,
101 json!(
102 {
103 "type": "object",
104 "required": ["name"],
105 "properties": {
106 "name": {
107 "type": "string"
108 }
109 }
110 }
111 )
112 );
113 }
114
115 #[test]
116 fn test_array() {
117 let hypothesis = SchemaHypothesis::new(ArrayNode::new_many(btreeset![
118 StringNode::new().into(),
119 IntegerNode::new().into()
120 ]));
121
122 let actual = render_json_schema(&hypothesis);
123
124 assert_eq!(
125 actual,
126 json!(
127 {
128 "type": "array",
129 "items": {
130 "anyOf": [
131 {
132 "type": "integer"
133 },
134 {
135 "type": "string"
136 }
137 ]
138 }
139 }
140 )
141 );
142 }
143
144 #[test]
145 fn test_array_single_type() {
146 let hypothesis =
147 SchemaHypothesis::new(ArrayNode::new_many(btreeset!(StringNode::new().into())));
148
149 let actual = render_json_schema(&hypothesis);
150
151 assert_eq!(
152 actual,
153 json!(
154 {
155 "type": "array",
156 "items": {
157 "type": "string"
158 }
159 }
160 )
161 );
162 }
163
164 #[test]
165 fn test_empty_array() {
166 let hypothesis = SchemaHypothesis::new(ArrayNode::new_untyped());
167
168 let actual = render_json_schema(&hypothesis);
169
170 assert_eq!(actual, json!({ "type": "array" }));
171 }
172
173 #[test]
174 fn test_any() {
175 let node_type = AnyNode::new(btreeset![StringNode::new().into(), NodeType::Boolean]).into();
176
177 let actual = render_node(&node_type);
178
179 assert_eq!(
180 actual,
181 json!({
182 "anyOf": [
183 {"type": "boolean"},
184 {"type": "string"},
185 ]
186 })
187 );
188 }
189
190 #[test]
191 fn test_any_one() {
192 let node_type = AnyNode::new(btreeset![StringNode::new().into()]).into();
193
194 let actual = render_node(&node_type);
195
196 assert_eq!(
197 actual,
198 json!({
199 "anyOf": [
200 {"type": "string"}
201 ]
202 })
203 );
204 }
205
206 #[test]
207 fn test_any_empty() {
208 let node_type = AnyNode::new(btreeset![]).into();
209
210 let actual = render_node(&node_type);
211
212 assert_eq!(
213 actual,
214 json!({
215 "anyOf": []
216 })
217 );
218 }
219
220 #[test]
221 fn test_any_complex_types() {
222 let node_type = AnyNode::new(btreeset![ObjectNode::new(btreemap! {
223 "id".to_string() => ObjectProperty::new(IntegerNode::new())
224 })
225 .into()])
226 .into();
227
228 let actual = render_node(&node_type);
229
230 assert_eq!(
231 actual,
232 json!({
233 "anyOf": [
234 {
235 "type": "object",
236 "properties": {
237 "id": {
238 "type": "integer"
239 }
240 },
241 "required": ["id"]
242 }
243 ]
244 })
245 );
246 }
247}