hexz_core/format/version.rs
1//! Format version management and compatibility checking.
2//!
3//! This module defines the versioning strategy for Hexz snapshot files (`.hxz`),
4//! enabling safe evolution of the on-disk format while maintaining backward and
5//! forward compatibility guarantees. Version negotiation ensures that readers can
6//! detect incompatible snapshots and provide actionable error messages.
7//!
8//! # Versioning Strategy
9//!
10//! Hexz uses a monotonic integer versioning scheme where each version number
11//! represents a distinct on-disk format:
12//!
13//! - **Version 1**: Initial format with two-level index, LZ4/Zstd compression,
14//! optional encryption, thin provisioning support
15//! - **Future versions**: Reserved for incompatible format changes (e.g., new
16//! index structures, alternative serialization formats, block metadata extensions)
17//!
18//! ## Semantic Versioning Alignment
19//!
20//! Format versions are independent of Hexz software versions (semver). A software
21//! release may support multiple format versions for backward compatibility. The
22//! mapping is defined as:
23//!
24//! | Format Version | Hexz Software Versions | Key Changes |
25//! |----------------|--------------------------|-------------|
26//! | 1 | 0.1.0+ | Initial release |
27//!
28//! # Compatibility Model
29//!
30//! ## Backward Compatibility (Reading Old Snapshots)
31//!
32//! Hexz readers maintain compatibility with snapshots created by older software:
33//!
34//! - **MIN_SUPPORTED_VERSION**: Oldest format version we can read (currently 1)
35//! - **Upgrade Path**: Snapshots older than MIN_SUPPORTED_VERSION must be migrated
36//! using the `hexz-migrate` tool (see Migration section below)
37//!
38//! ## Forward Compatibility (Reading New Snapshots)
39//!
40//! Hexz readers handle snapshots created by newer software:
41//!
42//! - **MAX_SUPPORTED_VERSION**: Newest format version we can read (currently 1)
43//! - **Degraded Mode**: Future versions may enable partial reads with warnings if
44//! minor features are unrecognized (not yet implemented)
45//! - **Strict Rejection**: Snapshots with `version > MAX_SUPPORTED_VERSION` are
46//! rejected with an actionable error message
47//!
48//! # Version Negotiation Workflow
49//!
50//! When opening a snapshot file:
51//!
52//! ```text
53//! 1. Read header magic bytes (validate "HEXZ" signature)
54//! 2. Read header.version field
55//! 3. Call check_version(header.version)
56//! 4. If VersionCompatibility::Incompatible:
57//! - Generate human-readable error via compatibility_message()
58//! - Suggest upgrade/migration path
59//! - Abort operation
60//! 5. If VersionCompatibility::Full:
61//! - Proceed with normal operations
62//! 6. If VersionCompatibility::Degraded (future):
63//! - Log warning about unsupported features
64//! - Proceed with limited functionality
65//! ```
66//!
67//! # Adding New Format Versions
68//!
69//! To introduce a breaking format change:
70//!
71//! ## Step 1: Increment Version Constant
72//!
73//! ```rust,ignore
74//! pub const CURRENT_VERSION: u32 = 2; // Changed from 1
75//! pub const MAX_SUPPORTED_VERSION: u32 = 2;
76//! ```
77//!
78//! ## Step 2: Update Serialization Logic
79//!
80//! Modify [`crate::format::header::Header`] or [`crate::format::index::MasterIndex`]
81//! to include new fields or change existing structures. Use serde attributes to
82//! maintain compatibility:
83//!
84//! ```rust,ignore
85//! #[derive(Serialize, Deserialize)]
86//! pub struct Header {
87//! // Existing fields...
88//! #[serde(skip_serializing_if = "Option::is_none")]
89//! pub new_feature: Option<NewFeatureData>, // Version 2+ only
90//! }
91//! ```
92//!
93//! ## Step 3: Conditional Deserialization
94//!
95//! Add version-aware deserialization in reader code:
96//!
97//! ```rust,ignore
98//! match header.version {
99//! 1 => {
100//! // Legacy path for version 1
101//! read_index_v1(reader)?
102//! }
103//! 2 => {
104//! // New path for version 2
105//! read_index_v2(reader)?
106//! }
107//! _ => unreachable!("check_version already validated"),
108//! }
109//! ```
110//!
111//! ## Step 4: Update Tests
112//!
113//! Add test fixtures for the new format version:
114//!
115//! ```rust,ignore
116//! #[test]
117//! fn test_read_v2_snapshot() {
118//! let snapshot = load_fixture("testdata/v2_snapshot.hxz");
119//! assert_eq!(snapshot.header.version, 2);
120//! // Verify new features work correctly
121//! }
122//! ```
123//!
124//! ## Step 5: Document Migration Path
125//!
126//! Update the migration guide with conversion instructions (see Migration section).
127//!
128//! # Migration Between Versions
129//!
130//! The `hexz-migrate` tool converts snapshots between format versions:
131//!
132//! ```bash
133//! # Upgrade old snapshot to current format
134//! hexz-migrate upgrade --input old_v1.hxz --output new_v2.hxz
135//!
136//! # Downgrade for compatibility (if supported)
137//! hexz-migrate downgrade --input new_v2.hxz --output legacy_v1.hxz \
138//! --target-version 1
139//! ```
140//!
141//! ## Migration Algorithm
142//!
143//! The migration tool performs a streaming rewrite:
144//!
145//! 1. Open source snapshot with reader for version N
146//! 2. Create destination snapshot with writer for version M
147//! 3. Stream all blocks through decompression/re-compression
148//! 4. Rebuild index in target format
149//! 5. Preserve metadata (encryption keys, parent references, etc.)
150//!
151//! ## Lossy Migrations
152//!
153//! Downgrading may lose features unsupported in the target version:
154//!
155//! - Version 2 → 1: Hypothetical new features would be discarded with warnings
156//!
157//! # Performance Considerations
158//!
159//! Version checking is performed once per snapshot open operation:
160//!
161//! - **Overhead**: Negligible (~100ns for integer comparison)
162//! - **Caching**: Version is cached in [`crate::api::file::File`] struct
163//! - **Hot Path**: Not in block read/write paths
164//!
165//! # Examples
166//!
167//! ## Basic Version Check
168//!
169//! ```
170//! use hexz_core::format::version::{check_version, VersionCompatibility};
171//!
172//! let snapshot_version = 1;
173//! let compat = check_version(snapshot_version);
174//!
175//! match compat {
176//! VersionCompatibility::Full => {
177//! println!("Snapshot is fully compatible");
178//! }
179//! VersionCompatibility::Incompatible => {
180//! eprintln!("Cannot read snapshot (incompatible version)");
181//! }
182//! VersionCompatibility::Degraded => {
183//! println!("Snapshot readable with warnings");
184//! }
185//! }
186//! ```
187//!
188//! ## User-Facing Error Messages
189//!
190//! ```
191//! use hexz_core::format::version::compatibility_message;
192//!
193//! let version = 999; // Future version
194//! let message = compatibility_message(version);
195//! println!("{}", message);
196//! // Output: "Version 999 is too new (max supported: 1). Please upgrade Hexz."
197//! ```
198//!
199//! ## Reader Implementation
200//!
201//! ```rust,ignore
202//! use hexz_core::format::version::check_version;
203//! use hexz_core::error::Error;
204//!
205//! fn open_snapshot(path: &Path) -> Result<Snapshot, Error> {
206//! let header = read_header(path)?;
207//!
208//! if !check_version(header.version).is_compatible() {
209//! return Err(Error::IncompatibleVersion {
210//! found: header.version,
211//! min_supported: MIN_SUPPORTED_VERSION,
212//! max_supported: MAX_SUPPORTED_VERSION,
213//! });
214//! }
215//!
216//! // Proceed with version-aware deserialization
217//! Ok(Snapshot { header, /* ... */ })
218//! }
219//! ```
220
221use std::fmt;
222
223/// Current format version written by this build of Hexz.
224///
225/// This constant defines the format version number written to the `version` field
226/// of new snapshot headers. It is incremented when the on-disk format changes in
227/// a way that requires readers to adapt their parsing logic.
228///
229/// # Incrementing Policy
230///
231/// Increment `CURRENT_VERSION` when:
232/// - Header fields are added/removed/reordered
233/// - Index structure changes (e.g., new page entry fields)
234/// - Serialization format changes (e.g., switch from bincode to another codec)
235/// - Block metadata semantics change
236///
237/// Do NOT increment for:
238/// - New compression algorithms (use header.compression field)
239/// - New encryption modes (use header.encryption field)
240/// - Performance optimizations that don't affect format
241///
242/// # Current Version
243///
244/// **Version 1** (initial release):
245/// - Two-level index (master index + pages)
246/// - bincode serialization
247/// - LZ4/Zstd compression support
248/// - Optional AES-256-GCM encryption
249/// - Thin provisioning via parent snapshots
250/// - Dual streams (disk + memory)
251pub const CURRENT_VERSION: u32 = 1;
252
253/// Minimum format version readable by this build.
254///
255/// Snapshots with `version < MIN_SUPPORTED_VERSION` are rejected with an
256/// [`VersionCompatibility::Incompatible`] error. Users must migrate such
257/// snapshots using `hexz-migrate upgrade` before reading.
258///
259/// # Rationale
260///
261/// Maintaining backward compatibility indefinitely is untenable as format
262/// complexity grows. This constant defines the "support horizon" for old
263/// snapshots. When incrementing, ensure migration tooling exists.
264///
265/// # Current Policy
266///
267/// - **Hexz 0.1.x - 0.9.x**: Support version 1 only
268/// - **Hexz 1.0+**: May drop support for version 1 (TBD based on adoption)
269pub const MIN_SUPPORTED_VERSION: u32 = 1;
270
271/// Maximum format version readable by this build.
272///
273/// Snapshots with `version > MAX_SUPPORTED_VERSION` are rejected unless
274/// degraded mode is enabled (not yet implemented). This prevents crashes
275/// when reading snapshots created by future Hexz versions.
276///
277/// # Forward Compatibility
278///
279/// Currently, Hexz uses strict versioning: any version mismatch results
280/// in incompatibility. Future releases may implement graceful degradation
281/// for minor version increments (e.g., ignore unknown header fields).
282///
283/// # Upgrade Path
284///
285/// If you encounter a snapshot with `version > MAX_SUPPORTED_VERSION`,
286/// upgrade Hexz to a newer release that supports that version.
287pub const MAX_SUPPORTED_VERSION: u32 = 1;
288
289/// Result of snapshot version compatibility analysis.
290///
291/// Returned by [`check_version`] to indicate whether a snapshot with a given
292/// format version can be read by this build of Hexz. This enum enables
293/// graceful handling of version mismatches with appropriate error messages.
294///
295/// # Variants
296///
297/// - **Full**: Version is within [`MIN_SUPPORTED_VERSION`]..=[`MAX_SUPPORTED_VERSION`]
298/// and all features are supported. Proceed with normal operations.
299///
300/// - **Degraded**: Version is newer than [`MAX_SUPPORTED_VERSION`] but forward
301/// compatibility rules allow partial reads with warnings. Unsupported features
302/// are ignored or substituted with defaults. (Not yet implemented; currently
303/// treated as Incompatible.)
304///
305/// - **Incompatible**: Version is outside the supported range and cannot be read.
306/// The user must upgrade Hexz (for newer snapshots) or migrate the snapshot
307/// (for older snapshots).
308///
309/// # Examples
310///
311/// ```
312/// use hexz_core::format::version::{check_version, VersionCompatibility};
313///
314/// let result = check_version(1);
315/// assert_eq!(result, VersionCompatibility::Full);
316///
317/// let result = check_version(999);
318/// assert_eq!(result, VersionCompatibility::Incompatible);
319/// assert!(!result.is_compatible());
320/// ```
321#[derive(Debug, Clone, Copy, PartialEq, Eq)]
322pub enum VersionCompatibility {
323 /// Fully supported version with all features available.
324 Full,
325 /// Newer version with partial support (warnings emitted, some features unavailable).
326 ///
327 /// This variant is reserved for future forward compatibility modes. Currently,
328 /// all versions outside the supported range are marked as Incompatible.
329 Degraded,
330 /// Incompatible version that cannot be read (too old or too new).
331 Incompatible,
332}
333
334impl VersionCompatibility {
335 /// Tests whether the snapshot can be read with this compatibility status.
336 ///
337 /// Returns `true` for [`Full`](VersionCompatibility::Full) and
338 /// [`Degraded`](VersionCompatibility::Degraded) compatibility, indicating
339 /// that read operations can proceed (possibly with warnings). Returns `false`
340 /// for [`Incompatible`](VersionCompatibility::Incompatible), indicating that
341 /// the snapshot must be rejected.
342 ///
343 /// # Returns
344 ///
345 /// - `true`: Snapshot can be opened (possibly with limited functionality)
346 /// - `false`: Snapshot cannot be opened (hard error)
347 ///
348 /// # Examples
349 ///
350 /// ```
351 /// use hexz_core::format::version::{check_version, VersionCompatibility};
352 ///
353 /// let compat = check_version(1);
354 /// if compat.is_compatible() {
355 /// println!("Snapshot can be read");
356 /// } else {
357 /// eprintln!("Snapshot is incompatible");
358 /// }
359 /// ```
360 pub fn is_compatible(&self) -> bool {
361 match self {
362 VersionCompatibility::Full | VersionCompatibility::Degraded => true,
363 VersionCompatibility::Incompatible => false,
364 }
365 }
366}
367
368/// Determines compatibility status of a snapshot format version.
369///
370/// This function implements the version negotiation logic by comparing the
371/// provided version number against the supported range defined by
372/// [`MIN_SUPPORTED_VERSION`] and [`MAX_SUPPORTED_VERSION`].
373///
374/// # Parameters
375///
376/// - `version`: The format version number read from a snapshot header
377///
378/// # Returns
379///
380/// A [`VersionCompatibility`] value indicating whether the snapshot can be read:
381///
382/// - [`Full`](VersionCompatibility::Full): Version is within supported range
383/// - [`Incompatible`](VersionCompatibility::Incompatible): Version is too old or too new
384/// - [`Degraded`](VersionCompatibility::Degraded): Currently unused (reserved for future)
385///
386/// # Version Ranges
387///
388/// | Condition | Result | Reason |
389/// |-----------|--------|--------|
390/// | `version < MIN_SUPPORTED_VERSION` | Incompatible | Too old, needs migration |
391/// | `MIN_SUPPORTED_VERSION <= version <= MAX_SUPPORTED_VERSION` | Full | Within supported range |
392/// | `version > MAX_SUPPORTED_VERSION` | Incompatible | Too new, upgrade Hexz |
393///
394/// # Future Extensions
395///
396/// In future versions, this function may return `Degraded` for snapshots with
397/// minor version mismatches, enabling partial reads with warnings. For example:
398///
399/// ```rust,ignore
400/// // Hypothetical future behavior with major.minor versioning
401/// if version.major == CURRENT_VERSION.major && version.minor > CURRENT_VERSION.minor {
402/// VersionCompatibility::Degraded // Newer minor version, try best-effort read
403/// }
404/// ```
405///
406/// # Performance
407///
408/// This function performs two integer comparisons and has negligible overhead
409/// (~100ns). It is called once per snapshot open operation and does not affect
410/// hot path performance.
411///
412/// # Examples
413///
414/// ```
415/// use hexz_core::format::version::{
416/// check_version, VersionCompatibility,
417/// MIN_SUPPORTED_VERSION, MAX_SUPPORTED_VERSION
418/// };
419///
420/// // Version within supported range
421/// let compat = check_version(1);
422/// assert_eq!(compat, VersionCompatibility::Full);
423///
424/// // Version too old (hypothetical example if MIN_SUPPORTED_VERSION > 1)
425/// let compat = check_version(0);
426/// assert_eq!(compat, VersionCompatibility::Incompatible);
427///
428/// // Version too new
429/// let compat = check_version(MAX_SUPPORTED_VERSION + 1);
430/// assert_eq!(compat, VersionCompatibility::Incompatible);
431/// ```
432///
433/// ## Error Handling
434///
435/// ```rust,ignore
436/// use hexz_core::format::version::check_version;
437///
438/// fn open_snapshot(header: &Header) -> Result<Snapshot, Error> {
439/// let compat = check_version(header.version);
440/// if !compat.is_compatible() {
441/// return Err(Error::IncompatibleVersion {
442/// found: header.version,
443/// supported_range: (MIN_SUPPORTED_VERSION, MAX_SUPPORTED_VERSION),
444/// });
445/// }
446/// // Proceed with read operations
447/// Ok(Snapshot::new(header))
448/// }
449/// ```
450pub fn check_version(version: u32) -> VersionCompatibility {
451 if version < MIN_SUPPORTED_VERSION {
452 VersionCompatibility::Incompatible
453 } else if version > MAX_SUPPORTED_VERSION {
454 // For now, strict versioning. Future: Check major/minor for degraded mode.
455 VersionCompatibility::Incompatible
456 } else {
457 VersionCompatibility::Full
458 }
459}
460
461/// Generates a user-facing message describing version compatibility status.
462///
463/// This function produces human-readable error messages and recommendations
464/// for handling version mismatches. The messages are designed to be displayed
465/// directly to end users in error logs or CLI output.
466///
467/// # Parameters
468///
469/// - `version`: The format version number from a snapshot header
470///
471/// # Returns
472///
473/// A `String` containing:
474/// - **Full compatibility**: Confirmation message
475/// - **Degraded compatibility**: Warning about potential feature limitations
476/// - **Incompatibility**: Error message with actionable remediation steps
477///
478/// # Message Content
479///
480/// ## Fully Supported Version
481///
482/// ```text
483/// "Version 1 is fully supported."
484/// ```
485///
486/// ## Too Old (version < MIN_SUPPORTED_VERSION)
487///
488/// ```text
489/// "Version 0 is too old (min supported: 1). Please upgrade the snapshot."
490/// ```
491///
492/// Remediation: Use `hexz-migrate upgrade` to convert the snapshot.
493///
494/// ## Too New (version > MAX_SUPPORTED_VERSION)
495///
496/// ```text
497/// "Version 2 is too new (max supported: 1). Please upgrade Hexz."
498/// ```
499///
500/// Remediation: Install a newer version of the Hexz toolchain.
501///
502/// ## Degraded Compatibility (Future)
503///
504/// ```text
505/// "Version 2 is newer than supported (1), features may be missing."
506/// ```
507///
508/// Remediation: Upgrade Hexz for full feature support, or proceed with warnings.
509///
510/// # Usage in Error Handling
511///
512/// This function is typically called when displaying compatibility errors to users:
513///
514/// ```rust,ignore
515/// use hexz_core::format::version::{check_version, compatibility_message};
516///
517/// fn validate_snapshot(header: &Header) -> Result<(), String> {
518/// let compat = check_version(header.version);
519/// if !compat.is_compatible() {
520/// return Err(compatibility_message(header.version));
521/// }
522/// Ok(())
523/// }
524/// ```
525///
526/// # Examples
527///
528/// ```
529/// use hexz_core::format::version::{compatibility_message, MAX_SUPPORTED_VERSION};
530///
531/// // Supported version
532/// let msg = compatibility_message(1);
533/// assert!(msg.contains("fully supported"));
534///
535/// // Version too new
536/// let msg = compatibility_message(MAX_SUPPORTED_VERSION + 1);
537/// assert!(msg.contains("too new"));
538/// assert!(msg.contains("upgrade Hexz"));
539///
540/// // Version too old (hypothetical if MIN_SUPPORTED_VERSION > 1)
541/// let msg = compatibility_message(0);
542/// assert!(msg.contains("too old"));
543/// assert!(msg.contains("upgrade the snapshot"));
544/// ```
545///
546/// ## CLI Integration
547///
548/// ```bash
549/// $ hexz open old_snapshot.hxz
550/// Error: Version 0 is too old (min supported: 1). Please upgrade the snapshot.
551///
552/// Run: hexz-migrate upgrade old_snapshot.hxz new_snapshot.hxz
553/// ```
554pub fn compatibility_message(version: u32) -> String {
555 match check_version(version) {
556 VersionCompatibility::Full => format!("Version {} is fully supported.", version),
557 VersionCompatibility::Degraded => format!(
558 "Version {} is newer than supported ({}), features may be missing.",
559 version, MAX_SUPPORTED_VERSION
560 ),
561 VersionCompatibility::Incompatible => {
562 if version < MIN_SUPPORTED_VERSION {
563 format!(
564 "Version {} is too old (min supported: {}). Please upgrade the snapshot.",
565 version, MIN_SUPPORTED_VERSION
566 )
567 } else {
568 format!(
569 "Version {} is too new (max supported: {}). Please upgrade Hexz.",
570 version, MAX_SUPPORTED_VERSION
571 )
572 }
573 }
574 }
575}
576
577impl fmt::Display for VersionCompatibility {
578 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
579 match self {
580 VersionCompatibility::Full => write!(f, "full"),
581 VersionCompatibility::Degraded => write!(f, "degraded"),
582 VersionCompatibility::Incompatible => write!(f, "incompatible"),
583 }
584 }
585}
586
587#[cfg(test)]
588mod tests {
589 use super::*;
590
591 #[test]
592 #[allow(clippy::assertions_on_constants)]
593 fn test_version_constants_are_consistent() {
594 // MIN_SUPPORTED_VERSION must be <= MAX_SUPPORTED_VERSION
595 assert!(
596 MIN_SUPPORTED_VERSION <= MAX_SUPPORTED_VERSION,
597 "MIN_SUPPORTED_VERSION ({}) must be <= MAX_SUPPORTED_VERSION ({})",
598 MIN_SUPPORTED_VERSION,
599 MAX_SUPPORTED_VERSION
600 );
601
602 // CURRENT_VERSION must be within supported range
603 assert!(
604 CURRENT_VERSION >= MIN_SUPPORTED_VERSION,
605 "CURRENT_VERSION ({}) must be >= MIN_SUPPORTED_VERSION ({})",
606 CURRENT_VERSION,
607 MIN_SUPPORTED_VERSION
608 );
609 assert!(
610 CURRENT_VERSION <= MAX_SUPPORTED_VERSION,
611 "CURRENT_VERSION ({}) must be <= MAX_SUPPORTED_VERSION ({})",
612 CURRENT_VERSION,
613 MAX_SUPPORTED_VERSION
614 );
615 }
616
617 #[test]
618 fn test_check_version_current_is_fully_supported() {
619 let compat = check_version(CURRENT_VERSION);
620 assert_eq!(compat, VersionCompatibility::Full);
621 assert!(compat.is_compatible());
622 }
623
624 #[test]
625 fn test_check_version_within_range_is_full() {
626 // Test all versions in the supported range
627 for version in MIN_SUPPORTED_VERSION..=MAX_SUPPORTED_VERSION {
628 let compat = check_version(version);
629 assert_eq!(
630 compat,
631 VersionCompatibility::Full,
632 "Version {} should be fully compatible",
633 version
634 );
635 assert!(compat.is_compatible());
636 }
637 }
638
639 #[test]
640 fn test_check_version_too_old_is_incompatible() {
641 if MIN_SUPPORTED_VERSION > 0 {
642 let old_version = MIN_SUPPORTED_VERSION - 1;
643 let compat = check_version(old_version);
644 assert_eq!(compat, VersionCompatibility::Incompatible);
645 assert!(!compat.is_compatible());
646 }
647
648 // Always test version 0 as too old
649 let compat = check_version(0);
650 assert_eq!(compat, VersionCompatibility::Incompatible);
651 assert!(!compat.is_compatible());
652 }
653
654 #[test]
655 fn test_check_version_too_new_is_incompatible() {
656 let new_version = MAX_SUPPORTED_VERSION + 1;
657 let compat = check_version(new_version);
658 assert_eq!(compat, VersionCompatibility::Incompatible);
659 assert!(!compat.is_compatible());
660
661 // Test very high version number
662 let compat = check_version(9999);
663 assert_eq!(compat, VersionCompatibility::Incompatible);
664 assert!(!compat.is_compatible());
665 }
666
667 #[test]
668 fn test_version_compatibility_is_compatible() {
669 // Full is compatible
670 assert!(VersionCompatibility::Full.is_compatible());
671
672 // Degraded is compatible (with warnings)
673 assert!(VersionCompatibility::Degraded.is_compatible());
674
675 // Incompatible is not compatible
676 assert!(!VersionCompatibility::Incompatible.is_compatible());
677 }
678
679 #[test]
680 fn test_version_compatibility_display() {
681 assert_eq!(format!("{}", VersionCompatibility::Full), "full");
682 assert_eq!(format!("{}", VersionCompatibility::Degraded), "degraded");
683 assert_eq!(
684 format!("{}", VersionCompatibility::Incompatible),
685 "incompatible"
686 );
687 }
688
689 #[test]
690 fn test_compatibility_message_for_supported_version() {
691 let msg = compatibility_message(CURRENT_VERSION);
692 assert!(msg.contains("fully supported"), "Message: {}", msg);
693 assert!(
694 msg.contains(&CURRENT_VERSION.to_string()),
695 "Message: {}",
696 msg
697 );
698 }
699
700 #[test]
701 fn test_compatibility_message_for_too_old_version() {
702 if MIN_SUPPORTED_VERSION > 0 {
703 let old_version = MIN_SUPPORTED_VERSION - 1;
704 let msg = compatibility_message(old_version);
705 assert!(msg.contains("too old"), "Message: {}", msg);
706 assert!(
707 msg.contains(&MIN_SUPPORTED_VERSION.to_string()),
708 "Message: {}",
709 msg
710 );
711 assert!(msg.contains("upgrade the snapshot"), "Message: {}", msg);
712 }
713 }
714
715 #[test]
716 fn test_compatibility_message_for_too_new_version() {
717 let new_version = MAX_SUPPORTED_VERSION + 1;
718 let msg = compatibility_message(new_version);
719 assert!(msg.contains("too new"), "Message: {}", msg);
720 assert!(
721 msg.contains(&MAX_SUPPORTED_VERSION.to_string()),
722 "Message: {}",
723 msg
724 );
725 assert!(msg.contains("upgrade Hexz"), "Message: {}", msg);
726 }
727
728 #[test]
729 fn test_compatibility_message_for_degraded() {
730 // Since we don't currently return Degraded, we can't directly test it
731 // But we can test the message format by checking what would happen
732 let msg = match VersionCompatibility::Degraded {
733 VersionCompatibility::Degraded => format!(
734 "Version {} is newer than supported ({}), features may be missing.",
735 99, MAX_SUPPORTED_VERSION
736 ),
737 _ => String::new(),
738 };
739 assert!(msg.contains("newer than supported"));
740 assert!(msg.contains("features may be missing"));
741 }
742
743 #[test]
744 fn test_version_compatibility_enum_properties() {
745 // Test Debug trait
746 let full = VersionCompatibility::Full;
747 assert!(format!("{:?}", full).contains("Full"));
748
749 // Test Clone and Copy
750 let degraded = VersionCompatibility::Degraded;
751 let degraded_copy = degraded;
752 let degraded_clone = degraded;
753 assert_eq!(degraded, degraded_copy);
754 assert_eq!(degraded, degraded_clone);
755
756 // Test PartialEq and Eq
757 assert_eq!(VersionCompatibility::Full, VersionCompatibility::Full);
758 assert_ne!(
759 VersionCompatibility::Full,
760 VersionCompatibility::Incompatible
761 );
762 }
763
764 #[test]
765 fn test_check_version_boundary_conditions() {
766 // Test MIN_SUPPORTED_VERSION boundary
767 let compat = check_version(MIN_SUPPORTED_VERSION);
768 assert_eq!(compat, VersionCompatibility::Full);
769
770 // Test MAX_SUPPORTED_VERSION boundary
771 let compat = check_version(MAX_SUPPORTED_VERSION);
772 assert_eq!(compat, VersionCompatibility::Full);
773
774 // Test just below MIN
775 if MIN_SUPPORTED_VERSION > 0 {
776 let compat = check_version(MIN_SUPPORTED_VERSION - 1);
777 assert_eq!(compat, VersionCompatibility::Incompatible);
778 }
779
780 // Test just above MAX
781 let compat = check_version(MAX_SUPPORTED_VERSION + 1);
782 assert_eq!(compat, VersionCompatibility::Incompatible);
783 }
784
785 #[test]
786 fn test_multiple_version_checks() {
787 // Verify check_version is consistent across multiple calls
788 let version = CURRENT_VERSION;
789 let result1 = check_version(version);
790 let result2 = check_version(version);
791 assert_eq!(result1, result2);
792 }
793
794 #[test]
795 fn test_compatibility_message_contains_version_number() {
796 // Test that messages include the actual version number
797 for version in [0u32, 1, 2, 99, 999] {
798 let msg = compatibility_message(version);
799 assert!(
800 msg.contains(&version.to_string()),
801 "Message for version {} should contain the version number: {}",
802 version,
803 msg
804 );
805 }
806 }
807
808 #[test]
809 #[allow(clippy::assertions_on_constants)]
810 fn test_version_constants_are_reasonable() {
811 // Ensure version numbers are in a reasonable range
812 assert!(CURRENT_VERSION > 0, "CURRENT_VERSION must be positive");
813 assert!(
814 CURRENT_VERSION < 1000,
815 "CURRENT_VERSION seems unreasonably high"
816 );
817 assert!(
818 MIN_SUPPORTED_VERSION > 0,
819 "MIN_SUPPORTED_VERSION must be positive"
820 );
821 assert!(
822 MAX_SUPPORTED_VERSION < 1000,
823 "MAX_SUPPORTED_VERSION seems unreasonably high"
824 );
825 }
826}