Skip to main content

agentic_workflow/template/
engine.rs

1use std::collections::HashMap;
2
3use chrono::Utc;
4use uuid::Uuid;
5
6use crate::types::{
7    SharedWorkflow, TemplateParameter, WorkflowTemplate,
8    WorkflowError, WorkflowResult,
9};
10
11/// Template engine — parameterized, reusable workflow patterns.
12pub struct TemplateEngine {
13    templates: HashMap<String, WorkflowTemplate>,
14    shared: Vec<SharedWorkflow>,
15}
16
17impl TemplateEngine {
18    pub fn new() -> Self {
19        Self {
20            templates: HashMap::new(),
21            shared: Vec::new(),
22        }
23    }
24
25    /// Register a template.
26    pub fn register(&mut self, template: WorkflowTemplate) -> WorkflowResult<()> {
27        self.templates.insert(template.id.clone(), template);
28        Ok(())
29    }
30
31    /// Create a template from a workflow definition.
32    pub fn create_template(
33        &mut self,
34        name: &str,
35        description: &str,
36        parameters: Vec<TemplateParameter>,
37        workflow_definition: serde_json::Value,
38        tags: Vec<String>,
39        author: &str,
40    ) -> WorkflowResult<String> {
41        let id = Uuid::new_v4().to_string();
42        let now = Utc::now();
43
44        let template = WorkflowTemplate {
45            id: id.clone(),
46            name: name.to_string(),
47            description: description.to_string(),
48            version: "1.0.0".to_string(),
49            parameters,
50            workflow_definition,
51            tags,
52            author: author.to_string(),
53            created_at: now,
54            updated_at: now,
55            rating: None,
56            usage_count: 0,
57        };
58
59        self.templates.insert(id.clone(), template);
60        Ok(id)
61    }
62
63    /// Instantiate a workflow from a template with parameters.
64    pub fn instantiate(
65        &mut self,
66        template_id: &str,
67        params: &HashMap<String, serde_json::Value>,
68    ) -> WorkflowResult<serde_json::Value> {
69        let template = self
70            .templates
71            .get_mut(template_id)
72            .ok_or_else(|| WorkflowError::TemplateNotFound(template_id.to_string()))?;
73
74        // Validate required parameters
75        for param in &template.parameters {
76            if param.required && !params.contains_key(&param.name) && param.default.is_none() {
77                return Err(WorkflowError::Internal(format!(
78                    "Missing required parameter: {}",
79                    param.name
80                )));
81            }
82        }
83
84        // Apply parameters to definition (simple string replacement)
85        let mut definition = serde_json::to_string(&template.workflow_definition)
86            .map_err(|e| WorkflowError::SerializationError(e.to_string()))?;
87
88        for (key, value) in params {
89            let placeholder = format!("{{{{{}}}}}", key);
90            let replacement = match value {
91                serde_json::Value::String(s) => s.clone(),
92                other => other.to_string(),
93            };
94            definition = definition.replace(&placeholder, &replacement);
95        }
96
97        // Apply defaults for missing params
98        for param in &template.parameters {
99            if !params.contains_key(&param.name) {
100                if let Some(default) = &param.default {
101                    let placeholder = format!("{{{{{}}}}}", param.name);
102                    let replacement = match default {
103                        serde_json::Value::String(s) => s.clone(),
104                        other => other.to_string(),
105                    };
106                    definition = definition.replace(&placeholder, &replacement);
107                }
108            }
109        }
110
111        template.usage_count += 1;
112
113        serde_json::from_str(&definition)
114            .map_err(|e| WorkflowError::SerializationError(e.to_string()))
115    }
116
117    /// List available templates.
118    pub fn list_templates(&self) -> Vec<&WorkflowTemplate> {
119        self.templates.values().collect()
120    }
121
122    /// Search templates by tag.
123    pub fn search_by_tag(&self, tag: &str) -> Vec<&WorkflowTemplate> {
124        self.templates
125            .values()
126            .filter(|t| t.tags.iter().any(|tt| tt == tag))
127            .collect()
128    }
129
130    /// Get a template by ID.
131    pub fn get_template(&self, template_id: &str) -> WorkflowResult<&WorkflowTemplate> {
132        self.templates
133            .get(template_id)
134            .ok_or_else(|| WorkflowError::TemplateNotFound(template_id.to_string()))
135    }
136
137    /// Share a template.
138    pub fn share_template(
139        &mut self,
140        template_id: &str,
141        shared_by: &str,
142    ) -> WorkflowResult<String> {
143        if !self.templates.contains_key(template_id) {
144            return Err(WorkflowError::TemplateNotFound(template_id.to_string()));
145        }
146
147        let shared_id = Uuid::new_v4().to_string();
148        self.shared.push(SharedWorkflow {
149            id: shared_id.clone(),
150            template_id: template_id.to_string(),
151            shared_by: shared_by.to_string(),
152            shared_at: Utc::now(),
153            rating: 0.0,
154            download_count: 0,
155            privacy_verified: false,
156        });
157
158        Ok(shared_id)
159    }
160
161    /// List shared workflows.
162    pub fn list_shared(&self) -> &[SharedWorkflow] {
163        &self.shared
164    }
165}
166
167impl Default for TemplateEngine {
168    fn default() -> Self {
169        Self::new()
170    }
171}
172
173#[cfg(test)]
174mod tests {
175    use super::*;
176
177    #[test]
178    fn test_template_instantiation() {
179        let mut engine = TemplateEngine::new();
180
181        let params = vec![TemplateParameter {
182            name: "app_name".to_string(),
183            description: "Application name".to_string(),
184            param_type: crate::types::template::ParameterType::String,
185            required: true,
186            default: None,
187            validation: None,
188        }];
189
190        let tid = engine
191            .create_template(
192                "deploy",
193                "Deploy an app",
194                params,
195                serde_json::json!({"app": "{{app_name}}", "action": "deploy"}),
196                vec!["deployment".to_string()],
197                "team",
198            )
199            .unwrap();
200
201        let mut p = HashMap::new();
202        p.insert("app_name".to_string(), serde_json::json!("my-service"));
203
204        let result = engine.instantiate(&tid, &p).unwrap();
205        assert_eq!(result["app"], "my-service");
206    }
207}