espforge_lib/resolver/actions/
mod.rs

1use crate::config::EspforgeConfiguration;
2use crate::manifest::ComponentManifest;
3use anyhow::{Result, anyhow};
4use inventory;
5use serde_yaml_ng::Value;
6use std::collections::HashMap;
7use tera::Tera;
8
9pub mod component;
10pub mod global;
11pub mod logic;
12
13/// The result of a validation check by a strategy.
14#[derive(Debug)]
15pub enum ValidationResult {
16    /// The strategy validated the action successfully.
17    Ok(String),
18    /// The strategy found an error (e.g. undefined method).
19    Error(String),
20    /// The strategy identified the action but found a warning.
21    Warning(String),
22    /// The strategy does not handle this specific key pattern.
23    Ignored,
24}
25
26/// Trait Definition (Design Pattern: Strategy)
27pub trait ActionStrategy: Send + Sync {
28    /// Returns true if this strategy claims responsibility for this action key.
29    fn can_handle(&self, key: &str) -> bool;
30
31    /// Validates the action logic (e.g. does the component exist? does the method exist?).
32    /// This is used by the AppNibbler.
33    fn validate(
34        &self,
35        key: &str,
36        _value: &Value,
37        config: &EspforgeConfiguration,
38        manifests: &HashMap<String, ComponentManifest>,
39    ) -> ValidationResult;
40
41    /// Renders the actual code for the action.
42    /// This is used by the ContextResolver.
43    fn render(
44        &self,
45        key: &str,
46        value: &Value,
47        config: &EspforgeConfiguration,
48        manifests: &HashMap<String, ComponentManifest>,
49        tera: &mut Tera,
50    ) -> Result<String>;
51}
52
53// --- Registration Plumbing ---
54
55pub type ActionStrategyFactory = fn() -> Box<dyn ActionStrategy>;
56
57pub struct ActionStrategyRegistration {
58    pub factory: ActionStrategyFactory,
59}
60
61inventory::collect!(ActionStrategyRegistration);
62
63#[macro_export]
64macro_rules! register_action_strategy {
65    ($strategy:ty) => {
66        inventory::submit! {
67            $crate::resolver::actions::ActionStrategyRegistration {
68                factory: || Box::new(<$strategy>::default())
69            }
70        }
71    };
72}
73
74// --- The Manager (Facade) ---
75
76pub struct ActionResolver {
77    strategies: Vec<Box<dyn ActionStrategy>>,
78}
79
80impl Default for ActionResolver {
81    fn default() -> Self {
82        Self::new()
83    }
84}
85
86impl ActionResolver {
87    pub fn new() -> Self {
88        let mut strategies = Vec::new();
89        for registration in inventory::iter::<ActionStrategyRegistration> {
90            strategies.push((registration.factory)());
91        }
92        Self { strategies }
93    }
94
95    /// Finds the appropriate strategy and runs validation.
96    pub fn validate(
97        &self,
98        key: &str,
99        value: &Value,
100        config: &EspforgeConfiguration,
101        manifests: &HashMap<String, ComponentManifest>,
102    ) -> ValidationResult {
103        for strategy in &self.strategies {
104            if strategy.can_handle(key) {
105                return strategy.validate(key, value, config, manifests);
106            }
107        }
108        ValidationResult::Warning(format!(
109            "Unknown action format '{}'. No strategy found.",
110            key
111        ))
112    }
113
114    /// Finds the appropriate strategy and renders the code.
115    pub fn resolve(
116        &self,
117        key: &str,
118        value: &Value,
119        config: &EspforgeConfiguration,
120        manifests: &HashMap<String, ComponentManifest>,
121        tera: &mut Tera,
122    ) -> Result<String> {
123        for strategy in &self.strategies {
124            if strategy.can_handle(key) {
125                return strategy.render(key, value, config, manifests, tera);
126            }
127        }
128        Err(anyhow!("No strategy found to resolve action '{}'", key))
129    }
130}