//! Built-in optimizer and format implementations //! Bridges existing ContextOptimizer to pluggable system use async_trait::async_trait; use super::plugin::{OptimizerPlugin, FormatHandler, OptimizationResult, PluginMetrics}; use super::ContextOptimizer; use std::sync::{Arc, Mutex}; /// Built-in context optimizer plugin pub struct BuiltinOptimizer { optimizer: Arc, metrics: Arc>, } impl BuiltinOptimizer { pub fn new(optimizer: Arc) -> Self { Self { optimizer, metrics: Arc::new(Mutex::new(PluginMetrics::default())), } } } #[async_trait] impl OptimizerPlugin for BuiltinOptimizer { fn name(&self) -> &str { "builtin-optimizer" } fn supported_types(&self) -> Vec<&str> { vec![ "text/plain", "text/x-log", "application/json", "text/x-diff", "application/x-yaml", ] } async fn optimize(&self, content: &str) -> Result { let start = std::time::Instant::now(); let original_len = content.len(); match self.optimizer.optimize(content) { Ok(chunk) => { let optimized_len = chunk.compressed.len(); let ratio = optimized_len as f32 / original_len as f32; // Update metrics { let mut m = self.metrics.lock().unwrap(); m.total_optimizations += 1; m.total_bytes_input += original_len as u64; m.total_bytes_output += optimized_len as u64; m.avg_latency_ms = start.elapsed().as_secs_f32() * 1000.0; } Ok(OptimizationResult { original: content.to_string(), optimized: chunk.compressed, ratio, plugin: self.name().to_string(), metadata: std::collections::HashMap::new(), }) } Err(e) => { let mut m = self.metrics.lock().unwrap(); m.errors += 1; Err(format!("Optimization failed: {}", e)) } } } fn metrics(&self) -> PluginMetrics { self.metrics.lock().unwrap().clone() } } // ============================================================================ // FORMAT HANDLERS // ============================================================================ /// JSON format handler pub struct JsonFormatter; #[async_trait] impl FormatHandler for JsonFormatter { fn name(&self) -> &str { "json" } async fn format(&self, result: &OptimizationResult) -> Result, String> { serde_json::to_vec(result).map_err(|e| format!("JSON serialization failed: {}", e)) } async fn parse(&self, data: &[u8]) -> Result { serde_json::from_slice(data) .map_err(|e| format!("JSON deserialization failed: {}", e)) } } /// JSONL (newline-delimited JSON) formatter pub struct JsonlFormatter; #[async_trait] impl FormatHandler for JsonlFormatter { fn name(&self) -> &str { "jsonl" } async fn format(&self, result: &OptimizationResult) -> Result, String> { let mut output = serde_json::to_string(result) .map_err(|e| format!("JSONL serialization failed: {}", e))?; output.push('\n'); Ok(output.into_bytes()) } async fn parse(&self, data: &[u8]) -> Result { let line = String::from_utf8(data.to_vec()) .map_err(|e| format!("UTF-8 decode failed: {}", e))?; serde_json::from_str(line.trim()) .map_err(|e| format!("JSONL deserialization failed: {}", e)) } } /// Raw text format (just optimized content) pub struct RawFormatter; #[async_trait] impl FormatHandler for RawFormatter { fn name(&self) -> &str { "raw" } async fn format(&self, result: &OptimizationResult) -> Result, String> { Ok(result.optimized.as_bytes().to_vec()) } async fn parse(&self, _data: &[u8]) -> Result { Err("Raw format does not support deserialization".to_string()) } } /// CSV format (for metrics) pub struct CsvFormatter; #[async_trait] impl FormatHandler for CsvFormatter { fn name(&self) -> &str { "csv" } async fn format(&self, result: &OptimizationResult) -> Result, String> { let output = format!( "{},{},{},{}\n", escape_csv(&result.plugin), result.original.len(), result.optimized.len(), format!("{:.2}", result.ratio) ); Ok(output.into_bytes()) } async fn parse(&self, _data: &[u8]) -> Result { Err("CSV format does not support deserialization".to_string()) } } /// YAML format pub struct YamlFormatter; #[async_trait] impl FormatHandler for YamlFormatter { fn name(&self) -> &str { "yaml" } async fn format(&self, result: &OptimizationResult) -> Result, String> { let yaml = format!( "plugin: {}\nratio: {:.2}\noriginal_bytes: {}\noptimized_bytes: {}\n", result.plugin, result.ratio, result.original.len(), result.optimized.len() ); Ok(yaml.into_bytes()) } async fn parse(&self, _data: &[u8]) -> Result { Err("YAML format does not support deserialization".to_string()) } } // ============================================================================ // HELPERS // ============================================================================ fn escape_csv(s: &str) -> String { if s.contains(',') || s.contains('"') || s.contains('\n') { format!("\"{}\"", s.replace('"', "\"\"")) } else { s.to_string() } } #[cfg(test)] mod tests { use super::*; #[tokio::test] async fn test_builtin_optimizer_logs() { let optimizer = Arc::new(ContextOptimizer::new().unwrap()); let plugin = BuiltinOptimizer::new(optimizer); let content = "ERROR: failed\nDEBUG: info\nERROR: error"; let result = plugin.optimize(content).await.unwrap(); assert_eq!(result.plugin, "builtin-optimizer"); assert!(result.ratio <= 1.0); } #[tokio::test] async fn test_json_formatter() { let formatter = JsonFormatter; let result = OptimizationResult { original: "test".to_string(), optimized: "tst".to_string(), ratio: 0.75, plugin: "test".to_string(), metadata: Default::default(), }; let formatted = formatter.format(&result).await.unwrap(); let parsed = formatter.parse(&formatted).await.unwrap(); assert_eq!(parsed.original, "test"); } #[tokio::test] async fn test_jsonl_formatter() { let formatter = JsonlFormatter; let result = OptimizationResult { original: "test".to_string(), optimized: "tst".to_string(), ratio: 0.75, plugin: "test".to_string(), metadata: Default::default(), }; let formatted = formatter.format(&result).await.unwrap(); assert!(String::from_utf8(formatted).unwrap().ends_with('\n')); } #[tokio::test] async fn test_raw_formatter() { let formatter = RawFormatter; let result = OptimizationResult { original: "original content".to_string(), optimized: "optimized".to_string(), ratio: 0.5, plugin: "test".to_string(), metadata: Default::default(), }; let formatted = formatter.format(&result).await.unwrap(); assert_eq!(formatted, b"optimized"); } #[tokio::test] async fn test_csv_formatter() { let formatter = CsvFormatter; let result = OptimizationResult { original: "test".to_string(), optimized: "tst".to_string(), ratio: 0.75, plugin: "my-plugin".to_string(), metadata: Default::default(), }; let formatted = formatter.format(&result).await.unwrap(); let csv = String::from_utf8(formatted).unwrap(); assert!(csv.contains("my-plugin")); } #[tokio::test] async fn test_yaml_formatter() { let formatter = YamlFormatter; let result = OptimizationResult { original: "original".to_string(), optimized: "opt".to_string(), ratio: 0.33, plugin: "test".to_string(), metadata: Default::default(), }; let formatted = formatter.format(&result).await.unwrap(); let yaml = String::from_utf8(formatted).unwrap(); assert!(yaml.contains("ratio:")); } }