1use indexmap::IndexMap;
8use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
12#[serde(deny_unknown_fields)]
13pub struct SchemaMetadata {
14 #[serde(skip_serializing_if = "Option::is_none")]
15 pub title: Option<String>,
16 #[serde(skip_serializing_if = "Option::is_none")]
17 pub description: Option<String>,
18 #[serde(skip_serializing_if = "Option::is_none")]
19 pub deprecated: Option<bool>,
20 #[serde(skip_serializing_if = "Option::is_none")]
21 pub examples: Option<Vec<serde_json::Value>>,
22}
23
24#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
29#[serde(untagged)]
30#[allow(clippy::large_enum_variant)]
31pub enum JsonSchema {
32 Boolean(bool),
34
35 Reference(ReferenceSchema),
37
38 Enum(EnumSchema),
40
41 Const(ConstSchema),
43
44 AllOf(AllOfSchema),
46
47 AnyOf(AnyOfSchema),
49
50 OneOf(OneOfSchema),
52
53 Not(NotSchema),
55
56 Typed(TypedSchema),
58
59 Generic(GenericSchema),
62}
63
64#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
66#[serde(deny_unknown_fields)]
67pub struct ReferenceSchema {
68 #[serde(rename = "$ref")]
69 pub reference: String,
70
71 #[serde(flatten)]
72 pub metadata: SchemaMetadata,
73}
74
75#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
77#[serde(deny_unknown_fields)]
78pub struct EnumSchema {
79 #[serde(rename = "enum")]
80 pub values: Vec<serde_json::Value>,
81
82 #[serde(flatten)]
83 pub metadata: SchemaMetadata,
84}
85
86#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
88#[serde(deny_unknown_fields)]
89pub struct ConstSchema {
90 #[serde(rename = "const")]
91 pub value: serde_json::Value,
92
93 #[serde(flatten)]
94 pub metadata: SchemaMetadata,
95}
96
97#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
99#[serde(deny_unknown_fields)]
100pub struct AllOfSchema {
101 #[serde(rename = "allOf")]
102 pub schemas: Vec<JsonSchema>,
103
104 #[serde(flatten)]
105 pub metadata: SchemaMetadata,
106}
107
108#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
110#[serde(deny_unknown_fields)]
111pub struct AnyOfSchema {
112 #[serde(rename = "anyOf")]
113 pub schemas: Vec<JsonSchema>,
114
115 #[serde(flatten)]
116 pub metadata: SchemaMetadata,
117}
118
119#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
121#[serde(deny_unknown_fields)]
122pub struct OneOfSchema {
123 #[serde(rename = "oneOf")]
124 pub schemas: Vec<JsonSchema>,
125
126 #[serde(flatten)]
127 pub metadata: SchemaMetadata,
128}
129
130#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
132#[serde(deny_unknown_fields)]
133pub struct NotSchema {
134 pub not: Box<JsonSchema>,
135
136 #[serde(flatten)]
137 pub metadata: SchemaMetadata,
138}
139
140#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
142#[serde(tag = "type", deny_unknown_fields)]
143pub enum TypedSchema {
144 #[serde(rename = "string")]
145 String(StringSchema),
146
147 #[serde(rename = "number")]
148 Number(NumberSchema),
149
150 #[serde(rename = "integer")]
151 Integer(IntegerSchema),
152
153 #[serde(rename = "boolean")]
154 Boolean(BooleanSchema),
155
156 #[serde(rename = "null")]
157 Null(NullSchema),
158
159 #[serde(rename = "array")]
160 Array(ArraySchema),
161
162 #[serde(rename = "object")]
163 Object(ObjectSchema),
164}
165
166#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
168#[serde(deny_unknown_fields)]
169pub struct StringSchema {
170 #[serde(rename = "minLength", skip_serializing_if = "Option::is_none")]
171 pub min_length: Option<u32>,
172
173 #[serde(rename = "maxLength", skip_serializing_if = "Option::is_none")]
174 pub max_length: Option<u32>,
175
176 #[serde(skip_serializing_if = "Option::is_none")]
177 pub pattern: Option<String>,
178
179 #[serde(skip_serializing_if = "Option::is_none")]
180 pub format: Option<String>,
181
182 #[serde(skip_serializing_if = "Option::is_none")]
183 pub default: Option<String>,
184
185 #[serde(flatten)]
186 pub metadata: SchemaMetadata,
187}
188
189#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
191#[serde(deny_unknown_fields)]
192pub struct NumberSchema {
193 #[serde(skip_serializing_if = "Option::is_none")]
194 pub minimum: Option<f64>,
195
196 #[serde(skip_serializing_if = "Option::is_none")]
197 pub maximum: Option<f64>,
198
199 #[serde(rename = "exclusiveMinimum", skip_serializing_if = "Option::is_none")]
200 pub exclusive_minimum: Option<f64>,
201
202 #[serde(rename = "exclusiveMaximum", skip_serializing_if = "Option::is_none")]
203 pub exclusive_maximum: Option<f64>,
204
205 #[serde(rename = "multipleOf", skip_serializing_if = "Option::is_none")]
206 pub multiple_of: Option<f64>,
207
208 #[serde(skip_serializing_if = "Option::is_none")]
209 pub default: Option<f64>,
210
211 #[serde(flatten)]
212 pub metadata: SchemaMetadata,
213}
214
215#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
217#[serde(deny_unknown_fields)]
218pub struct IntegerSchema {
219 #[serde(skip_serializing_if = "Option::is_none")]
220 pub minimum: Option<i64>,
221
222 #[serde(skip_serializing_if = "Option::is_none")]
223 pub maximum: Option<i64>,
224
225 #[serde(rename = "exclusiveMinimum", skip_serializing_if = "Option::is_none")]
226 pub exclusive_minimum: Option<i64>,
227
228 #[serde(rename = "exclusiveMaximum", skip_serializing_if = "Option::is_none")]
229 pub exclusive_maximum: Option<i64>,
230
231 #[serde(rename = "multipleOf", skip_serializing_if = "Option::is_none")]
232 pub multiple_of: Option<i64>,
233
234 #[serde(skip_serializing_if = "Option::is_none")]
235 pub default: Option<i64>,
236
237 #[serde(flatten)]
238 pub metadata: SchemaMetadata,
239}
240
241#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
243#[serde(deny_unknown_fields)]
244pub struct BooleanSchema {
245 #[serde(skip_serializing_if = "Option::is_none")]
246 pub default: Option<bool>,
247
248 #[serde(flatten)]
249 pub metadata: SchemaMetadata,
250}
251
252#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
254#[serde(deny_unknown_fields)]
255pub struct NullSchema {
256 #[serde(flatten)]
257 pub metadata: SchemaMetadata,
258}
259
260#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
262#[serde(deny_unknown_fields)]
263pub struct ArraySchema {
264 #[serde(skip_serializing_if = "Option::is_none")]
265 pub items: Option<Box<JsonSchema>>,
266
267 #[serde(rename = "prefixItems", skip_serializing_if = "Option::is_none")]
270 pub prefix_items: Option<Vec<JsonSchema>>,
271
272 #[serde(rename = "minItems", skip_serializing_if = "Option::is_none")]
273 pub min_items: Option<u32>,
274
275 #[serde(rename = "maxItems", skip_serializing_if = "Option::is_none")]
276 pub max_items: Option<u32>,
277
278 #[serde(rename = "uniqueItems", skip_serializing_if = "Option::is_none")]
279 pub unique_items: Option<bool>,
280
281 #[serde(skip_serializing_if = "Option::is_none")]
282 pub contains: Option<Box<JsonSchema>>,
283
284 #[serde(flatten)]
285 pub metadata: SchemaMetadata,
286}
287
288#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
290#[serde(deny_unknown_fields)]
291pub struct ObjectSchema {
292 #[serde(skip_serializing_if = "Option::is_none")]
293 pub properties: Option<IndexMap<String, JsonSchema>>,
294
295 #[serde(skip_serializing_if = "Option::is_none")]
296 pub required: Option<Vec<String>>,
297
298 #[serde(
299 rename = "additionalProperties",
300 skip_serializing_if = "Option::is_none"
301 )]
302 pub additional_properties: Option<AdditionalProperties>,
303
304 #[serde(flatten)]
307 pub metadata: SchemaMetadata,
308}
309
310#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
312#[serde(untagged)]
313pub enum AdditionalProperties {
314 Bool(bool),
315 Schema(Box<JsonSchema>),
316}
317
318#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
325#[serde(deny_unknown_fields)]
326pub struct GenericSchema {
327 #[serde(skip_serializing_if = "Option::is_none")]
329 pub properties: Option<IndexMap<String, JsonSchema>>,
330 #[serde(skip_serializing_if = "Option::is_none")]
331 pub required: Option<Vec<String>>,
332
333 #[serde(
334 rename = "additionalProperties",
335 skip_serializing_if = "Option::is_none"
336 )]
337 pub additional_properties: Option<AdditionalProperties>,
338
339 #[serde(skip_serializing_if = "Option::is_none")]
340 pub default: Option<serde_json::Value>,
341
342 #[serde(rename = "$schema", skip_serializing_if = "Option::is_none")]
344 pub schema: Option<String>,
345
346 #[serde(rename = "$id", skip_serializing_if = "Option::is_none")]
347 pub id: Option<String>,
348
349 #[serde(rename = "$defs", skip_serializing_if = "Option::is_none")]
350 pub defs: Option<IndexMap<String, JsonSchema>>,
351
352 #[serde(skip_serializing_if = "Option::is_none")]
353 pub definitions: Option<IndexMap<String, JsonSchema>>,
354
355 #[serde(flatten)]
356 pub metadata: SchemaMetadata,
357}
358
359#[cfg(test)]
360mod tests {
361 use super::*;
362
363 #[test]
364 fn test_parse_boolean_schema_true() {
365 let json = "true";
366 let schema: JsonSchema = serde_json::from_str(json).unwrap();
367 match schema {
368 JsonSchema::Boolean(true) => {}
369 _ => panic!("Expected Boolean(true)"),
370 }
371 }
372
373 #[test]
374 fn test_parse_boolean_schema_false() {
375 let json = "false";
376 let schema: JsonSchema = serde_json::from_str(json).unwrap();
377 match schema {
378 JsonSchema::Boolean(false) => {}
379 _ => panic!("Expected Boolean(false)"),
380 }
381 }
382
383 #[test]
384 fn test_parse_simple_string_schema() {
385 let json = r#"{"type": "string"}"#;
386 let schema: JsonSchema = serde_json::from_str(json).unwrap();
387 assert_eq!(
388 schema,
389 JsonSchema::Typed(TypedSchema::String(StringSchema {
390 min_length: None,
391 max_length: None,
392 pattern: None,
393 format: None,
394 default: None,
395 metadata: SchemaMetadata::default(),
396 }))
397 );
398 }
399
400 #[test]
401 fn test_parse_string_with_constraints() {
402 let json = r#"{
403 "type": "string",
404 "minLength": 3,
405 "maxLength": 20,
406 "pattern": "^[a-z]+$"
407 }"#;
408 let schema: JsonSchema = serde_json::from_str(json).unwrap();
409 assert_eq!(
410 schema,
411 JsonSchema::Typed(TypedSchema::String(StringSchema {
412 min_length: Some(3),
413 max_length: Some(20),
414 pattern: Some("^[a-z]+$".to_string()),
415 format: None,
416 default: None,
417 metadata: SchemaMetadata::default(),
418 }))
419 );
420 }
421
422 #[test]
423 fn test_parse_integer_schema() {
424 let json = r#"{
425 "type": "integer",
426 "minimum": 0,
427 "maximum": 100
428 }"#;
429 let schema: JsonSchema = serde_json::from_str(json).unwrap();
430 match schema {
431 JsonSchema::Typed(TypedSchema::Integer(i)) => {
432 assert_eq!(i.minimum, Some(0));
433 assert_eq!(i.maximum, Some(100));
434 }
435 _ => panic!("Expected Typed(Integer)"),
436 }
437 }
438
439 #[test]
440 fn test_parse_number_schema() {
441 let json = r#"{
442 "type": "number",
443 "minimum": 0.0,
444 "exclusiveMaximum": 1.0
445 }"#;
446 let schema: JsonSchema = serde_json::from_str(json).unwrap();
447 match schema {
448 JsonSchema::Typed(TypedSchema::Number(n)) => {
449 assert_eq!(n.minimum, Some(0.0));
450 assert_eq!(n.exclusive_maximum, Some(1.0));
451 }
452 _ => panic!("Expected Typed(Number)"),
453 }
454 }
455
456 #[test]
457 fn test_parse_array_schema() {
458 let json = r#"{
459 "type": "array",
460 "items": {"type": "string"},
461 "minItems": 1,
462 "uniqueItems": true
463 }"#;
464 let schema: JsonSchema = serde_json::from_str(json).unwrap();
465 match schema {
466 JsonSchema::Typed(TypedSchema::Array(a)) => {
467 assert_eq!(a.min_items, Some(1));
468 assert_eq!(a.unique_items, Some(true));
469 assert!(a.items.is_some());
470 }
471 _ => panic!("Expected Typed(Array)"),
472 }
473 }
474
475 #[test]
476 fn test_parse_object_with_properties() {
477 let json = r#"{
478 "type": "object",
479 "properties": {
480 "name": {"type": "string"},
481 "age": {"type": "integer"}
482 },
483 "required": ["name"]
484 }"#;
485 let schema: JsonSchema = serde_json::from_str(json).unwrap();
486 match schema {
487 JsonSchema::Typed(TypedSchema::Object(o)) => {
488 assert!(o.properties.is_some());
489 let props = o.properties.unwrap();
490 assert_eq!(props.len(), 2);
491 assert!(props.contains_key("name"));
492 assert!(props.contains_key("age"));
493 assert_eq!(o.required, Some(vec!["name".to_string()]));
494 }
495 _ => panic!("Expected Typed(Object)"),
496 }
497 }
498
499 #[test]
500 fn test_parse_object_with_additional_properties_bool() {
501 let json = r#"{
502 "type": "object",
503 "additionalProperties": false
504 }"#;
505 let schema: JsonSchema = serde_json::from_str(json).unwrap();
506 match schema {
507 JsonSchema::Typed(TypedSchema::Object(o)) => match o.additional_properties {
508 Some(AdditionalProperties::Bool(false)) => {}
509 _ => panic!("Expected additionalProperties: false"),
510 },
511 _ => panic!("Expected Typed(Object)"),
512 }
513 }
514
515 #[test]
516 fn test_parse_object_with_additional_properties_schema() {
517 let json = r#"{
518 "type": "object",
519 "additionalProperties": {"type": "string"}
520 }"#;
521 let schema: JsonSchema = serde_json::from_str(json).unwrap();
522 match schema {
523 JsonSchema::Typed(TypedSchema::Object(o)) => match o.additional_properties {
524 Some(AdditionalProperties::Schema(_)) => {}
525 _ => panic!("Expected additionalProperties with schema"),
526 },
527 _ => panic!("Expected Typed(Object)"),
528 }
529 }
530
531 #[test]
532 fn test_parse_reference_schema() {
533 let json = r##"{"$ref": "#/definitions/User"}"##;
534 let schema: JsonSchema = serde_json::from_str(json).unwrap();
535 match schema {
536 JsonSchema::Reference(r) => {
537 assert_eq!(r.reference, "#/definitions/User");
538 }
539 _ => panic!("Expected Reference"),
540 }
541 }
542
543 #[test]
544 fn test_parse_enum_schema() {
545 let json = r#"{"enum": ["red", "green", "blue"]}"#;
546 let schema: JsonSchema = serde_json::from_str(json).unwrap();
547 match schema {
548 JsonSchema::Enum(e) => {
549 assert_eq!(e.values.len(), 3);
550 }
551 _ => panic!("Expected Enum"),
552 }
553 }
554
555 #[test]
556 fn test_parse_const_schema() {
557 let json = r#"{"const": "fixed-value"}"#;
558 let schema: JsonSchema = serde_json::from_str(json).unwrap();
559 match schema {
560 JsonSchema::Const(c) => {
561 assert_eq!(c.value, serde_json::json!("fixed-value"));
562 }
563 _ => panic!("Expected Const"),
564 }
565 }
566
567 #[test]
568 fn test_parse_anyof_schema() {
569 let json = r#"{
570 "anyOf": [
571 {"type": "string"},
572 {"type": "number"}
573 ]
574 }"#;
575 let schema: JsonSchema = serde_json::from_str(json).unwrap();
576 match schema {
577 JsonSchema::AnyOf(a) => {
578 assert_eq!(a.schemas.len(), 2);
579 }
580 _ => panic!("Expected AnyOf"),
581 }
582 }
583
584 #[test]
585 fn test_parse_allof_schema() {
586 let json = r#"{
587 "allOf": [
588 {"type": "object"},
589 {"properties": {"name": {"type": "string"}}}
590 ]
591 }"#;
592 let schema: JsonSchema = serde_json::from_str(json).unwrap();
593 match schema {
594 JsonSchema::AllOf(a) => {
595 assert_eq!(a.schemas.len(), 2);
596 }
597 other => panic!("Expected AllOf, got: {:?}", other),
598 }
599 }
600
601 #[test]
602 fn test_parse_empty_schema() {
603 let json = r#"{}"#;
604 let schema: JsonSchema = serde_json::from_str(json).unwrap();
605 match schema {
606 JsonSchema::Generic(g) => {
607 assert!(g.properties.is_none());
609 assert!(g.metadata.title.is_none());
610 }
611 _ => panic!("Expected Generic"),
612 }
613 }
614
615 #[test]
616 fn test_parse_empty_schema_with_metadata() {
617 let json = r#"{
618 "title": "My Schema",
619 "description": "A test schema"
620 }"#;
621 let schema: JsonSchema = serde_json::from_str(json).unwrap();
622 match schema {
623 JsonSchema::Generic(g) => {
624 assert_eq!(g.metadata.title, Some("My Schema".to_string()));
625 assert_eq!(g.metadata.description, Some("A test schema".to_string()));
626 }
627 _ => panic!("Expected Generic"),
628 }
629 }
630
631 #[test]
632 #[should_panic]
633 fn test_reject_unknown_field_in_string_schema() {
634 let json = r#"{
635 "type": "string",
636 "unknownField": "value"
637 }"#;
638 let _schema: JsonSchema = serde_json::from_str(json).unwrap();
639 }
640
641 #[test]
642 #[should_panic]
643 fn test_reject_unknown_field_in_object_schema() {
644 let json = r#"{
645 "type": "object",
646 "invalidField": true
647 }"#;
648 let _schema: JsonSchema = serde_json::from_str(json).unwrap();
649 }
650
651 #[test]
652 fn test_roundtrip_string_schema() {
653 let original = JsonSchema::Typed(TypedSchema::String(StringSchema {
654 min_length: Some(5),
655 max_length: Some(100),
656 pattern: Some("^[A-Z]".to_string()),
657 format: Some("email".to_string()),
658 default: None,
659 metadata: SchemaMetadata {
660 title: Some("Email".to_string()),
661 description: Some("User email address".to_string()),
662 deprecated: None,
663 examples: None,
664 },
665 }));
666
667 let json = serde_json::to_string(&original).unwrap();
669
670 let deserialized: JsonSchema = serde_json::from_str(&json).unwrap();
672
673 assert_eq!(original, deserialized);
675 }
676
677 #[test]
678 fn test_roundtrip_object_schema() {
679 let mut properties = IndexMap::new();
680 properties.insert(
681 "name".to_string(),
682 JsonSchema::Typed(TypedSchema::String(StringSchema {
683 min_length: Some(1),
684 max_length: None,
685 pattern: None,
686 format: None,
687 default: None,
688 metadata: SchemaMetadata::default(),
689 })),
690 );
691 properties.insert(
692 "age".to_string(),
693 JsonSchema::Typed(TypedSchema::Integer(IntegerSchema {
694 minimum: Some(0),
695 maximum: Some(150),
696 exclusive_minimum: None,
697 exclusive_maximum: None,
698 multiple_of: None,
699 default: None,
700 metadata: SchemaMetadata::default(),
701 })),
702 );
703
704 let original = JsonSchema::Typed(TypedSchema::Object(ObjectSchema {
705 properties: Some(properties),
706 required: Some(vec!["name".to_string()]),
707 additional_properties: Some(AdditionalProperties::Bool(false)),
708 metadata: SchemaMetadata::default(),
709 }));
710
711 let json = serde_json::to_string(&original).unwrap();
713
714 let deserialized: JsonSchema = serde_json::from_str(&json).unwrap();
716
717 assert_eq!(original, deserialized);
719 }
720
721 #[test]
722 fn test_roundtrip_anyof_schema() {
723 let original = JsonSchema::AnyOf(AnyOfSchema {
724 schemas: vec![
725 JsonSchema::Typed(TypedSchema::String(StringSchema::default())),
726 JsonSchema::Typed(TypedSchema::Integer(IntegerSchema::default())),
727 ],
728 metadata: SchemaMetadata::default(),
729 });
730
731 let json = serde_json::to_string(&original).unwrap();
733
734 let deserialized: JsonSchema = serde_json::from_str(&json).unwrap();
736
737 assert_eq!(original, deserialized);
739 }
740
741 #[test]
742 fn test_parse_boolean_type_schema() {
743 let json = r#"{"type": "boolean"}"#;
744 let schema: JsonSchema = serde_json::from_str(json).unwrap();
745 match schema {
746 JsonSchema::Typed(TypedSchema::Boolean(_)) => {}
747 _ => panic!("Expected Typed(Boolean)"),
748 }
749 }
750
751 #[test]
752 fn test_parse_null_type_schema() {
753 let json = r#"{"type": "null"}"#;
754 let schema: JsonSchema = serde_json::from_str(json).unwrap();
755 match schema {
756 JsonSchema::Typed(TypedSchema::Null(_)) => {}
757 _ => panic!("Expected Typed(Null)"),
758 }
759 }
760
761 #[test]
762 fn test_parse_oneof_schema() {
763 let json = r#"{
764 "oneOf": [
765 {"type": "string"},
766 {"type": "number"}
767 ]
768 }"#;
769 let schema: JsonSchema = serde_json::from_str(json).unwrap();
770 match schema {
771 JsonSchema::OneOf(o) => {
772 assert_eq!(o.schemas.len(), 2);
773 }
774 _ => panic!("Expected OneOf"),
775 }
776 }
777
778 #[test]
779 fn test_parse_not_schema() {
780 let json = r#"{
781 "not": {"type": "string"}
782 }"#;
783 let schema: JsonSchema = serde_json::from_str(json).unwrap();
784 match schema {
785 JsonSchema::Not(n) => {
786 assert!(matches!(*n.not, JsonSchema::Typed(TypedSchema::String(_))));
787 }
788 _ => panic!("Expected Not"),
789 }
790 }
791
792 #[test]
793 fn test_parse_generic_schema_with_properties() {
794 let json = r#"{
795 "properties": {
796 "name": {"type": "string"}
797 }
798 }"#;
799 let schema: JsonSchema = serde_json::from_str(json).unwrap();
800 match schema {
801 JsonSchema::Generic(g) => {
802 assert!(g.properties.is_some());
803 let props = g.properties.unwrap();
804 assert_eq!(props.len(), 1);
805 assert!(props.contains_key("name"));
806 }
807 _ => panic!("Expected Generic"),
808 }
809 }
810
811 #[test]
812 fn test_parse_nested_allof() {
813 let json = r#"{
814 "allOf": [
815 {
816 "allOf": [
817 {"type": "object"},
818 {"properties": {"id": {"type": "string"}}}
819 ]
820 },
821 {"properties": {"name": {"type": "string"}}}
822 ]
823 }"#;
824 let schema: JsonSchema = serde_json::from_str(json).unwrap();
825 match schema {
826 JsonSchema::AllOf(a) => {
827 assert_eq!(a.schemas.len(), 2);
828 assert!(matches!(a.schemas[0], JsonSchema::AllOf(_)));
830 }
831 _ => panic!("Expected AllOf"),
832 }
833 }
834
835 #[test]
836 fn test_roundtrip_with_metadata() {
837 let original = JsonSchema::Typed(TypedSchema::Integer(IntegerSchema {
838 minimum: Some(0),
839 maximum: Some(100),
840 exclusive_minimum: None,
841 exclusive_maximum: None,
842 multiple_of: None,
843 default: Some(50),
844 metadata: SchemaMetadata {
845 title: Some("Age".to_string()),
846 description: Some("User's age in years".to_string()),
847 deprecated: None,
848 examples: None,
849 },
850 }));
851
852 let json = serde_json::to_string(&original).unwrap();
853 let deserialized: JsonSchema = serde_json::from_str(&json).unwrap();
854
855 assert_eq!(original, deserialized);
856 }
857
858 #[test]
859 fn test_parse_array_with_contains() {
860 let json = r#"{
861 "type": "array",
862 "contains": {"type": "string"}
863 }"#;
864 let schema: JsonSchema = serde_json::from_str(json).unwrap();
865 match schema {
866 JsonSchema::Typed(TypedSchema::Array(a)) => {
867 assert!(a.contains.is_some());
868 }
869 _ => panic!("Expected Typed(Array)"),
870 }
871 }
872
873 #[test]
874 fn test_parse_integer_with_multiple_of() {
875 let json = r#"{
876 "type": "integer",
877 "multipleOf": 5
878 }"#;
879 let schema: JsonSchema = serde_json::from_str(json).unwrap();
880 match schema {
881 JsonSchema::Typed(TypedSchema::Integer(i)) => {
882 assert_eq!(i.multiple_of, Some(5));
883 }
884 _ => panic!("Expected Typed(Integer)"),
885 }
886 }
887
888 #[test]
889 fn test_parse_string_with_format() {
890 let json = r#"{
891 "type": "string",
892 "format": "email"
893 }"#;
894 let schema: JsonSchema = serde_json::from_str(json).unwrap();
895 match schema {
896 JsonSchema::Typed(TypedSchema::String(s)) => {
897 assert_eq!(s.format, Some("email".to_string()));
898 }
899 _ => panic!("Expected Typed(String)"),
900 }
901 }
902
903 #[test]
904 fn test_roundtrip_reference_with_metadata() {
905 let original = JsonSchema::Reference(ReferenceSchema {
906 reference: "#/definitions/User".to_string(),
907 metadata: SchemaMetadata {
908 title: Some("User Reference".to_string()),
909 description: Some("Reference to User type".to_string()),
910 deprecated: None,
911 examples: None,
912 },
913 });
914
915 let json = serde_json::to_string(&original).unwrap();
916 let deserialized: JsonSchema = serde_json::from_str(&json).unwrap();
917
918 assert_eq!(original, deserialized);
919 }
920}