1use serde::{Deserialize, Serialize};
4use std::cell::RefCell;
5use std::collections::HashMap;
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
9#[serde(untagged)]
10pub enum Schema {
11 Boolean(bool),
13 Ref(RefSchema),
15 Object(ObjectSchema),
17 Array(ArraySchema),
19 Primitive(PrimitiveSchema),
21 Enum(EnumSchema),
23}
24
25impl Schema {
26 pub fn string() -> Self {
28 Schema::Primitive(PrimitiveSchema::string())
29 }
30
31 pub fn integer(format: Option<&str>) -> Self {
33 Schema::Primitive(PrimitiveSchema::integer(format))
34 }
35
36 pub fn number(format: Option<&str>) -> Self {
38 Schema::Primitive(PrimitiveSchema::number(format))
39 }
40
41 pub fn boolean() -> Self {
43 Schema::Primitive(PrimitiveSchema::boolean())
44 }
45
46 pub fn reference(name: &str) -> Self {
48 Schema::Ref(RefSchema {
49 reference: format!("#/components/schemas/{name}"),
50 })
51 }
52
53 pub fn array(items: Schema) -> Self {
55 Schema::Array(ArraySchema {
56 items: Box::new(items),
57 min_items: None,
58 max_items: None,
59 })
60 }
61
62 pub fn object(properties: HashMap<String, Schema>, required: Vec<String>) -> Self {
64 Schema::Object(ObjectSchema {
65 title: None,
66 description: None,
67 properties,
68 required,
69 additional_properties: None,
70 example: None,
71 })
72 }
73
74 #[must_use]
76 pub fn nullable(mut self) -> Self {
77 if let Schema::Primitive(ref mut p) = self {
78 p.nullable = true;
79 }
80 self
81 }
82
83 #[must_use]
85 pub fn with_title(mut self, title: impl Into<String>) -> Self {
86 if let Schema::Object(ref mut o) = self {
87 o.title = Some(title.into());
88 }
89 self
90 }
91
92 #[must_use]
94 pub fn with_description(mut self, description: impl Into<String>) -> Self {
95 if let Schema::Object(ref mut o) = self {
96 o.description = Some(description.into());
97 }
98 self
99 }
100
101 pub fn one_of(schemas: Vec<Schema>) -> Self {
103 Schema::Enum(EnumSchema {
104 one_of: schemas,
105 ..Default::default()
106 })
107 }
108
109 pub fn any_of(schemas: Vec<Schema>) -> Self {
111 Schema::Enum(EnumSchema {
112 any_of: schemas,
113 ..Default::default()
114 })
115 }
116
117 pub fn all_of(schemas: Vec<Schema>) -> Self {
119 Schema::Enum(EnumSchema {
120 all_of: schemas,
121 ..Default::default()
122 })
123 }
124
125 pub fn string_enum(values: Vec<String>) -> Self {
127 Schema::Primitive(PrimitiveSchema {
128 schema_type: SchemaType::String,
129 format: None,
130 nullable: false,
131 minimum: None,
132 maximum: None,
133 exclusive_minimum: None,
134 exclusive_maximum: None,
135 min_length: None,
136 max_length: None,
137 pattern: None,
138 enum_values: Some(values),
139 example: None,
140 })
141 }
142
143 pub fn one_of_with_discriminator(
145 schemas: Vec<Schema>,
146 property_name: impl Into<String>,
147 mapping: HashMap<String, String>,
148 ) -> Self {
149 Schema::Enum(EnumSchema {
150 one_of: schemas,
151 discriminator: Some(Discriminator {
152 property_name: property_name.into(),
153 mapping,
154 }),
155 ..Default::default()
156 })
157 }
158}
159
160#[derive(Debug, Clone, Serialize, Deserialize)]
162pub struct RefSchema {
163 #[serde(rename = "$ref")]
165 pub reference: String,
166}
167
168#[derive(Debug, Clone, Default, Serialize, Deserialize)]
170pub struct ObjectSchema {
171 #[serde(default, skip_serializing_if = "Option::is_none")]
173 pub title: Option<String>,
174 #[serde(default, skip_serializing_if = "Option::is_none")]
176 pub description: Option<String>,
177 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
179 pub properties: HashMap<String, Schema>,
180 #[serde(default, skip_serializing_if = "Vec::is_empty")]
182 pub required: Vec<String>,
183 #[serde(default, skip_serializing_if = "Option::is_none")]
185 pub additional_properties: Option<Box<Schema>>,
186 #[serde(default, skip_serializing_if = "Option::is_none")]
188 pub example: Option<serde_json::Value>,
189}
190
191#[derive(Debug, Clone, Serialize, Deserialize)]
193pub struct ArraySchema {
194 pub items: Box<Schema>,
196 #[serde(default, skip_serializing_if = "Option::is_none")]
198 pub min_items: Option<usize>,
199 #[serde(default, skip_serializing_if = "Option::is_none")]
201 pub max_items: Option<usize>,
202}
203
204#[derive(Debug, Clone, Default, Serialize, Deserialize)]
211pub struct EnumSchema {
212 #[serde(default, skip_serializing_if = "Option::is_none")]
214 pub title: Option<String>,
215 #[serde(default, skip_serializing_if = "Option::is_none")]
217 pub description: Option<String>,
218 #[serde(default, skip_serializing_if = "Vec::is_empty", rename = "oneOf")]
220 pub one_of: Vec<Schema>,
221 #[serde(default, skip_serializing_if = "Vec::is_empty", rename = "anyOf")]
223 pub any_of: Vec<Schema>,
224 #[serde(default, skip_serializing_if = "Vec::is_empty", rename = "allOf")]
226 pub all_of: Vec<Schema>,
227 #[serde(default, skip_serializing_if = "Option::is_none")]
229 pub discriminator: Option<Discriminator>,
230}
231
232#[derive(Debug, Clone, Serialize, Deserialize)]
234pub struct Discriminator {
235 #[serde(rename = "propertyName")]
237 pub property_name: String,
238 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
240 pub mapping: HashMap<String, String>,
241}
242
243#[derive(Debug, Clone, Serialize, Deserialize)]
245pub struct ConstSchema {
246 #[serde(rename = "const")]
248 pub const_value: serde_json::Value,
249}
250
251#[derive(Debug, Clone, Serialize, Deserialize)]
253pub struct StringEnumSchema {
254 #[serde(rename = "type")]
256 pub schema_type: SchemaType,
257 #[serde(rename = "enum")]
259 pub enum_values: Vec<String>,
260}
261
262#[derive(Debug, Clone, Serialize, Deserialize)]
264pub struct PrimitiveSchema {
265 #[serde(rename = "type")]
267 pub schema_type: SchemaType,
268 #[serde(default, skip_serializing_if = "Option::is_none")]
270 pub format: Option<String>,
271 #[serde(default, skip_serializing_if = "is_false")]
273 pub nullable: bool,
274 #[serde(default, skip_serializing_if = "Option::is_none")]
276 pub minimum: Option<i64>,
277 #[serde(default, skip_serializing_if = "Option::is_none")]
279 pub maximum: Option<i64>,
280 #[serde(
282 default,
283 skip_serializing_if = "Option::is_none",
284 rename = "exclusiveMinimum"
285 )]
286 pub exclusive_minimum: Option<i64>,
287 #[serde(
289 default,
290 skip_serializing_if = "Option::is_none",
291 rename = "exclusiveMaximum"
292 )]
293 pub exclusive_maximum: Option<i64>,
294 #[serde(default, skip_serializing_if = "Option::is_none", rename = "minLength")]
296 pub min_length: Option<usize>,
297 #[serde(default, skip_serializing_if = "Option::is_none", rename = "maxLength")]
299 pub max_length: Option<usize>,
300 #[serde(default, skip_serializing_if = "Option::is_none")]
302 pub pattern: Option<String>,
303 #[serde(default, skip_serializing_if = "Option::is_none", rename = "enum")]
305 pub enum_values: Option<Vec<String>>,
306 #[serde(default, skip_serializing_if = "Option::is_none")]
308 pub example: Option<serde_json::Value>,
309}
310
311impl PrimitiveSchema {
312 pub fn string() -> Self {
314 Self {
315 schema_type: SchemaType::String,
316 format: None,
317 nullable: false,
318 minimum: None,
319 maximum: None,
320 exclusive_minimum: None,
321 exclusive_maximum: None,
322 min_length: None,
323 max_length: None,
324 pattern: None,
325 enum_values: None,
326 example: None,
327 }
328 }
329
330 pub fn integer(format: Option<&str>) -> Self {
332 Self {
333 schema_type: SchemaType::Integer,
334 format: format.map(String::from),
335 nullable: false,
336 minimum: None,
337 maximum: None,
338 exclusive_minimum: None,
339 exclusive_maximum: None,
340 min_length: None,
341 max_length: None,
342 pattern: None,
343 enum_values: None,
344 example: None,
345 }
346 }
347
348 pub fn unsigned_integer(format: Option<&str>) -> Self {
350 Self {
351 schema_type: SchemaType::Integer,
352 format: format.map(String::from),
353 nullable: false,
354 minimum: Some(0),
355 maximum: None,
356 exclusive_minimum: None,
357 exclusive_maximum: None,
358 min_length: None,
359 max_length: None,
360 pattern: None,
361 enum_values: None,
362 example: None,
363 }
364 }
365
366 pub fn number(format: Option<&str>) -> Self {
368 Self {
369 schema_type: SchemaType::Number,
370 format: format.map(String::from),
371 nullable: false,
372 minimum: None,
373 maximum: None,
374 exclusive_minimum: None,
375 exclusive_maximum: None,
376 min_length: None,
377 max_length: None,
378 pattern: None,
379 enum_values: None,
380 example: None,
381 }
382 }
383
384 pub fn boolean() -> Self {
386 Self {
387 schema_type: SchemaType::Boolean,
388 format: None,
389 nullable: false,
390 minimum: None,
391 maximum: None,
392 exclusive_minimum: None,
393 exclusive_maximum: None,
394 min_length: None,
395 max_length: None,
396 pattern: None,
397 enum_values: None,
398 example: None,
399 }
400 }
401
402 #[must_use]
404 pub fn with_minimum(mut self, value: i64) -> Self {
405 self.minimum = Some(value);
406 self
407 }
408
409 #[must_use]
411 pub fn with_maximum(mut self, value: i64) -> Self {
412 self.maximum = Some(value);
413 self
414 }
415
416 #[must_use]
418 pub fn with_exclusive_minimum(mut self, value: i64) -> Self {
419 self.exclusive_minimum = Some(value);
420 self
421 }
422
423 #[must_use]
425 pub fn with_exclusive_maximum(mut self, value: i64) -> Self {
426 self.exclusive_maximum = Some(value);
427 self
428 }
429
430 #[must_use]
432 pub fn with_min_length(mut self, len: usize) -> Self {
433 self.min_length = Some(len);
434 self
435 }
436
437 #[must_use]
439 pub fn with_max_length(mut self, len: usize) -> Self {
440 self.max_length = Some(len);
441 self
442 }
443
444 #[must_use]
446 pub fn with_pattern(mut self, pattern: impl Into<String>) -> Self {
447 self.pattern = Some(pattern.into());
448 self
449 }
450}
451
452#[allow(clippy::trivially_copy_pass_by_ref)]
453fn is_false(b: &bool) -> bool {
454 !*b
455}
456
457#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
459#[serde(rename_all = "lowercase")]
460pub enum SchemaType {
461 String,
463 Number,
465 Integer,
467 Boolean,
469 Null,
471}
472
473pub trait JsonSchema {
475 fn schema() -> Schema;
477
478 #[must_use]
480 fn schema_name() -> Option<&'static str> {
481 None
482 }
483
484 fn schema_with_registry(registry: &SchemaRegistry) -> Schema
489 where
490 Self: Sized,
491 {
492 if let Some(name) = Self::schema_name() {
493 registry.get_or_register::<Self>(name)
494 } else {
495 Self::schema()
496 }
497 }
498}
499
500#[derive(Debug, Default)]
536pub struct SchemaRegistry {
537 schemas: RefCell<HashMap<String, Schema>>,
539}
540
541impl SchemaRegistry {
542 #[must_use]
544 pub fn new() -> Self {
545 Self {
546 schemas: RefCell::new(HashMap::new()),
547 }
548 }
549
550 pub fn get_or_register<T: JsonSchema>(&self, name: &str) -> Schema {
555 let mut schemas = self.schemas.borrow_mut();
556
557 if !schemas.contains_key(name) {
558 schemas.insert(name.to_string(), T::schema());
560 }
561
562 Schema::reference(name)
564 }
565
566 pub fn register(&self, name: impl Into<String>, schema: Schema) -> Schema {
570 let name = name.into();
571 self.schemas.borrow_mut().insert(name.clone(), schema);
572 Schema::reference(&name)
573 }
574
575 #[must_use]
577 pub fn contains(&self, name: &str) -> bool {
578 self.schemas.borrow().contains_key(name)
579 }
580
581 #[must_use]
583 pub fn len(&self) -> usize {
584 self.schemas.borrow().len()
585 }
586
587 #[must_use]
589 pub fn is_empty(&self) -> bool {
590 self.schemas.borrow().is_empty()
591 }
592
593 #[must_use]
597 pub fn into_schemas(self) -> HashMap<String, Schema> {
598 self.schemas.into_inner()
599 }
600
601 #[must_use]
603 pub fn schemas(&self) -> HashMap<String, Schema> {
604 self.schemas.borrow().clone()
605 }
606
607 pub fn merge(&self, other: &SchemaRegistry) {
611 let mut schemas = self.schemas.borrow_mut();
612 for (name, schema) in other.schemas.borrow().iter() {
613 schemas
614 .entry(name.clone())
615 .or_insert_with(|| schema.clone());
616 }
617 }
618}
619
620impl Clone for SchemaRegistry {
621 fn clone(&self) -> Self {
622 Self {
623 schemas: RefCell::new(self.schemas.borrow().clone()),
624 }
625 }
626}
627
628impl JsonSchema for String {
634 fn schema() -> Schema {
635 Schema::Primitive(PrimitiveSchema::string())
636 }
637}
638
639impl JsonSchema for &str {
640 fn schema() -> Schema {
641 Schema::Primitive(PrimitiveSchema::string())
642 }
643}
644
645impl JsonSchema for bool {
646 fn schema() -> Schema {
647 Schema::Primitive(PrimitiveSchema::boolean())
648 }
649}
650
651impl JsonSchema for i8 {
653 fn schema() -> Schema {
654 Schema::Primitive(PrimitiveSchema::integer(Some("int8")))
655 }
656}
657
658impl JsonSchema for i16 {
659 fn schema() -> Schema {
660 Schema::Primitive(PrimitiveSchema::integer(Some("int16")))
661 }
662}
663
664impl JsonSchema for i32 {
665 fn schema() -> Schema {
666 Schema::Primitive(PrimitiveSchema::integer(Some("int32")))
667 }
668}
669
670impl JsonSchema for i64 {
671 fn schema() -> Schema {
672 Schema::Primitive(PrimitiveSchema::integer(Some("int64")))
673 }
674}
675
676impl JsonSchema for i128 {
677 fn schema() -> Schema {
678 Schema::Primitive(PrimitiveSchema::integer(None))
680 }
681}
682
683impl JsonSchema for isize {
684 fn schema() -> Schema {
685 Schema::Primitive(PrimitiveSchema::integer(Some("int64")))
687 }
688}
689
690impl JsonSchema for u8 {
692 fn schema() -> Schema {
693 Schema::Primitive(PrimitiveSchema::unsigned_integer(Some("uint8")))
694 }
695}
696
697impl JsonSchema for u16 {
698 fn schema() -> Schema {
699 Schema::Primitive(PrimitiveSchema::unsigned_integer(Some("uint16")))
700 }
701}
702
703impl JsonSchema for u32 {
704 fn schema() -> Schema {
705 Schema::Primitive(PrimitiveSchema::unsigned_integer(Some("uint32")))
706 }
707}
708
709impl JsonSchema for u64 {
710 fn schema() -> Schema {
711 Schema::Primitive(PrimitiveSchema::unsigned_integer(Some("uint64")))
712 }
713}
714
715impl JsonSchema for u128 {
716 fn schema() -> Schema {
717 Schema::Primitive(PrimitiveSchema::unsigned_integer(None))
719 }
720}
721
722impl JsonSchema for usize {
723 fn schema() -> Schema {
724 Schema::Primitive(PrimitiveSchema::unsigned_integer(Some("uint64")))
726 }
727}
728
729impl JsonSchema for f32 {
731 fn schema() -> Schema {
732 Schema::Primitive(PrimitiveSchema::number(Some("float")))
733 }
734}
735
736impl JsonSchema for f64 {
737 fn schema() -> Schema {
738 Schema::Primitive(PrimitiveSchema::number(Some("double")))
739 }
740}
741
742impl JsonSchema for std::num::NonZeroI8 {
744 fn schema() -> Schema {
745 Schema::Primitive(PrimitiveSchema::integer(Some("int8")))
746 }
747}
748
749impl JsonSchema for std::num::NonZeroI16 {
750 fn schema() -> Schema {
751 Schema::Primitive(PrimitiveSchema::integer(Some("int16")))
752 }
753}
754
755impl JsonSchema for std::num::NonZeroI32 {
756 fn schema() -> Schema {
757 Schema::Primitive(PrimitiveSchema::integer(Some("int32")))
758 }
759}
760
761impl JsonSchema for std::num::NonZeroI64 {
762 fn schema() -> Schema {
763 Schema::Primitive(PrimitiveSchema::integer(Some("int64")))
764 }
765}
766
767impl JsonSchema for std::num::NonZeroI128 {
768 fn schema() -> Schema {
769 Schema::Primitive(PrimitiveSchema::integer(None))
770 }
771}
772
773impl JsonSchema for std::num::NonZeroIsize {
774 fn schema() -> Schema {
775 Schema::Primitive(PrimitiveSchema::integer(Some("int64")))
776 }
777}
778
779impl JsonSchema for std::num::NonZeroU8 {
780 fn schema() -> Schema {
781 let mut schema = PrimitiveSchema::unsigned_integer(Some("uint8"));
782 schema.minimum = Some(1); Schema::Primitive(schema)
784 }
785}
786
787impl JsonSchema for std::num::NonZeroU16 {
788 fn schema() -> Schema {
789 let mut schema = PrimitiveSchema::unsigned_integer(Some("uint16"));
790 schema.minimum = Some(1);
791 Schema::Primitive(schema)
792 }
793}
794
795impl JsonSchema for std::num::NonZeroU32 {
796 fn schema() -> Schema {
797 let mut schema = PrimitiveSchema::unsigned_integer(Some("uint32"));
798 schema.minimum = Some(1);
799 Schema::Primitive(schema)
800 }
801}
802
803impl JsonSchema for std::num::NonZeroU64 {
804 fn schema() -> Schema {
805 let mut schema = PrimitiveSchema::unsigned_integer(Some("uint64"));
806 schema.minimum = Some(1);
807 Schema::Primitive(schema)
808 }
809}
810
811impl JsonSchema for std::num::NonZeroU128 {
812 fn schema() -> Schema {
813 let mut schema = PrimitiveSchema::unsigned_integer(None);
814 schema.minimum = Some(1);
815 Schema::Primitive(schema)
816 }
817}
818
819impl JsonSchema for std::num::NonZeroUsize {
820 fn schema() -> Schema {
821 let mut schema = PrimitiveSchema::unsigned_integer(Some("uint64"));
822 schema.minimum = Some(1);
823 Schema::Primitive(schema)
824 }
825}
826
827impl<T: JsonSchema> JsonSchema for Option<T> {
828 fn schema() -> Schema {
829 match T::schema() {
830 Schema::Primitive(mut p) => {
831 p.nullable = true;
832 Schema::Primitive(p)
833 }
834 other => other,
835 }
836 }
837}
838
839impl<T: JsonSchema> JsonSchema for Vec<T> {
840 fn schema() -> Schema {
841 Schema::Array(ArraySchema {
842 items: Box::new(T::schema()),
843 min_items: None,
844 max_items: None,
845 })
846 }
847}
848
849#[cfg(test)]
850mod tests {
851 use super::*;
852
853 fn get_primitive(schema: Schema) -> PrimitiveSchema {
855 match schema {
856 Schema::Primitive(p) => p,
857 _ => panic!("Expected primitive schema"),
858 }
859 }
860
861 #[test]
862 fn test_signed_integers() {
863 let s = get_primitive(i8::schema());
865 assert!(matches!(s.schema_type, SchemaType::Integer));
866 assert_eq!(s.format.as_deref(), Some("int8"));
867 assert_eq!(s.minimum, None);
868
869 let s = get_primitive(i16::schema());
871 assert!(matches!(s.schema_type, SchemaType::Integer));
872 assert_eq!(s.format.as_deref(), Some("int16"));
873
874 let s = get_primitive(i32::schema());
876 assert!(matches!(s.schema_type, SchemaType::Integer));
877 assert_eq!(s.format.as_deref(), Some("int32"));
878
879 let s = get_primitive(i64::schema());
881 assert!(matches!(s.schema_type, SchemaType::Integer));
882 assert_eq!(s.format.as_deref(), Some("int64"));
883
884 let s = get_primitive(i128::schema());
886 assert!(matches!(s.schema_type, SchemaType::Integer));
887 assert_eq!(s.format, None);
888
889 let s = get_primitive(isize::schema());
891 assert!(matches!(s.schema_type, SchemaType::Integer));
892 assert_eq!(s.format.as_deref(), Some("int64"));
893 }
894
895 #[test]
896 fn test_unsigned_integers() {
897 let s = get_primitive(u8::schema());
899 assert!(matches!(s.schema_type, SchemaType::Integer));
900 assert_eq!(s.format.as_deref(), Some("uint8"));
901 assert_eq!(s.minimum, Some(0));
902
903 let s = get_primitive(u16::schema());
905 assert!(matches!(s.schema_type, SchemaType::Integer));
906 assert_eq!(s.format.as_deref(), Some("uint16"));
907 assert_eq!(s.minimum, Some(0));
908
909 let s = get_primitive(u32::schema());
911 assert!(matches!(s.schema_type, SchemaType::Integer));
912 assert_eq!(s.format.as_deref(), Some("uint32"));
913 assert_eq!(s.minimum, Some(0));
914
915 let s = get_primitive(u64::schema());
917 assert!(matches!(s.schema_type, SchemaType::Integer));
918 assert_eq!(s.format.as_deref(), Some("uint64"));
919 assert_eq!(s.minimum, Some(0));
920
921 let s = get_primitive(u128::schema());
923 assert!(matches!(s.schema_type, SchemaType::Integer));
924 assert_eq!(s.format, None);
925 assert_eq!(s.minimum, Some(0));
926
927 let s = get_primitive(usize::schema());
929 assert!(matches!(s.schema_type, SchemaType::Integer));
930 assert_eq!(s.format.as_deref(), Some("uint64"));
931 assert_eq!(s.minimum, Some(0));
932 }
933
934 #[test]
935 fn test_floats() {
936 let s = get_primitive(f32::schema());
938 assert!(matches!(s.schema_type, SchemaType::Number));
939 assert_eq!(s.format.as_deref(), Some("float"));
940
941 let s = get_primitive(f64::schema());
943 assert!(matches!(s.schema_type, SchemaType::Number));
944 assert_eq!(s.format.as_deref(), Some("double"));
945 }
946
947 #[test]
948 fn test_nonzero_signed() {
949 use std::num::{NonZeroI32, NonZeroI64};
950
951 let s = get_primitive(NonZeroI32::schema());
952 assert!(matches!(s.schema_type, SchemaType::Integer));
953 assert_eq!(s.format.as_deref(), Some("int32"));
954
955 let s = get_primitive(NonZeroI64::schema());
956 assert!(matches!(s.schema_type, SchemaType::Integer));
957 assert_eq!(s.format.as_deref(), Some("int64"));
958 }
959
960 #[test]
961 fn test_nonzero_unsigned() {
962 use std::num::{NonZeroU32, NonZeroU64};
963
964 let s = get_primitive(NonZeroU32::schema());
966 assert!(matches!(s.schema_type, SchemaType::Integer));
967 assert_eq!(s.format.as_deref(), Some("uint32"));
968 assert_eq!(s.minimum, Some(1));
969
970 let s = get_primitive(NonZeroU64::schema());
971 assert!(matches!(s.schema_type, SchemaType::Integer));
972 assert_eq!(s.format.as_deref(), Some("uint64"));
973 assert_eq!(s.minimum, Some(1));
974 }
975
976 #[test]
977 fn test_string_and_bool() {
978 let s = get_primitive(String::schema());
979 assert!(matches!(s.schema_type, SchemaType::String));
980
981 let s = get_primitive(bool::schema());
982 assert!(matches!(s.schema_type, SchemaType::Boolean));
983 }
984
985 #[test]
986 fn test_serialization() {
987 let schema = u32::schema();
989 let json = serde_json::to_string(&schema).unwrap();
990 assert!(json.contains(r#""type":"integer""#));
991 assert!(json.contains(r#""format":"uint32""#));
992 assert!(json.contains(r#""minimum":0"#));
993
994 let schema = std::num::NonZeroU32::schema();
996 let json = serde_json::to_string(&schema).unwrap();
997 assert!(json.contains(r#""minimum":1"#));
998
999 let schema = i32::schema();
1001 let json = serde_json::to_string(&schema).unwrap();
1002 assert!(!json.contains("minimum"));
1003 }
1004
1005 #[test]
1006 fn test_string_enum_schema() {
1007 let schema = Schema::string_enum(vec![
1009 "Red".to_string(),
1010 "Green".to_string(),
1011 "Blue".to_string(),
1012 ]);
1013 let json = serde_json::to_string(&schema).unwrap();
1014 assert!(json.contains(r#""type":"string""#));
1015 assert!(json.contains(r#""enum":["Red","Green","Blue"]"#));
1016 }
1017
1018 #[test]
1019 fn test_one_of_schema() {
1020 let schema = Schema::one_of(vec![Schema::string(), Schema::integer(Some("int32"))]);
1022 let json = serde_json::to_string(&schema).unwrap();
1023 assert!(json.contains(r#""oneOf""#));
1024 }
1025
1026 #[test]
1027 fn test_any_of_schema() {
1028 let schema = Schema::any_of(vec![Schema::string(), Schema::boolean()]);
1030 let json = serde_json::to_string(&schema).unwrap();
1031 assert!(json.contains(r#""anyOf""#));
1032 }
1033
1034 #[test]
1035 fn test_enum_schema_with_discriminator() {
1036 let mut mapping = HashMap::new();
1038 mapping.insert("dog".to_string(), "#/components/schemas/Dog".to_string());
1039 mapping.insert("cat".to_string(), "#/components/schemas/Cat".to_string());
1040
1041 let schema = Schema::one_of_with_discriminator(
1042 vec![Schema::reference("Dog"), Schema::reference("Cat")],
1043 "petType",
1044 mapping,
1045 );
1046 let json = serde_json::to_string(&schema).unwrap();
1047 assert!(json.contains(r#""oneOf""#));
1048 assert!(json.contains(r#""discriminator""#));
1049 assert!(json.contains(r#""propertyName":"petType""#));
1050 }
1051
1052 #[test]
1057 fn test_exclusive_minimum_serialization() {
1058 let schema = PrimitiveSchema::integer(Some("int32")).with_exclusive_minimum(5);
1059 let json = serde_json::to_string(&Schema::Primitive(schema)).unwrap();
1060 assert!(json.contains(r#""exclusiveMinimum":5"#));
1061 assert!(!json.contains(r#""minimum""#));
1063 }
1064
1065 #[test]
1066 fn test_exclusive_maximum_serialization() {
1067 let schema = PrimitiveSchema::integer(Some("int32")).with_exclusive_maximum(100);
1068 let json = serde_json::to_string(&Schema::Primitive(schema)).unwrap();
1069 assert!(json.contains(r#""exclusiveMaximum":100"#));
1070 assert!(!json.contains(r#""maximum""#));
1072 }
1073
1074 #[test]
1075 fn test_min_length_serialization() {
1076 let schema = PrimitiveSchema::string().with_min_length(3);
1077 let json = serde_json::to_string(&Schema::Primitive(schema)).unwrap();
1078 assert!(json.contains(r#""minLength":3"#));
1079 }
1080
1081 #[test]
1082 fn test_max_length_serialization() {
1083 let schema = PrimitiveSchema::string().with_max_length(255);
1084 let json = serde_json::to_string(&Schema::Primitive(schema)).unwrap();
1085 assert!(json.contains(r#""maxLength":255"#));
1086 }
1087
1088 #[test]
1089 fn test_pattern_serialization() {
1090 let schema = PrimitiveSchema::string().with_pattern(r"^[a-z]+$");
1091 let json = serde_json::to_string(&Schema::Primitive(schema)).unwrap();
1092 assert!(json.contains(r#""pattern":"^[a-z]+$""#));
1093 }
1094
1095 #[test]
1096 fn test_combined_string_constraints() {
1097 let schema = PrimitiveSchema::string()
1098 .with_min_length(1)
1099 .with_max_length(50)
1100 .with_pattern(r"^[A-Z][a-z]+$");
1101 let json = serde_json::to_string(&Schema::Primitive(schema)).unwrap();
1102 assert!(json.contains(r#""minLength":1"#));
1103 assert!(json.contains(r#""maxLength":50"#));
1104 assert!(json.contains(r#""pattern":"^[A-Z][a-z]+$""#));
1105 }
1106
1107 #[test]
1108 fn test_combined_number_constraints() {
1109 let schema = PrimitiveSchema::integer(Some("int32"))
1110 .with_minimum(0)
1111 .with_maximum(100);
1112 let json = serde_json::to_string(&Schema::Primitive(schema)).unwrap();
1113 assert!(json.contains(r#""minimum":0"#));
1114 assert!(json.contains(r#""maximum":100"#));
1115 }
1116
1117 #[test]
1118 fn test_constraints_not_serialized_when_none() {
1119 let schema = PrimitiveSchema::string();
1120 let json = serde_json::to_string(&Schema::Primitive(schema)).unwrap();
1121 assert!(!json.contains("minimum"));
1123 assert!(!json.contains("maximum"));
1124 assert!(!json.contains("exclusiveMinimum"));
1125 assert!(!json.contains("exclusiveMaximum"));
1126 assert!(!json.contains("minLength"));
1127 assert!(!json.contains("maxLength"));
1128 assert!(!json.contains("pattern"));
1129 }
1130
1131 #[test]
1136 fn test_registry_new_is_empty() {
1137 let registry = SchemaRegistry::new();
1138 assert!(registry.is_empty());
1139 assert_eq!(registry.len(), 0);
1140 }
1141
1142 #[test]
1143 fn test_registry_register_direct() {
1144 let registry = SchemaRegistry::new();
1145 let schema = Schema::string();
1146
1147 let result = registry.register("Username", schema);
1148
1149 assert!(registry.contains("Username"));
1150 assert_eq!(registry.len(), 1);
1151
1152 if let Schema::Ref(ref_schema) = result {
1154 assert_eq!(ref_schema.reference, "#/components/schemas/Username");
1155 } else {
1156 panic!("Expected Schema::Ref");
1157 }
1158 }
1159
1160 #[test]
1161 fn test_registry_get_or_register_new() {
1162 let registry = SchemaRegistry::new();
1163
1164 let result = registry.get_or_register::<String>("StringType");
1166
1167 assert!(registry.contains("StringType"));
1168 if let Schema::Ref(ref_schema) = result {
1169 assert_eq!(ref_schema.reference, "#/components/schemas/StringType");
1170 } else {
1171 panic!("Expected Schema::Ref");
1172 }
1173 }
1174
1175 #[test]
1176 fn test_registry_get_or_register_existing() {
1177 let registry = SchemaRegistry::new();
1178
1179 let _result1 = registry.get_or_register::<String>("StringType");
1181 let initial_len = registry.len();
1182
1183 let result2 = registry.get_or_register::<String>("StringType");
1185
1186 assert_eq!(registry.len(), initial_len);
1187 if let Schema::Ref(ref_schema) = result2 {
1188 assert_eq!(ref_schema.reference, "#/components/schemas/StringType");
1189 } else {
1190 panic!("Expected Schema::Ref");
1191 }
1192 }
1193
1194 #[test]
1195 fn test_registry_into_schemas() {
1196 let registry = SchemaRegistry::new();
1197 registry.register("Type1", Schema::string());
1198 registry.register("Type2", Schema::boolean());
1199
1200 let schemas = registry.into_schemas();
1201
1202 assert_eq!(schemas.len(), 2);
1203 assert!(schemas.contains_key("Type1"));
1204 assert!(schemas.contains_key("Type2"));
1205 }
1206
1207 #[test]
1208 fn test_registry_schemas_clone() {
1209 let registry = SchemaRegistry::new();
1210 registry.register("Type1", Schema::string());
1211
1212 let schemas = registry.schemas();
1213
1214 assert!(registry.contains("Type1"));
1216 assert_eq!(schemas.len(), 1);
1217 }
1218
1219 #[test]
1220 fn test_registry_merge() {
1221 let registry1 = SchemaRegistry::new();
1222 registry1.register("Type1", Schema::string());
1223
1224 let registry2 = SchemaRegistry::new();
1225 registry2.register("Type2", Schema::boolean());
1226 registry2.register("Type1", Schema::integer(Some("int32"))); registry1.merge(®istry2);
1229
1230 assert_eq!(registry1.len(), 2);
1232 assert!(registry1.contains("Type1"));
1233 assert!(registry1.contains("Type2"));
1234
1235 let schemas = registry1.into_schemas();
1237 if let Schema::Primitive(p) = &schemas["Type1"] {
1238 assert!(matches!(p.schema_type, SchemaType::String));
1239 } else {
1240 panic!("Expected primitive string schema");
1241 }
1242 }
1243
1244 #[test]
1245 fn test_registry_clone() {
1246 let registry1 = SchemaRegistry::new();
1247 registry1.register("Type1", Schema::string());
1248
1249 let registry2 = registry1.clone();
1250
1251 assert!(registry2.contains("Type1"));
1252 assert_eq!(registry2.len(), 1);
1253
1254 registry2.register("Type2", Schema::boolean());
1256 assert!(!registry1.contains("Type2"));
1257 assert!(registry2.contains("Type2"));
1258 }
1259
1260 #[test]
1261 fn test_ref_schema_serialization() {
1262 let ref_schema = Schema::reference("User");
1263 let json = serde_json::to_string(&ref_schema).unwrap();
1264 assert!(json.contains(r##""$ref":"#/components/schemas/User""##));
1265 }
1266
1267 #[test]
1268 fn test_registry_with_object_schema() {
1269 let registry = SchemaRegistry::new();
1270
1271 let user_schema = Schema::object(
1272 [
1273 ("id".to_string(), Schema::integer(Some("int64"))),
1274 ("name".to_string(), Schema::string()),
1275 ]
1276 .into_iter()
1277 .collect(),
1278 vec!["id".to_string(), "name".to_string()],
1279 );
1280
1281 let result = registry.register("User", user_schema);
1282
1283 if let Schema::Ref(ref_schema) = result {
1285 assert_eq!(ref_schema.reference, "#/components/schemas/User");
1286 } else {
1287 panic!("Expected Schema::Ref");
1288 }
1289
1290 let schemas = registry.into_schemas();
1292 if let Schema::Object(obj) = &schemas["User"] {
1293 assert!(obj.properties.contains_key("id"));
1294 assert!(obj.properties.contains_key("name"));
1295 assert!(obj.required.contains(&"id".to_string()));
1296 } else {
1297 panic!("Expected object schema");
1298 }
1299 }
1300
1301 #[test]
1302 fn test_registry_nested_refs() {
1303 let registry = SchemaRegistry::new();
1304
1305 let address_schema = Schema::object(
1307 [
1308 ("street".to_string(), Schema::string()),
1309 ("city".to_string(), Schema::string()),
1310 ]
1311 .into_iter()
1312 .collect(),
1313 vec!["street".to_string(), "city".to_string()],
1314 );
1315 let _address_ref = registry.register("Address", address_schema);
1316
1317 let user_schema = Schema::object(
1319 [
1320 ("name".to_string(), Schema::string()),
1321 ("address".to_string(), Schema::reference("Address")),
1322 ]
1323 .into_iter()
1324 .collect(),
1325 vec!["name".to_string()],
1326 );
1327 let _user_ref = registry.register("User", user_schema);
1328
1329 let schemas = registry.into_schemas();
1330 assert_eq!(schemas.len(), 2);
1331
1332 if let Schema::Object(obj) = &schemas["User"] {
1334 if let Schema::Ref(ref_schema) = &obj.properties["address"] {
1335 assert_eq!(ref_schema.reference, "#/components/schemas/Address");
1336 } else {
1337 panic!("Expected address to be a $ref");
1338 }
1339 } else {
1340 panic!("Expected User to be an object");
1341 }
1342 }
1343
1344 #[test]
1345 fn test_registry_default() {
1346 let registry = SchemaRegistry::default();
1347 assert!(registry.is_empty());
1348 }
1349}