1use std::fmt;
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
60pub struct SchemaVersion {
61 pub major: u32,
63 pub minor: u32,
65 pub patch: u32,
67}
68
69impl SchemaVersion {
70 pub const fn new(major: u32, minor: u32, patch: u32) -> Self {
89 Self {
90 major,
91 minor,
92 patch,
93 }
94 }
95
96 pub const fn v1() -> Self {
98 Self::new(1, 0, 0)
99 }
100
101 pub fn is_compatible_with(&self, other: &Self) -> bool {
134 if self.major != other.major {
136 return false;
137 }
138
139 self.minor >= other.minor
142 }
143
144 pub fn is_breaking_from(&self, other: &Self) -> bool {
148 self.major != other.major
149 }
150
151 pub fn parse(s: &str) -> Option<Self> {
177 let parts: Vec<&str> = s.trim().split('.').collect();
178
179 if parts.is_empty() || parts.len() > 3 {
180 return None;
181 }
182
183 let major = parts.first()?.parse().ok()?;
184 let minor = parts.get(1).and_then(|s| s.parse().ok()).unwrap_or(0);
185 let patch = parts.get(2).and_then(|s| s.parse().ok()).unwrap_or(0);
186
187 Some(Self::new(major, minor, patch))
188 }
189}
190
191impl Default for SchemaVersion {
192 fn default() -> Self {
193 Self::v1()
194 }
195}
196
197impl fmt::Display for SchemaVersion {
198 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
199 write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
200 }
201}
202
203impl std::str::FromStr for SchemaVersion {
205 type Err = &'static str;
206
207 fn from_str(s: &str) -> Result<Self, Self::Err> {
208 Self::parse(s).ok_or("invalid version format")
209 }
210}
211
212#[derive(Debug, Clone, PartialEq)]
238pub struct FieldDef {
239 pub name: String,
241 pub optional: bool,
243 pub default: Option<crate::Value>,
245}
246
247impl FieldDef {
248 pub fn required(name: impl Into<String>) -> Self {
264 Self {
265 name: name.into(),
266 optional: false,
267 default: None,
268 }
269 }
270
271 pub fn optional(name: impl Into<String>) -> Self {
287 Self {
288 name: name.into(),
289 optional: true,
290 default: None,
291 }
292 }
293
294 pub fn with_default(name: impl Into<String>, default: crate::Value) -> Self {
312 Self {
313 name: name.into(),
314 optional: true,
315 default: Some(default),
316 }
317 }
318}
319
320#[derive(Debug, Clone)]
338pub struct Schema {
339 pub version: SchemaVersion,
341 pub types: std::collections::BTreeMap<String, Vec<FieldDef>>,
343}
344
345impl Schema {
346 pub fn new(version: SchemaVersion) -> Self {
362 Self {
363 version,
364 types: std::collections::BTreeMap::new(),
365 }
366 }
367
368 pub fn add_type(&mut self, name: &str, fields: Vec<FieldDef>) {
388 self.types.insert(name.to_string(), fields);
389 }
390
391 pub fn get_fields(&self, type_name: &str) -> Option<&Vec<FieldDef>> {
413 self.types.get(type_name)
414 }
415
416 pub fn is_compatible_with(&self, other: &Self) -> bool {
442 self.version.is_compatible_with(&other.version)
443 }
444}
445
446#[cfg(test)]
447mod tests {
448 use super::*;
449
450 #[test]
453 fn test_new() {
454 let v = SchemaVersion::new(1, 2, 3);
455 assert_eq!(v.major, 1);
456 assert_eq!(v.minor, 2);
457 assert_eq!(v.patch, 3);
458 }
459
460 #[test]
461 fn test_v1() {
462 let v = SchemaVersion::v1();
463 assert_eq!(v, SchemaVersion::new(1, 0, 0));
464 }
465
466 #[test]
467 fn test_default() {
468 let v = SchemaVersion::default();
469 assert_eq!(v, SchemaVersion::v1());
470 }
471
472 #[test]
475 fn test_parse_full() {
476 let v = SchemaVersion::parse("1.2.3").unwrap();
477 assert_eq!(v, SchemaVersion::new(1, 2, 3));
478 }
479
480 #[test]
481 fn test_parse_major_minor() {
482 let v = SchemaVersion::parse("1.2").unwrap();
483 assert_eq!(v, SchemaVersion::new(1, 2, 0));
484 }
485
486 #[test]
487 fn test_parse_major_only() {
488 let v = SchemaVersion::parse("1").unwrap();
489 assert_eq!(v, SchemaVersion::new(1, 0, 0));
490 }
491
492 #[test]
493 fn test_parse_with_whitespace() {
494 let v = SchemaVersion::parse(" 1.2.3 ").unwrap();
495 assert_eq!(v, SchemaVersion::new(1, 2, 3));
496 }
497
498 #[test]
499 fn test_parse_invalid_empty() {
500 assert!(SchemaVersion::parse("").is_none());
501 }
502
503 #[test]
504 fn test_parse_invalid_non_numeric() {
505 assert!(SchemaVersion::parse("a.b.c").is_none());
506 }
507
508 #[test]
509 fn test_parse_invalid_too_many_parts() {
510 assert!(SchemaVersion::parse("1.2.3.4").is_none());
511 }
512
513 #[test]
514 fn test_from_str() {
515 let v: SchemaVersion = "1.2.3".parse().unwrap();
516 assert_eq!(v, SchemaVersion::new(1, 2, 3));
517 }
518
519 #[test]
520 fn test_from_str_invalid() {
521 let result: Result<SchemaVersion, _> = "invalid".parse();
522 assert!(result.is_err());
523 }
524
525 #[test]
528 fn test_display() {
529 let v = SchemaVersion::new(1, 2, 3);
530 assert_eq!(v.to_string(), "1.2.3");
531 }
532
533 #[test]
534 fn test_display_zeros() {
535 let v = SchemaVersion::new(1, 0, 0);
536 assert_eq!(v.to_string(), "1.0.0");
537 }
538
539 #[test]
542 fn test_equality() {
543 let v1 = SchemaVersion::new(1, 2, 3);
544 let v2 = SchemaVersion::new(1, 2, 3);
545 assert_eq!(v1, v2);
546 }
547
548 #[test]
549 fn test_inequality() {
550 let v1 = SchemaVersion::new(1, 2, 3);
551 let v2 = SchemaVersion::new(1, 2, 4);
552 assert_ne!(v1, v2);
553 }
554
555 #[test]
556 fn test_ordering_major() {
557 let v1 = SchemaVersion::new(1, 0, 0);
558 let v2 = SchemaVersion::new(2, 0, 0);
559 assert!(v2 > v1);
560 }
561
562 #[test]
563 fn test_ordering_minor() {
564 let v1 = SchemaVersion::new(1, 1, 0);
565 let v2 = SchemaVersion::new(1, 2, 0);
566 assert!(v2 > v1);
567 }
568
569 #[test]
570 fn test_ordering_patch() {
571 let v1 = SchemaVersion::new(1, 0, 1);
572 let v2 = SchemaVersion::new(1, 0, 2);
573 assert!(v2 > v1);
574 }
575
576 #[test]
579 fn test_compatible_same_version() {
580 let v = SchemaVersion::new(1, 2, 3);
581 assert!(v.is_compatible_with(&v));
582 }
583
584 #[test]
585 fn test_compatible_higher_minor() {
586 let v1_0 = SchemaVersion::new(1, 0, 0);
587 let v1_1 = SchemaVersion::new(1, 1, 0);
588 assert!(v1_1.is_compatible_with(&v1_0));
590 }
591
592 #[test]
593 fn test_incompatible_lower_minor() {
594 let v1_0 = SchemaVersion::new(1, 0, 0);
595 let v1_1 = SchemaVersion::new(1, 1, 0);
596 assert!(!v1_0.is_compatible_with(&v1_1));
598 }
599
600 #[test]
601 fn test_incompatible_different_major() {
602 let v1 = SchemaVersion::new(1, 0, 0);
603 let v2 = SchemaVersion::new(2, 0, 0);
604 assert!(!v2.is_compatible_with(&v1));
605 assert!(!v1.is_compatible_with(&v2));
606 }
607
608 #[test]
609 fn test_compatible_different_patch() {
610 let v1 = SchemaVersion::new(1, 0, 0);
611 let v2 = SchemaVersion::new(1, 0, 5);
612 assert!(v1.is_compatible_with(&v2));
614 assert!(v2.is_compatible_with(&v1));
615 }
616
617 #[test]
620 fn test_breaking_change_true() {
621 let v1 = SchemaVersion::new(1, 0, 0);
622 let v2 = SchemaVersion::new(2, 0, 0);
623 assert!(v2.is_breaking_from(&v1));
624 }
625
626 #[test]
627 fn test_breaking_change_false_same_major() {
628 let v1 = SchemaVersion::new(1, 0, 0);
629 let v2 = SchemaVersion::new(1, 5, 0);
630 assert!(!v2.is_breaking_from(&v1));
631 }
632
633 #[test]
634 fn test_breaking_change_false_same_version() {
635 let v = SchemaVersion::new(1, 2, 3);
636 assert!(!v.is_breaking_from(&v));
637 }
638
639 #[test]
642 fn test_hash_consistency() {
643 use std::collections::HashSet;
644
645 let mut set = HashSet::new();
646 set.insert(SchemaVersion::new(1, 0, 0));
647 set.insert(SchemaVersion::new(1, 0, 0)); assert_eq!(set.len(), 1);
650 }
651
652 #[test]
653 fn test_hash_different_versions() {
654 use std::collections::HashSet;
655
656 let mut set = HashSet::new();
657 set.insert(SchemaVersion::new(1, 0, 0));
658 set.insert(SchemaVersion::new(1, 1, 0));
659 set.insert(SchemaVersion::new(2, 0, 0));
660
661 assert_eq!(set.len(), 3);
662 }
663
664 #[test]
667 fn test_clone() {
668 let v1 = SchemaVersion::new(1, 2, 3);
669 let v2 = v1;
670 assert_eq!(v1, v2);
671 }
672
673 #[test]
674 fn test_copy() {
675 let v1 = SchemaVersion::new(1, 2, 3);
676 let v2 = v1; assert_eq!(v1, v2);
678 }
679
680 #[test]
683 fn test_debug() {
684 let v = SchemaVersion::new(1, 2, 3);
685 let debug = format!("{:?}", v);
686 assert!(debug.contains("SchemaVersion"));
687 assert!(debug.contains("major: 1"));
688 assert!(debug.contains("minor: 2"));
689 assert!(debug.contains("patch: 3"));
690 }
691
692 #[test]
695 fn test_max_values() {
696 let v = SchemaVersion::new(u32::MAX, u32::MAX, u32::MAX);
697 assert_eq!(v.major, u32::MAX);
698 assert_eq!(v.minor, u32::MAX);
699 assert_eq!(v.patch, u32::MAX);
700 }
701
702 #[test]
703 fn test_zero_version() {
704 let v = SchemaVersion::new(0, 0, 0);
705 assert_eq!(v.to_string(), "0.0.0");
706 }
707
708 #[test]
711 fn test_field_def_required() {
712 let field = FieldDef::required("id");
713 assert_eq!(field.name, "id");
714 assert!(!field.optional);
715 assert!(field.default.is_none());
716 }
717
718 #[test]
719 fn test_field_def_optional() {
720 let field = FieldDef::optional("description");
721 assert_eq!(field.name, "description");
722 assert!(field.optional);
723 assert!(field.default.is_none());
724 }
725
726 #[test]
727 fn test_field_def_with_default() {
728 use crate::Value;
729 let field = FieldDef::with_default("active", Value::Bool(true));
730 assert_eq!(field.name, "active");
731 assert!(field.optional);
732 assert_eq!(field.default, Some(Value::Bool(true)));
733 }
734
735 #[test]
736 fn test_field_def_with_default_string() {
737 use crate::Value;
738 let field = FieldDef::with_default("status", Value::String("pending".to_string().into()));
739 assert_eq!(field.name, "status");
740 assert!(field.optional);
741 assert_eq!(
742 field.default,
743 Some(Value::String("pending".to_string().into()))
744 );
745 }
746
747 #[test]
748 fn test_field_def_with_default_int() {
749 use crate::Value;
750 let field = FieldDef::with_default("count", Value::Int(0));
751 assert_eq!(field.name, "count");
752 assert_eq!(field.default, Some(Value::Int(0)));
753 }
754
755 #[test]
756 fn test_field_def_equality() {
757 let a = FieldDef::required("id");
758 let b = FieldDef::required("id");
759 assert_eq!(a, b);
760 }
761
762 #[test]
763 fn test_field_def_inequality() {
764 let a = FieldDef::required("id");
765 let b = FieldDef::optional("id");
766 assert_ne!(a, b);
767 }
768
769 #[test]
770 fn test_field_def_clone() {
771 use crate::Value;
772 let original = FieldDef::with_default("test", Value::Int(42));
773 let cloned = original.clone();
774 assert_eq!(original, cloned);
775 }
776
777 #[test]
778 fn test_field_def_debug() {
779 let field = FieldDef::required("id");
780 let debug = format!("{:?}", field);
781 assert!(debug.contains("FieldDef"));
782 assert!(debug.contains("id"));
783 }
784
785 #[test]
788 fn test_schema_new() {
789 let schema = Schema::new(SchemaVersion::new(1, 0, 0));
790 assert_eq!(schema.version, SchemaVersion::new(1, 0, 0));
791 assert!(schema.types.is_empty());
792 }
793
794 #[test]
795 fn test_schema_add_type() {
796 let mut schema = Schema::new(SchemaVersion::new(1, 0, 0));
797 schema.add_type(
798 "User",
799 vec![FieldDef::required("id"), FieldDef::required("name")],
800 );
801 assert!(schema.types.contains_key("User"));
802 assert_eq!(schema.types["User"].len(), 2);
803 }
804
805 #[test]
806 fn test_schema_add_multiple_types() {
807 let mut schema = Schema::new(SchemaVersion::new(1, 0, 0));
808 schema.add_type("User", vec![FieldDef::required("id")]);
809 schema.add_type("Post", vec![FieldDef::required("id")]);
810 assert_eq!(schema.types.len(), 2);
811 assert!(schema.types.contains_key("User"));
812 assert!(schema.types.contains_key("Post"));
813 }
814
815 #[test]
816 fn test_schema_get_fields() {
817 let mut schema = Schema::new(SchemaVersion::new(1, 0, 0));
818 schema.add_type(
819 "User",
820 vec![FieldDef::required("id"), FieldDef::optional("email")],
821 );
822
823 let fields = schema.get_fields("User").unwrap();
824 assert_eq!(fields.len(), 2);
825 assert_eq!(fields[0].name, "id");
826 assert_eq!(fields[1].name, "email");
827 }
828
829 #[test]
830 fn test_schema_get_fields_missing() {
831 let schema = Schema::new(SchemaVersion::new(1, 0, 0));
832 assert!(schema.get_fields("MissingType").is_none());
833 }
834
835 #[test]
836 fn test_schema_is_compatible_with() {
837 let v1 = Schema::new(SchemaVersion::new(1, 0, 0));
838 let v1_1 = Schema::new(SchemaVersion::new(1, 1, 0));
839 let v2 = Schema::new(SchemaVersion::new(2, 0, 0));
840
841 assert!(v1_1.is_compatible_with(&v1));
842 assert!(!v1.is_compatible_with(&v1_1));
843 assert!(!v2.is_compatible_with(&v1));
844 }
845
846 #[test]
847 fn test_schema_replace_type() {
848 let mut schema = Schema::new(SchemaVersion::new(1, 0, 0));
849 schema.add_type("User", vec![FieldDef::required("id")]);
850 schema.add_type(
851 "User",
852 vec![FieldDef::required("id"), FieldDef::required("name")],
853 );
854
855 let fields = schema.get_fields("User").unwrap();
856 assert_eq!(fields.len(), 2);
857 }
858
859 #[test]
860 fn test_schema_clone() {
861 let mut schema = Schema::new(SchemaVersion::new(1, 0, 0));
862 schema.add_type("User", vec![FieldDef::required("id")]);
863 let cloned = schema.clone();
864 assert_eq!(schema.version, cloned.version);
865 assert_eq!(schema.types.len(), cloned.types.len());
866 }
867
868 #[test]
869 fn test_schema_debug() {
870 let schema = Schema::new(SchemaVersion::new(1, 0, 0));
871 let debug = format!("{:?}", schema);
872 assert!(debug.contains("Schema"));
873 assert!(debug.contains("version"));
874 }
875
876 #[test]
877 fn test_schema_with_optional_and_default_fields() {
878 use crate::Value;
879 let mut schema = Schema::new(SchemaVersion::new(1, 0, 0));
880 schema.add_type(
881 "User",
882 vec![
883 FieldDef::required("id"),
884 FieldDef::optional("name"),
885 FieldDef::with_default("active", Value::Bool(true)),
886 FieldDef::with_default("role", Value::String("user".to_string().into())),
887 ],
888 );
889
890 let fields = schema.get_fields("User").unwrap();
891 assert_eq!(fields.len(), 4);
892 assert!(!fields[0].optional); assert!(fields[1].optional); assert!(fields[2].optional); assert!(fields[3].optional); assert!(fields[2].default.is_some());
897 assert!(fields[3].default.is_some());
898 }
899
900 #[test]
903 fn test_schema_evolution_scenario() {
904 use crate::Value;
905
906 let mut v1 = Schema::new(SchemaVersion::new(1, 0, 0));
908 v1.add_type(
909 "User",
910 vec![FieldDef::required("id"), FieldDef::required("name")],
911 );
912
913 let mut v1_1 = Schema::new(SchemaVersion::new(1, 1, 0));
915 v1_1.add_type(
916 "User",
917 vec![
918 FieldDef::required("id"),
919 FieldDef::required("name"),
920 FieldDef::optional("email"),
921 ],
922 );
923
924 let mut v1_2 = Schema::new(SchemaVersion::new(1, 2, 0));
926 v1_2.add_type(
927 "User",
928 vec![
929 FieldDef::required("id"),
930 FieldDef::required("name"),
931 FieldDef::optional("email"),
932 FieldDef::with_default("active", Value::Bool(true)),
933 ],
934 );
935
936 assert!(v1_1.is_compatible_with(&v1)); assert!(v1_2.is_compatible_with(&v1_1)); assert!(v1_2.is_compatible_with(&v1)); assert!(!v1.is_compatible_with(&v1_1));
943 assert!(!v1_1.is_compatible_with(&v1_2));
944 }
945
946 #[test]
947 fn test_breaking_schema_change() {
948 let mut v1 = Schema::new(SchemaVersion::new(1, 0, 0));
950 v1.add_type("User", vec![FieldDef::required("id")]);
951
952 let mut v2 = Schema::new(SchemaVersion::new(2, 0, 0));
954 v2.add_type(
955 "User",
956 vec![
957 FieldDef::required("user_id"), FieldDef::required("name"),
959 ],
960 );
961
962 assert!(!v2.is_compatible_with(&v1));
964 assert!(!v1.is_compatible_with(&v2));
965 }
966
967 #[test]
968 fn test_field_def_with_null_default() {
969 use crate::Value;
970 let field = FieldDef::with_default("optional_value", Value::Null);
971 assert_eq!(field.default, Some(Value::Null));
972 }
973
974 #[test]
975 fn test_empty_schema() {
976 let schema = Schema::new(SchemaVersion::new(0, 0, 1));
977 assert!(schema.types.is_empty());
978 assert!(schema.get_fields("AnyType").is_none());
979 }
980
981 #[test]
982 fn test_schema_with_empty_field_list() {
983 let mut schema = Schema::new(SchemaVersion::new(1, 0, 0));
984 schema.add_type("EmptyType", vec![]);
985 let fields = schema.get_fields("EmptyType").unwrap();
986 assert!(fields.is_empty());
987 }
988}