adaptive_pipeline_domain/value_objects/
algorithm.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//! # Algorithm Value Object
9//!
10//! This module defines the algorithm value object for the adaptive pipeline
11//! system. It provides type-safe algorithm specification with validation,
12//! categorization, and cross-language compatibility.
13//!
14//! ## Overview
15//!
16//! The algorithm value object provides:
17//!
18//! - **Type Safety**: Type-safe algorithm specification and validation
19//! - **Algorithm Categories**: Support for different algorithm categories
20//! - **Validation**: Comprehensive validation of algorithm names and formats
21//! - **Cross-Language**: Consistent representation across different languages
22//! - **Extensibility**: Support for custom and user-defined algorithms
23//!
24//! ## Architecture
25//!
26//! The algorithm follows Domain-Driven Design principles:
27//!
28//! - **Value Object**: Immutable value object with equality semantics
29//! - **Rich Domain Model**: Encapsulates algorithm-related business logic
30//! - **Validation**: Comprehensive validation of algorithm specifications
31//! - **Serialization**: Support for persistence and cross-language
32//!   communication
33//!
34//! ## Key Features
35//!
36//! ### Algorithm Categories
37//!
38//! - **Compression**: Data compression algorithms (brotli, gzip, zstd, lz4)
39//! - **Encryption**: Data encryption algorithms (AES-256-GCM,
40//!   ChaCha20-Poly1305)
41//! - **Hashing**: Cryptographic hash algorithms (SHA-256, SHA-512, Blake3)
42//! - **Custom**: User-defined and application-specific algorithms
43//!
44//! ### Validation and Safety
45//!
46//! - **Format Validation**: Validate algorithm name format and structure
47//! - **Category Validation**: Ensure algorithms belong to valid categories
48//! - **Parameter Validation**: Validate algorithm-specific parameters
49//! - **Security Validation**: Ensure algorithms meet security requirements
50//!
51//! ### Cross-Language Support
52//!
53//! - **Consistent Representation**: Same algorithm representation across
54//!   languages
55//! - **JSON Serialization**: Standard JSON serialization format
56//! - **Database Storage**: Consistent database storage format
57//! - **API Compatibility**: Compatible with REST and gRPC APIs
58//!
59//! ## Usage Examples
60//!
61//! ### Creating Algorithms
62
63//!
64//! ### Algorithm Validation
65
66//!
67//! ### Algorithm Categories
68
69//!
70//! ### Algorithm Comparison and Sorting
71
72//!
73//! ### Serialization and Deserialization
74
75//!
76//! ## Supported Algorithms
77//!
78//! ### Compression Algorithms
79//!
80//! - **brotli**: Google's Brotli compression algorithm
81//! - **gzip**: GNU zip compression algorithm
82//! - **zstd**: Facebook's Zstandard compression algorithm
83//! - **lz4**: LZ4 fast compression algorithm
84//! - **deflate**: DEFLATE compression algorithm
85//!
86//! ### Encryption Algorithms
87//!
88//! - **aes-256-gcm**: AES-256 with Galois/Counter Mode
89//! - **aes-128-gcm**: AES-128 with Galois/Counter Mode
90//! - **chacha20-poly1305**: ChaCha20-Poly1305 AEAD cipher
91//! - **aes-256-cbc**: AES-256 with Cipher Block Chaining
92//!
93//! ### Hashing Algorithms
94//!
95//! - **sha256**: SHA-256 cryptographic hash function
96//! - **sha512**: SHA-512 cryptographic hash function
97//! - **blake3**: BLAKE3 cryptographic hash function
98//! - **md5**: MD5 hash function (legacy, not recommended)
99//!
100//! ### Custom Algorithms
101//!
102//! - **custom-***: User-defined algorithms with "custom-" prefix
103//! - **Application-specific**: Domain-specific algorithms
104//! - **Experimental**: Experimental or research algorithms
105//!
106//! ## Validation Rules
107//!
108//! ### Name Format
109//!
110//! - **Length**: 1-64 characters
111//! - **Characters**: Lowercase letters, numbers, hyphens
112//! - **Start/End**: Must start and end with alphanumeric characters
113//! - **Hyphens**: Cannot have consecutive hyphens
114//!
115//! ### Category Rules
116//!
117//! - **Compression**: Must be a known compression algorithm
118//! - **Encryption**: Must be a known encryption algorithm
119//! - **Hashing**: Must be a known hashing algorithm
120//! - **Custom**: Must start with "custom-" prefix
121//!
122//! ### Security Requirements
123//!
124//! - **Encryption**: Must use authenticated encryption (AEAD)
125//! - **Hashing**: Must use cryptographically secure hash functions
126//! - **Key Length**: Must meet minimum key length requirements
127//! - **Deprecation**: Deprecated algorithms are rejected
128//!
129//! ## Error Handling
130//!
131//! ### Validation Errors
132//!
133//! - **Empty Name**: Algorithm name cannot be empty
134//! - **Invalid Format**: Name doesn't match required format
135//! - **Unknown Algorithm**: Algorithm is not recognized
136//! - **Deprecated Algorithm**: Algorithm is deprecated or insecure
137//!
138//! ### Usage Errors
139//!
140//! - **Category Mismatch**: Algorithm used in wrong category context
141//! - **Parameter Errors**: Invalid algorithm parameters
142//! - **Compatibility Errors**: Algorithm not compatible with system
143//!
144//! ## Performance Considerations
145//!
146//! ### Memory Usage
147//!
148//! - **Compact Storage**: Efficient string storage
149//! - **String Interning**: Intern common algorithm names
150//! - **Copy Optimization**: Optimize copying for frequent use
151//!
152//! ### Validation Performance
153//!
154//! - **Fast Validation**: Optimized validation routines
155//! - **Caching**: Cache validation results
156//! - **Lazy Evaluation**: Lazy evaluation of expensive checks
157//!
158//! ## Cross-Language Compatibility
159//!
160//! ### Language Mappings
161//!
162//! - **Rust**: `Algorithm` newtype wrapper
163//! - **Go**: `Algorithm` struct with validation
164//! - **Python**: `Algorithm` class with validation
165//! - **JavaScript**: Algorithm validation functions
166//!
167//! ### Serialization Formats
168//!
169//! - **JSON**: String representation
170//! - **Protocol Buffers**: String field with validation
171//! - **MessagePack**: String representation
172//! - **CBOR**: String representation
173//!
174//! ### Database Storage
175//!
176//! - **SQLite**: TEXT column with CHECK constraint
177//! - **PostgreSQL**: VARCHAR with domain validation
178//! - **MySQL**: VARCHAR with validation triggers
179//!
180//! ## Integration
181//!
182//! The algorithm value object integrates with:
183//!
184//! - **Processing Pipeline**: Specify algorithms for processing stages
185//! - **Configuration**: Algorithm configuration and selection
186//! - **Validation**: Validate algorithm compatibility and security
187//! - **Monitoring**: Track algorithm usage and performance
188//!
189//! ## Thread Safety
190//!
191//! The algorithm value object is thread-safe:
192//!
193//! - **Immutable**: Algorithms are immutable after creation
194//! - **Safe Sharing**: Safe to share between threads
195//! - **Concurrent Access**: Safe concurrent access to algorithm data
196//!
197//! ## Future Enhancements
198//!
199//! Planned enhancements include:
200//!
201//! - **Algorithm Registry**: Centralized algorithm registry
202//! - **Performance Benchmarks**: Built-in performance benchmarking
203//! - **Security Analysis**: Automated security analysis
204//! - **Plugin System**: Plugin system for custom algorithms
205
206use serde::{Deserialize, Serialize};
207use std::cmp::{Ord, PartialOrd};
208use std::fmt::{self, Display};
209
210use crate::PipelineError;
211
212/// Algorithm value object for pipeline stage processing
213/// # Purpose
214/// Type-safe algorithm specification that provides:
215/// - Validation of algorithm names and formats
216/// - Algorithm-specific behavior and constraints
217/// - Immutable value semantics (DDD value object)
218/// - Cross-language compatibility
219/// # Supported Algorithms
220/// - **Compression**: brotli, gzip, zstd, lz4
221/// - **Encryption**: aes-256-gcm, chacha20-poly1305, aes-128-gcm
222/// - **Hashing**: sha256, sha512, blake3
223/// - **Custom**: User-defined algorithms
224/// # Cross-Language Mapping
225/// - **Rust**: `Algorithm` (newtype wrapper)
226/// - **Go**: `Algorithm` struct with same interface
227/// - **JSON**: String representation
228/// - **SQLite**: TEXT column with validation
229/// # Examples
230#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
231pub struct Algorithm(String);
232
233impl Algorithm {
234    /// Creates a new algorithm with validated name
235    /// # Purpose
236    /// Creates an `Algorithm` value object after validating the algorithm name
237    /// against strict format rules. This ensures all algorithm instances
238    /// are valid and type-safe. # Why
239    /// Algorithm names must follow a consistent format for:
240    /// - Cross-language compatibility (Rust, Go, JSON)
241    /// - Database storage validation
242    /// - Configuration file parsing
243    /// - Prevention of injection attacks
244    /// # Arguments
245    /// * `name` - The algorithm name string. Must:
246    ///   - Not be empty
247    ///   - Be 1-64 characters long
248    ///   - Contain only lowercase letters, hyphens, and digits
249    ///   - Not start with a hyphen or digit
250    ///   - Not end with a hyphen
251    ///   - Not contain consecutive hyphens
252    /// # Returns
253    /// * `Ok(Algorithm)` - Successfully validated algorithm
254    /// * `Err(PipelineError::InvalidConfiguration)` - Name validation failed
255    /// # Errors
256    /// Returns `PipelineError::InvalidConfiguration` when:
257    /// - Name is empty
258    /// - Name exceeds 64 characters
259    /// - Name contains invalid characters (uppercase, special chars)
260    /// - Name starts/ends with hyphen
261    /// - Name starts with digit
262    /// - Name contains consecutive hyphens ("--")
263    /// # Examples
264    pub fn new(name: String) -> Result<Self, PipelineError> {
265        Self::validate_name(&name)?;
266        Ok(Self(name))
267    }
268
269    /// Creates an algorithm from a string slice
270    /// # Purpose
271    /// Convenience constructor that accepts a string slice instead of an owned
272    /// String. Internally converts to String and delegates to `new()`.
273    /// # Arguments
274    /// * `name` - String slice containing the algorithm name
275    /// # Returns
276    /// * `Ok(Algorithm)` - Successfully validated algorithm
277    /// * `Err(PipelineError::InvalidConfiguration)` - Name validation failed
278    /// # Errors
279    /// See [`Algorithm::new`] for validation rules and error conditions.
280    /// # Examples
281    pub fn parse(name: &str) -> Result<Self, PipelineError> {
282        Self::new(name.to_string())
283    }
284
285    /// Gets the algorithm name as a string reference
286    /// # Purpose
287    /// Provides read-only access to the algorithm's underlying name string.
288    /// # Returns
289    /// String slice containing the algorithm name
290    /// # Examples
291    pub fn name(&self) -> &str {
292        &self.0
293    }
294
295    /// Checks if this is a compression algorithm
296    /// # Purpose
297    /// Determines whether the algorithm belongs to the compression category.
298    /// Used for algorithm validation and compatibility checks.
299    /// # Why
300    /// Compression algorithms require specific stage configurations and have
301    /// different performance characteristics than encryption or hashing
302    /// algorithms. # Returns
303    /// * `true` - Algorithm is one of: brotli, gzip, zstd, lz4, deflate
304    /// * `false` - Algorithm is not a compression algorithm
305    /// # Examples
306    pub fn is_compression(&self) -> bool {
307        matches!(self.0.as_str(), "brotli" | "gzip" | "zstd" | "lz4" | "deflate")
308    }
309
310    /// Checks if this is an encryption algorithm
311    /// # Purpose
312    /// Determines whether the algorithm belongs to the encryption category.
313    /// Used for security validation and stage compatibility checks.
314    /// # Why
315    /// Encryption algorithms require key management and have different
316    /// security properties than compression or hashing algorithms.
317    /// # Returns
318    /// * `true` - Algorithm is one of: aes-256-gcm, aes-128-gcm, aes-128-cbc,
319    ///   chacha20-poly1305, aes-256-cbc
320    /// * `false` - Algorithm is not an encryption algorithm
321    /// # Examples
322    pub fn is_encryption(&self) -> bool {
323        matches!(
324            self.0.as_str(),
325            "aes-256-gcm" | "aes-128-gcm" | "aes-128-cbc" | "chacha20-poly1305" | "aes-256-cbc"
326        )
327    }
328
329    /// Checks if this is a hashing algorithm
330    /// # Purpose
331    /// Determines whether the algorithm belongs to the hashing category.
332    /// Used for integrity validation and stage compatibility checks.
333    /// # Why
334    /// Hashing algorithms are one-way functions used for integrity
335    /// verification, with different properties than compression or
336    /// encryption algorithms. # Returns
337    /// * `true` - Algorithm is one of: sha256, sha512, sha3-256, blake3, md5,
338    ///   sha1
339    /// * `false` - Algorithm is not a hashing algorithm
340    /// # Note
341    /// MD5 and SHA1 are included for backward compatibility but are not
342    /// recommended for security-critical applications.
343    /// # Examples
344    pub fn is_hashing(&self) -> bool {
345        matches!(
346            self.0.as_str(),
347            "sha256" | "sha512" | "sha3-256" | "blake3" | "md5" | "sha1"
348        )
349    }
350
351    /// Checks if this is a custom algorithm
352    /// # Purpose
353    /// Determines whether the algorithm is a custom (user-defined) algorithm
354    /// that doesn't match any of the predefined categories.
355    /// # Why
356    /// Custom algorithms allow extensibility for domain-specific processing
357    /// while maintaining type safety and validation.
358    /// # Returns
359    /// * `true` - Algorithm starts with "custom-" prefix OR doesn't match any
360    ///   predefined compression, encryption, or hashing algorithm
361    /// * `false` - Algorithm is a predefined standard algorithm
362    /// # Examples
363    pub fn is_custom(&self) -> bool {
364        self.0.starts_with("custom-") || (!self.is_compression() && !self.is_encryption() && !self.is_hashing())
365    }
366
367    /// Gets the algorithm category
368    /// # Purpose
369    /// Classifies the algorithm into one of the predefined categories.
370    /// Used for compatibility validation and processing stage selection.
371    /// # Returns
372    /// One of:
373    /// * `AlgorithmCategory::Compression` - For compression algorithms
374    /// * `AlgorithmCategory::Encryption` - For encryption algorithms
375    /// * `AlgorithmCategory::Hashing` - For hashing algorithms
376    /// * `AlgorithmCategory::Custom` - For custom/unknown algorithms
377    /// # Examples
378    pub fn category(&self) -> AlgorithmCategory {
379        if self.is_compression() {
380            AlgorithmCategory::Compression
381        } else if self.is_encryption() {
382            AlgorithmCategory::Encryption
383        } else if self.is_hashing() {
384            AlgorithmCategory::Hashing
385        } else {
386            AlgorithmCategory::Custom
387        }
388    }
389
390    /// Validates the algorithm name format
391    fn validate_name(name: &str) -> Result<(), PipelineError> {
392        if name.is_empty() {
393            return Err(PipelineError::InvalidConfiguration(
394                "Algorithm name cannot be empty".to_string(),
395            ));
396        }
397
398        if name.len() > 64 {
399            return Err(PipelineError::InvalidConfiguration(
400                "Algorithm name cannot exceed 64 characters".to_string(),
401            ));
402        }
403
404        // Algorithm names should be lowercase with hyphens and numbers
405        if !name
406            .chars()
407            .all(|c| c.is_ascii_lowercase() || c == '-' || c.is_ascii_digit())
408        {
409            return Err(PipelineError::InvalidConfiguration(
410                "Algorithm name must contain only lowercase letters, hyphens, and digits".to_string(),
411            ));
412        }
413
414        // Cannot start or end with hyphen
415        if name.starts_with('-') || name.ends_with('-') {
416            return Err(PipelineError::InvalidConfiguration(
417                "Algorithm name cannot start or end with hyphen".to_string(),
418            ));
419        }
420
421        // Cannot start with a digit
422        if name.chars().next().is_some_and(|c| c.is_ascii_digit()) {
423            return Err(PipelineError::InvalidConfiguration(
424                "Algorithm name cannot start with a digit".to_string(),
425            ));
426        }
427
428        // Cannot have consecutive hyphens
429        if name.contains("--") {
430            return Err(PipelineError::InvalidConfiguration(
431                "Algorithm name cannot contain consecutive hyphens".to_string(),
432            ));
433        }
434
435        Ok(())
436    }
437
438    /// Validates the algorithm
439    /// # Purpose
440    /// Re-validates the algorithm name format. Useful for ensuring algorithm
441    /// integrity after deserialization or when working with external data.
442    /// # Why
443    /// While algorithms are validated on creation, this method allows
444    /// revalidation after deserialization or when algorithm constraints
445    /// change. # Returns
446    /// * `Ok(())` - Algorithm name is valid
447    /// * `Err(PipelineError::InvalidConfiguration)` - Name validation failed
448    /// # Errors
449    /// See [`Algorithm::new`] for validation rules and error conditions.
450    /// # Examples
451    pub fn validate(&self) -> Result<(), PipelineError> {
452        Self::validate_name(&self.0)
453    }
454}
455
456/// Algorithm categories for classification
457#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Serialize, Deserialize)]
458pub enum AlgorithmCategory {
459    Compression,
460    Encryption,
461    Hashing,
462    Custom,
463    Unknown,
464}
465
466impl Display for Algorithm {
467    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
468        write!(f, "{}", self.0)
469    }
470}
471
472impl std::str::FromStr for Algorithm {
473    type Err = PipelineError;
474
475    fn from_str(s: &str) -> Result<Self, Self::Err> {
476        Self::parse(s)
477    }
478}
479
480impl From<Algorithm> for String {
481    fn from(algorithm: Algorithm) -> Self {
482        algorithm.0
483    }
484}
485
486impl AsRef<str> for Algorithm {
487    fn as_ref(&self) -> &str {
488        &self.0
489    }
490}
491
492/// Predefined algorithms for common use cases
493impl Algorithm {
494    /// Creates a Brotli compression algorithm
495    /// # Purpose
496    /// Factory method for the Brotli compression algorithm.
497    /// Brotli offers excellent compression ratios with good performance.
498    /// # Returns
499    /// Validated `Algorithm` instance for Brotli
500    /// # Examples
501    pub fn brotli() -> Self {
502        Self("brotli".to_string())
503    }
504
505    /// Creates a Gzip compression algorithm
506    /// # Purpose
507    /// Factory method for the Gzip compression algorithm.
508    /// Widely supported compression with balanced performance.
509    /// # Returns
510    /// Validated `Algorithm` instance for Gzip
511    /// # Examples
512    pub fn gzip() -> Self {
513        Self("gzip".to_string())
514    }
515
516    /// Creates a Zstandard compression algorithm
517    /// # Purpose
518    /// Factory method for the Zstandard compression algorithm.
519    /// Excellent balance of compression ratio and speed.
520    /// # Returns
521    /// Validated `Algorithm` instance for Zstandard
522    /// # Examples
523    pub fn zstd() -> Self {
524        Self("zstd".to_string())
525    }
526
527    /// Creates an LZ4 compression algorithm
528    /// # Purpose
529    /// Factory method for the LZ4 compression algorithm.
530    /// Extremely fast compression with moderate compression ratios.
531    /// # Returns
532    /// Validated `Algorithm` instance for LZ4
533    /// # Examples
534    pub fn lz4() -> Self {
535        Self("lz4".to_string())
536    }
537
538    /// Creates an AES-256-GCM encryption algorithm
539    /// # Purpose
540    /// Factory method for AES-256 with Galois/Counter Mode.
541    /// Provides authenticated encryption with strong security.
542    /// # Returns
543    /// Validated `Algorithm` instance for AES-256-GCM
544    /// # Examples
545    pub fn aes_256_gcm() -> Self {
546        Self("aes-256-gcm".to_string())
547    }
548
549    /// Creates a ChaCha20-Poly1305 encryption algorithm
550    /// # Purpose
551    /// Factory method for ChaCha20 stream cipher with Poly1305 MAC.
552    /// Modern authenticated encryption with excellent performance on
553    /// mobile/embedded. # Returns
554    /// Validated `Algorithm` instance for ChaCha20-Poly1305
555    /// # Examples
556    pub fn chacha20_poly1305() -> Self {
557        Self("chacha20-poly1305".to_string())
558    }
559
560    /// Creates a SHA-256 hashing algorithm
561    /// # Purpose
562    /// Factory method for SHA-256 cryptographic hash function.
563    /// Industry-standard hashing for integrity verification.
564    /// # Returns
565    /// Validated `Algorithm` instance for SHA-256
566    /// # Examples
567    pub fn sha256() -> Self {
568        Self("sha256".to_string())
569    }
570
571    /// Creates a SHA-512 hashing algorithm
572    /// # Purpose
573    /// Factory method for SHA-512 cryptographic hash function.
574    /// Stronger variant of SHA-2 family with 512-bit output.
575    /// # Returns
576    /// Validated `Algorithm` instance for SHA-512
577    /// # Examples
578    pub fn sha512() -> Self {
579        Self("sha512".to_string())
580    }
581
582    /// Creates a BLAKE3 hashing algorithm
583    /// # Purpose
584    /// Factory method for BLAKE3 cryptographic hash function.
585    /// Modern, high-performance hashing with strong security properties.
586    /// # Returns
587    /// Validated `Algorithm` instance for BLAKE3
588    /// # Examples
589    pub fn blake3() -> Self {
590        Self("blake3".to_string())
591    }
592
593    /// AES-256-GCM encryption algorithm (alias for test framework
594    /// compatibility)
595    pub fn aes256_gcm() -> Self {
596        Self::aes_256_gcm()
597    }
598
599    /// No algorithm / passthrough
600    pub fn none() -> Self {
601        Self("none".to_string())
602    }
603
604    /// AES-128-CBC encryption
605    pub fn aes_128_cbc() -> Self {
606        Self("aes-128-cbc".to_string())
607    }
608
609    /// SHA3-256 hashing
610    pub fn sha3_256() -> Self {
611        Self("sha3-256".to_string())
612    }
613}
614
615/// Utility functions for algorithm operations
616pub mod algorithm_utils {
617    use super::*;
618
619    /// Gets all supported compression algorithms
620    /// # Purpose
621    /// Returns a collection of all predefined compression algorithms.
622    /// Useful for configuration validation and algorithm selection UIs.
623    /// # Returns
624    /// Vector containing: brotli, gzip, zstd, lz4
625    /// # Examples
626    pub fn compression_algorithms() -> Vec<Algorithm> {
627        vec![
628            Algorithm::brotli(),
629            Algorithm::gzip(),
630            Algorithm::zstd(),
631            Algorithm::lz4(),
632        ]
633    }
634
635    /// Gets all supported encryption algorithms
636    /// # Purpose
637    /// Returns a collection of all predefined encryption algorithms.
638    /// Useful for security configuration and key management setup.
639    /// # Returns
640    /// Vector containing: aes-256-gcm, chacha20-poly1305
641    /// # Examples
642    pub fn encryption_algorithms() -> Vec<Algorithm> {
643        vec![Algorithm::aes_256_gcm(), Algorithm::chacha20_poly1305()]
644    }
645
646    /// Gets all supported hashing algorithms
647    /// # Purpose
648    /// Returns a collection of all predefined hashing algorithms.
649    /// Useful for integrity verification configuration.
650    /// # Returns
651    /// Vector containing: sha256, sha512, blake3
652    /// # Examples
653    pub fn hashing_algorithms() -> Vec<Algorithm> {
654        vec![Algorithm::sha256(), Algorithm::sha512(), Algorithm::blake3()]
655    }
656
657    /// Validates algorithm compatibility with stage type
658    /// # Purpose
659    /// Ensures an algorithm is compatible with its intended processing stage
660    /// type. Prevents misconfiguration like using compression algorithms
661    /// for encryption stages. # Why
662    /// Early validation of algorithm-stage compatibility:
663    /// - Prevents runtime errors in processing pipelines
664    /// - Ensures type safety across pipeline configuration
665    /// - Provides clear error messages for configuration issues
666    /// # Arguments
667    /// * `algorithm` - The algorithm to validate
668    /// * `stage_type` - Stage type string (case-insensitive):
669    ///   - "compression" - Requires compression algorithm
670    ///   - "encryption" - Requires encryption algorithm
671    ///   - "hashing" - Requires hashing algorithm
672    ///   - "custom" - Accepts any algorithm
673    /// # Returns
674    /// * `Ok(())` - Algorithm is compatible with stage type
675    /// * `Err(PipelineError::InvalidConfiguration)` - Incompatible or unknown
676    ///   stage type
677    /// # Errors
678    /// Returns `PipelineError::InvalidConfiguration` when:
679    /// - Algorithm doesn't match required stage type category
680    /// - Stage type is unknown/unsupported
681    /// # Examples
682    pub fn validate_compatibility(algorithm: &Algorithm, stage_type: &str) -> Result<(), PipelineError> {
683        match stage_type.to_lowercase().as_str() {
684            "compression" => {
685                if !algorithm.is_compression() {
686                    return Err(PipelineError::InvalidConfiguration(format!(
687                        "Algorithm '{}' is not compatible with compression stage",
688                        algorithm
689                    )));
690                }
691            }
692            "encryption" => {
693                if !algorithm.is_encryption() {
694                    return Err(PipelineError::InvalidConfiguration(format!(
695                        "Algorithm '{}' is not compatible with encryption stage",
696                        algorithm
697                    )));
698                }
699            }
700            "hashing" => {
701                if !algorithm.is_hashing() {
702                    return Err(PipelineError::InvalidConfiguration(format!(
703                        "Algorithm '{}' is not compatible with hashing stage",
704                        algorithm
705                    )));
706                }
707            }
708            "custom" => {
709                // Custom stages can use any algorithm
710            }
711            _ => {
712                return Err(PipelineError::InvalidConfiguration(format!(
713                    "Unknown stage type: {}",
714                    stage_type
715                )));
716            }
717        }
718        Ok(())
719    }
720}
721
722#[cfg(test)]
723mod tests {
724
725    // Unit tests for Algorithm value object.
726    //
727    // Tests cover creation, validation, categorization, and serialization.
728
729    use crate::value_objects::algorithm::AlgorithmCategory;
730    use crate::value_objects::Algorithm;
731    use serde_json;
732    use std::collections::HashMap;
733
734    /// Tests Algorithm creation with valid input values.
735    /// Validates that:
736    /// - Basic algorithm creation works correctly
737    /// - from_str creation method functions properly
738    /// - Various valid algorithm name formats are accepted
739    /// - Algorithm names are stored and retrieved accurately
740    /// - Edge cases like single character and long names work
741    #[test]
742    fn test_algorithm_creation_valid_cases() {
743        // Test basic creation
744        let algo = Algorithm::new("brotli".to_string()).unwrap();
745        assert_eq!(algo.name(), "brotli");
746
747        // Test parse creation
748        let algo = Algorithm::parse("aes-256-gcm").unwrap();
749        assert_eq!(algo.name(), "aes-256-gcm");
750
751        // Test various valid formats
752        let valid_algorithms = vec![
753            "gzip",
754            "lz4",
755            "zstd",
756            "aes-128-cbc",
757            "aes-256-gcm",
758            "chacha20-poly1305",
759            "sha256",
760            "sha3-256",
761            "blake3",
762            "custom-algo-v2",
763            "algo123",
764            "a", // Single character
765            "very-long-algorithm-name-with-many-hyphens-and-numbers123",
766        ];
767
768        for name in valid_algorithms {
769            let result = Algorithm::new(name.to_string());
770            assert!(result.is_ok(), "Algorithm '{}' should be valid", name);
771            assert_eq!(result.unwrap().name(), name);
772        }
773    }
774
775    /// Tests Algorithm creation with invalid input values.
776    /// Validates that:
777    /// - Empty strings are rejected
778    /// - Uppercase and mixed case names are rejected
779    /// - Names with invalid characters are rejected
780    /// - Names starting/ending with hyphens are rejected
781    /// - Names starting with numbers are rejected
782    /// - Appropriate error messages are returned
783    #[test]
784    fn test_algorithm_creation_invalid_cases() {
785        let invalid_algorithms = vec![
786            ("", "empty string"),
787            ("UPPERCASE", "uppercase letters"),
788            ("MixedCase", "mixed case"),
789            ("-starts-with-hyphen", "starts with hyphen"),
790            ("ends-with-hyphen-", "ends with hyphen"),
791            ("has spaces", "contains spaces"),
792            ("has_underscores", "contains underscores"),
793            ("has.dots", "contains dots"),
794            ("has@symbols", "contains special symbols"),
795            ("has/slashes", "contains slashes"),
796            ("has\\backslashes", "contains backslashes"),
797            ("has(parentheses)", "contains parentheses"),
798            ("has[brackets]", "contains brackets"),
799            ("has{braces}", "contains braces"),
800            ("has:colons", "contains colons"),
801            ("has;semicolons", "contains semicolons"),
802            ("has,commas", "contains commas"),
803            ("has'quotes", "contains quotes"),
804            ("has\"doublequotes", "contains double quotes"),
805            ("\t\n\r", "whitespace characters"),
806            ("--double-hyphen", "consecutive hyphens"),
807            ("123-starts-with-number", "starts with number"),
808        ];
809
810        for (name, reason) in invalid_algorithms {
811            let result = Algorithm::new(name.to_string());
812            assert!(result.is_err(), "Algorithm '{}' should be invalid ({})", name, reason);
813        }
814    }
815
816    /// Tests predefined algorithm constructor methods.
817    /// Validates that:
818    /// - Compression algorithm constructors work correctly
819    /// - Encryption algorithm constructors work correctly
820    /// - Hashing algorithm constructors work correctly
821    /// - Predefined algorithms have correct names and categories
822    /// - All predefined algorithms are properly categorized
823    #[test]
824    fn test_algorithm_predefined_constructors() {
825        // Test compression algorithms
826        let brotli = Algorithm::brotli();
827        assert_eq!(brotli.name(), "brotli");
828        assert!(brotli.is_compression());
829
830        let gzip = Algorithm::gzip();
831        assert_eq!(gzip.name(), "gzip");
832        assert!(gzip.is_compression());
833
834        let lz4 = Algorithm::lz4();
835        assert_eq!(lz4.name(), "lz4");
836        assert!(lz4.is_compression());
837
838        let zstd = Algorithm::zstd();
839        assert_eq!(zstd.name(), "zstd");
840        assert!(zstd.is_compression());
841
842        // Test encryption algorithms
843        let aes_128 = Algorithm::aes_128_cbc();
844        assert_eq!(aes_128.name(), "aes-128-cbc");
845        assert!(aes_128.is_encryption());
846
847        let aes_256 = Algorithm::aes_256_gcm();
848        assert_eq!(aes_256.name(), "aes-256-gcm");
849        assert!(aes_256.is_encryption());
850
851        let chacha = Algorithm::chacha20_poly1305();
852        assert_eq!(chacha.name(), "chacha20-poly1305");
853        assert!(chacha.is_encryption());
854
855        // Test hashing algorithms
856        let sha256 = Algorithm::sha256();
857        assert_eq!(sha256.name(), "sha256");
858        assert!(sha256.is_hashing());
859
860        let sha3 = Algorithm::sha3_256();
861        assert_eq!(sha3.name(), "sha3-256");
862        assert!(sha3.is_hashing());
863
864        let blake3 = Algorithm::blake3();
865        assert_eq!(blake3.name(), "blake3");
866        assert!(blake3.is_hashing());
867    }
868
869    /// Tests algorithm category classification system.
870    /// Validates that:
871    /// - Compression algorithms are classified correctly
872    /// - Encryption algorithms are classified correctly
873    /// - Hashing algorithms are classified correctly
874    /// - Custom algorithms default to appropriate category
875    /// - Category classification is consistent and accurate
876    #[test]
877    fn test_algorithm_categories() {
878        // Test compression category
879        let brotli = Algorithm::brotli();
880        assert!(brotli.is_compression());
881        assert!(!brotli.is_encryption());
882        assert!(!brotli.is_hashing());
883        assert_eq!(brotli.category(), AlgorithmCategory::Compression);
884
885        // Test encryption category
886        let aes = Algorithm::aes_256_gcm();
887        assert!(aes.is_encryption());
888        assert!(!aes.is_compression());
889        assert!(!aes.is_hashing());
890        assert_eq!(aes.category(), AlgorithmCategory::Encryption);
891
892        // Test hashing category
893        let sha = Algorithm::sha256();
894        assert!(sha.is_hashing());
895        assert!(!sha.is_encryption());
896        assert!(!sha.is_compression());
897        assert_eq!(sha.category(), AlgorithmCategory::Hashing);
898
899        // Test unknown/custom algorithm category
900        let custom = Algorithm::new("custom-algo".to_string()).unwrap();
901        assert!(!custom.is_compression());
902        assert!(!custom.is_encryption());
903        assert!(!custom.is_hashing());
904        assert_eq!(custom.category(), AlgorithmCategory::Custom);
905    }
906
907    // Tests JSON serialization and deserialization of Algorithm objects.
908    // Validates that:
909    // - Algorithm objects serialize to valid JSON
910    // - Deserialized algorithms maintain original properties
911    // - Serialization roundtrip preserves data integrity
912    // - JSON format is compatible with external systems
913    // - Algorithm metadata is preserved during serialization
914
915    #[test]
916    fn test_algorithm_serialization() {
917        let original = Algorithm::aes_256_gcm();
918
919        // Test JSON serialization
920        let json = serde_json::to_string(&original).unwrap();
921        let deserialized: Algorithm = serde_json::from_str(&json).unwrap();
922
923        assert_eq!(original.name(), deserialized.name());
924        assert_eq!(original.category(), deserialized.category());
925
926        // Test with custom algorithm
927        let custom = Algorithm::new("custom-test-algo".to_string()).unwrap();
928        let json = serde_json::to_string(&custom).unwrap();
929        let deserialized: Algorithm = serde_json::from_str(&json).unwrap();
930
931        assert_eq!(custom.name(), deserialized.name());
932        assert_eq!(custom.category(), deserialized.category());
933    }
934
935    /// Tests equality comparison and ordering for Algorithm objects.
936    /// Validates that:
937    /// - Identical algorithms compare as equal
938    /// - Different algorithms compare as not equal
939    /// - Algorithm ordering follows lexicographic name order
940    /// - Equality is consistent across multiple calls
941    /// - Ordering supports sorting and collection operations
942
943    #[test]
944    fn test_algorithm_equality_and_ordering() {
945        let algo1 = Algorithm::brotli();
946        let algo2 = Algorithm::brotli();
947        let algo3 = Algorithm::gzip();
948
949        // Test equality
950        assert_eq!(algo1, algo2);
951        assert_ne!(algo1, algo3);
952
953        // Test ordering (lexicographic by name)
954        assert!(algo1 < algo3); // "brotli" < "gzip"
955        assert!(algo3 > algo1);
956        assert!(algo1 <= algo2);
957        assert!(algo2 >= algo1);
958    }
959
960    /// Tests hash consistency for Algorithm objects.
961    /// Validates that:
962    /// - Equal algorithms produce identical hash values
963    /// - Hash values are consistent across multiple calls
964    /// - Different algorithms produce different hash values
965    /// - Hash implementation supports HashMap usage
966    /// - Hash distribution is reasonable for performance
967
968    #[test]
969    fn test_algorithm_hash_consistency() {
970        let algo1 = Algorithm::aes_256_gcm();
971        let algo2 = Algorithm::aes_256_gcm();
972
973        let mut map = HashMap::new();
974        map.insert(algo1, "test_value");
975
976        // Should be able to retrieve with equivalent Algorithm
977        assert_eq!(map.get(&algo2), Some(&"test_value"));
978    }
979
980    /// Tests Display trait implementation for Algorithm objects.
981    /// Validates that:
982    /// - Display format shows algorithm name clearly
983    /// - Display output is human-readable
984    /// - Display format is consistent across algorithm types
985    /// - Display output is suitable for logging and debugging
986    /// - Display format matches expected conventions
987
988    #[test]
989    fn test_algorithm_display_formatting() {
990        let algo = Algorithm::aes_256_gcm();
991        assert_eq!(format!("{}", algo), "aes-256-gcm");
992
993        let custom = Algorithm::new("custom-algo".to_string()).unwrap();
994        assert_eq!(format!("{}", custom), "custom-algo");
995    }
996
997    /// Tests algorithm validation with edge cases and boundary conditions.
998    /// Validates that:
999    /// - Very long algorithm names are handled correctly
1000    /// - Minimum length algorithm names work properly
1001    /// - Special character combinations are validated
1002    /// - Unicode characters are handled appropriately
1003    /// - Edge cases don't cause validation failures
1004
1005    #[test]
1006    fn test_algorithm_validation_edge_cases() {
1007        // Test minimum length (1 character)
1008        let min_algo = Algorithm::new("a".to_string()).unwrap();
1009        assert_eq!(min_algo.name(), "a");
1010
1011        // Test maximum valid length (64 characters)
1012        let long_name = "a".repeat(64);
1013        let long_algo = Algorithm::new(long_name.clone()).unwrap();
1014        assert_eq!(long_algo.name(), long_name);
1015
1016        // Test length exceeding limit should fail
1017        let too_long_name = "a".repeat(65);
1018        assert!(Algorithm::new(too_long_name).is_err());
1019
1020        // Test algorithm with numbers
1021        let numbered = Algorithm::new("algo123".to_string()).unwrap();
1022        assert_eq!(numbered.name(), "algo123");
1023
1024        // Test algorithm with multiple hyphens
1025        let multi_hyphen = Algorithm::new("multi-hyphen-algo".to_string()).unwrap();
1026        assert_eq!(multi_hyphen.name(), "multi-hyphen-algo");
1027    }
1028
1029    /// Tests comprehensive algorithm category classification logic.
1030    /// Validates that:
1031    /// - All predefined algorithms are classified correctly
1032    /// - Custom algorithms receive appropriate default categories
1033    /// - Category classification is deterministic
1034    /// - Classification logic handles edge cases
1035    /// - Category system is extensible and maintainable
1036
1037    #[test]
1038    fn test_algorithm_category_classification() {
1039        // Test all known compression algorithms
1040        let compression_algos = vec![
1041            Algorithm::brotli(),
1042            Algorithm::gzip(),
1043            Algorithm::lz4(),
1044            Algorithm::zstd(),
1045        ];
1046
1047        for algo in compression_algos {
1048            assert!(
1049                algo.is_compression(),
1050                "Algorithm '{}' should be compression",
1051                algo.name()
1052            );
1053            assert_eq!(algo.category(), AlgorithmCategory::Compression);
1054        }
1055
1056        // Test all known encryption algorithms
1057        let encryption_algos = vec![
1058            Algorithm::aes_128_cbc(),
1059            Algorithm::aes_256_gcm(),
1060            Algorithm::chacha20_poly1305(),
1061        ];
1062
1063        for algo in encryption_algos {
1064            assert!(algo.is_encryption(), "Algorithm '{}' should be encryption", algo.name());
1065            assert_eq!(algo.category(), AlgorithmCategory::Encryption);
1066        }
1067
1068        // Test all known hashing algorithms
1069        let hashing_algos = vec![Algorithm::sha256(), Algorithm::sha3_256(), Algorithm::blake3()];
1070
1071        for algo in hashing_algos {
1072            assert!(algo.is_hashing(), "Algorithm '{}' should be hashing", algo.name());
1073            assert_eq!(algo.category(), AlgorithmCategory::Hashing);
1074        }
1075    }
1076
1077    /// Tests error handling in Algorithm::parse method.
1078    /// Validates that:
1079    /// - Invalid algorithm strings produce appropriate errors
1080    /// - Error messages are descriptive and helpful
1081    /// - Error types match expected validation failures
1082    /// - Error handling is consistent across input types
1083    /// - Error recovery provides useful feedback
1084
1085    #[test]
1086    fn test_algorithm_from_str_error_handling() {
1087        // Test FromStr trait error cases
1088        let invalid_cases = vec!["", "INVALID", "has spaces", "-invalid", "invalid-"];
1089
1090        for case in invalid_cases {
1091            let result: Result<Algorithm, _> = case.parse();
1092            assert!(result.is_err(), "Parsing '{}' should fail", case);
1093        }
1094
1095        // Test valid cases
1096        let valid_cases = vec!["brotli", "aes-256-gcm", "custom-algo"];
1097
1098        for case in valid_cases {
1099            let result: Result<Algorithm, _> = case.parse();
1100            assert!(result.is_ok(), "Parsing '{}' should succeed", case);
1101            assert_eq!(result.unwrap().name(), case);
1102        }
1103    }
1104
1105    /// Tests comprehensive algorithm validation logic.
1106    /// Validates that:
1107    /// - All validation rules are applied consistently
1108    /// - Validation covers all algorithm name requirements
1109    /// - Validation errors provide specific feedback
1110    /// - Validation performance is acceptable
1111    /// - Validation logic is thorough and reliable
1112
1113    #[test]
1114    fn test_algorithm_validation_comprehensive() {
1115        // Test the validate method directly
1116        let valid_algo = Algorithm::brotli();
1117        assert!(valid_algo.validate().is_ok());
1118
1119        // Test validation with custom algorithms
1120        let custom_valid = Algorithm::new("my-custom-algo123".to_string()).unwrap();
1121        assert!(custom_valid.validate().is_ok());
1122
1123        // Test edge cases for validation
1124        let edge_cases = vec![
1125            "a",          // Single character
1126            "algo1",      // Ends with number
1127            "1algo",      // Starts with number (should be invalid)
1128            "algo-1-2-3", // Multiple numbers with hyphens
1129        ];
1130
1131        for case in edge_cases {
1132            let result = Algorithm::new(case.to_string());
1133            if case == "1algo" {
1134                assert!(result.is_err(), "Algorithm starting with number should be invalid");
1135            } else {
1136                assert!(result.is_ok(), "Algorithm '{}' should be valid", case);
1137            }
1138        }
1139    }
1140
1141    /// Tests AlgorithmCategory enum functionality.
1142    /// Validates that:
1143    /// - All algorithm categories are properly defined
1144    /// - Category enum supports all required operations
1145    /// - Category conversion works correctly
1146    /// - Category display formatting is appropriate
1147    /// - Category enum is extensible for future algorithms
1148
1149    #[test]
1150    fn test_algorithm_category_enum() {
1151        // Test AlgorithmCategory enum behavior
1152        assert_ne!(AlgorithmCategory::Compression, AlgorithmCategory::Encryption);
1153        assert_ne!(AlgorithmCategory::Encryption, AlgorithmCategory::Hashing);
1154        assert_ne!(AlgorithmCategory::Hashing, AlgorithmCategory::Unknown);
1155
1156        // Test Debug formatting
1157        assert_eq!(format!("{:?}", AlgorithmCategory::Compression), "Compression");
1158        assert_eq!(format!("{:?}", AlgorithmCategory::Encryption), "Encryption");
1159        assert_eq!(format!("{:?}", AlgorithmCategory::Hashing), "Hashing");
1160        assert_eq!(format!("{:?}", AlgorithmCategory::Unknown), "Unknown");
1161    }
1162
1163    /// Tests Clone and Copy trait implementations for Algorithm.
1164    /// Validates that:
1165    /// - Algorithm objects can be cloned correctly
1166    /// - Cloned algorithms maintain all properties
1167    /// - Copy semantics work for algorithm objects
1168    /// - Clone and copy operations are efficient
1169    /// - Memory management is handled properly
1170
1171    #[test]
1172    fn test_algorithm_clone_and_copy_semantics() {
1173        let original = Algorithm::aes_256_gcm();
1174        let cloned = original.clone();
1175
1176        assert_eq!(original, cloned);
1177        assert_eq!(original.name(), cloned.name());
1178        assert_eq!(original.category(), cloned.category());
1179
1180        // Test that they're independent
1181        drop(original);
1182        assert_eq!(cloned.name(), "aes-256-gcm");
1183    }
1184}