mockforge_plugin_core/
template.rs

1//! Template plugin interface
2//!
3//! This module defines the TemplatePlugin trait and related types for implementing
4//! custom template functions and data generators in MockForge. Template plugins
5//! extend the templating system with custom functions, filters, and data sources.
6
7use crate::{PluginCapabilities, PluginContext, PluginResult, Result};
8use serde::{Deserialize, Serialize};
9use serde_json::Value;
10use std::collections::HashMap;
11
12/// Template plugin trait
13///
14/// Implement this trait to create custom template functions and data generators.
15/// Template plugins are called during template expansion to provide custom
16/// functions, filters, and data sources.
17#[async_trait::async_trait]
18pub trait TemplatePlugin: Send + Sync {
19    /// Get plugin capabilities (permissions and limits)
20    fn capabilities(&self) -> PluginCapabilities;
21
22    /// Initialize the plugin with configuration
23    async fn initialize(&self, config: &TemplatePluginConfig) -> Result<()>;
24
25    /// Register template functions
26    ///
27    /// This method is called during plugin initialization to register custom
28    /// template functions. The plugin should return a map of function names
29    /// to function metadata.
30    ///
31    /// # Arguments
32    /// * `context` - Plugin execution context
33    /// * `config` - Plugin configuration
34    ///
35    /// # Returns
36    /// Map of function names to function metadata
37    async fn register_functions(
38        &self,
39        context: &PluginContext,
40        config: &TemplatePluginConfig,
41    ) -> Result<PluginResult<HashMap<String, TemplateFunction>>>;
42
43    /// Execute a template function
44    ///
45    /// This method is called when a registered template function is invoked
46    /// during template expansion.
47    ///
48    /// # Arguments
49    /// * `context` - Plugin execution context
50    /// * `function_name` - Name of the function being called
51    /// * `args` - Function arguments
52    /// * `config` - Plugin configuration
53    ///
54    /// # Returns
55    /// Function execution result
56    async fn execute_function(
57        &self,
58        context: &PluginContext,
59        function_name: &str,
60        args: &[Value],
61        config: &TemplatePluginConfig,
62    ) -> Result<PluginResult<Value>>;
63
64    /// Provide data sources
65    ///
66    /// This method can be called to retrieve data that can be used in templates.
67    /// The plugin can provide dynamic data sources that are refreshed periodically.
68    ///
69    /// # Arguments
70    /// * `context` - Plugin execution context
71    /// * `data_source` - Name of the requested data source
72    /// * `config` - Plugin configuration
73    ///
74    /// # Returns
75    /// Data source content
76    async fn get_data_source(
77        &self,
78        context: &PluginContext,
79        data_source: &str,
80        config: &TemplatePluginConfig,
81    ) -> Result<PluginResult<Value>>;
82
83    /// Validate plugin configuration
84    fn validate_config(&self, config: &TemplatePluginConfig) -> Result<()>;
85
86    /// Get list of available data sources
87    fn available_data_sources(&self) -> Vec<String>;
88
89    /// Cleanup plugin resources
90    async fn cleanup(&self) -> Result<()>;
91}
92
93/// Template plugin configuration
94#[derive(Debug, Clone, Serialize, Deserialize)]
95pub struct TemplatePluginConfig {
96    /// Plugin-specific configuration
97    pub config: HashMap<String, serde_json::Value>,
98    /// Enable/disable the plugin
99    pub enabled: bool,
100    /// Function prefix (to avoid conflicts)
101    pub function_prefix: Option<String>,
102    /// Data source refresh interval in seconds
103    pub data_refresh_interval_secs: Option<u64>,
104    /// Custom settings
105    pub settings: HashMap<String, serde_json::Value>,
106}
107
108impl Default for TemplatePluginConfig {
109    fn default() -> Self {
110        Self {
111            config: HashMap::new(),
112            enabled: true,
113            function_prefix: None,
114            data_refresh_interval_secs: Some(300), // 5 minutes
115            settings: HashMap::new(),
116        }
117    }
118}
119
120/// Template function metadata
121#[derive(Debug, Clone, Serialize, Deserialize)]
122pub struct TemplateFunction {
123    /// Function name
124    pub name: String,
125    /// Function description
126    pub description: String,
127    /// Function parameters
128    pub parameters: Vec<FunctionParameter>,
129    /// Return type description
130    pub return_type: String,
131    /// Examples of usage
132    pub examples: Vec<String>,
133    /// Function category/tag
134    pub category: Option<String>,
135    /// Whether function is pure (same inputs = same outputs)
136    pub pure: bool,
137}
138
139impl TemplateFunction {
140    /// Create a new template function
141    pub fn new<S: Into<String>>(name: S, description: S, return_type: S) -> Self {
142        Self {
143            name: name.into(),
144            description: description.into(),
145            parameters: Vec::new(),
146            return_type: return_type.into(),
147            examples: Vec::new(),
148            category: None,
149            pure: true,
150        }
151    }
152
153    /// Add a parameter
154    pub fn with_parameter(mut self, param: FunctionParameter) -> Self {
155        self.parameters.push(param);
156        self
157    }
158
159    /// Add multiple parameters
160    pub fn with_parameters(mut self, params: Vec<FunctionParameter>) -> Self {
161        self.parameters.extend(params);
162        self
163    }
164
165    /// Add an example
166    pub fn with_example<S: Into<String>>(mut self, example: S) -> Self {
167        self.examples.push(example.into());
168        self
169    }
170
171    /// Set category
172    pub fn with_category<S: Into<String>>(mut self, category: S) -> Self {
173        self.category = Some(category.into());
174        self
175    }
176
177    /// Mark as impure
178    pub fn impure(mut self) -> Self {
179        self.pure = false;
180        self
181    }
182
183    /// Get parameter count
184    pub fn param_count(&self) -> usize {
185        self.parameters.len()
186    }
187
188    /// Check if function has variable arguments
189    pub fn has_var_args(&self) -> bool {
190        self.parameters.iter().any(|p| p.var_args)
191    }
192}
193
194/// Function parameter definition
195#[derive(Debug, Clone, Serialize, Deserialize)]
196pub struct FunctionParameter {
197    /// Parameter name
198    pub name: String,
199    /// Parameter type
200    pub param_type: String,
201    /// Parameter description
202    pub description: String,
203    /// Whether parameter is required
204    pub required: bool,
205    /// Default value (if optional)
206    pub default_value: Option<Value>,
207    /// Whether this parameter accepts variable arguments
208    pub var_args: bool,
209}
210
211impl FunctionParameter {
212    /// Create a required parameter
213    pub fn required<S: Into<String>>(name: S, param_type: S, description: S) -> Self {
214        Self {
215            name: name.into(),
216            param_type: param_type.into(),
217            description: description.into(),
218            required: true,
219            default_value: None,
220            var_args: false,
221        }
222    }
223
224    /// Create an optional parameter
225    pub fn optional<S: Into<String>>(name: S, param_type: S, description: S) -> Self {
226        Self {
227            name: name.into(),
228            param_type: param_type.into(),
229            description: description.into(),
230            required: false,
231            default_value: None,
232            var_args: false,
233        }
234    }
235
236    /// Create an optional parameter with default value
237    pub fn with_default<S: Into<String>>(
238        name: S,
239        param_type: S,
240        description: S,
241        default: Value,
242    ) -> Self {
243        Self {
244            name: name.into(),
245            param_type: param_type.into(),
246            description: description.into(),
247            required: false,
248            default_value: Some(default),
249            var_args: false,
250        }
251    }
252
253    /// Create a variable arguments parameter
254    pub fn var_args<S: Into<String>>(name: S, param_type: S, description: S) -> Self {
255        Self {
256            name: name.into(),
257            param_type: param_type.into(),
258            description: description.into(),
259            required: false,
260            default_value: None,
261            var_args: true,
262        }
263    }
264}
265
266/// Template execution context
267#[derive(Debug, Clone)]
268pub struct TemplateExecutionContext {
269    /// Template being processed
270    pub template: String,
271    /// Current position in template
272    pub position: usize,
273    /// Available variables
274    pub variables: HashMap<String, Value>,
275    /// Request context (if available)
276    pub request_context: Option<HashMap<String, Value>>,
277    /// Custom context data
278    pub custom: HashMap<String, Value>,
279}
280
281impl TemplateExecutionContext {
282    /// Create new execution context
283    pub fn new<S: Into<String>>(template: S) -> Self {
284        Self {
285            template: template.into(),
286            position: 0,
287            variables: HashMap::new(),
288            request_context: None,
289            custom: HashMap::new(),
290        }
291    }
292
293    /// Set position in template
294    pub fn with_position(mut self, position: usize) -> Self {
295        self.position = position;
296        self
297    }
298
299    /// Add variable
300    pub fn with_variable<S: Into<String>>(mut self, key: S, value: Value) -> Self {
301        self.variables.insert(key.into(), value);
302        self
303    }
304
305    /// Add request context
306    pub fn with_request_context(mut self, context: HashMap<String, Value>) -> Self {
307        self.request_context = Some(context);
308        self
309    }
310
311    /// Add custom data
312    pub fn with_custom<S: Into<String>>(mut self, key: S, value: Value) -> Self {
313        self.custom.insert(key.into(), value);
314        self
315    }
316
317    /// Get variable value
318    pub fn get_variable(&self, key: &str) -> Option<&Value> {
319        self.variables.get(key)
320    }
321
322    /// Get request context value
323    pub fn get_request_value(&self, key: &str) -> Option<&Value> {
324        self.request_context.as_ref()?.get(key)
325    }
326
327    /// Get custom value
328    pub fn get_custom_value(&self, key: &str) -> Option<&Value> {
329        self.custom.get(key)
330    }
331}
332
333/// Template function registry entry
334pub struct TemplateFunctionEntry {
335    /// Plugin ID that provides this function
336    pub plugin_id: crate::PluginId,
337    /// Function metadata
338    pub function: TemplateFunction,
339    /// Plugin instance
340    pub plugin: std::sync::Arc<dyn TemplatePlugin>,
341    /// Function configuration
342    pub config: TemplatePluginConfig,
343}
344
345impl TemplateFunctionEntry {
346    /// Create new function entry
347    pub fn new(
348        plugin_id: crate::PluginId,
349        function: TemplateFunction,
350        plugin: std::sync::Arc<dyn TemplatePlugin>,
351        config: TemplatePluginConfig,
352    ) -> Self {
353        Self {
354            plugin_id,
355            function,
356            plugin,
357            config,
358        }
359    }
360
361    /// Get full function name (with prefix if configured)
362    pub fn full_name(&self) -> String {
363        if let Some(prefix) = &self.config.function_prefix {
364            format!("{}_{}", prefix, self.function.name)
365        } else {
366            self.function.name.clone()
367        }
368    }
369
370    /// Check if function is enabled
371    pub fn is_enabled(&self) -> bool {
372        self.config.enabled
373    }
374}
375
376/// Helper trait for creating template plugins
377pub trait TemplatePluginFactory: Send + Sync {
378    /// Create a new template plugin instance
379    fn create_plugin(&self) -> Result<Box<dyn TemplatePlugin>>;
380}
381
382/// Built-in template functions that plugins can use as helpers
383pub mod builtin {
384    use super::*;
385
386    /// Generate a random UUID
387    pub fn uuid_v4() -> String {
388        uuid::Uuid::new_v4().to_string()
389    }
390
391    /// Get current timestamp in RFC3339 format
392    pub fn now_rfc3339() -> String {
393        chrono::Utc::now().to_rfc3339()
394    }
395
396    /// Generate random integer in range
397    pub fn random_int(min: i64, max: i64) -> i64 {
398        use rand::Rng;
399        rand::rng().random_range(min..=max)
400    }
401
402    /// Generate random float between 0 and 1
403    pub fn random_float() -> f64 {
404        rand::random::<f64>()
405    }
406
407    /// URL encode a string
408    pub fn url_encode(input: &str) -> String {
409        urlencoding::encode(input).to_string()
410    }
411
412    /// URL decode a string
413    pub fn url_decode(input: &str) -> Result<String> {
414        urlencoding::decode(input)
415            .map(|s| s.to_string())
416            .map_err(|e| crate::PluginError::execution(format!("URL decode error: {}", e)))
417    }
418
419    /// JSON stringify a value
420    pub fn json_stringify(value: &Value) -> Result<String> {
421        serde_json::to_string(value)
422            .map_err(|e| crate::PluginError::execution(format!("JSON stringify error: {}", e)))
423    }
424
425    /// JSON parse a string
426    pub fn json_parse(input: &str) -> Result<Value> {
427        serde_json::from_str(input)
428            .map_err(|e| crate::PluginError::execution(format!("JSON parse error: {}", e)))
429    }
430}
431
432#[cfg(test)]
433mod tests {
434
435    #[test]
436    fn test_module_compiles() {
437        // Basic compilation test
438    }
439}