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}