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}