1use std::collections::HashMap;
2
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
6pub struct JsonSchema {
7 #[serde(skip_serializing_if = "Option::is_none")]
8 pub title: Option<String>,
9 #[serde(skip_serializing_if = "Option::is_none")]
10 pub description: Option<String>,
11 #[serde(flatten)]
12 pub r#type: Type,
13}
14
15#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
16#[serde(untagged)]
17pub enum Type {
18 PrimitiveFormat {
19 r#type: PrimitiveFormat,
20 format: Format,
21 },
22 SingleFormat {
23 r#type: [PrimitiveFormat; 1],
24 format: Format,
25 },
26 VariantFormat {
27 r#type: [PrimitiveFormat; 2],
28 format: Format,
29 },
30 Primitive {
31 r#type: Primitive,
32 },
33 Single {
34 r#type: [Primitive; 1],
35 },
36 Variant {
37 r#type: [Primitive; 2],
38 },
39 Compound(Compound),
40 Empty(Empty),
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
44#[serde(rename_all = "lowercase")]
45pub enum Primitive {
46 Null,
47 Boolean,
48 Integer,
49 Number,
50 String,
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
54#[serde(rename_all = "lowercase")]
55pub enum PrimitiveFormat {
56 Null,
57 String,
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
61#[serde(rename_all = "kebab-case")]
62pub enum Format {
63 Date,
64 Time,
65 DateTime,
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
69#[serde(tag = "type", rename_all = "lowercase")]
70pub enum Compound {
71 Array(Array),
72 Object(Object),
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
76#[serde(rename_all = "camelCase")]
77pub struct Array {
78 pub items: Box<Type>,
79}
80
81#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
82#[serde(rename_all = "camelCase")]
83pub struct Object {
84 pub properties: HashMap<String, Type>,
85 #[serde(skip_serializing_if = "Option::is_none")]
86 pub required: Option<Vec<String>>,
87 #[serde(skip_serializing_if = "Option::is_none")]
88 pub additional_properties: Option<bool>,
89}
90
91#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
92pub struct Empty {}
93
94#[cfg(test)]
95pub mod tests {
96 use std::collections::HashMap;
97
98 use crate::schema::{
99 Array, Compound, Empty, Format, JsonSchema, Object, Primitive, PrimitiveFormat, Type,
100 };
101
102 #[test]
103 fn test_null() {
104 let input = r#"{"type": "null"}"#;
105
106 let value: JsonSchema = serde_json::from_str(input).unwrap();
107
108 let expected = JsonSchema {
109 title: None,
110 description: None,
111 r#type: Type::Primitive {
112 r#type: Primitive::Null,
113 },
114 };
115 assert_eq!(value, expected);
116 }
117
118 #[test]
119 fn test_boolean() {
120 let input = r#"{"type": "boolean"}"#;
121
122 let value: JsonSchema = serde_json::from_str(input).unwrap();
123
124 let expected = JsonSchema {
125 title: None,
126 description: None,
127 r#type: Type::Primitive {
128 r#type: Primitive::Boolean,
129 },
130 };
131 assert_eq!(value, expected);
132 }
133
134 #[test]
135 fn test_integer() {
136 let input = r#"{"type": "integer"}"#;
137
138 let value: JsonSchema = serde_json::from_str(input).unwrap();
139
140 let expected = JsonSchema {
141 title: None,
142 description: None,
143 r#type: Type::Primitive {
144 r#type: Primitive::Integer,
145 },
146 };
147 assert_eq!(value, expected);
148 }
149
150 #[test]
151 fn test_string() {
152 let input = r#"{"type": "string"}"#;
153
154 let value: JsonSchema = serde_json::from_str(input).unwrap();
155
156 let expected = JsonSchema {
157 title: None,
158 description: None,
159 r#type: Type::Primitive {
160 r#type: Primitive::String,
161 },
162 };
163 assert_eq!(value, expected);
164 }
165
166 #[test]
167 fn test_single() {
168 let input = r#"{"type": ["string"]}"#;
169
170 let value: JsonSchema = serde_json::from_str(input).unwrap();
171
172 let expected = JsonSchema {
173 title: None,
174 description: None,
175 r#type: Type::Single {
176 r#type: [Primitive::String; 1],
177 },
178 };
179 assert_eq!(value, expected);
180 }
181
182 #[test]
183 fn test_array() {
184 let input = r#"{"type": "array", "items": {"type": "integer"}}"#;
185
186 let value: JsonSchema = serde_json::from_str(input).unwrap();
187
188 let expected = JsonSchema {
189 title: None,
190 description: None,
191 r#type: Type::Compound(Compound::Array(Array {
192 items: Box::new(Type::Primitive {
193 r#type: Primitive::Integer,
194 }),
195 })),
196 };
197 assert_eq!(value, expected);
198 }
199
200 #[test]
201 fn test_object() {
202 let input =
203 r#"{"required": ["id"], "type": "object", "properties": {"id": {"type": "integer"}}}"#;
204
205 let schema: JsonSchema = serde_json::from_str(input).unwrap();
206
207 let expected = JsonSchema {
208 title: None,
209 description: None,
210 r#type: Type::Compound(Compound::Object(Object {
211 properties: HashMap::from_iter(vec![(
212 "id".to_string(),
213 Type::Primitive {
214 r#type: Primitive::Integer,
215 },
216 )]),
217 required: Some(vec!["id".to_string()]),
218 additional_properties: None,
219 })),
220 };
221 assert_eq!(schema, expected);
222 }
223
224 #[test]
225 fn test_variant() {
226 let input = r#"{"required": ["id"], "type": "object", "properties": {"id": {"type": ["integer", "null"]}}}"#;
227
228 let schema: JsonSchema = serde_json::from_str(input).unwrap();
229
230 let expected = JsonSchema {
231 title: None,
232 description: None,
233 r#type: Type::Compound(Compound::Object(Object {
234 properties: HashMap::from_iter(vec![(
235 "id".to_string(),
236 Type::Variant {
237 r#type: [Primitive::Integer, Primitive::Null],
238 },
239 )]),
240 required: Some(vec!["id".to_string()]),
241 additional_properties: None,
242 })),
243 };
244 assert_eq!(schema, expected);
245 }
246
247 #[test]
248 fn test_date() {
249 let input = r#"
250 {
251 "type": "object",
252 "properties": {
253 "order_date": {
254 "type": [
255 "null",
256 "string"
257 ],
258 "format": "date-time"
259 }
260 }}
261 "#;
262
263 let schema: JsonSchema = serde_json::from_str(input).unwrap();
264
265 let expected = JsonSchema {
266 title: None,
267 description: None,
268 r#type: Type::Compound(Compound::Object(Object {
269 properties: HashMap::from_iter(vec![(
270 "order_date".to_string(),
271 Type::VariantFormat {
272 r#type: [PrimitiveFormat::Null, PrimitiveFormat::String],
273 format: Format::DateTime,
274 },
275 )]),
276 required: None,
277 additional_properties: None,
278 })),
279 };
280 assert_eq!(schema, expected);
281 }
282
283 #[test]
284 fn test_empty() {
285 let input = r#"{"required": ["id"], "type": "object", "properties": {"id": {}}}"#;
286
287 let schema: JsonSchema = serde_json::from_str(input).unwrap();
288
289 let expected = JsonSchema {
290 title: None,
291 description: None,
292 r#type: Type::Compound(Compound::Object(Object {
293 properties: HashMap::from_iter(vec![("id".to_string(), Type::Empty(Empty {}))]),
294 required: Some(vec!["id".to_string()]),
295 additional_properties: None,
296 })),
297 };
298 assert_eq!(schema, expected);
299 }
300}