agentic_workflow/template/
engine.rs1use std::collections::HashMap;
2
3use chrono::Utc;
4use uuid::Uuid;
5
6use crate::types::{
7 SharedWorkflow, TemplateParameter, WorkflowTemplate,
8 WorkflowError, WorkflowResult,
9};
10
11pub 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 pub fn register(&mut self, template: WorkflowTemplate) -> WorkflowResult<()> {
27 self.templates.insert(template.id.clone(), template);
28 Ok(())
29 }
30
31 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 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 for param in &template.parameters {
76 if param.required && !params.contains_key(¶m.name) && param.default.is_none() {
77 return Err(WorkflowError::Internal(format!(
78 "Missing required parameter: {}",
79 param.name
80 )));
81 }
82 }
83
84 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 for param in &template.parameters {
99 if !params.contains_key(¶m.name) {
100 if let Some(default) = ¶m.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 pub fn list_templates(&self) -> Vec<&WorkflowTemplate> {
119 self.templates.values().collect()
120 }
121
122 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 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 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 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}