espforge_lib/resolver/actions/
global.rs

1use 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 GlobalActionStrategy;
14
15impl GlobalActionStrategy {
16    fn parse_key<'a>(&self, key: &'a str) -> Option<(&'a str, &'a str)> {
17        // format: module.method
18        let parts: Vec<&str> = key.split('.').collect();
19        if parts.len() < 2 {
20            return None;
21        }
22        Some((parts[0], parts[1]))
23    }
24}
25
26impl ActionStrategy for GlobalActionStrategy {
27    fn can_handle(&self, key: &str) -> bool {
28        // We handle it if it DOESN'T start with $
29        // AND looks like "something.method"
30        !key.starts_with('$') && key.contains('.')
31    }
32
33    fn validate(
34        &self,
35        key: &str,
36        _value: &Value,
37        _config: &EspforgeConfiguration,
38        manifests: &HashMap<String, ComponentManifest>,
39    ) -> ValidationResult {
40        let Some((module_name, method_name)) = self.parse_key(key) else {
41            return ValidationResult::Ignored;
42        };
43
44        if let Some(manifest) = manifests.get(module_name) {
45            if manifest.methods.contains_key(method_name) {
46                ValidationResult::Ok(format!(
47                    "Validated global action '{}' on module '{}'",
48                    method_name, module_name
49                ))
50            } else {
51                ValidationResult::Error(format!(
52                    "Method '{}' not found on global module '{}'",
53                    method_name, module_name
54                ))
55            }
56        } else {
57            // If we can't find the manifest, this strategy shouldn't definitively error
58            // (another strategy might handle it), but since we are the "catch-all" for
59            // dot-notation globals, we can issue a warning or error.
60            // For now, we assume if it looks like a global call but isn't in manifests, it's invalid.
61            ValidationResult::Ignored
62        }
63    }
64
65    fn render(
66        &self,
67        key: &str,
68        value: &Value,
69        _config: &EspforgeConfiguration,
70        manifests: &HashMap<String, ComponentManifest>,
71        tera: &mut Tera,
72    ) -> Result<String> {
73        let (module_name, method_name) = self
74            .parse_key(key)
75            .ok_or_else(|| anyhow!("Invalid key format"))?;
76
77        let manifest = manifests
78            .get(module_name)
79            .ok_or_else(|| anyhow!("Global module '{}' not found", module_name))?;
80
81        let method_def = manifest.methods.get(method_name).ok_or_else(|| {
82            anyhow!(
83                "Method '{}' not found in module '{}'",
84                method_name,
85                module_name
86            )
87        })?;
88
89        let mut context = tera::Context::new();
90        context.insert("target", module_name); // Usually ignored in global templates or used as static ref
91        context.insert("args", value);
92
93        tera.render_str(&method_def.template, &context)
94            .with_context(|| format!("Failed to render global action {}", key))
95    }
96}
97
98//register_action_strategy!(GlobalActionStrategy);