plexus_substrate/activations/mustache/
activation.rs1use super::storage::{MustacheStorage, MustacheStorageConfig};
8use super::types::MustacheEvent;
9use async_stream::stream;
10use futures::Stream;
11use serde_json::Value;
12use std::sync::Arc;
13use uuid::Uuid;
14
15pub struct Mustache {
17 storage: Arc<MustacheStorage>,
18}
19
20impl Mustache {
21 pub async fn new(config: MustacheStorageConfig) -> Result<Self, String> {
23 let storage = MustacheStorage::new(config)
24 .await
25 .map_err(|e| format!("Failed to initialize mustache storage: {}", e))?;
26
27 Ok(Self {
28 storage: Arc::new(storage),
29 })
30 }
31
32 pub async fn with_defaults() -> Result<Self, String> {
34 Self::new(MustacheStorageConfig::default()).await
35 }
36
37 pub fn storage(&self) -> &MustacheStorage {
39 &self.storage
40 }
41
42 pub async fn register_template_direct(
47 &self,
48 plugin_id: Uuid,
49 method: &str,
50 name: &str,
51 template: &str,
52 ) -> Result<(), String> {
53 if let Err(e) = mustache::compile_str(template) {
55 return Err(format!("Invalid template: {}", e));
56 }
57
58 self.storage
59 .set_template(&plugin_id, method, name, template)
60 .await
61 .map_err(|e| format!("Failed to register template: {}", e))?;
62
63 Ok(())
64 }
65
66 pub async fn register_templates(
71 &self,
72 plugin_id: Uuid,
73 templates: &[(&str, &str, &str)],
74 ) -> Result<(), String> {
75 for (method, name, template) in templates {
76 self.register_template_direct(plugin_id, method, name, template).await?;
77 }
78 Ok(())
79 }
80}
81
82impl Clone for Mustache {
83 fn clone(&self) -> Self {
84 Self {
85 storage: Arc::clone(&self.storage),
86 }
87 }
88}
89
90#[plexus_macros::activation(namespace = "mustache",
92version = "1.0.0",
93description = "Mustache template rendering for handle values", crate_path = "plexus_core")]
94impl Mustache {
95 #[plexus_macros::method(description = "Render a value using a registered mustache template",
101 params(
102 plugin_id = "UUID of the plugin that owns the template",
103 method = "Method name the template is for",
104 template_name = "Template name (defaults to 'default' if not specified)",
105 value = "JSON value to render with the template"
106 ))]
107 async fn render(
108 &self,
109 plugin_id: Uuid,
110 method: String,
111 template_name: Option<String>,
112 value: Value,
113 ) -> impl Stream<Item = MustacheEvent> + Send + 'static {
114 let storage = Arc::clone(&self.storage);
115 let name = template_name.unwrap_or_else(|| "default".to_string());
116
117 stream! {
118 match storage.get_template(&plugin_id, &method, &name).await {
120 Ok(Some(template_str)) => {
121 match mustache::compile_str(&template_str) {
123 Ok(template) => {
124 let mut output = Vec::new();
125 match template.render(&mut output, &value) {
126 Ok(()) => {
127 match String::from_utf8(output) {
128 Ok(rendered) => {
129 yield MustacheEvent::Rendered { output: rendered };
130 }
131 Err(e) => {
132 yield MustacheEvent::Error {
133 message: format!("UTF-8 conversion error: {}", e),
134 };
135 }
136 }
137 }
138 Err(e) => {
139 yield MustacheEvent::Error {
140 message: format!("Template render error: {}", e),
141 };
142 }
143 }
144 }
145 Err(e) => {
146 yield MustacheEvent::Error {
147 message: format!("Template compile error: {}", e),
148 };
149 }
150 }
151 }
152 Ok(None) => {
153 yield MustacheEvent::NotFound {
154 message: format!(
155 "Template not found: plugin={}, method={}, name={}",
156 plugin_id, method, name
157 ),
158 };
159 }
160 Err(e) => {
161 yield MustacheEvent::Error {
162 message: format!("Storage error: {}", e),
163 };
164 }
165 }
166 }
167 }
168
169 #[plexus_macros::method(description = "Register a mustache template for a plugin method",
174 params(
175 plugin_id = "UUID of the plugin registering the template",
176 method = "Method name this template is for",
177 name = "Template name (e.g., 'default', 'compact', 'verbose')",
178 template = "Mustache template content"
179 ))]
180 async fn register_template(
181 &self,
182 plugin_id: Uuid,
183 method: String,
184 name: String,
185 template: String,
186 ) -> impl Stream<Item = MustacheEvent> + Send + 'static {
187 let storage = Arc::clone(&self.storage);
188
189 stream! {
190 if let Err(e) = mustache::compile_str(&template) {
192 yield MustacheEvent::Error {
193 message: format!("Invalid template: {}", e),
194 };
195 return;
196 }
197
198 match storage.set_template(&plugin_id, &method, &name, &template).await {
199 Ok(info) => {
200 yield MustacheEvent::Registered { template: info };
201 }
202 Err(e) => {
203 yield MustacheEvent::Error {
204 message: format!("Failed to register template: {}", e),
205 };
206 }
207 }
208 }
209 }
210
211 #[plexus_macros::method(description = "List all templates registered for a plugin",
213 params(plugin_id = "UUID of the plugin to list templates for"))]
214 async fn list_templates(
215 &self,
216 plugin_id: Uuid,
217 ) -> impl Stream<Item = MustacheEvent> + Send + 'static {
218 let storage = Arc::clone(&self.storage);
219
220 stream! {
221 match storage.list_templates(&plugin_id).await {
222 Ok(templates) => {
223 yield MustacheEvent::Templates { templates };
224 }
225 Err(e) => {
226 yield MustacheEvent::Error {
227 message: format!("Failed to list templates: {}", e),
228 };
229 }
230 }
231 }
232 }
233
234 #[plexus_macros::method(description = "Get a specific template by plugin, method, and name",
236 params(
237 plugin_id = "UUID of the plugin that owns the template",
238 method = "Method name the template is for",
239 name = "Template name"
240 ))]
241 async fn get_template(
242 &self,
243 plugin_id: Uuid,
244 method: String,
245 name: String,
246 ) -> impl Stream<Item = MustacheEvent> + Send + 'static {
247 let storage = Arc::clone(&self.storage);
248
249 stream! {
250 match storage.get_template(&plugin_id, &method, &name).await {
251 Ok(Some(template)) => {
252 yield MustacheEvent::Template { template };
253 }
254 Ok(None) => {
255 yield MustacheEvent::NotFound {
256 message: format!(
257 "Template not found: plugin={}, method={}, name={}",
258 plugin_id, method, name
259 ),
260 };
261 }
262 Err(e) => {
263 yield MustacheEvent::Error {
264 message: format!("Failed to get template: {}", e),
265 };
266 }
267 }
268 }
269 }
270
271 #[plexus_macros::method(description = "Delete a specific template",
273 params(
274 plugin_id = "UUID of the plugin that owns the template",
275 method = "Method name the template is for",
276 name = "Template name"
277 ))]
278 async fn delete_template(
279 &self,
280 plugin_id: Uuid,
281 method: String,
282 name: String,
283 ) -> impl Stream<Item = MustacheEvent> + Send + 'static {
284 let storage = Arc::clone(&self.storage);
285
286 stream! {
287 match storage.delete_template(&plugin_id, &method, &name).await {
288 Ok(deleted) => {
289 yield MustacheEvent::Deleted {
290 count: if deleted { 1 } else { 0 },
291 };
292 }
293 Err(e) => {
294 yield MustacheEvent::Error {
295 message: format!("Failed to delete template: {}", e),
296 };
297 }
298 }
299 }
300 }
301}