//! M3.8 Pluggable Optimizer Architecture (DRY + SOLID) //! //! Enables custom optimizers and formats without recompiling. //! Follows SOLID principles for extensibility. use async_trait::async_trait; use std::collections::HashMap; use std::sync::Arc; use serde::{Serialize, Deserialize}; // ============================================================================ // ABSTRACTIONS (Interface Segregation + Open/Closed) // ============================================================================ /// Trait for content optimizers /// Single Responsibility: optimize content #[async_trait] pub trait OptimizerPlugin: Send + Sync { fn name(&self) -> &str; fn supported_types(&self) -> Vec<&str>; fn can_handle(&self, content_type: &str) -> bool { self.supported_types().contains(&content_type) } async fn optimize(&self, content: &str) -> Result; fn metrics(&self) -> PluginMetrics; } /// Trait for output format handlers /// Single Responsibility: format/parse data #[async_trait] pub trait FormatHandler: Send + Sync { fn name(&self) -> &str; async fn format(&self, result: &OptimizationResult) -> Result, String>; async fn parse(&self, data: &[u8]) -> Result; } /// Trait for registries (DRY: generic registration pattern) /// Single Responsibility: store and retrieve plugins/formats pub trait Registry: Send + Sync { fn register(&mut self, item: Arc); fn get(&self, name: &str) -> Option>; fn list(&self) -> Vec; } // ============================================================================ // DATA STRUCTURES // ============================================================================ /// Result of optimization #[derive(Debug, Clone, Serialize, Deserialize)] pub struct OptimizationResult { pub original: String, pub optimized: String, pub ratio: f32, pub plugin: String, pub metadata: HashMap, } /// Plugin metrics #[derive(Debug, Clone, Default)] pub struct PluginMetrics { pub total_optimizations: u64, pub total_bytes_input: u64, pub total_bytes_output: u64, pub errors: u64, pub avg_latency_ms: f32, } // ============================================================================ // GENERIC REGISTRY (DRY: avoid duplication) // ============================================================================ /// Generic registry implementation for any plugin type pub struct SimpleRegistry { items: HashMap>, } impl SimpleRegistry { pub fn new() -> Self { Self { items: HashMap::new(), } } } impl Default for SimpleRegistry { fn default() -> Self { Self::new() } } impl Registry for SimpleRegistry { fn register(&mut self, item: Arc) { self.items.insert(item.name().to_string(), item); } fn get(&self, name: &str) -> Option> { self.items.get(name).cloned() } fn list(&self) -> Vec { self.items.keys().cloned().collect() } } impl Registry for SimpleRegistry { fn register(&mut self, item: Arc) { self.items.insert(item.name().to_string(), item); } fn get(&self, name: &str) -> Option> { self.items.get(name).cloned() } fn list(&self) -> Vec { self.items.keys().cloned().collect() } } // ============================================================================ // LOCATOR (Dependency Injection) // ============================================================================ /// Trait for locating plugins (Strategy pattern) /// Allows different lookup strategies (e.g., by name, by type, by priority) pub trait PluginLocator: Send + Sync { fn find_optimizer( &self, registry: &SimpleRegistry, content_type: &str, ) -> Result, String>; fn find_format( &self, registry: &SimpleRegistry, name: &str, ) -> Result, String>; } /// Default locator: first match by type pub struct DefaultLocator; impl PluginLocator for DefaultLocator { fn find_optimizer( &self, registry: &SimpleRegistry, content_type: &str, ) -> Result, String> { registry .list() .iter() .find_map(|name| registry.get(name).filter(|opt| opt.can_handle(content_type))) .ok_or_else(|| { format!( "No optimizer for type {}. Available: {:?}", content_type, registry.list() ) }) } fn find_format( &self, registry: &SimpleRegistry, name: &str, ) -> Result, String> { registry.get(name).ok_or_else(|| { format!("Format {} not found. Available: {:?}", name, registry.list()) }) } } // ============================================================================ // SERVICE (Composition + Dependency Inversion) // ============================================================================ /// Optimizer service: composes registry + locator /// Single Responsibility: orchestrate optimization workflow pub struct OptimizerService { optimizer_registry: Arc>, format_registry: Arc>, locator: Arc, default_format: String, } impl OptimizerService { /// Create service with injected dependencies pub fn new( optimizer_registry: Arc>, format_registry: Arc>, locator: Arc, default_format: String, ) -> Result { if format_registry.get(&default_format).is_none() { return Err(format!("Default format {} not registered", default_format)); } Ok(Self { optimizer_registry, format_registry, locator, default_format, }) } /// Optimize with auto-format conversion pub async fn optimize( &self, content: &str, content_type: &str, format: Option<&str>, ) -> Result, String> { // Find optimizer let optimizer = self .locator .find_optimizer(&self.optimizer_registry, content_type)?; // Optimize let result = optimizer.optimize(content).await?; // Find formatter let format_name = format.unwrap_or(&self.default_format); let formatter = self .locator .find_format(&self.format_registry, format_name)?; // Format output formatter.format(&result).await } /// Get all metrics (avoid duplication with generic collect) pub fn get_metrics(&self) -> HashMap { self.optimizer_registry .list() .iter() .filter_map(|name| { self.optimizer_registry .get(name) .map(|opt| (name.clone(), opt.metrics())) }) .collect() } /// List available optimizers pub fn optimizers(&self) -> Vec { self.optimizer_registry.list() } /// List available formats pub fn formats(&self) -> Vec { self.format_registry.list() } } // ============================================================================ // BUILDER (Make service creation ergonomic) // ============================================================================ pub struct OptimizerServiceBuilder { optimizer_registry: SimpleRegistry, format_registry: SimpleRegistry, locator: Arc, default_format: String, } impl OptimizerServiceBuilder { pub fn new() -> Self { Self { optimizer_registry: SimpleRegistry::new(), format_registry: SimpleRegistry::new(), locator: Arc::new(DefaultLocator), default_format: "json".to_string(), } } pub fn with_optimizer(mut self, optimizer: Arc) -> Self { self.optimizer_registry.register(optimizer); self } pub fn with_format(mut self, format: Arc) -> Self { self.format_registry.register(format); self } pub fn with_locator(mut self, locator: Arc) -> Self { self.locator = locator; self } pub fn with_default_format(mut self, format: String) -> Self { self.default_format = format; self } pub fn build(self) -> Result { OptimizerService::new( Arc::new(self.optimizer_registry), Arc::new(self.format_registry), self.locator, self.default_format, ) } } impl Default for OptimizerServiceBuilder { fn default() -> Self { Self::new() } } // ============================================================================ // TESTS // ============================================================================ #[cfg(test)] mod tests { use super::*; struct MockOptimizer; #[async_trait] impl OptimizerPlugin for MockOptimizer { fn name(&self) -> &str { "mock" } fn supported_types(&self) -> Vec<&str> { vec!["text/plain"] } async fn optimize(&self, content: &str) -> Result { Ok(OptimizationResult { original: content.to_string(), optimized: content.to_lowercase(), ratio: 0.8, plugin: self.name().to_string(), metadata: Default::default(), }) } fn metrics(&self) -> PluginMetrics { PluginMetrics::default() } } struct MockFormat; #[async_trait] impl FormatHandler for MockFormat { fn name(&self) -> &str { "json" } async fn format(&self, result: &OptimizationResult) -> Result, String> { let json = serde_json::to_string(result).map_err(|e| e.to_string())?; Ok(json.into_bytes()) } async fn parse(&self, data: &[u8]) -> Result { serde_json::from_slice(data).map_err(|e| e.to_string()) } } #[test] fn test_registry_register_and_get() { let mut registry: SimpleRegistry = SimpleRegistry::new(); let opt: Arc = Arc::new(MockOptimizer); registry.register(opt); assert!(registry.get("mock").is_some()); } #[test] fn test_registry_list() { let mut registry: SimpleRegistry = SimpleRegistry::new(); registry.register(Arc::new(MockOptimizer) as Arc); assert_eq!(registry.list(), vec!["mock"]); } #[test] fn test_locator_find_by_type() { let mut registry: SimpleRegistry = SimpleRegistry::new(); registry.register(Arc::new(MockOptimizer) as Arc); let locator = DefaultLocator; let found = locator.find_optimizer(®istry, "text/plain"); assert!(found.is_ok()); } #[test] fn test_locator_not_found() { let registry: SimpleRegistry = SimpleRegistry::new(); let locator = DefaultLocator; let result = locator.find_optimizer(®istry, "unknown/type"); assert!(result.is_err()); } #[tokio::test] async fn test_service_optimize() { let service = OptimizerServiceBuilder::new() .with_optimizer(Arc::new(MockOptimizer) as Arc) .with_format(Arc::new(MockFormat) as Arc) .build() .unwrap(); let result = service.optimize("HELLO", "text/plain", None).await; assert!(result.is_ok()); } #[test] fn test_builder_creates_service() { let service = OptimizerServiceBuilder::new() .with_optimizer(Arc::new(MockOptimizer) as Arc) .with_format(Arc::new(MockFormat) as Arc) .build(); assert!(service.is_ok()); } #[test] fn test_builder_fails_on_missing_format() { let service = OptimizerServiceBuilder::new() .with_optimizer(Arc::new(MockOptimizer) as Arc) .with_default_format("missing".to_string()) .build(); assert!(service.is_err()); } }