adaptive_pipeline_domain/value_objects/processing_step_descriptor.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//! # Processing Step Descriptor Value Object
9//!
10//! This module defines the processing step descriptor value object for the
11//! adaptive pipeline system. It provides a comprehensive description of
12//! processing steps with validation, parameters, and metadata.
13//!
14//! ## Overview
15//!
16//! The processing step descriptor provides:
17//!
18//! - **Step Definition**: Complete definition of processing steps
19//! - **Algorithm Validation**: Validated algorithm names and parameters
20//! - **Parameter Management**: Type-safe parameter handling
21//! - **Metadata Tracking**: Comprehensive metadata for each step
22//! - **Serialization**: Support for persistence and transmission
23//!
24//! ## Architecture
25//!
26//! The descriptor follows Domain-Driven Design principles:
27//!
28//! - **Value Object**: Immutable value object with equality semantics
29//! - **Rich Domain Model**: Encapsulates processing step business logic
30//! - **Validation**: Comprehensive validation of step definitions
31//! - **Type Safety**: Type-safe parameter and algorithm handling
32//!
33//! ## Key Features
34//!
35//! ### Step Definition
36//!
37//! - **Step Types**: Support for different processing step types
38//! - **Algorithm Specification**: Validated algorithm names and versions
39//! - **Parameter Configuration**: Type-safe parameter configuration
40//! - **Dependency Management**: Step dependency tracking
41//!
42//! ### Algorithm Management
43//!
44//! - **Name Validation**: Comprehensive algorithm name validation
45//! - **Version Control**: Algorithm version tracking and compatibility
46//! - **Parameter Schema**: Algorithm-specific parameter schemas
47//! - **Capability Detection**: Detect algorithm capabilities and features
48//!
49//! ### Parameter Handling
50//!
51//! - **Type Safety**: Type-safe parameter values
52//! - **Validation**: Parameter validation against schemas
53//! - **Default Values**: Support for default parameter values
54//! - **Documentation**: Parameter documentation and help text
55//!
56//! ## Usage Examples
57//!
58//! ### Basic Step Creation
59
60//!
61//! ### Algorithm Validation
62
63//!
64//! ### Parameter Management
65
66//!
67//! ### Step Composition and Chaining
68
69//!
70//! ### Serialization and Configuration
71
72//!
73//! ## Processing Step Types
74//!
75//! ### Built-in Step Types
76//!
77//! - **Validation**: Input validation and integrity checking
78//! - Algorithms: checksum, signature, format validation
79//! - Use case: Validate input files before processing
80//!
81//! - **Compression**: Data compression and decompression
82//! - Algorithms: brotli, gzip, zstd, lz4, deflate
83//! - Use case: Reduce file size for storage or transmission
84//!
85//! - **Encryption**: Data encryption and decryption
86//! - Algorithms: aes-256-gcm, chacha20-poly1305, aes-128-gcm
87//! - Use case: Secure data storage and transmission
88//!
89//! - **Transformation**: Data format transformation
90//! - Algorithms: json-to-binary, xml-to-json, custom transforms
91//! - Use case: Convert between different data formats
92//!
93//! - **Analysis**: Data analysis and metrics collection
94//! - Algorithms: statistics, pattern detection, anomaly detection
95//! - Use case: Analyze data patterns and collect metrics
96//!
97//! ### Custom Step Types
98//!
99//! Create custom step types by extending the ProcessingStepType enum:
100//!
101//! - **Domain-Specific**: Steps specific to your application domain
102//! - **Integration**: Steps for integrating with external systems
103//! - **Monitoring**: Steps for monitoring and alerting
104//!
105//! ## Algorithm Validation
106//!
107//! ### Name Format Rules
108//!
109//! - **Characters**: Alphanumeric, hyphens, and underscores only
110//! - **Case**: Converted to lowercase for consistency
111//! - **Length**: Must be non-empty after trimming
112//! - **Format**: Must match regex pattern `^[a-zA-Z0-9_-]+$`
113//!
114//! ### Validation Process
115//!
116//! 1. **Trim Whitespace**: Remove leading/trailing whitespace
117//! 2. **Empty Check**: Ensure name is not empty
118//! 3. **Character Validation**: Check allowed characters
119//! 4. **Normalization**: Convert to lowercase
120//! 5. **Registration**: Check against algorithm registry
121//!
122//! ## Parameter Management
123//!
124//! ### Parameter Types
125//!
126//! - **String Parameters**: Text values and identifiers
127//! - **Numeric Parameters**: Integer and floating-point values
128//! - **Boolean Parameters**: True/false flags
129//! - **Array Parameters**: Lists of values
130//! - **Object Parameters**: Nested parameter structures
131//!
132//! ### Parameter Validation
133//!
134//! - **Type Checking**: Validate parameter types
135//! - **Range Validation**: Check numeric ranges
136//! - **Format Validation**: Validate string formats
137//! - **Dependency Validation**: Check parameter dependencies
138//!
139//! ### Default Values
140//!
141//! - **Algorithm Defaults**: Default values for each algorithm
142//! - **Override Support**: Allow overriding default values
143//! - **Validation**: Validate default values
144//!
145//! ## Error Handling
146//!
147//! ### Validation Errors
148//!
149//! - **Invalid Algorithm**: Algorithm name is invalid
150//! - **Invalid Parameters**: Parameter values are invalid
151//! - **Missing Parameters**: Required parameters are missing
152//! - **Type Mismatch**: Parameter type doesn't match expected type
153//!
154//! ### Configuration Errors
155//!
156//! - **Invalid Step Type**: Step type is not supported
157//! - **Incompatible Parameters**: Parameters are incompatible
158//! - **Circular Dependencies**: Circular step dependencies detected
159//!
160//! ## Performance Considerations
161//!
162//! ### Memory Usage
163//!
164//! - **Efficient Storage**: Compact storage of step definitions
165//! - **String Interning**: Intern common algorithm names
166//! - **Parameter Optimization**: Optimize parameter storage
167//!
168//! ### Validation Performance
169//!
170//! - **Lazy Validation**: Validate only when necessary
171//! - **Caching**: Cache validation results
172//! - **Batch Validation**: Validate multiple steps together
173//!
174//! ## Integration
175//!
176//! The processing step descriptor integrates with:
177//!
178//! - **Processing Pipeline**: Define pipeline processing steps
179//! - **Configuration System**: Store and load step configurations
180//! - **Validation Framework**: Validate step definitions
181//! - **Execution Engine**: Execute defined processing steps
182//!
183//! ## Thread Safety
184//!
185//! The processing step descriptor is thread-safe:
186//!
187//! - **Immutable**: Descriptors are immutable after creation
188//! - **Safe Sharing**: Safe to share between threads
189//! - **Concurrent Access**: Safe concurrent access to descriptor data
190//!
191//! ## Future Enhancements
192//!
193//! Planned enhancements include:
194//!
195//! - **Dynamic Parameters**: Runtime parameter modification
196//! - **Parameter Templates**: Template-based parameter generation
197//! - **Advanced Validation**: More sophisticated validation rules
198//! - **Performance Optimization**: Further performance improvements
199
200use super::binary_file_format::ProcessingStepType;
201use crate::PipelineError;
202use serde::{Deserialize, Serialize};
203use std::collections::HashMap;
204
205/// Value object representing a validated algorithm name
206///
207/// This value object encapsulates algorithm names with comprehensive validation
208/// to ensure they meet the required format and naming conventions.
209///
210/// # Key Features
211///
212/// - **Name Validation**: Comprehensive validation of algorithm names
213/// - **Format Normalization**: Normalize names to lowercase
214/// - **Character Restrictions**: Only alphanumeric, hyphens, and underscores
215/// - **Immutability**: Algorithm names cannot be changed after creation
216///
217/// # Validation Rules
218///
219/// - Must be non-empty after trimming whitespace
220/// - Must contain only alphanumeric characters, hyphens, and underscores
221/// - Is normalized to lowercase for consistency
222/// - Must match the pattern: `^[a-zA-Z0-9_-]+$`
223///
224/// # Examples
225#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
226pub struct Algorithm(String);
227
228impl Algorithm {
229 /// Create a new Algorithm with validation
230 ///
231 /// # Purpose
232 /// Creates a validated algorithm name with format normalization.
233 /// Ensures algorithm names follow consistent naming conventions.
234 ///
235 /// # Why
236 /// Validated algorithm names provide:
237 /// - Prevention of configuration errors
238 /// - Consistent naming across systems
239 /// - Type-safe algorithm specification
240 /// - Cross-platform compatibility
241 ///
242 /// # Arguments
243 /// * `value` - Algorithm name (alphanumeric, hyphens, underscores)
244 ///
245 /// # Returns
246 /// * `Ok(Algorithm)` - Validated algorithm (normalized to lowercase)
247 /// * `Err(PipelineError)` - Invalid format
248 ///
249 /// # Errors
250 /// - Empty name after trimming
251 /// - Invalid characters (only alphanumeric, `-`, `_` allowed)
252 ///
253 /// # Examples
254 pub fn new(value: &str) -> Result<Self, PipelineError> {
255 let trimmed = value.trim();
256 if trimmed.is_empty() {
257 return Err(PipelineError::InvalidConfiguration(
258 "Algorithm cannot be empty".to_string(),
259 ));
260 }
261
262 // Validate algorithm name format (alphanumeric, hyphens, underscores)
263 if !trimmed.chars().all(|c| c.is_alphanumeric() || c == '-' || c == '_') {
264 return Err(PipelineError::InvalidConfiguration(format!(
265 "Invalid algorithm name '{}': only alphanumeric, hyphens, and underscores allowed",
266 trimmed
267 )));
268 }
269
270 Ok(Algorithm(trimmed.to_lowercase()))
271 }
272
273 /// Get the algorithm name as a string
274 pub fn as_str(&self) -> &str {
275 &self.0
276 }
277
278 /// Convert to owned String
279 pub fn into_string(self) -> String {
280 self.0
281 }
282}
283
284impl std::fmt::Display for Algorithm {
285 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
286 write!(f, "{}", self.0)
287 }
288}
289
290/// Value object representing validated stage parameters
291#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
292pub struct StageParameters(HashMap<String, String>);
293
294impl StageParameters {
295 /// Create new empty parameters
296 pub fn new() -> Self {
297 Self(HashMap::new())
298 }
299
300 /// Create from existing HashMap with validation
301 pub fn from_map(map: HashMap<String, String>) -> Result<Self, PipelineError> {
302 // Validate parameter keys and values
303 for (key, value) in &map {
304 if key.trim().is_empty() {
305 return Err(PipelineError::InvalidConfiguration(
306 "Parameter key cannot be empty".to_string(),
307 ));
308 }
309 if value.len() > 1024 {
310 return Err(PipelineError::InvalidConfiguration(format!(
311 "Parameter value for '{}' exceeds maximum length of 1024 characters",
312 key
313 )));
314 }
315 }
316 Ok(Self(map))
317 }
318
319 /// Add a parameter with validation
320 pub fn add_parameter(&mut self, key: &str, value: &str) -> Result<(), PipelineError> {
321 let trimmed_key = key.trim();
322 if trimmed_key.is_empty() {
323 return Err(PipelineError::InvalidConfiguration(
324 "Parameter key cannot be empty".to_string(),
325 ));
326 }
327 if value.len() > 1024 {
328 return Err(PipelineError::InvalidConfiguration(format!(
329 "Parameter value for '{}' exceeds maximum length of 1024 characters",
330 trimmed_key
331 )));
332 }
333
334 self.0.insert(trimmed_key.to_string(), value.to_string());
335 Ok(())
336 }
337
338 /// Get parameter value
339 pub fn get(&self, key: &str) -> Option<&String> {
340 self.0.get(key)
341 }
342
343 /// Get all parameters as HashMap reference
344 pub fn as_map(&self) -> &HashMap<String, String> {
345 &self.0
346 }
347
348 /// Convert to owned HashMap
349 pub fn into_map(self) -> HashMap<String, String> {
350 self.0
351 }
352
353 /// Check if parameters are empty
354 pub fn is_empty(&self) -> bool {
355 self.0.is_empty()
356 }
357}
358
359impl Default for StageParameters {
360 fn default() -> Self {
361 Self::new()
362 }
363}
364
365/// Value object representing the order of a processing step
366#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
367pub struct StepOrder(u32);
368
369impl StepOrder {
370 /// Create a new StepOrder
371 pub fn new(order: u32) -> Self {
372 Self(order)
373 }
374
375 /// Get the order value
376 pub fn value(&self) -> u32 {
377 self.0
378 }
379
380 /// Get the next order
381 pub fn next(&self) -> Self {
382 Self(self.0 + 1)
383 }
384}
385
386impl std::fmt::Display for StepOrder {
387 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
388 write!(f, "{}", self.0)
389 }
390}
391
392/// Value object describing a complete processing step
393#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
394pub struct ProcessingStepDescriptor {
395 step_type: ProcessingStepType,
396 algorithm: Algorithm,
397 parameters: StageParameters,
398 order: StepOrder,
399}
400
401impl ProcessingStepDescriptor {
402 /// Create a new ProcessingStepDescriptor
403 pub fn new(
404 step_type: ProcessingStepType,
405 algorithm: Algorithm,
406 parameters: StageParameters,
407 order: StepOrder,
408 ) -> Self {
409 Self {
410 step_type,
411 algorithm,
412 parameters,
413 order,
414 }
415 }
416
417 /// Create a compression step descriptor
418 pub fn compression(algorithm: Algorithm, order: StepOrder) -> Self {
419 Self::new(
420 ProcessingStepType::Compression,
421 algorithm,
422 StageParameters::new(),
423 order,
424 )
425 }
426
427 /// Create an encryption step descriptor
428 pub fn encryption(algorithm: Algorithm, order: StepOrder) -> Self {
429 Self::new(ProcessingStepType::Encryption, algorithm, StageParameters::new(), order)
430 }
431
432 /// Create a checksum step descriptor
433 pub fn checksum(algorithm: Algorithm, order: StepOrder) -> Self {
434 Self::new(ProcessingStepType::Checksum, algorithm, StageParameters::new(), order)
435 }
436
437 /// Create a pass-through step descriptor
438 pub fn pass_through(algorithm: Algorithm, order: StepOrder) -> Self {
439 Self::new(
440 ProcessingStepType::PassThrough,
441 algorithm,
442 StageParameters::new(),
443 order,
444 )
445 }
446
447 /// Get the step type
448 pub fn step_type(&self) -> &ProcessingStepType {
449 &self.step_type
450 }
451
452 /// Get the algorithm
453 pub fn algorithm(&self) -> &Algorithm {
454 &self.algorithm
455 }
456
457 /// Get the parameters
458 pub fn parameters(&self) -> &StageParameters {
459 &self.parameters
460 }
461
462 /// Get the order
463 pub fn order(&self) -> StepOrder {
464 self.order
465 }
466
467 /// Add a parameter to this descriptor
468 pub fn with_parameter(mut self, key: &str, value: &str) -> Result<Self, PipelineError> {
469 self.parameters.add_parameter(key, value)?;
470 Ok(self)
471 }
472}
473
474#[cfg(test)]
475mod tests {
476 use super::*;
477
478 /// Tests algorithm validation rules and constraint enforcement.
479 ///
480 /// This test validates that algorithm names are properly validated
481 /// according to business rules and that invalid algorithm names
482 /// are rejected with appropriate error handling.
483 ///
484 /// # Test Coverage
485 ///
486 /// - Valid algorithm name acceptance
487 /// - Standard algorithm validation (brotli, aes256-gcm, sha256)
488 /// - Custom algorithm name support
489 /// - Empty string rejection
490 /// - Whitespace-only string rejection
491 /// - Algorithm names with spaces rejection
492 /// - Algorithm names with special characters rejection
493 ///
494 /// # Test Scenario
495 ///
496 /// Tests various algorithm name formats including valid standard
497 /// algorithms, custom algorithms, and invalid formats.
498 ///
499 /// # Domain Concerns
500 ///
501 /// - Algorithm name validation
502 /// - Processing step configuration
503 /// - Input validation and sanitization
504 /// - Business rule enforcement
505 ///
506 /// # Assertions
507 ///
508 /// - Standard algorithms are accepted
509 /// - Custom algorithms are accepted
510 /// - Empty strings are rejected
511 /// - Whitespace-only strings are rejected
512 /// - Names with spaces are rejected
513 /// - Names with special characters are rejected
514 #[test]
515 fn test_algorithm_validation() {
516 // Valid algorithms
517 assert!(Algorithm::new("brotli").is_ok());
518 assert!(Algorithm::new("aes256-gcm").is_ok());
519 assert!(Algorithm::new("sha256").is_ok());
520 assert!(Algorithm::new("custom_algo").is_ok());
521
522 // Invalid algorithms
523 assert!(Algorithm::new("").is_err());
524 assert!(Algorithm::new(" ").is_err());
525 assert!(Algorithm::new("algo with spaces").is_err());
526 assert!(Algorithm::new("algo@special").is_err());
527 }
528
529 /// Tests stage parameters management and validation.
530 ///
531 /// This test validates that stage parameters can be properly
532 /// managed including parameter addition, validation, and
533 /// retrieval for processing step configuration.
534 ///
535 /// # Test Coverage
536 ///
537 /// - Parameter addition with valid key-value pairs
538 /// - Parameter validation for empty keys
539 /// - Parameter retrieval functionality
540 /// - Parameter storage and access
541 /// - Validation error handling
542 ///
543 /// # Test Scenario
544 ///
545 /// Creates stage parameters, adds valid and invalid parameters,
546 /// and verifies parameter management functionality.
547 ///
548 /// # Domain Concerns
549 ///
550 /// - Processing step configuration
551 /// - Parameter validation and management
552 /// - Configuration storage and retrieval
553 /// - Input validation
554 ///
555 /// # Assertions
556 ///
557 /// - Valid parameters are added successfully
558 /// - Empty key parameters are rejected
559 /// - Parameter values can be retrieved
560 /// - Parameter storage works correctly
561 #[test]
562 fn test_stage_parameters() {
563 let mut params = StageParameters::new();
564 assert!(params.add_parameter("level", "6").is_ok());
565 assert!(params.add_parameter("", "value").is_err());
566 assert_eq!(params.get("level"), Some(&"6".to_string()));
567 }
568
569 /// Tests step order creation and navigation functionality.
570 ///
571 /// This test validates that step orders can be created with
572 /// proper value storage and that navigation to next orders
573 /// works correctly for processing sequence management.
574 ///
575 /// # Test Coverage
576 ///
577 /// - Step order creation with value
578 /// - Value storage and retrieval
579 /// - Next order calculation
580 /// - Order navigation functionality
581 /// - Sequential order management
582 ///
583 /// # Test Scenario
584 ///
585 /// Creates a step order and tests value access and navigation
586 /// to the next order in the sequence.
587 ///
588 /// # Domain Concerns
589 ///
590 /// - Processing step sequencing
591 /// - Order management and navigation
592 /// - Sequential processing logic
593 /// - Step execution order
594 ///
595 /// # Assertions
596 ///
597 /// - Order value is stored correctly
598 /// - Next order is calculated correctly
599 /// - Navigation functionality works
600 /// - Sequential ordering is maintained
601 #[test]
602 fn test_step_order() {
603 let order = StepOrder::new(5);
604 assert_eq!(order.value(), 5);
605 assert_eq!(order.next().value(), 6);
606 }
607
608 /// Tests processing step descriptor creation and properties.
609 ///
610 /// This test validates that processing step descriptors can be
611 /// created with proper algorithm, order, and step type
612 /// configuration for pipeline processing steps.
613 ///
614 /// # Test Coverage
615 ///
616 /// - Compression step descriptor creation
617 /// - Step type property validation
618 /// - Algorithm property access
619 /// - Order property access
620 /// - Descriptor configuration integrity
621 ///
622 /// # Test Scenario
623 ///
624 /// Creates a compression processing step descriptor and verifies
625 /// all properties are correctly configured and accessible.
626 ///
627 /// # Domain Concerns
628 ///
629 /// - Processing step configuration
630 /// - Step type classification
631 /// - Algorithm and order management
632 /// - Pipeline step definition
633 ///
634 /// # Assertions
635 ///
636 /// - Step type is compression
637 /// - Algorithm is stored correctly
638 /// - Order value is preserved
639 /// - Descriptor properties are accessible
640 #[test]
641 fn test_processing_step_descriptor() {
642 let algorithm = Algorithm::new("brotli").unwrap();
643 let order = StepOrder::new(1);
644
645 let descriptor = ProcessingStepDescriptor::compression(algorithm, order);
646 assert_eq!(descriptor.step_type(), &ProcessingStepType::Compression);
647 assert_eq!(descriptor.algorithm().as_str(), "brotli");
648 assert_eq!(descriptor.order().value(), 1);
649 }
650}