use crate::config::Config;
use crate::token_resolvers::*;
use mockforge_plugin_core::{TokenResolver, PluginMetadata};
use std::sync::Arc;
pub struct PluginTemplateEngine {
base_engine: crate::templating::TemplateEngine,
plugin_resolver: Arc<PluginTokenResolver>,
integration: Arc<TemplatePluginIntegration>,
}
impl PluginTemplateEngine {
pub fn new(config: &Config) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
let base_engine = crate::templating::TemplateEngine::new(config.clone())?;
let plugin_resolver = Arc::new(PluginTokenResolver::new(Arc::new(config.clone())));
let integration = Arc::new(TemplatePluginIntegration::new(Arc::clone(&plugin_resolver)));
Ok(Self {
base_engine,
plugin_resolver,
integration,
})
}
pub async fn register_plugin(&self, name: &str, resolver: Arc<dyn TokenResolver>) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
self.plugin_resolver.register_resolver(name, resolver).await.map_err(|e| e.into())
}
pub async fn unregister_plugin(&self, name: &str) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
self.plugin_resolver.unregister_resolver(name).await.map_err(|e| e.into())
}
pub async fn process_template(&self, template: &str) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
let context = mockforge_plugin_core::ResolutionContext::new();
self.integration.process_template(template, &context).await.map_err(|e| e.into())
}
pub async fn process_template_with_context(
&self,
template: &str,
context: &mockforge_plugin_core::ResolutionContext,
) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
self.integration.process_template(template, context).await.map_err(|e| e.into())
}
pub async fn extract_resolvable_tokens(&self, template: &str) -> Vec<String> {
self.integration.extract_tokens(template).await
}
pub async fn load_standard_resolvers(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let factory = TokenResolverFactory;
let standard_resolvers = presets::create_standard_resolvers().await;
for (name, resolver) in standard_resolvers {
self.register_plugin(&name, resolver).await?;
}
crate::tracing::info!("Loaded {} standard token resolver plugins", name);
Ok(())
}
pub async fn get_plugin_metadata(&self) -> std::collections::HashMap<String, PluginMetadata> {
self.plugin_resolver.get_resolver_metadata().await
}
pub async fn list_plugins(&self) -> Vec<String> {
self.plugin_resolver.list_resolvers().await
}
pub async fn get_resolution_stats(&self) -> std::collections::HashMap<String, usize> {
self.integration.get_resolution_stats().await
}
}
impl std::fmt::Debug for PluginTemplateEngine {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PluginTemplateEngine")
.field("base_engine", &"TemplateEngine")
.field("plugin_count", &self.plugin_resolver.list_resolvers())
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::token_resolvers::TokenResolverFactory;
use mockforge_plugin_core::{ResolutionContext, TokenResolver};
#[tokio::test]
async fn test_plugin_template_engine_creation() {
let config = &Config::default();
let engine = PluginTemplateEngine::new(config).unwrap();
assert!(engine.list_plugins().await.is_empty());
}
#[tokio::test]
async fn test_plugin_registration() {
let config = &Config::default();
let engine = PluginTemplateEngine::new(config).unwrap();
let resolver = TokenResolverFactory::create_env_resolver().unwrap();
engine.register_plugin("test-env", resolver).await.unwrap();
let plugins = engine.list_plugins().await;
assert_eq!(plugins.len(), 1);
assert!(plugins.contains(&"test-env".to_string()));
}
#[tokio::test]
async fn test_template_processing_with_plugins() {
let config = &Config::default();
let engine = PluginTemplateEngine::new(config).unwrap();
engine.load_standard_resolvers().await.unwrap();
let template = "Current time is {{time:iso8601}} and UUID is {{uuid}}";
let result = engine.process_template(template).await.unwrap();
assert!(!result.contains("{{time:iso8601}}"));
assert!(!result.contains("{{uuid}}"));
assert!(result.contains("Current time is"));
}
#[tokio::test]
async fn test_token_extraction() {
let config = &Config::default();
let engine = PluginTemplateEngine::new(config).unwrap();
engine.load_standard_resolvers().await.unwrap();
let template = "Hello {{time:iso8601}} and {{uuid:v4}} with {{unknown:token}}";
let tokens = engine.extract_resolvable_tokens(template).await;
assert!(tokens.contains(&"{{time:iso8601}}".to_string()));
assert!(tokens.contains(&"{{uuid:v4}}".to_string()));
assert!(!tokens.contains(&"{{unknown:token}}".to_string()));
}
#[tokio::test]
async fn test_custom_context_processing() {
let config = &Config::default();
let engine = PluginTemplateEngine::new(config).unwrap();
engine.register_plugin("uuid-resolver", TokenResolverFactory::create_uuid_resolver()).await.unwrap();
let context = ResolutionContext::new().with_request(
mockforge_plugin_core::RequestMetadata::new("GET", "/api/test")
.with_header("User-Agent", "TestAgent")
);
let template = "Processing {{uuid}} for {{env:HOME}}";
let result = engine.process_template_with_context(template, &context).await.unwrap();
assert!(!result.contains("{{uuid}}"));
println!("Resolution result: {}", result);
}
}