adaptive_pipeline_domain/value_objects/
generic_size.rs

1// /////////////////////////////////////////////////////////////////////////////
2// Adaptive Pipeline
3// Copyright (c) 2025 Michael Gardner, A Bit of Help, Inc.
4// SPDX-License-Identifier: BSD-3-Clause
5// See LICENSE file in the project root.
6// /////////////////////////////////////////////////////////////////////////////
7
8//! # Generic Size Value Object
9//!
10//! This module provides a generic, type-safe size value object system for the
11//! adaptive pipeline system. It uses phantom types to enforce compile-time
12//! size category safety while providing shared arithmetic operations and
13//! unit conversions.
14//!
15//! ## Overview
16//!
17//! The generic size system provides:
18//!
19//! - **Type Safety**: Compile-time enforcement of size categories
20//! - **Unit Conversions**: Automatic conversions between size units
21//! - **Arithmetic Operations**: Safe arithmetic operations on sizes
22//! - **Zero-Cost Abstractions**: Phantom types with no runtime overhead
23//! - **Validation**: Category-specific size validation and constraints
24//!
25//! ## Architecture
26//!
27//! The size system follows Domain-Driven Design principles:
28//!
29//! - **Value Object**: Immutable value object with equality semantics
30//! - **Type Safety**: Phantom types prevent category mixing at compile time
31//! - **Rich Domain Model**: Encapsulates size-related business logic
32//! - **Validation**: Comprehensive validation of size values and constraints
33//!
34//! ## Key Features
35//!
36//! ### Type-Safe Size Categories
37//!
38//! - **File Sizes**: Sizes for files and file operations
39//! - **Memory Sizes**: Sizes for memory allocation and usage
40//! - **Network Sizes**: Sizes for network bandwidth and data transfer
41//! - **Storage Sizes**: Sizes for storage capacity and usage
42//! - **Custom Categories**: Support for custom size categories
43//!
44//! ### Unit Conversions
45//!
46//! - **Automatic Conversion**: Automatic conversion between units
47//! - **Multiple Units**: Support for bytes, KB, MB, GB, TB, PB
48//! - **Binary/Decimal**: Support for both binary (1024) and decimal (1000)
49//!   units
50//! - **Precision Handling**: Proper handling of precision during conversions
51//!
52//! ### Arithmetic Operations
53//!
54//! - **Addition**: Add sizes of the same category
55//! - **Subtraction**: Subtract sizes with underflow protection
56//! - **Multiplication**: Multiply sizes by scalars
57//! - **Division**: Divide sizes with division by zero protection
58//!
59//! ## Usage Examples
60//!
61//! ### Basic Size Creation
62
63//!
64//! ### Unit Conversions
65
66//!
67//! ### Arithmetic Operations
68
69//!
70//! ### Type Safety Demonstration
71
72//!
73//! ### Custom Size Categories
74
75//!
76//! ### Size Validation and Constraints
77
78//!
79//! ## Size Categories
80//!
81//! ### Built-in Categories
82//!
83//! - **FileSizeCategory**: For file sizes and file operations
84//!   - Validation: Standard file size validation
85//!   - Use case: File processing and storage
86//!
87//! - **MemorySizeCategory**: For memory allocation and usage
88//!   - Validation: Memory-specific constraints
89//!   - Use case: Memory management and allocation
90//!
91//! - **NetworkSizeCategory**: For network bandwidth and data transfer
92//!   - Validation: Network-specific constraints
93//!   - Use case: Network operations and bandwidth management
94//!
95//! - **StorageSizeCategory**: For storage capacity and usage
96//!   - Validation: Storage-specific constraints
97//!   - Use case: Storage planning and management
98//!
99//! ### Custom Categories
100//!
101//! Create custom size categories by implementing the `SizeCategory` trait:
102//!
103//! - **Category Name**: Unique identifier for the category
104//! - **Validation Logic**: Custom validation rules
105//! - **Size Limits**: Category-specific size limits
106//!
107//! ## Unit Systems
108//!
109//! ### Binary Units (Base 1024)
110//!
111//! - **Byte**: 1 byte
112//! - **KiB**: 1,024 bytes
113//! - **MiB**: 1,048,576 bytes
114//! - **GiB**: 1,073,741,824 bytes
115//! - **TiB**: 1,099,511,627,776 bytes
116//!
117//! ### Decimal Units (Base 1000)
118//!
119//! - **Byte**: 1 byte
120//! - **KB**: 1,000 bytes
121//! - **MB**: 1,000,000 bytes
122//! - **GB**: 1,000,000,000 bytes
123//! - **TB**: 1,000,000,000,000 bytes
124//!
125//! ## Arithmetic Operations
126//!
127//! ### Supported Operations
128//!
129//! - **Addition**: `size1 + size2`
130//! - **Subtraction**: `size1 - size2` (with underflow protection)
131//! - **Multiplication**: `size * scalar`
132//! - **Division**: `size / scalar` (with division by zero protection)
133//!
134//! ### Safety Features
135//!
136//! - **Overflow Protection**: Detect and handle arithmetic overflow
137//! - **Underflow Protection**: Prevent negative sizes
138//! - **Division by Zero**: Prevent division by zero
139//! - **Type Safety**: Ensure operations are on same category
140//!
141//! ## Validation Rules
142//!
143//! ### General Validation
144//!
145//! - **Non-negative**: Sizes must be non-negative
146//! - **Finite**: Sizes must be finite values
147//! - **Range Limits**: Sizes must be within valid ranges
148//!
149//! ### Category-Specific Validation
150//!
151//! - **File Sizes**: Validate against file system limits
152//! - **Memory Sizes**: Validate against available memory
153//! - **Network Sizes**: Validate against bandwidth limits
154//! - **Custom Categories**: Apply custom validation rules
155//!
156//! ## Error Handling
157//!
158//! ### Size Errors
159//!
160//! - **Invalid Size**: Size value is invalid
161//! - **Overflow**: Arithmetic operation caused overflow
162//! - **Underflow**: Arithmetic operation caused underflow
163//! - **Division by Zero**: Division by zero attempted
164//!
165//! ### Validation Errors
166//!
167//! - **Constraint Violation**: Size violates category constraints
168//! - **Range Error**: Size is outside valid range
169//! - **Type Error**: Invalid size type or category
170//!
171//! ## Performance Considerations
172//!
173//! ### Memory Usage
174//!
175//! - **Compact Storage**: Efficient storage of size values
176//! - **Zero-Cost Types**: Phantom types have no runtime cost
177//! - **Efficient Arithmetic**: Optimized arithmetic operations
178//!
179//! ### Conversion Performance
180//!
181//! - **Fast Conversions**: Efficient unit conversions
182//! - **Minimal Allocations**: Avoid unnecessary allocations
183//! - **Cached Results**: Cache expensive conversion results
184//!
185//! ## Integration
186//!
187//! The generic size system integrates with:
188//!
189//! - **File System**: File size management and validation
190//! - **Memory Management**: Memory allocation and tracking
191//! - **Network Operations**: Bandwidth and transfer size management
192//! - **Storage Systems**: Storage capacity planning and monitoring
193//!
194//! ## Thread Safety
195//!
196//! The generic size system is thread-safe:
197//!
198//! - **Immutable**: Sizes are immutable after creation
199//! - **Safe Sharing**: Safe to share between threads
200//! - **Concurrent Operations**: Safe concurrent arithmetic operations
201//!
202//! ## Future Enhancements
203//!
204//! Planned enhancements include:
205//!
206//! - **More Unit Types**: Support for additional unit types
207//! - **Precision Control**: Better precision control for conversions
208//! - **Performance Optimization**: Further performance optimizations
209//! - **Enhanced Validation**: More sophisticated validation rules
210
211use serde::{Deserialize, Serialize};
212use std::fmt::{self, Display};
213use std::marker::PhantomData;
214use std::ops::{Add, Div, Mul, Sub};
215
216use crate::PipelineError;
217
218/// Generic size value object with type-safe size categories
219///
220/// # Purpose
221/// Type-safe size measurement that provides:
222/// - Compile-time size category enforcement (File vs Memory vs Network)
223/// - Shared arithmetic and conversion operations
224/// - Zero-cost abstractions with phantom types
225/// - Category-specific validation and constraints
226///
227/// # Generic Benefits
228/// - **Type Safety**: Cannot mix file sizes with memory sizes at compile time
229/// - **Code Reuse**: Shared implementation for all size types
230/// - **Extensibility**: Easy to add new size categories
231/// - **Zero Cost**: Phantom types have no runtime overhead
232///
233/// # Use Cases
234/// - File size management and validation
235/// - Memory allocation tracking
236/// - Network bandwidth measurement
237/// - Storage capacity planning
238///
239/// # Cross-Language Mapping
240/// - **Rust**: `GenericSize<T>` with marker types
241///
242/// # Examples
243///
244/// - **Go**: Separate types with shared interface
245/// - **JSON**: Number with unit metadata
246/// - **SQLite**: INTEGER with category column
247#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
248pub struct GenericSize<T> {
249    bytes: u64,
250    #[serde(skip)]
251    _phantom: PhantomData<T>,
252}
253
254/// Marker type for file sizes
255#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
256pub struct FileSizeMarker;
257
258/// Marker type for memory sizes
259#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
260pub struct MemorySizeMarker;
261
262/// Marker type for network transfer sizes
263#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
264pub struct NetworkSizeMarker;
265
266/// Marker type for storage capacity sizes
267#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
268pub struct StorageSizeMarker;
269
270/// Type aliases for common size types
271pub type FileSize = GenericSize<FileSizeMarker>;
272pub type MemorySize = GenericSize<MemorySizeMarker>;
273pub type NetworkSize = GenericSize<NetworkSizeMarker>;
274pub type StorageSize = GenericSize<StorageSizeMarker>;
275
276/// Size category trait for type-specific behavior
277pub trait SizeCategory {
278    /// Gets the category name for this size type
279    fn category_name() -> &'static str;
280
281    /// Gets the maximum allowed size for this category
282    fn max_size() -> u64;
283
284    /// Gets the default unit for display
285    fn default_unit() -> &'static str;
286
287    /// Validates category-specific constraints
288    fn validate_size(bytes: u64) -> Result<(), PipelineError> {
289        if bytes > Self::max_size() {
290            return Err(PipelineError::InvalidConfiguration(format!(
291                "{} size {} exceeds maximum allowed {}",
292                Self::category_name(),
293                bytes,
294                Self::max_size()
295            )));
296        }
297        Ok(())
298    }
299}
300
301impl SizeCategory for FileSizeMarker {
302    fn category_name() -> &'static str {
303        "file"
304    }
305
306    fn max_size() -> u64 {
307        10 * 1024 * 1024 * 1024 * 1024 // 10 TB
308    }
309
310    fn default_unit() -> &'static str {
311        "MB"
312    }
313}
314
315impl SizeCategory for MemorySizeMarker {
316    fn category_name() -> &'static str {
317        "memory"
318    }
319
320    fn max_size() -> u64 {
321        1024 * 1024 * 1024 * 1024 // 1 TB (for very large systems)
322    }
323
324    fn default_unit() -> &'static str {
325        "MB"
326    }
327
328    fn validate_size(bytes: u64) -> Result<(), PipelineError> {
329        if bytes > Self::max_size() {
330            return Err(PipelineError::InvalidConfiguration(format!(
331                "Memory size {} exceeds maximum allowed {}",
332                bytes,
333                Self::max_size()
334            )));
335        }
336
337        // Memory sizes should be power of 2 aligned for efficiency
338        if bytes > 0 && !is_power_of_2_aligned(bytes) {
339            // This is a warning, not an error
340            eprintln!("Warning: Memory size {} is not power-of-2 aligned", bytes);
341        }
342
343        Ok(())
344    }
345}
346
347impl SizeCategory for NetworkSizeMarker {
348    fn category_name() -> &'static str {
349        "network"
350    }
351
352    fn max_size() -> u64 {
353        100 * 1024 * 1024 * 1024 // 100 GB per transfer
354    }
355
356    fn default_unit() -> &'static str {
357        "MB"
358    }
359
360    fn validate_size(bytes: u64) -> Result<(), PipelineError> {
361        if bytes > Self::max_size() {
362            return Err(PipelineError::InvalidConfiguration(format!(
363                "Network transfer size {} exceeds maximum allowed {}",
364                bytes,
365                Self::max_size()
366            )));
367        }
368
369        // Network transfers should be reasonably sized chunks
370        if bytes > 0 && bytes < 1024 {
371            eprintln!("Warning: Very small network transfer size: {} bytes", bytes);
372        }
373
374        Ok(())
375    }
376}
377
378impl SizeCategory for StorageSizeMarker {
379    fn category_name() -> &'static str {
380        "storage"
381    }
382
383    fn max_size() -> u64 {
384        u64::MAX // Essentially unlimited for storage capacity
385    }
386
387    fn default_unit() -> &'static str {
388        "GB"
389    }
390}
391
392impl<T: SizeCategory> GenericSize<T> {
393    /// Creates a new size with category-specific validation
394    ///
395    /// # Purpose
396    /// Creates a type-safe size value with category-specific validation.
397    /// Uses phantom types to prevent mixing different size categories at
398    /// compile time.
399    ///
400    /// # Why
401    /// Type-safe sizes provide:
402    /// - Compile-time prevention of mixing file/memory/network sizes
403    /// - Category-specific validation and constraints
404    /// - Zero-cost abstractions with phantom types
405    /// - Clear API contracts for size requirements
406    ///
407    /// # Arguments
408    /// * `bytes` - Size in bytes (validated against category limits)
409    ///
410    /// # Returns
411    /// * `Ok(GenericSize<T>)` - Valid size for category T
412    /// * `Err(PipelineError::InvalidConfiguration)` - Exceeds category maximum
413    ///
414    /// # Errors
415    /// Returns error when size exceeds category-specific maximum:
416    /// - File sizes: 10 TB maximum
417    /// - Memory sizes: 1 TB maximum
418    /// - Network sizes: 100 GB per transfer
419    /// - Storage sizes: essentially unlimited
420    ///
421    /// # Examples
422    pub fn new(bytes: u64) -> Result<Self, PipelineError> {
423        T::validate_size(bytes)?;
424        Ok(Self {
425            bytes,
426            _phantom: PhantomData,
427        })
428    }
429
430    /// Creates a zero size
431    pub fn zero() -> Self {
432        Self {
433            bytes: 0,
434            _phantom: PhantomData,
435        }
436    }
437
438    /// Creates size from kilobytes
439    pub fn from_kb(kb: u64) -> Result<Self, PipelineError> {
440        let bytes = kb
441            .checked_mul(1024)
442            .ok_or_else(|| PipelineError::InvalidConfiguration("Kilobyte value too large".to_string()))?;
443        Self::new(bytes)
444    }
445
446    /// Creates size from megabytes
447    ///
448    /// # Purpose
449    /// Convenience constructor for creating sizes from megabyte values.
450    /// Automatically converts to bytes and validates.
451    ///
452    /// # Why
453    /// MB-based construction provides:
454    /// - Human-readable size specification
455    /// - Common unit for file and memory sizes
456    /// - Overflow protection during conversion
457    /// - Category validation
458    ///
459    /// # Arguments
460    /// * `mb` - Size in megabytes (1 MB = 1,048,576 bytes)
461    ///
462    /// # Returns
463    /// * `Ok(GenericSize<T>)` - Valid size
464    /// * `Err(PipelineError)` - Overflow or validation error
465    ///
466    /// # Errors
467    /// - Multiplication overflow during byte conversion
468    /// - Category maximum exceeded
469    ///
470    /// # Examples
471    pub fn from_mb(mb: u64) -> Result<Self, PipelineError> {
472        let bytes = mb
473            .checked_mul(1024 * 1024)
474            .ok_or_else(|| PipelineError::InvalidConfiguration("Megabyte value too large".to_string()))?;
475        Self::new(bytes)
476    }
477
478    /// Creates size from gigabytes
479    pub fn from_gb(gb: u64) -> Result<Self, PipelineError> {
480        let bytes = gb
481            .checked_mul(1024 * 1024 * 1024)
482            .ok_or_else(|| PipelineError::InvalidConfiguration("Gigabyte value too large".to_string()))?;
483        Self::new(bytes)
484    }
485
486    /// Gets the raw byte count
487    pub fn bytes(&self) -> u64 {
488        self.bytes
489    }
490
491    /// Gets the size category name
492    pub fn category(&self) -> &'static str {
493        T::category_name()
494    }
495
496    /// Converts to a different size category (with validation)
497    pub fn into_category<U: SizeCategory>(self) -> Result<GenericSize<U>, PipelineError> {
498        U::validate_size(self.bytes)?;
499        Ok(GenericSize {
500            bytes: self.bytes,
501            _phantom: PhantomData,
502        })
503    }
504
505    /// Gets size as kilobytes
506    pub fn as_kb(&self) -> u64 {
507        self.bytes / 1024
508    }
509
510    /// Gets size as megabytes
511    pub fn as_mb(&self) -> u64 {
512        self.bytes / (1024 * 1024)
513    }
514
515    /// Gets size as gigabytes
516    pub fn as_gb(&self) -> u64 {
517        self.bytes / (1024 * 1024 * 1024)
518    }
519
520    /// Gets size as floating point megabytes
521    pub fn as_mb_f64(&self) -> f64 {
522        (self.bytes as f64) / (1024.0 * 1024.0)
523    }
524
525    /// Gets size as floating point gigabytes
526    pub fn as_gb_f64(&self) -> f64 {
527        (self.bytes as f64) / (1024.0 * 1024.0 * 1024.0)
528    }
529
530    /// Checks if size is zero
531    pub fn is_zero(&self) -> bool {
532        self.bytes == 0
533    }
534
535    /// Formats as human-readable string
536    pub fn human_readable(&self) -> String {
537        if self.bytes >= 1024 * 1024 * 1024 {
538            format!("{:.2} GB", self.as_gb_f64())
539        } else if self.bytes >= 1024 * 1024 {
540            format!("{:.2} MB", self.as_mb_f64())
541        } else if self.bytes >= 1024 {
542            format!("{:.2} KB", (self.bytes as f64) / 1024.0)
543        } else {
544            format!("{} bytes", self.bytes)
545        }
546    }
547
548    /// Safely adds sizes (checked arithmetic)
549    pub fn checked_add(&self, other: Self) -> Result<Self, PipelineError> {
550        let result = self
551            .bytes
552            .checked_add(other.bytes)
553            .ok_or_else(|| PipelineError::InvalidConfiguration("Size addition would overflow".to_string()))?;
554        Self::new(result)
555    }
556
557    /// Safely subtracts sizes (checked arithmetic)
558    pub fn checked_sub(&self, other: Self) -> Result<Self, PipelineError> {
559        let result = self
560            .bytes
561            .checked_sub(other.bytes)
562            .ok_or_else(|| PipelineError::InvalidConfiguration("Size subtraction would underflow".to_string()))?;
563        Self::new(result)
564    }
565
566    /// Validates the size
567    pub fn validate(&self) -> Result<(), PipelineError> {
568        T::validate_size(self.bytes)
569    }
570}
571
572// Arithmetic operations for same-category sizes
573impl<T: SizeCategory> Add for GenericSize<T> {
574    type Output = GenericSize<T>;
575
576    fn add(self, rhs: GenericSize<T>) -> Self::Output {
577        GenericSize {
578            bytes: self.bytes + rhs.bytes,
579            _phantom: PhantomData,
580        }
581    }
582}
583
584impl<T: SizeCategory> Sub for GenericSize<T> {
585    type Output = GenericSize<T>;
586
587    fn sub(self, rhs: GenericSize<T>) -> Self::Output {
588        GenericSize {
589            bytes: self.bytes - rhs.bytes,
590            _phantom: PhantomData,
591        }
592    }
593}
594
595impl<T: SizeCategory> Mul<u64> for GenericSize<T> {
596    type Output = GenericSize<T>;
597
598    fn mul(self, rhs: u64) -> Self::Output {
599        GenericSize {
600            bytes: self.bytes * rhs,
601            _phantom: PhantomData,
602        }
603    }
604}
605
606impl<T: SizeCategory> Div<u64> for GenericSize<T> {
607    type Output = GenericSize<T>;
608
609    fn div(self, rhs: u64) -> Self::Output {
610        GenericSize {
611            bytes: self.bytes / rhs,
612            _phantom: PhantomData,
613        }
614    }
615}
616
617impl<T: SizeCategory> Default for GenericSize<T> {
618    fn default() -> Self {
619        Self::zero()
620    }
621}
622
623impl<T: SizeCategory> Display for GenericSize<T> {
624    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
625        write!(f, "{} ({})", self.human_readable(), T::category_name())
626    }
627}
628
629impl<T> From<u64> for GenericSize<T> {
630    fn from(bytes: u64) -> Self {
631        // Note: This bypasses validation, use new() for validation
632        Self {
633            bytes,
634            _phantom: PhantomData,
635        }
636    }
637}
638
639impl<T> From<GenericSize<T>> for u64 {
640    fn from(size: GenericSize<T>) -> Self {
641        size.bytes
642    }
643}
644
645/// Specialized methods for different size categories
646impl FileSize {
647    /// Checks if this is a large file (> 1 GB)
648    pub fn is_large_file(&self) -> bool {
649        self.bytes > 1024 * 1024 * 1024
650    }
651
652    /// Estimates transfer time for given bandwidth (MB/s)
653    pub fn transfer_time_seconds(&self, bandwidth_mbps: f64) -> f64 {
654        if bandwidth_mbps <= 0.0 {
655            f64::INFINITY
656        } else {
657            self.as_mb_f64() / bandwidth_mbps
658        }
659    }
660}
661
662impl MemorySize {
663    /// Checks if this is aligned to page boundaries (typically 4KB)
664    pub fn is_page_aligned(&self) -> bool {
665        self.bytes.is_multiple_of(4096)
666    }
667
668    /// Rounds up to next page boundary
669    pub fn round_up_to_page(&self) -> MemorySize {
670        let page_size = 4096;
671        let aligned_bytes = (self.bytes + page_size - 1) & !(page_size - 1);
672        MemorySize::from(aligned_bytes)
673    }
674
675    /// Checks if size is reasonable for allocation
676    pub fn is_reasonable_allocation(&self) -> bool {
677        // Between 1 byte and 1 GB for typical allocations
678        self.bytes > 0 && self.bytes <= 1024 * 1024 * 1024
679    }
680}
681
682impl NetworkSize {
683    /// Gets optimal chunk size for network transfer
684    pub fn optimal_chunk_size(&self) -> NetworkSize {
685        // Use 64KB chunks for small transfers, 1MB for large
686        if self.bytes <= 1024 * 1024 {
687            NetworkSize::from(64 * 1024) // 64KB
688        } else {
689            NetworkSize::from(1024 * 1024) // 1MB
690        }
691    }
692
693    /// Estimates number of network round trips
694    pub fn estimated_round_trips(&self, mtu: u64) -> u64 {
695        self.bytes.div_ceil(mtu)
696    }
697}
698
699impl StorageSize {
700    /// Converts to storage units (TB, PB)
701    pub fn as_tb(&self) -> u64 {
702        self.bytes / (1024 * 1024 * 1024 * 1024)
703    }
704
705    /// Gets storage cost estimate ($/month at given rate per GB)
706    pub fn monthly_cost(&self, cost_per_gb: f64) -> f64 {
707        self.as_gb_f64() * cost_per_gb
708    }
709}
710
711/// Helper function to check power-of-2 alignment
712fn is_power_of_2_aligned(size: u64) -> bool {
713    if size == 0 {
714        return true;
715    }
716
717    // Check if size is a power of 2 or aligned to common boundaries
718    let common_alignments = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096];
719    common_alignments.iter().any(|&align| size.is_multiple_of(align))
720}
721
722#[cfg(test)]
723mod tests {
724    use super::*;
725
726    /// Tests generic size creation for different size categories.
727    ///
728    /// This test validates that generic sizes can be created for
729    /// different categories (file, memory) with proper type safety
730    /// and category identification.
731    ///
732    /// # Test Coverage
733    ///
734    /// - File size creation with `new()`
735    /// - Memory size creation with `from_mb()`
736    /// - Byte value storage and retrieval
737    /// - Category identification
738    /// - Type-specific constructors
739    ///
740    /// # Test Scenario
741    ///
742    /// Creates file and memory sizes using different constructors
743    /// and verifies the values and categories are set correctly.
744    ///
745    /// # Assertions
746    ///
747    /// - File size stores bytes correctly
748    /// - File size has correct category
749    /// - Memory size converts MB correctly
750    /// - Memory size has correct category
751    #[test]
752    fn test_generic_size_creation() {
753        let file_size = FileSize::new(1024).unwrap();
754        assert_eq!(file_size.bytes(), 1024);
755        assert_eq!(file_size.category(), "file");
756
757        let memory_size = MemorySize::from_mb(512).unwrap();
758        assert_eq!(memory_size.as_mb(), 512);
759        assert_eq!(memory_size.category(), "memory");
760    }
761
762    /// Tests size category validation and constraints.
763    ///
764    /// This test validates that different size categories enforce
765    /// their specific validation rules and constraints, including
766    /// maximum size limits and alignment considerations.
767    ///
768    /// # Test Coverage
769    ///
770    /// - File size validation within limits
771    /// - File size maximum limit enforcement
772    /// - File size overflow rejection
773    /// - Memory size validation with alignment
774    /// - Memory size page alignment warnings
775    ///
776    /// # Test Scenario
777    ///
778    /// Tests various size values against category-specific validation
779    /// rules to ensure proper constraint enforcement.
780    ///
781    /// # Assertions
782    ///
783    /// - Valid file sizes are accepted
784    /// - Maximum file size is accepted
785    /// - Oversized file sizes are rejected
786    /// - Page-aligned memory sizes are accepted
787    /// - Unaligned memory sizes are accepted with warnings
788    #[test]
789    fn test_size_category_validation() {
790        // File size validation
791        assert!(FileSize::new(1024).is_ok());
792        assert!(FileSize::new(FileSizeMarker::max_size()).is_ok());
793        assert!(FileSize::new(FileSizeMarker::max_size() + 1).is_err());
794
795        // Memory size validation (with alignment warning)
796        assert!(MemorySize::new(4096).is_ok()); // Page aligned
797        assert!(MemorySize::new(1000).is_ok()); // Not aligned but valid
798    }
799
800    /// Tests size arithmetic operations and mathematical operations.
801    ///
802    /// This test validates that generic sizes support standard
803    /// arithmetic operations (addition, subtraction, multiplication,
804    /// division) with proper type safety.
805    ///
806    /// # Test Coverage
807    ///
808    /// - Size addition with `+` operator
809    /// - Size subtraction with `-` operator
810    /// - Size multiplication with `*` operator
811    /// - Size division with `/` operator
812    /// - Result value correctness
813    /// - Type safety preservation
814    ///
815    /// # Test Scenario
816    ///
817    /// Creates two file sizes and performs various arithmetic
818    /// operations, verifying the results are calculated correctly.
819    ///
820    /// # Assertions
821    ///
822    /// - Addition produces correct sum
823    /// - Subtraction produces correct difference
824    /// - Multiplication produces correct product
825    /// - Division produces correct quotient
826    #[test]
827    fn test_size_arithmetic() {
828        let size1 = FileSize::new(1000).unwrap();
829        let size2 = FileSize::new(500).unwrap();
830
831        let sum = size1 + size2;
832        assert_eq!(sum.bytes(), 1500);
833
834        let diff = size1 - size2;
835        assert_eq!(diff.bytes(), 500);
836
837        let doubled = size1 * 2;
838        assert_eq!(doubled.bytes(), 2000);
839
840        let halved = size1 / 2;
841        assert_eq!(halved.bytes(), 500);
842    }
843
844    /// Tests size category conversions and type transformations.
845    ///
846    /// This test validates that generic sizes can be converted
847    /// between different categories while preserving byte values
848    /// and updating category information.
849    ///
850    /// # Test Coverage
851    ///
852    /// - File size creation from GB
853    /// - Category conversion with `into_category()`
854    /// - Byte value preservation during conversion
855    /// - Category update after conversion
856    /// - Type transformation validation
857    ///
858    /// # Test Scenario
859    ///
860    /// Creates a file size and converts it to a memory size,
861    /// verifying the byte value is preserved and category is updated.
862    ///
863    /// # Assertions
864    ///
865    /// - Byte values are preserved during conversion
866    /// - Category is updated correctly
867    /// - Type transformation succeeds
868    /// - Converted size has correct properties
869    #[test]
870    fn test_size_conversions() {
871        let file_size = FileSize::from_gb(2).unwrap();
872
873        // Convert to memory size (should work if within limits)
874        let memory_size: MemorySize = file_size.into_category().unwrap();
875        assert_eq!(memory_size.bytes(), file_size.bytes());
876        assert_eq!(memory_size.category(), "memory");
877    }
878
879    /// Tests specialized methods for different size categories.
880    ///
881    /// This test validates that each size category provides
882    /// specialized methods relevant to its domain (file operations,
883    /// memory management, network transfers, storage costs).
884    ///
885    /// # Test Coverage
886    ///
887    /// - File size: large file detection, transfer time calculation
888    /// - Memory size: page alignment, reasonable allocation checks
889    /// - Network size: optimal chunk size, round trip estimation
890    /// - Storage size: TB conversion, monthly cost calculation
891    /// - Domain-specific functionality
892    ///
893    /// # Test Scenario
894    ///
895    /// Creates sizes for different categories and tests their
896    /// specialized methods to ensure domain-specific functionality
897    /// works correctly.
898    ///
899    /// # Assertions
900    ///
901    /// - Large file detection works
902    /// - Transfer time calculation is positive
903    /// - Page alignment detection works
904    /// - Memory allocation reasonableness checks work
905    /// - Network chunk size optimization works
906    /// - Storage cost calculation is accurate
907    #[test]
908    fn test_specialized_methods() {
909        // File size methods
910        let large_file = FileSize::from_gb(2).unwrap();
911        assert!(large_file.is_large_file());
912
913        let transfer_time = large_file.transfer_time_seconds(100.0); // 100 MB/s
914        assert!(transfer_time > 0.0);
915
916        // Memory size methods
917        let memory = MemorySize::new(4096).unwrap();
918        assert!(memory.is_page_aligned());
919        assert!(memory.is_reasonable_allocation());
920
921        let unaligned = MemorySize::new(1000).unwrap();
922        let aligned = unaligned.round_up_to_page();
923        assert!(aligned.is_page_aligned());
924        assert!(aligned.bytes() >= unaligned.bytes());
925
926        // Network size methods
927        let network_transfer = NetworkSize::from_mb(10).unwrap();
928        let chunk_size = network_transfer.optimal_chunk_size();
929        assert!(chunk_size.bytes() > 0);
930
931        let round_trips = network_transfer.estimated_round_trips(1500); // Ethernet MTU
932        assert!(round_trips > 0);
933
934        // Storage size methods
935        let storage = StorageSize::from_gb(1000).unwrap();
936        assert_eq!(storage.as_tb(), 0); // Less than 1 TB
937
938        let cost = storage.monthly_cost(0.023); // $0.023 per GB
939        assert_eq!(cost, 23.0); // 1000 GB * $0.023
940    }
941
942    /// Tests human-readable formatting for different size units.
943    ///
944    /// This test validates that generic sizes provide proper
945    /// human-readable string representations with appropriate
946    /// units and formatting.
947    ///
948    /// # Test Coverage
949    ///
950    /// - Byte-level formatting
951    /// - Kilobyte formatting with decimals
952    /// - Megabyte formatting with decimals
953    /// - Gigabyte formatting with decimals
954    /// - Unit selection and precision
955    ///
956    /// # Test Scenario
957    ///
958    /// Creates sizes at different scales and verifies their
959    /// human-readable representations use appropriate units
960    /// and formatting.
961    ///
962    /// # Assertions
963    ///
964    /// - Byte sizes show "bytes" unit
965    /// - KB sizes show "KB" with 2 decimal places
966    /// - MB sizes show "MB" with 2 decimal places
967    /// - GB sizes show "GB" with 2 decimal places
968    #[test]
969    fn test_human_readable_formatting() {
970        assert_eq!(FileSize::new(512).unwrap().human_readable(), "512 bytes");
971        assert_eq!(FileSize::from_kb(2).unwrap().human_readable(), "2.00 KB");
972        assert_eq!(FileSize::from_mb(5).unwrap().human_readable(), "5.00 MB");
973        assert_eq!(FileSize::from_gb(3).unwrap().human_readable(), "3.00 GB");
974    }
975
976    /// Tests type safety and compile-time guarantees.
977    ///
978    /// This test validates that generic sizes provide compile-time
979    /// type safety, preventing operations between incompatible
980    /// size categories while allowing explicit conversions.
981    ///
982    /// # Test Coverage
983    ///
984    /// - Type safety between different size categories
985    /// - Compile-time prevention of invalid operations
986    /// - Explicit type conversion with `into_category()`
987    /// - Operations after type conversion
988    /// - Type system guarantees
989    ///
990    /// # Test Scenario
991    ///
992    /// Creates different size types and demonstrates that direct
993    /// operations are prevented while explicit conversions work.
994    ///
995    /// # Assertions
996    ///
997    /// - Type conversion succeeds
998    /// - Operations work after conversion
999    /// - Result has correct byte value
1000    /// - Type safety is maintained
1001    #[test]
1002    fn test_type_safety() {
1003        let file_size = FileSize::new(1024).unwrap();
1004        let memory_size = MemorySize::new(1024).unwrap();
1005
1006        // This would be a compile error - cannot add different size types
1007        // let sum = file_size + memory_size; // Compile error!
1008
1009        // But we can convert between types
1010        let converted: MemorySize = file_size.into_category().unwrap();
1011        let sum = converted + memory_size;
1012        assert_eq!(sum.bytes(), 2048);
1013    }
1014
1015    /// Tests checked arithmetic operations with overflow protection.
1016    ///
1017    /// This test validates that generic sizes provide checked
1018    /// arithmetic operations that detect and handle overflow
1019    /// conditions gracefully.
1020    ///
1021    /// # Test Coverage
1022    ///
1023    /// - Checked addition with `checked_add()`
1024    /// - Checked subtraction with `checked_sub()`
1025    /// - Overflow detection and prevention
1026    /// - Error handling for overflow conditions
1027    /// - Safe arithmetic operations
1028    ///
1029    /// # Test Scenario
1030    ///
1031    /// Performs checked arithmetic operations on normal values
1032    /// and tests overflow conditions to ensure proper error handling.
1033    ///
1034    /// # Assertions
1035    ///
1036    /// - Checked addition produces correct result
1037    /// - Checked subtraction produces correct result
1038    /// - Overflow conditions are detected
1039    /// - Overflow results in error
1040    #[test]
1041    fn test_checked_arithmetic() {
1042        let size1 = FileSize::new(1000).unwrap();
1043        let size2 = FileSize::new(500).unwrap();
1044
1045        let sum = size1.checked_add(size2).unwrap();
1046        assert_eq!(sum.bytes(), 1500);
1047
1048        let diff = size1.checked_sub(size2).unwrap();
1049        assert_eq!(diff.bytes(), 500);
1050
1051        // Test overflow protection
1052        let max_size = FileSize::new(FileSizeMarker::max_size()).unwrap();
1053        let overflow_result = max_size.checked_add(FileSize::new(1).unwrap());
1054        assert!(overflow_result.is_err());
1055    }
1056}