1use serde_json::{Map, Value as JsonValue};
8
9#[derive(Debug, Clone, PartialEq, Eq)]
11pub enum ShapeSchema {
12 Any,
14 Object(Vec<(String, ShapeSchema)>, Vec<String>),
17 Array(Box<ShapeSchema>),
20 String,
22 Number,
24 Integer,
26 Boolean,
28 Null,
30}
31
32pub fn shape_to_json_schema(schema: &ShapeSchema) -> JsonValue {
34 match schema {
35 ShapeSchema::Any => JsonValue::Object(Map::new()),
36 ShapeSchema::Object(properties, required) => {
37 let mut props = Map::new();
38 for (name, property) in properties {
39 props.insert(name.clone(), shape_to_json_schema(property));
40 }
41 let mut object = Map::new();
42 object.insert("type".to_owned(), JsonValue::String("object".to_owned()));
43 object.insert("properties".to_owned(), JsonValue::Object(props));
44 object.insert(
45 "required".to_owned(),
46 JsonValue::Array(
47 required
48 .iter()
49 .map(|name| JsonValue::String(name.clone()))
50 .collect(),
51 ),
52 );
53 JsonValue::Object(object)
54 }
55 ShapeSchema::Array(items) => {
56 let mut object = Map::new();
57 object.insert("type".to_owned(), JsonValue::String("array".to_owned()));
58 object.insert("items".to_owned(), shape_to_json_schema(items));
59 JsonValue::Object(object)
60 }
61 ShapeSchema::String => typed("string"),
62 ShapeSchema::Number => typed("number"),
63 ShapeSchema::Integer => typed("integer"),
64 ShapeSchema::Boolean => typed("boolean"),
65 ShapeSchema::Null => typed("null"),
66 }
67}
68
69fn typed(kind: &str) -> JsonValue {
70 let mut object = Map::new();
71 object.insert("type".to_owned(), JsonValue::String(kind.to_owned()));
72 JsonValue::Object(object)
73}
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78 use serde_json::json;
79
80 #[test]
81 fn any_is_empty_schema() {
82 assert_eq!(shape_to_json_schema(&ShapeSchema::Any), json!({}));
83 }
84
85 #[test]
86 fn scalar_types() {
87 assert_eq!(
88 shape_to_json_schema(&ShapeSchema::String),
89 json!({"type": "string"})
90 );
91 assert_eq!(
92 shape_to_json_schema(&ShapeSchema::Number),
93 json!({"type": "number"})
94 );
95 assert_eq!(
96 shape_to_json_schema(&ShapeSchema::Integer),
97 json!({"type": "integer"})
98 );
99 assert_eq!(
100 shape_to_json_schema(&ShapeSchema::Boolean),
101 json!({"type": "boolean"})
102 );
103 assert_eq!(
104 shape_to_json_schema(&ShapeSchema::Null),
105 json!({"type": "null"})
106 );
107 }
108
109 #[test]
110 fn array_wraps_items() {
111 let schema = ShapeSchema::Array(Box::new(ShapeSchema::String));
112 assert_eq!(
113 shape_to_json_schema(&schema),
114 json!({"type": "array", "items": {"type": "string"}})
115 );
116 }
117
118 #[test]
119 fn object_with_properties_and_required() {
120 let schema = ShapeSchema::Object(
121 vec![
122 ("name".to_owned(), ShapeSchema::String),
123 ("age".to_owned(), ShapeSchema::Integer),
124 ],
125 vec!["name".to_owned()],
126 );
127 assert_eq!(
128 shape_to_json_schema(&schema),
129 json!({
130 "type": "object",
131 "properties": {
132 "name": {"type": "string"},
133 "age": {"type": "integer"},
134 },
135 "required": ["name"],
136 })
137 );
138 }
139
140 #[test]
141 fn nested_object_and_array() {
142 let schema = ShapeSchema::Object(
143 vec![(
144 "tags".to_owned(),
145 ShapeSchema::Array(Box::new(ShapeSchema::String)),
146 )],
147 vec![],
148 );
149 assert_eq!(
150 shape_to_json_schema(&schema),
151 json!({
152 "type": "object",
153 "properties": {
154 "tags": {"type": "array", "items": {"type": "string"}},
155 },
156 "required": [],
157 })
158 );
159 }
160
161 #[test]
162 fn any_nested_in_object() {
163 let schema = ShapeSchema::Object(vec![("data".to_owned(), ShapeSchema::Any)], vec![]);
164 assert_eq!(
165 shape_to_json_schema(&schema),
166 json!({
167 "type": "object",
168 "properties": {"data": {}},
169 "required": [],
170 })
171 );
172 }
173}