Skip to main content

pdf_ast/plugins/
mod.rs

1use crate::ast::{AstNode, NodeId, NodeType, PdfAstGraph, PdfDocument};
2use crate::validation::{SchemaConstraint, ValidationReport};
3use serde::{Deserialize, Serialize};
4use std::any::Any;
5use std::collections::HashMap;
6
7pub mod api;
8pub mod loader;
9pub mod registry;
10
11pub use api::*;
12pub use loader::*;
13pub use registry::*;
14
15/// Plugin execution result
16#[derive(Debug, Clone)]
17pub enum PluginResult {
18    Success,
19    Modified(Vec<NodeId>),
20    Error(String),
21    Warning(String),
22}
23
24impl PluginResult {
25    pub fn is_success(&self) -> bool {
26        matches!(self, PluginResult::Success | PluginResult::Modified(_))
27    }
28
29    pub fn is_error(&self) -> bool {
30        matches!(self, PluginResult::Error(_))
31    }
32
33    pub fn get_error(&self) -> Option<&String> {
34        match self {
35            PluginResult::Error(msg) => Some(msg),
36            _ => None,
37        }
38    }
39}
40
41/// Plugin metadata
42#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct PluginMetadata {
44    pub name: String,
45    pub version: String,
46    pub description: String,
47    pub author: String,
48    pub license: Option<String>,
49    pub homepage: Option<String>,
50    pub repository: Option<String>,
51    pub tags: Vec<String>,
52    pub supported_node_types: Vec<String>,
53    pub dependencies: Vec<String>,
54    pub api_version: String,
55}
56
57impl PluginMetadata {
58    pub fn new(name: &str, version: &str, description: &str, author: &str) -> Self {
59        Self {
60            name: name.to_string(),
61            version: version.to_string(),
62            description: description.to_string(),
63            author: author.to_string(),
64            license: None,
65            homepage: None,
66            repository: None,
67            tags: Vec::new(),
68            supported_node_types: Vec::new(),
69            dependencies: Vec::new(),
70            api_version: "0.1.0".to_string(),
71        }
72    }
73
74    pub fn with_license(mut self, license: &str) -> Self {
75        self.license = Some(license.to_string());
76        self
77    }
78
79    pub fn with_tags(mut self, tags: Vec<&str>) -> Self {
80        self.tags = tags.into_iter().map(|s| s.to_string()).collect();
81        self
82    }
83
84    pub fn with_supported_types(mut self, types: Vec<NodeType>) -> Self {
85        self.supported_node_types = types.into_iter().map(|t| format!("{:?}", t)).collect();
86        self
87    }
88}
89
90/// Base trait for all plugins
91pub trait AstPlugin: Send + Sync {
92    /// Get plugin metadata
93    fn metadata(&self) -> &PluginMetadata;
94
95    /// Initialize the plugin
96    fn initialize(&mut self, context: &mut PluginContext) -> PluginResult {
97        let _ = context;
98        PluginResult::Success
99    }
100
101    /// Process a single node
102    fn process_node(&self, node: &mut AstNode, context: &mut PluginContext) -> PluginResult {
103        let _ = (node, context);
104        PluginResult::Success
105    }
106
107    /// Process the entire document
108    fn process_document(
109        &self,
110        document: &mut PdfDocument,
111        context: &mut PluginContext,
112    ) -> PluginResult {
113        let _ = (document, context);
114        PluginResult::Success
115    }
116
117    /// Finalize processing
118    fn finalize(&self, context: &mut PluginContext) -> PluginResult {
119        let _ = context;
120        PluginResult::Success
121    }
122
123    /// Get plugin configuration schema
124    fn config_schema(&self) -> Option<serde_json::Value> {
125        None
126    }
127
128    /// Set plugin configuration
129    fn set_config(&mut self, config: serde_json::Value) -> PluginResult {
130        let _ = config;
131        PluginResult::Success
132    }
133
134    /// Check if plugin can process specific node type
135    fn can_process_node_type(&self, node_type: &NodeType) -> bool {
136        let node_type_str = format!("{:?}", node_type);
137        self.metadata().supported_node_types.is_empty()
138            || self
139                .metadata()
140                .supported_node_types
141                .contains(&node_type_str)
142    }
143
144    /// Get plugin capabilities
145    fn capabilities(&self) -> PluginCapabilities {
146        PluginCapabilities::default()
147    }
148
149    /// Clone the plugin (for plugin instances)
150    fn clone_plugin(&self) -> Box<dyn AstPlugin>;
151}
152
153/// Plugin capabilities
154#[derive(Debug, Clone, Default)]
155pub struct PluginCapabilities {
156    pub can_modify_nodes: bool,
157    pub can_add_nodes: bool,
158    pub can_remove_nodes: bool,
159    pub can_validate: bool,
160    pub can_transform: bool,
161    pub requires_document_context: bool,
162    pub thread_safe: bool,
163}
164
165/// Plugin execution context
166pub struct PluginContext {
167    pub document: Option<*const PdfDocument>,
168    pub current_node: Option<NodeId>,
169    pub graph: Option<*mut PdfAstGraph>,
170    pub config: HashMap<String, serde_json::Value>,
171    pub shared_data: HashMap<String, Box<dyn Any + Send + Sync>>,
172    pub statistics: PluginStatistics,
173}
174
175impl PluginContext {
176    pub fn new() -> Self {
177        Self {
178            document: None,
179            current_node: None,
180            graph: None,
181            config: HashMap::new(),
182            shared_data: HashMap::new(),
183            statistics: PluginStatistics::default(),
184        }
185    }
186
187    pub fn with_document(mut self, document: &PdfDocument) -> Self {
188        self.document = Some(document as *const PdfDocument);
189        self
190    }
191
192    pub fn with_graph(mut self, graph: &mut PdfAstGraph) -> Self {
193        self.graph = Some(graph as *mut PdfAstGraph);
194        self
195    }
196
197    pub fn set_config(&mut self, key: String, value: serde_json::Value) {
198        self.config.insert(key, value);
199    }
200
201    pub fn get_config(&self, key: &str) -> Option<&serde_json::Value> {
202        self.config.get(key)
203    }
204
205    pub fn set_shared_data<T: Any + Send + Sync>(&mut self, key: String, data: T) {
206        self.shared_data.insert(key, Box::new(data));
207    }
208
209    pub fn get_shared_data<T: Any + Send + Sync>(&self, key: &str) -> Option<&T> {
210        self.shared_data.get(key)?.downcast_ref::<T>()
211    }
212
213    pub fn get_document(&self) -> Option<&PdfDocument> {
214        self.document.map(|ptr| unsafe { &*ptr })
215    }
216
217    pub fn get_graph_mut(&mut self) -> Option<&mut PdfAstGraph> {
218        self.graph.map(|ptr| unsafe { &mut *ptr })
219    }
220
221    pub fn set_data(&mut self, key: &str, value: String) {
222        self.set_config(key.to_string(), serde_json::Value::String(value));
223    }
224
225    pub fn add_warning(&mut self, message: &str) {
226        self.statistics.warnings.push(message.to_string());
227    }
228
229    pub fn add_error(&mut self, message: &str) {
230        self.statistics.errors.push(message.to_string());
231    }
232
233    pub fn add_info(&mut self, message: String) {
234        self.statistics.info_messages.push(message);
235    }
236}
237
238unsafe impl Send for PluginContext {}
239unsafe impl Sync for PluginContext {}
240
241impl Default for PluginContext {
242    fn default() -> Self {
243        Self::new()
244    }
245}
246
247/// Plugin execution statistics
248#[derive(Debug, Clone, Default)]
249pub struct PluginStatistics {
250    pub nodes_processed: usize,
251    pub nodes_modified: usize,
252    pub nodes_added: usize,
253    pub nodes_removed: usize,
254    pub execution_time_ms: u64,
255    pub memory_used_bytes: usize,
256    pub errors: Vec<String>,
257    pub warnings: Vec<String>,
258    pub info_messages: Vec<String>,
259}
260
261/// Specialized plugin types
262/// Node transformer plugin
263pub trait NodeTransformerPlugin: AstPlugin {
264    fn transform_node(&self, node: &mut AstNode, context: &mut PluginContext) -> PluginResult;
265}
266
267/// Validator plugin
268pub trait ValidatorPlugin: AstPlugin {
269    fn validate_document(&self, document: &PdfDocument) -> ValidationReport;
270    fn get_constraints(&self) -> Vec<Box<dyn SchemaConstraint>>;
271}
272
273/// Filter plugin for content processing
274pub trait FilterPlugin: AstPlugin {
275    fn filter_content(
276        &self,
277        content: &[u8],
278        filter_params: &HashMap<String, String>,
279    ) -> Result<Vec<u8>, String>;
280    fn get_supported_filters(&self) -> Vec<String>;
281}
282
283/// Analyzer plugin for extracting information
284pub trait AnalyzerPlugin: AstPlugin {
285    fn analyze_document(&self, document: &PdfDocument) -> AnalysisResult;
286    fn get_analysis_types(&self) -> Vec<String>;
287}
288
289/// Analysis result from analyzer plugins
290#[derive(Debug, Clone, Serialize, Deserialize)]
291pub struct AnalysisResult {
292    pub analyzer_name: String,
293    pub analysis_type: String,
294    pub timestamp: chrono::DateTime<chrono::Utc>,
295    pub data: serde_json::Value,
296    pub metadata: HashMap<String, String>,
297}
298
299impl AnalysisResult {
300    pub fn new(analyzer_name: &str, analysis_type: &str, data: serde_json::Value) -> Self {
301        Self {
302            analyzer_name: analyzer_name.to_string(),
303            analysis_type: analysis_type.to_string(),
304            timestamp: chrono::Utc::now(),
305            data,
306            metadata: HashMap::new(),
307        }
308    }
309}
310
311/// Plugin execution pipeline
312pub struct PluginPipeline {
313    plugins: Vec<Box<dyn AstPlugin>>,
314    context: PluginContext,
315    parallel_execution: bool,
316}
317
318impl PluginPipeline {
319    pub fn new() -> Self {
320        Self {
321            plugins: Vec::new(),
322            context: PluginContext::new(),
323            parallel_execution: false,
324        }
325    }
326
327    pub fn add_plugin(&mut self, plugin: Box<dyn AstPlugin>) {
328        self.plugins.push(plugin);
329    }
330
331    pub fn with_parallel_execution(mut self, parallel: bool) -> Self {
332        self.parallel_execution = parallel;
333        self
334    }
335
336    pub fn execute(&mut self, document: &mut PdfDocument) -> Vec<PluginResult> {
337        let mut results = Vec::new();
338
339        // Initialize context
340        self.context = PluginContext::new()
341            .with_document(document)
342            .with_graph(&mut document.ast);
343
344        // Initialize all plugins
345        for plugin in &mut self.plugins {
346            let result = plugin.initialize(&mut self.context);
347            results.push(result);
348        }
349
350        // Process document with each plugin
351        if self.parallel_execution {
352            // Parallel execution would require Arc<Mutex<>> or similar
353            // For now, execute sequentially
354            self.execute_sequential(document, &mut results);
355        } else {
356            self.execute_sequential(document, &mut results);
357        }
358
359        // Finalize all plugins
360        for plugin in &mut self.plugins {
361            let result = plugin.finalize(&mut self.context);
362            results.push(result);
363        }
364
365        results
366    }
367
368    fn execute_sequential(&mut self, document: &mut PdfDocument, results: &mut Vec<PluginResult>) {
369        for plugin in &self.plugins {
370            // Process entire document
371            let doc_result = plugin.process_document(document, &mut self.context);
372            results.push(doc_result);
373
374            // Process individual nodes if plugin supports it
375            if plugin.capabilities().can_modify_nodes {
376                let node_ids: Vec<NodeId> =
377                    document.ast.get_all_nodes().iter().map(|n| n.id).collect();
378
379                for node_id in node_ids {
380                    if let Some(mut node) = document.ast.get_node(node_id).cloned() {
381                        if plugin.can_process_node_type(&node.node_type) {
382                            let node_result = plugin.process_node(&mut node, &mut self.context);
383                            results.push(node_result);
384
385                            // Update node in graph if modified
386                            if let Some(graph_node) = document.ast.get_node_mut(node_id) {
387                                *graph_node = node;
388                            }
389                        }
390                    }
391                }
392            }
393        }
394    }
395
396    pub fn get_statistics(&self) -> &PluginStatistics {
397        &self.context.statistics
398    }
399}
400
401impl Default for PluginPipeline {
402    fn default() -> Self {
403        Self::new()
404    }
405}