Skip to main content

hedl_core/
schema_version.rs

1// Dweve HEDL - Hierarchical Entity Data Language
2//
3// Copyright (c) 2025 Dweve IP B.V. and individual contributors.
4//
5// SPDX-License-Identifier: Apache-2.0
6//
7// Licensed under the Apache License, Version 2.0 (the "License");
8// you may not use this file except in compliance with the License.
9// You may obtain a copy of the License in the LICENSE file at the
10// root of this repository or at: http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! Schema versioning for HEDL types.
19//!
20//! This module provides version tracking for schemas, enabling schema evolution
21//! and compatibility checking across different versions of HEDL documents.
22//!
23//! # Schema Evolution
24//!
25//! Schema versioning allows HEDL documents to specify which version of a schema
26//! they conform to. This enables:
27//!
28//! - Forward compatibility: Older parsers can recognize newer schemas
29//! - Backward compatibility: Newer parsers can read older documents
30//! - Migration tooling: Automated schema migrations between versions
31//!
32//! # Version Format
33//!
34//! Versions follow semantic versioning: `major.minor.patch`
35//!
36//! - `major`: Breaking changes (field removals, type changes)
37//! - `minor`: Backward-compatible additions (new optional fields)
38//! - `patch`: Bug fixes to schema definitions
39
40use std::fmt;
41
42/// Schema version identifier.
43///
44/// Represents the version of a type's schema using semantic versioning.
45///
46/// # Examples
47///
48/// ```
49/// use hedl_core::schema_version::SchemaVersion;
50///
51/// // Create a version
52/// let v1 = SchemaVersion::new(1, 0, 0);
53/// let v2 = SchemaVersion::new(1, 1, 0);
54///
55/// // Compare versions
56/// assert!(v2 > v1);
57/// assert!(v2.is_compatible_with(&v1));
58/// ```
59#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
60pub struct SchemaVersion {
61    /// Major version (breaking changes)
62    pub major: u32,
63    /// Minor version (backward-compatible additions)
64    pub minor: u32,
65    /// Patch version (bug fixes)
66    pub patch: u32,
67}
68
69impl SchemaVersion {
70    /// Create a new schema version.
71    ///
72    /// # Arguments
73    ///
74    /// * `major` - Major version number
75    /// * `minor` - Minor version number
76    /// * `patch` - Patch version number
77    ///
78    /// # Examples
79    ///
80    /// ```
81    /// use hedl_core::schema_version::SchemaVersion;
82    ///
83    /// let version = SchemaVersion::new(1, 2, 3);
84    /// assert_eq!(version.major, 1);
85    /// assert_eq!(version.minor, 2);
86    /// assert_eq!(version.patch, 3);
87    /// ```
88    pub const fn new(major: u32, minor: u32, patch: u32) -> Self {
89        Self {
90            major,
91            minor,
92            patch,
93        }
94    }
95
96    /// Create version 1.0.0 (common default).
97    pub const fn v1() -> Self {
98        Self::new(1, 0, 0)
99    }
100
101    /// Check if this version is compatible with another version.
102    ///
103    /// Compatibility rules:
104    /// - Same major version required
105    /// - This version's minor must be >= other's minor
106    ///
107    /// # Arguments
108    ///
109    /// * `other` - The version to check compatibility with
110    ///
111    /// # Returns
112    ///
113    /// `true` if this schema can read data written for `other`
114    ///
115    /// # Examples
116    ///
117    /// ```
118    /// use hedl_core::schema_version::SchemaVersion;
119    ///
120    /// let v1_0 = SchemaVersion::new(1, 0, 0);
121    /// let v1_1 = SchemaVersion::new(1, 1, 0);
122    /// let v2_0 = SchemaVersion::new(2, 0, 0);
123    ///
124    /// // v1.1 can read v1.0 data (backward compatible)
125    /// assert!(v1_1.is_compatible_with(&v1_0));
126    ///
127    /// // v1.0 cannot read v1.1 data (missing new fields)
128    /// assert!(!v1_0.is_compatible_with(&v1_1));
129    ///
130    /// // Different major versions are not compatible
131    /// assert!(!v2_0.is_compatible_with(&v1_0));
132    /// ```
133    pub fn is_compatible_with(&self, other: &Self) -> bool {
134        // Same major version required
135        if self.major != other.major {
136            return false;
137        }
138
139        // This version must be >= other's minor version
140        // (can read data with same or fewer features)
141        self.minor >= other.minor
142    }
143
144    /// Check if this is a breaking change from another version.
145    ///
146    /// A breaking change occurs when the major version increases.
147    pub fn is_breaking_from(&self, other: &Self) -> bool {
148        self.major != other.major
149    }
150
151    /// Parse a version string in "major.minor.patch" format.
152    ///
153    /// # Arguments
154    ///
155    /// * `s` - Version string to parse
156    ///
157    /// # Returns
158    ///
159    /// `Some(SchemaVersion)` if parsing succeeds, `None` otherwise
160    ///
161    /// # Examples
162    ///
163    /// ```
164    /// use hedl_core::schema_version::SchemaVersion;
165    ///
166    /// let v = SchemaVersion::parse("1.2.3").unwrap();
167    /// assert_eq!(v, SchemaVersion::new(1, 2, 3));
168    ///
169    /// // Short forms also work
170    /// let v = SchemaVersion::parse("1.2").unwrap();
171    /// assert_eq!(v, SchemaVersion::new(1, 2, 0));
172    ///
173    /// let v = SchemaVersion::parse("1").unwrap();
174    /// assert_eq!(v, SchemaVersion::new(1, 0, 0));
175    /// ```
176    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
203/// Parse a schema version from a string.
204impl 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/// Field definition with optional default value.
213///
214/// Represents a field in a schema with its name, whether it's optional,
215/// and an optional default value.
216///
217/// # Examples
218///
219/// ```
220/// use hedl_core::schema_version::FieldDef;
221/// use hedl_core::Value;
222///
223/// // Required field
224/// let id_field = FieldDef {
225///     name: "id".to_string(),
226///     optional: false,
227///     default: None,
228/// };
229///
230/// // Optional field with default
231/// let status_field = FieldDef {
232///     name: "status".to_string(),
233///     optional: true,
234///     default: Some(Value::String("active".to_string().into())),
235/// };
236/// ```
237#[derive(Debug, Clone, PartialEq)]
238pub struct FieldDef {
239    /// The field name.
240    pub name: String,
241    /// Whether the field is optional.
242    pub optional: bool,
243    /// Default value if the field is omitted.
244    pub default: Option<crate::Value>,
245}
246
247impl FieldDef {
248    /// Create a new required field definition.
249    ///
250    /// # Arguments
251    ///
252    /// * `name` - The field name
253    ///
254    /// # Examples
255    ///
256    /// ```
257    /// use hedl_core::schema_version::FieldDef;
258    ///
259    /// let field = FieldDef::required("id");
260    /// assert!(!field.optional);
261    /// assert!(field.default.is_none());
262    /// ```
263    pub fn required(name: impl Into<String>) -> Self {
264        Self {
265            name: name.into(),
266            optional: false,
267            default: None,
268        }
269    }
270
271    /// Create a new optional field definition.
272    ///
273    /// # Arguments
274    ///
275    /// * `name` - The field name
276    ///
277    /// # Examples
278    ///
279    /// ```
280    /// use hedl_core::schema_version::FieldDef;
281    ///
282    /// let field = FieldDef::optional("description");
283    /// assert!(field.optional);
284    /// assert!(field.default.is_none());
285    /// ```
286    pub fn optional(name: impl Into<String>) -> Self {
287        Self {
288            name: name.into(),
289            optional: true,
290            default: None,
291        }
292    }
293
294    /// Create a new optional field with a default value.
295    ///
296    /// # Arguments
297    ///
298    /// * `name` - The field name
299    /// * `default` - The default value
300    ///
301    /// # Examples
302    ///
303    /// ```
304    /// use hedl_core::schema_version::FieldDef;
305    /// use hedl_core::Value;
306    ///
307    /// let field = FieldDef::with_default("active", Value::Bool(true));
308    /// assert!(field.optional);
309    /// assert_eq!(field.default, Some(Value::Bool(true)));
310    /// ```
311    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/// Schema definition with versioning.
321///
322/// Represents a complete schema for a type, including version information
323/// and field definitions.
324///
325/// # Examples
326///
327/// ```
328/// use hedl_core::schema_version::{Schema, SchemaVersion, FieldDef};
329///
330/// let mut schema = Schema::new(SchemaVersion::new(1, 0, 0));
331/// schema.add_type("User", vec![
332///     FieldDef::required("id"),
333///     FieldDef::required("name"),
334///     FieldDef::optional("email"),
335/// ]);
336/// ```
337#[derive(Debug, Clone)]
338pub struct Schema {
339    /// Schema version.
340    pub version: SchemaVersion,
341    /// Type definitions: type name -> field definitions.
342    pub types: std::collections::BTreeMap<String, Vec<FieldDef>>,
343}
344
345impl Schema {
346    /// Create a new schema with the specified version.
347    ///
348    /// # Arguments
349    ///
350    /// * `version` - The schema version
351    ///
352    /// # Examples
353    ///
354    /// ```
355    /// use hedl_core::schema_version::{Schema, SchemaVersion};
356    ///
357    /// let schema = Schema::new(SchemaVersion::new(1, 0, 0));
358    /// assert_eq!(schema.version, SchemaVersion::new(1, 0, 0));
359    /// assert!(schema.types.is_empty());
360    /// ```
361    pub fn new(version: SchemaVersion) -> Self {
362        Self {
363            version,
364            types: std::collections::BTreeMap::new(),
365        }
366    }
367
368    /// Add a type definition to the schema.
369    ///
370    /// # Arguments
371    ///
372    /// * `name` - The type name
373    /// * `fields` - Field definitions for the type
374    ///
375    /// # Examples
376    ///
377    /// ```
378    /// use hedl_core::schema_version::{Schema, SchemaVersion, FieldDef};
379    ///
380    /// let mut schema = Schema::new(SchemaVersion::new(1, 0, 0));
381    /// schema.add_type("User", vec![
382    ///     FieldDef::required("id"),
383    ///     FieldDef::required("name"),
384    /// ]);
385    /// assert!(schema.types.contains_key("User"));
386    /// ```
387    pub fn add_type(&mut self, name: &str, fields: Vec<FieldDef>) {
388        self.types.insert(name.to_string(), fields);
389    }
390
391    /// Get field definitions for a type.
392    ///
393    /// # Arguments
394    ///
395    /// * `type_name` - The type name to look up
396    ///
397    /// # Returns
398    ///
399    /// `Some(&Vec<FieldDef>)` if the type exists, `None` otherwise
400    ///
401    /// # Examples
402    ///
403    /// ```
404    /// use hedl_core::schema_version::{Schema, SchemaVersion, FieldDef};
405    ///
406    /// let mut schema = Schema::new(SchemaVersion::new(1, 0, 0));
407    /// schema.add_type("User", vec![FieldDef::required("id")]);
408    ///
409    /// assert!(schema.get_fields("User").is_some());
410    /// assert!(schema.get_fields("Post").is_none());
411    /// ```
412    pub fn get_fields(&self, type_name: &str) -> Option<&Vec<FieldDef>> {
413        self.types.get(type_name)
414    }
415
416    /// Check if this schema is compatible with another schema.
417    ///
418    /// Schemas are compatible if their versions are compatible.
419    ///
420    /// # Arguments
421    ///
422    /// * `other` - The schema to check compatibility with
423    ///
424    /// # Returns
425    ///
426    /// `true` if this schema can read data written for `other`
427    ///
428    /// # Examples
429    ///
430    /// ```
431    /// use hedl_core::schema_version::{Schema, SchemaVersion};
432    ///
433    /// let v1 = Schema::new(SchemaVersion::new(1, 0, 0));
434    /// let v1_1 = Schema::new(SchemaVersion::new(1, 1, 0));
435    /// let v2 = Schema::new(SchemaVersion::new(2, 0, 0));
436    ///
437    /// assert!(v1_1.is_compatible_with(&v1));
438    /// assert!(!v1.is_compatible_with(&v1_1));
439    /// assert!(!v2.is_compatible_with(&v1));
440    /// ```
441    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    // ==================== Construction tests ====================
451
452    #[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    // ==================== Parsing tests ====================
473
474    #[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    // ==================== Display tests ====================
526
527    #[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    // ==================== Comparison tests ====================
540
541    #[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    // ==================== Compatibility tests ====================
577
578    #[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        // v1.1 can read v1.0 data
589        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        // v1.0 cannot read v1.1 data
597        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        // Patch versions don't affect compatibility
613        assert!(v1.is_compatible_with(&v2));
614        assert!(v2.is_compatible_with(&v1));
615    }
616
617    // ==================== Breaking change tests ====================
618
619    #[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    // ==================== Hash tests ====================
640
641    #[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)); // duplicate
648
649        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    // ==================== Clone and Copy tests ====================
665
666    #[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; // Copy, not move
677        assert_eq!(v1, v2);
678    }
679
680    // ==================== Debug tests ====================
681
682    #[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    // ==================== Edge cases ====================
693
694    #[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    // ==================== FieldDef tests ====================
709
710    #[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    // ==================== Schema tests ====================
786
787    #[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); // id is required
893        assert!(fields[1].optional); // name is optional
894        assert!(fields[2].optional); // active has default
895        assert!(fields[3].optional); // role has default
896        assert!(fields[2].default.is_some());
897        assert!(fields[3].default.is_some());
898    }
899
900    // ==================== Integration tests ====================
901
902    #[test]
903    fn test_schema_evolution_scenario() {
904        use crate::Value;
905
906        // Version 1.0.0: Initial schema
907        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        // Version 1.1.0: Add optional field (backward compatible)
914        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        // Version 1.2.0: Add field with default (backward compatible)
925        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        // Verify compatibility
937        assert!(v1_1.is_compatible_with(&v1)); // v1.1 can read v1.0 data
938        assert!(v1_2.is_compatible_with(&v1_1)); // v1.2 can read v1.1 data
939        assert!(v1_2.is_compatible_with(&v1)); // v1.2 can read v1.0 data
940
941        // Older versions cannot read newer data
942        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        // Version 1.0.0
949        let mut v1 = Schema::new(SchemaVersion::new(1, 0, 0));
950        v1.add_type("User", vec![FieldDef::required("id")]);
951
952        // Version 2.0.0: Breaking change
953        let mut v2 = Schema::new(SchemaVersion::new(2, 0, 0));
954        v2.add_type(
955            "User",
956            vec![
957                FieldDef::required("user_id"), // Changed field name
958                FieldDef::required("name"),
959            ],
960        );
961
962        // Breaking changes are not compatible
963        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}