espforge_lib/resolver/actions/
component.rs1use crate::config::EspforgeConfiguration;
2use crate::manifest::ComponentManifest;
3use crate::register_action_strategy;
4use crate::resolver::actions::{ActionStrategy, ValidationResult};
5use anyhow::{Context, Result, anyhow};
6use espforge_macros::auto_register_action_strategy;
7use serde_yaml_ng::Value;
8use std::collections::HashMap;
9use tera::Tera;
10
11#[derive(Default)]
12#[auto_register_action_strategy]
13pub struct ComponentActionStrategy;
14
15impl ComponentActionStrategy {
16 fn parse_key<'a>(&self, key: &'a str) -> Option<(&'a str, &'a str)> {
17 if !key.starts_with('$') {
19 return None;
20 }
21
22 let parts: Vec<&str> = key.split('.').collect();
23 if parts.len() < 2 {
24 return None;
25 }
26
27 let instance_name = &parts[0][1..]; let method_name = parts[1];
29 Some((instance_name, method_name))
30 }
31
32 fn get_manifest<'a>(
33 &self,
34 instance_name: &str,
35 config: &EspforgeConfiguration,
36 manifests: &'a HashMap<String, ComponentManifest>,
37 ) -> Result<(&'a ComponentManifest, String)> {
38 let components = config
39 .components
40 .as_ref()
41 .ok_or_else(|| anyhow!("No components defined"))?;
42
43 let instance = components
44 .get(instance_name)
45 .ok_or_else(|| anyhow!("Component instance '{}' not found", instance_name))?;
46
47 let manifest = manifests.get(&instance.using).ok_or_else(|| {
48 anyhow!(
49 "Manifest '{}' not found for instance '{}'",
50 instance.using,
51 instance_name
52 )
53 })?;
54
55 Ok((manifest, instance.using.clone()))
56 }
57}
58
59impl ActionStrategy for ComponentActionStrategy {
60 fn can_handle(&self, key: &str) -> bool {
61 key.starts_with('$')
62 }
63
64 fn validate(
65 &self,
66 key: &str,
67 _value: &Value,
68 config: &EspforgeConfiguration,
69 manifests: &HashMap<String, ComponentManifest>,
70 ) -> ValidationResult {
71 let Some((instance_name, method_name)) = self.parse_key(key) else {
72 return ValidationResult::Error(format!(
73 "Invalid component syntax '{}'. Expected $name.method",
74 key
75 ));
76 };
77
78 match self.get_manifest(instance_name, config, manifests) {
79 Ok((manifest, _)) => {
80 if manifest.methods.contains_key(method_name) {
81 ValidationResult::Ok(format!(
82 "Validated action '{}' on component '{}'",
83 method_name, instance_name
84 ))
85 } else {
86 ValidationResult::Error(format!(
87 "Method '{}' not found on component '{}' (type {})",
88 method_name, instance_name, manifest.name
89 ))
90 }
91 }
92 Err(e) => ValidationResult::Error(e.to_string()),
93 }
94 }
95
96 fn render(
97 &self,
98 key: &str,
99 value: &Value,
100 config: &EspforgeConfiguration,
101 manifests: &HashMap<String, ComponentManifest>,
102 tera: &mut Tera,
103 ) -> Result<String> {
104 let (instance_name, method_name) = self
105 .parse_key(key)
106 .ok_or_else(|| anyhow!("Invalid key format"))?;
107
108 let (manifest, _) = self.get_manifest(instance_name, config, manifests)?;
109
110 let method_def = manifest
111 .methods
112 .get(method_name)
113 .ok_or_else(|| anyhow!("Method not found"))?;
114
115 let mut context = tera::Context::new();
116 context.insert("target", instance_name);
117 context.insert("args", value);
118
119 tera.render_str(&method_def.template, &context)
120 .with_context(|| format!("Failed to render component action {}", key))
121 }
122}
123
124