1use core::str;
45
46pub use json_schema_derive_macro::JsonSchema;
47pub trait JsonSchema {
54 fn json_schema() -> serde_json::Value;
58}
59
60macro_rules! impl_json_schema {
61 ($name:expr, $($t:ty),*) => {
62 $(
63 impl JsonSchema for $t {
64 fn json_schema() -> serde_json::Value {
65 serde_json::json!({ "type": $name })
66 }
67 }
68 )*
69 };
70}
71
72impl_json_schema!("number", u8, u16, u32, u64, i8, i16, i32, i64, f32, f64);
73impl_json_schema!("boolean", bool);
74impl_json_schema!("string", String, &str);
75
76impl JsonSchema for () {
77 fn json_schema() -> serde_json::Value {
78 serde_json::json!({ "type": "null" })
79 }
80}
81
82impl<T: JsonSchema> JsonSchema for Vec<T> {
83 fn json_schema() -> serde_json::Value {
84 serde_json::json!({ "type": "array", "items": T::json_schema() })
85 }
86}
87
88impl<T: JsonSchema, const N: usize> JsonSchema for [T; N] {
89 fn json_schema() -> serde_json::Value {
90 serde_json::json!({ "type": "array", "items": T::json_schema(), "maxItems": N, "minItems": N })
91 }
92}
93
94impl<T: JsonSchema> JsonSchema for Option<T> {
95 fn json_schema() -> serde_json::Value {
96 T::json_schema()
97 }
98}
99
100impl<T: JsonSchema> JsonSchema for &Option<T> {
101 fn json_schema() -> serde_json::Value {
102 T::json_schema()
103 }
104}
105
106impl<T: JsonSchema> JsonSchema for Box<T> {
107 fn json_schema() -> serde_json::Value {
108 T::json_schema()
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115 use serde::Serialize;
116 use serde_json::json;
117
118 pub fn valid<T: JsonSchema + Serialize>(instance: &T) -> bool {
119 let schema = T::json_schema();
120 let json = serde_json::to_value(instance).unwrap();
121 jsonschema::is_valid(&schema, &json)
122 }
123
124 #[test]
125 fn test_impl_json_schema() {
126 assert_eq!(u32::json_schema(), json!({ "type": "number" }));
127 assert_eq!(bool::json_schema(), json!({ "type": "boolean" }));
128 assert_eq!(String::json_schema(), json!({ "type": "string" }));
129 assert_eq!(
130 <Vec<u32>>::json_schema(),
131 json!({ "type": "array", "items": { "type": "number" } })
132 );
133 assert_eq!(<Option<bool>>::json_schema(), json!({ "type": "boolean" }));
134 assert_eq!(
135 <[u32; 3]>::json_schema(),
136 json!({ "type": "array", "items": { "type": "number" }, "maxItems": 3, "minItems": 3 })
137 );
138
139 assert!(valid::<u32>(&10));
140 assert!(valid::<bool>(&true));
141 assert!(valid::<String>(&"test".to_string()));
142 assert!(valid::<Vec<u32>>(&vec![1, 2, 3]));
143 assert!(valid::<Option<bool>>(&Some(true)));
144 assert!(valid::<[u32; 3]>(&[1, 2, 3]));
145 }
146
147 #[derive(JsonSchema, Serialize)]
148 #[json_schema(comment = "Test comment")]
149 #[allow(dead_code)]
150 struct TestStruct {
151 #[json_schema(comment = "test field", minLength = 3)]
152 name: String,
153 age: u32,
154 active: Option<bool>,
155 scores: Vec<i32>,
156 }
157
158 #[test]
159 fn test_struct_schema() {
160 let schema = TestStruct::json_schema();
161 let expected = json!({
162 "type": "object",
163 "properties": {
164 "name": {
165 "type": "string",
166 "comment": "test field",
167 "minLength": 3
168 },
169 "age": {
170 "type": "number"
171 },
172 "active": {
173 "type": "boolean"
174 },
175 "scores": {
176 "type": "array",
177 "items": {"type": "number"}
178 }
179 },
180 "required": ["name", "age", "scores"],
181 "comment": "Test comment"
182 });
183 assert_eq!(schema, expected);
184 assert!(valid(&TestStruct {
185 name: "test".to_string(),
186 age: 10,
187 active: Some(true),
188 scores: vec![1, 2, 3],
189 }));
190 }
191
192 #[derive(JsonSchema)]
193 #[allow(dead_code)]
194 struct NestedStruct {
195 inner: Option<TestStruct>,
196 tags: Option<Vec<String>>,
197 }
198
199 #[test]
200 fn test_nested_struct() {
201 let schema = NestedStruct::json_schema();
202 let expected = json!({
203 "type": "object",
204 "properties": {
205 "inner": {
206 "type": "object",
207 "properties": {
208 "name": {
209 "type": "string",
210 "comment": "test field",
211 "minLength": 3
212 },
213 "age": {
214 "type": "number"
215 },
216 "active": {
217 "type": "boolean"
218 },
219 "scores": {
220 "type": "array",
221 "items": {"type": "number"}
222 }
223 },
224 "required": ["name", "age", "scores"],
225 "comment": "Test comment"
226 },
227 "tags": {
228 "type": "array",
229 "items": {"type": "string"}
230 }
231 },
232 "required": []
233 });
234 assert_eq!(schema, expected);
235 }
236
237 #[derive(JsonSchema, Serialize)]
238 #[json_schema(comment = "Test comment")]
239 #[allow(dead_code)]
240 struct TestStructUnnamed(String);
241
242 #[test]
243 fn test_struct_unnamed() {
244 let schema = TestStructUnnamed::json_schema();
245 let expected = json!({ "comment": "Test comment", "type": "string" });
246 assert_eq!(schema, expected);
247 assert!(valid(&TestStructUnnamed("test".to_string())));
248 }
249
250 #[derive(JsonSchema, Serialize)]
251 #[json_schema(comment = "Test comment")]
252 #[allow(dead_code)]
253 struct TestStructUnnamedMultiple(String, u32);
254
255 #[test]
256 fn test_struct_unnamed_multiple() {
257 let schema = TestStructUnnamedMultiple::json_schema();
258 let expected = json!({
259 "comment": "Test comment",
260 "type": "array",
261 "prefixItems": [{ "type": "string" }, { "type": "number" }],
262 "minItems": 2,
263 "maxItems": 2,
264 "unevaluatedItems": false,
265 });
266 assert_eq!(schema, expected);
267 assert!(valid(&TestStructUnnamedMultiple("test".to_string(), 10)));
268 }
269
270 #[derive(JsonSchema, Serialize)]
271 #[json_schema(comment = "Test comment")]
272 #[allow(dead_code)]
273 enum EnumUnit {
274 A,
275 B,
276 C,
277 }
278
279 #[test]
280 fn test_enum_unit() {
281 let schema = EnumUnit::json_schema();
282 let expected = json!({
283 "type": "string",
284 "comment": "Test comment",
285 "enum": ["A", "B", "C"],
286 });
287 println!("{:#?}", serde_json::to_value(&EnumUnit::A).unwrap());
288 assert_eq!(schema, expected);
289 assert!(valid(&EnumUnit::A));
290 assert!(valid(&EnumUnit::B));
291 assert!(valid(&EnumUnit::C));
292 }
293
294 #[derive(JsonSchema, Serialize)]
295 #[json_schema(comment = "Test comment")]
296 #[allow(dead_code)]
297 enum EnumUnnamed {
298 A(String),
299 B(u32),
300 }
301
302 #[test]
303 fn test_enum_unit_unnamed() {
304 let schema = EnumUnnamed::json_schema();
305 let expected = json!({
306 "type": "object",
307 "comment": "Test comment",
308 "properties": {
309 "A": { "type": "string" },
310 "B": { "type": "number" },
311 }
312 });
313 assert_eq!(schema, expected);
314 assert!(valid(&EnumUnnamed::A("test".to_string())));
315 assert!(valid(&EnumUnnamed::B(10)));
316 }
317
318 #[derive(JsonSchema, Serialize)]
319 #[json_schema(comment = "Test comment")]
320 #[allow(dead_code)]
321 enum EnumNamed {
322 A { name: String },
323 B { age: u32 },
324 }
325
326 #[test]
327 fn test_enum_named() {
328 let schema = EnumNamed::json_schema();
329 let expected = json!({
330 "type": "object",
331 "comment": "Test comment",
332 "properties": {
333 "A": { "type": "object", "properties": { "name": { "type": "string" } }, "required": ["name"] },
334 "B": { "type": "object", "properties": { "age": { "type": "number" } }, "required": ["age"] },
335 }
336 });
337 assert_eq!(schema, expected);
338 assert!(valid(&EnumNamed::A {
339 name: "test".to_string()
340 }));
341 assert!(valid(&EnumNamed::B { age: 10 }));
342 }
343
344 #[derive(JsonSchema, Serialize)]
345 #[allow(dead_code)]
346 struct TestStructDoc {
348 name: String,
350 }
351
352 #[test]
353 fn test_struct_doc() {
354 let schema = TestStructDoc::json_schema();
355 let expected = json!({ "type": "object", "description": "Test description", "properties": { "name": { "type": "string", "description": "Test field description" } }, "required": ["name"] });
356 assert_eq!(schema, expected);
357 assert!(valid(&TestStructDoc {
358 name: "test".to_string()
359 }));
360 }
361}
362
363#[cfg(feature = "serde-compat")]
364#[cfg(test)]
365mod tests_serde_compat {
366 use super::*;
367 use serde::Serialize;
368 use serde_json::json;
369
370 #[derive(JsonSchema, Serialize)]
371 #[json_schema(comment = "Test comment")]
372 #[allow(dead_code)]
373 struct TestStructWithSerde {
374 #[serde(skip)]
375 skip: u32,
376 #[serde(rename = "foo")]
377 renamed: u32,
378 }
379
380 #[test]
381 fn test_struct_with_serde() {
382 let schema = TestStructWithSerde::json_schema();
383 let expected = json!({
384 "type": "object",
385 "properties": { "foo": { "type": "number" } },
386 "required": ["foo"],
387 "comment": "Test comment"
388 });
389 assert_eq!(schema, expected);
390 assert!(tests::valid(&TestStructWithSerde {
391 skip: 0,
392 renamed: 10,
393 }));
394 }
395
396 #[derive(JsonSchema, Serialize)]
397 #[json_schema(comment = "Test comment")]
398 #[allow(dead_code)]
399 struct TestStructWithFlatten {
400 #[serde(flatten)]
401 inner: TestStructWithSerde,
402 }
403
404 #[test]
405 fn test_struct_with_flatten() {
406 let schema = TestStructWithFlatten::json_schema();
407 let expected = json!({
408 "type": "object",
409 "properties": { "foo": { "type": "number" } },
410 "required": ["foo"],
411 "comment": "Test comment"
412 });
413 println!("{:#?}", schema);
414 assert_eq!(schema, expected);
415 assert!(tests::valid(&TestStructWithFlatten {
416 inner: TestStructWithSerde {
417 skip: 0,
418 renamed: 10,
419 }
420 }));
421 }
422
423 #[derive(JsonSchema, Serialize)]
424 #[allow(dead_code)]
425 #[serde(tag = "type")]
426 enum EnumUnitSerdeTag {
427 A,
428 B,
429 }
430
431 #[test]
432 fn test_enum_serde_tag() {
433 let schema = EnumUnitSerdeTag::json_schema();
434 let expected = json!({
435 "oneOf": [
436 { "type": "object", "properties": { "type": { "type": "string", "const": "A" } }, "required": ["type"] },
437 { "type": "object", "properties": { "type": { "type": "string", "const": "B" } }, "required": ["type"] }
438 ]
439 });
440 assert_eq!(schema, expected);
441 assert!(tests::valid(&EnumUnitSerdeTag::A));
442 assert!(tests::valid(&EnumUnitSerdeTag::B));
443 }
444
445 #[derive(JsonSchema, Serialize)]
446 #[allow(dead_code)]
447 #[serde(tag = "type")]
448 enum EnumNamedSerdeTag {
449 A { name: String },
450 B { age: u32 },
451 C,
452 }
453
454 #[test]
455 fn test_enum_named_serde_tag() {
456 let schema = EnumNamedSerdeTag::json_schema();
457 let expected = json!({
458 "oneOf": [
459 { "type": "object", "properties": { "type": { "type": "string", "const": "A" }, "name": { "type": "string" } }, "required": ["name", "type"] },
460 { "type": "object", "properties": { "type": { "type": "string", "const": "B" }, "age": { "type": "number" } }, "required": ["age", "type"] },
461 { "type": "object", "properties": { "type": { "type": "string", "const": "C" } }, "required": ["type"] }
462 ]
463 });
464 assert_eq!(schema, expected);
465 assert!(tests::valid(&EnumNamedSerdeTag::A {
466 name: "test".to_string()
467 }));
468 assert!(tests::valid(&EnumNamedSerdeTag::B { age: 10 }));
469 }
470}