use crate::{PluginCapabilities, PluginContext, PluginResult, Result};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
#[async_trait::async_trait]
pub trait TemplatePlugin: Send + Sync {
fn capabilities(&self) -> PluginCapabilities;
async fn initialize(&self, config: &TemplatePluginConfig) -> Result<()>;
async fn register_functions(
&self,
context: &PluginContext,
config: &TemplatePluginConfig,
) -> Result<PluginResult<HashMap<String, TemplateFunction>>>;
async fn execute_function(
&self,
context: &PluginContext,
function_name: &str,
args: &[Value],
config: &TemplatePluginConfig,
) -> Result<PluginResult<Value>>;
async fn get_data_source(
&self,
context: &PluginContext,
data_source: &str,
config: &TemplatePluginConfig,
) -> Result<PluginResult<Value>>;
fn validate_config(&self, config: &TemplatePluginConfig) -> Result<()>;
fn available_data_sources(&self) -> Vec<String>;
async fn cleanup(&self) -> Result<()>;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TemplatePluginConfig {
pub config: HashMap<String, Value>,
pub enabled: bool,
pub function_prefix: Option<String>,
pub data_refresh_interval_secs: Option<u64>,
pub settings: HashMap<String, Value>,
}
impl Default for TemplatePluginConfig {
fn default() -> Self {
Self {
config: HashMap::new(),
enabled: true,
function_prefix: None,
data_refresh_interval_secs: Some(300), settings: HashMap::new(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TemplateFunction {
pub name: String,
pub description: String,
pub parameters: Vec<FunctionParameter>,
pub return_type: String,
pub examples: Vec<String>,
pub category: Option<String>,
pub pure: bool,
}
impl TemplateFunction {
pub fn new<S: Into<String>>(name: S, description: S, return_type: S) -> Self {
Self {
name: name.into(),
description: description.into(),
parameters: Vec::new(),
return_type: return_type.into(),
examples: Vec::new(),
category: None,
pure: true,
}
}
pub fn with_parameter(mut self, param: FunctionParameter) -> Self {
self.parameters.push(param);
self
}
pub fn with_parameters(mut self, params: Vec<FunctionParameter>) -> Self {
self.parameters.extend(params);
self
}
pub fn with_example<S: Into<String>>(mut self, example: S) -> Self {
self.examples.push(example.into());
self
}
pub fn with_category<S: Into<String>>(mut self, category: S) -> Self {
self.category = Some(category.into());
self
}
pub fn impure(mut self) -> Self {
self.pure = false;
self
}
pub fn param_count(&self) -> usize {
self.parameters.len()
}
pub fn has_var_args(&self) -> bool {
self.parameters.iter().any(|p| p.var_args)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FunctionParameter {
pub name: String,
pub param_type: String,
pub description: String,
pub required: bool,
pub default_value: Option<Value>,
pub var_args: bool,
}
impl FunctionParameter {
pub fn required<S: Into<String>>(name: S, param_type: S, description: S) -> Self {
Self {
name: name.into(),
param_type: param_type.into(),
description: description.into(),
required: true,
default_value: None,
var_args: false,
}
}
pub fn optional<S: Into<String>>(name: S, param_type: S, description: S) -> Self {
Self {
name: name.into(),
param_type: param_type.into(),
description: description.into(),
required: false,
default_value: None,
var_args: false,
}
}
pub fn with_default<S: Into<String>>(
name: S,
param_type: S,
description: S,
default: Value,
) -> Self {
Self {
name: name.into(),
param_type: param_type.into(),
description: description.into(),
required: false,
default_value: Some(default),
var_args: false,
}
}
pub fn var_args<S: Into<String>>(name: S, param_type: S, description: S) -> Self {
Self {
name: name.into(),
param_type: param_type.into(),
description: description.into(),
required: false,
default_value: None,
var_args: true,
}
}
}
#[derive(Debug, Clone)]
pub struct TemplateExecutionContext {
pub template: String,
pub position: usize,
pub variables: HashMap<String, Value>,
pub request_context: Option<HashMap<String, Value>>,
pub custom: HashMap<String, Value>,
}
impl TemplateExecutionContext {
pub fn new<S: Into<String>>(template: S) -> Self {
Self {
template: template.into(),
position: 0,
variables: HashMap::new(),
request_context: None,
custom: HashMap::new(),
}
}
pub fn with_position(mut self, position: usize) -> Self {
self.position = position;
self
}
pub fn with_variable<S: Into<String>>(mut self, key: S, value: Value) -> Self {
self.variables.insert(key.into(), value);
self
}
pub fn with_request_context(mut self, context: HashMap<String, Value>) -> Self {
self.request_context = Some(context);
self
}
pub fn with_custom<S: Into<String>>(mut self, key: S, value: Value) -> Self {
self.custom.insert(key.into(), value);
self
}
pub fn get_variable(&self, key: &str) -> Option<&Value> {
self.variables.get(key)
}
pub fn get_request_value(&self, key: &str) -> Option<&Value> {
self.request_context.as_ref()?.get(key)
}
pub fn get_custom_value(&self, key: &str) -> Option<&Value> {
self.custom.get(key)
}
}
pub struct TemplateFunctionEntry {
pub plugin_id: crate::PluginId,
pub function: TemplateFunction,
pub plugin: std::sync::Arc<dyn TemplatePlugin>,
pub config: TemplatePluginConfig,
}
impl TemplateFunctionEntry {
pub fn new(
plugin_id: crate::PluginId,
function: TemplateFunction,
plugin: std::sync::Arc<dyn TemplatePlugin>,
config: TemplatePluginConfig,
) -> Self {
Self {
plugin_id,
function,
plugin,
config,
}
}
pub fn full_name(&self) -> String {
if let Some(prefix) = &self.config.function_prefix {
format!("{}_{}", prefix, self.function.name)
} else {
self.function.name.clone()
}
}
pub fn is_enabled(&self) -> bool {
self.config.enabled
}
}
pub trait TemplatePluginFactory: Send + Sync {
fn create_plugin(&self) -> Result<Box<dyn TemplatePlugin>>;
}
pub mod builtin {
use super::*;
pub fn uuid_v4() -> String {
uuid::Uuid::new_v4().to_string()
}
pub fn now_rfc3339() -> String {
chrono::Utc::now().to_rfc3339()
}
pub fn random_int(min: i64, max: i64) -> i64 {
use rand::Rng;
rand::rng().random_range(min..=max)
}
pub fn random_float() -> f64 {
rand::random::<f64>()
}
pub fn url_encode(input: &str) -> String {
urlencoding::encode(input).to_string()
}
pub fn url_decode(input: &str) -> Result<String> {
urlencoding::decode(input)
.map(|s| s.to_string())
.map_err(|e| crate::PluginError::execution(format!("URL decode error: {}", e)))
}
pub fn json_stringify(value: &Value) -> Result<String> {
serde_json::to_string(value)
.map_err(|e| crate::PluginError::execution(format!("JSON stringify error: {}", e)))
}
pub fn json_parse(input: &str) -> Result<Value> {
serde_json::from_str(input)
.map_err(|e| crate::PluginError::execution(format!("JSON parse error: {}", e)))
}
}
#[cfg(test)]
mod tests {
#[test]
fn test_module_compiles() {
}
}