1use crate::context::TemplateContext;
10use crate::error::{Result, TemplateError};
11use crate::renderer::{OutputFormat, TemplateRenderer};
12use serde_json::Value;
13use std::collections::HashMap;
14use std::path::{Path, PathBuf};
15
16pub struct AsyncTemplateRenderer {
20 renderer: TemplateRenderer,
22}
23
24impl AsyncTemplateRenderer {
25 pub async fn new() -> Result<Self> {
27 let renderer = TemplateRenderer::new()?;
28 Ok(Self { renderer })
29 }
30
31 pub async fn with_defaults() -> Result<Self> {
33 let renderer = TemplateRenderer::with_defaults()?;
34 Ok(Self { renderer })
35 }
36
37 pub fn with_context(mut self, context: TemplateContext) -> Self {
39 self.renderer = self.renderer.with_context(context);
40 self
41 }
42
43 pub async fn render_str(&mut self, template: &str, name: &str) -> Result<String> {
49 let template = template.to_string();
52 let name = name.to_string();
53 let mut renderer = self.renderer.clone();
54 tokio::task::spawn_blocking(move || renderer.render_str(&template, &name))
55 .await
56 .map_err(|e| TemplateError::InternalError(format!("Async rendering failed: {}", e)))?
57 }
58
59 pub async fn render_to_format(
66 &mut self,
67 template: &str,
68 name: &str,
69 format: OutputFormat,
70 ) -> Result<String> {
71 let rendered = self.render_str(template, name).await?;
72
73 match format {
74 OutputFormat::Toml => Ok(rendered),
75 OutputFormat::Json => crate::simple::convert_to_json(&rendered),
76 OutputFormat::Yaml => crate::simple::convert_to_yaml(&rendered),
77 OutputFormat::Plain => crate::simple::strip_template_syntax(&rendered),
78 }
79 }
80
81 pub async fn render_file<P: AsRef<Path>>(&mut self, path: P) -> Result<String> {
86 let path = path.as_ref().to_path_buf();
87 tokio::task::spawn_blocking(move || {
88 let mut renderer = TemplateRenderer::new()?;
89 renderer.render_file(&path)
90 })
91 .await
92 .map_err(|e| TemplateError::InternalError(format!("Async file rendering failed: {}", e)))?
93 }
94
95 pub fn merge_user_vars(&mut self, user_vars: HashMap<String, Value>) {
97 self.renderer.merge_user_vars(user_vars);
98 }
99
100 pub fn renderer(&self) -> &TemplateRenderer {
102 &self.renderer
103 }
104
105 pub fn renderer_mut(&mut self) -> &mut TemplateRenderer {
107 &mut self.renderer
108 }
109}
110
111pub async fn async_render(template: &str, vars: HashMap<&str, &str>) -> Result<String> {
119 let mut json_vars = HashMap::new();
120 for (key, value) in vars {
121 json_vars.insert(key.to_string(), Value::String(value.to_string()));
122 }
123
124 let mut renderer = AsyncTemplateRenderer::new().await?;
125 renderer.merge_user_vars(json_vars);
126 renderer.render_str(template, "async_template").await
127}
128
129pub async fn async_render_file<P: AsRef<Path>>(
135 path: P,
136 vars: HashMap<&str, &str>,
137) -> Result<String> {
138 let mut json_vars = HashMap::new();
139 for (key, value) in vars {
140 json_vars.insert(key.to_string(), Value::String(value.to_string()));
141 }
142
143 let mut renderer = AsyncTemplateRenderer::new().await?;
144 renderer.merge_user_vars(json_vars);
145 renderer.render_file(path).await
146}
147
148pub async fn async_render_with_json(template: &str, vars: HashMap<&str, Value>) -> Result<String> {
150 let mut json_vars = HashMap::new();
151 for (key, value) in vars {
152 json_vars.insert(key.to_string(), value);
153 }
154
155 let mut renderer = AsyncTemplateRenderer::new().await?;
156 renderer.merge_user_vars(json_vars);
157 renderer.render_str(template, "async_template").await
158}
159
160pub struct AsyncTemplateBuilder {
164 template: Option<String>,
165 variables: HashMap<String, Value>,
166 format: OutputFormat,
167 context: Option<TemplateContext>,
168}
169
170impl Default for AsyncTemplateBuilder {
171 fn default() -> Self {
172 Self {
173 template: None,
174 variables: HashMap::new(),
175 format: OutputFormat::Toml,
176 context: None,
177 }
178 }
179}
180
181impl AsyncTemplateBuilder {
182 pub fn new() -> Self {
184 Self::default()
185 }
186
187 pub fn template<S: Into<String>>(mut self, template: S) -> Self {
189 self.template = Some(template.into());
190 self
191 }
192
193 pub fn variable<K: Into<String>, V: Into<String>>(mut self, key: K, value: V) -> Self {
195 self.variables
196 .insert(key.into(), Value::String(value.into()));
197 self
198 }
199
200 pub fn json_variable<K: Into<String>>(mut self, key: K, value: Value) -> Self {
202 self.variables.insert(key.into(), value);
203 self
204 }
205
206 pub fn format(mut self, format: OutputFormat) -> Self {
208 self.format = format;
209 self
210 }
211
212 pub fn context(mut self, context: TemplateContext) -> Self {
214 self.context = Some(context);
215 self
216 }
217
218 pub async fn render(self) -> Result<String> {
220 let template = self
221 .template
222 .ok_or_else(|| TemplateError::ValidationError("No template provided".to_string()))?;
223
224 if let Some(context) = self.context {
225 let mut renderer = AsyncTemplateRenderer::new().await?.with_context(context);
226 let result = renderer.render_str(&template, "async_template").await?;
227
228 match self.format {
229 OutputFormat::Toml => Ok(result),
230 OutputFormat::Json => crate::simple::convert_to_json(&result),
231 OutputFormat::Yaml => crate::simple::convert_to_yaml(&result),
232 OutputFormat::Plain => crate::simple::strip_template_syntax(&result),
233 }
234 } else {
235 let mut json_vars = HashMap::new();
236 for (key, value) in self.variables {
237 json_vars.insert(key, value);
238 }
239
240 let mut renderer = AsyncTemplateRenderer::new().await?;
241 renderer.merge_user_vars(json_vars);
242 let result = renderer.render_str(&template, "async_template").await?;
243
244 match self.format {
245 OutputFormat::Toml => Ok(result),
246 OutputFormat::Json => crate::simple::convert_to_json(&result),
247 OutputFormat::Yaml => crate::simple::convert_to_yaml(&result),
248 OutputFormat::Plain => crate::simple::strip_template_syntax(&result),
249 }
250 }
251 }
252}
253
254pub mod async_toml {
256 use super::*;
257 use crate::toml::{TomlFile, TomlLoader, TomlWriter};
258 use std::collections::HashMap;
259
260 pub async fn load_toml_file<P: AsRef<Path>>(path: P) -> Result<TomlFile> {
265 let path = path.as_ref().to_path_buf();
266 tokio::task::spawn_blocking(move || {
267 let loader = TomlLoader::new();
268 loader.load_file(path)
269 })
270 .await
271 .map_err(|e| TemplateError::InternalError(format!("Async TOML loading failed: {}", e)))?
272 }
273
274 pub async fn load_all_toml_files(
279 search_paths: Vec<&Path>,
280 ) -> Result<HashMap<PathBuf, TomlFile>> {
281 let paths: Vec<PathBuf> = search_paths.iter().map(|p| p.to_path_buf()).collect();
282 tokio::task::spawn_blocking(move || {
283 let loader = TomlLoader::new().with_search_paths(paths);
284 loader.load_all()
285 })
286 .await
287 .map_err(|e| {
288 TemplateError::InternalError(format!("Async TOML directory loading failed: {}", e))
289 })?
290 }
291
292 pub async fn write_toml_file<P: AsRef<Path>>(
299 path: P,
300 content: &str,
301 validator: Option<&crate::validation::TemplateValidator>,
302 ) -> Result<()> {
303 let path = path.as_ref().to_path_buf();
304 let content = content.to_string();
305 let validator = validator.cloned();
306
307 tokio::task::spawn_blocking(move || {
308 let writer = TomlWriter::new();
309 writer.write_file(path, &content, validator.as_ref())
310 })
311 .await
312 .map_err(|e| TemplateError::InternalError(format!("Async TOML writing failed: {}", e)))?
313 }
314}
315
316pub mod async_discovery {
318 use super::*;
319 use crate::discovery::{TemplateDiscovery, TemplateLoader};
320
321 pub async fn discover_templates(
327 search_paths: Vec<&Path>,
328 patterns: Vec<&str>,
329 ) -> Result<TemplateLoader> {
330 let paths: Vec<PathBuf> = search_paths.iter().map(|p| p.to_path_buf()).collect();
331 let patterns: Vec<String> = patterns.iter().map(|s| s.to_string()).collect();
332
333 tokio::task::spawn_blocking(move || {
334 let mut discovery = TemplateDiscovery::new();
335 for path in paths {
336 discovery = discovery.with_search_path(path);
337 }
338 for pattern in patterns {
339 discovery = discovery.with_glob_pattern(&pattern);
340 }
341 discovery.load()
342 })
343 .await
344 .map_err(|e| {
345 TemplateError::InternalError(format!("Async template discovery failed: {}", e))
346 })?
347 }
348}
349
350pub mod async_validation {
352 use super::*;
353
354 pub async fn validate_async(
361 output: &str,
362 template_name: &str,
363 validator: &crate::validation::TemplateValidator,
364 ) -> Result<()> {
365 let output = output.to_string();
366 let template_name = template_name.to_string();
367 let validator = validator.clone();
368 tokio::task::spawn_blocking(move || validator.validate(&output, &template_name))
369 .await
370 .map_err(|e| TemplateError::InternalError(format!("Async validation failed: {}", e)))?
371 }
372}
373
374pub mod async_cache {
376 use super::*;
377 use crate::cache::CachedRenderer;
378
379 pub async fn create_async_cached_renderer(
385 context: TemplateContext,
386 hot_reload: bool,
387 ) -> Result<CachedRenderer> {
388 tokio::task::spawn_blocking(move || CachedRenderer::new(context, hot_reload))
389 .await
390 .map_err(|e| {
391 TemplateError::InternalError(format!(
392 "Async cached renderer creation failed: {}",
393 e
394 ))
395 })?
396 }
397}
398
399#[cfg(test)]
400mod tests {
401 use super::*;
402
403 #[tokio::test]
404 async fn test_async_render() {
405 let result = async_render(
406 "Hello {{ name }}!",
407 [("name", "World")].iter().cloned().collect(),
408 )
409 .await
410 .unwrap();
411 assert_eq!(result, "Hello World!");
412 }
413
414 #[tokio::test]
415 async fn test_async_template_builder() {
416 let result = AsyncTemplateBuilder::new()
417 .template("Service: {{ service }}")
418 .variable("service", "my-service")
419 .render()
420 .await
421 .unwrap();
422
423 assert_eq!(result, "Service: my-service");
424 }
425
426 #[tokio::test]
427 async fn test_async_toml_loading() {
428 use std::fs;
429 use tempfile::tempdir;
430
431 let temp_dir = tempdir().unwrap();
432 let toml_file = temp_dir.path().join("test.toml");
433
434 let content = r#"
435[service]
436name = "test-service"
437 "#;
438
439 fs::write(&toml_file, content).unwrap();
440
441 let file = async_toml::load_toml_file(&toml_file).await.unwrap();
442 assert_eq!(file.path, toml_file);
443 assert!(file.parsed.get("service").is_some());
444 }
445}