espforge_lib/resolver/
mod.rs

1use crate::config::EspforgeConfiguration;
2use crate::manifest::{ComponentManifest, ParameterDef, ParameterType};
3use crate::resolver::actions::ActionResolver;
4use crate::resolver::strategies::{ParameterStrategy, ResolutionContext, StrategyRegistration};
5use anyhow::{Context, Result, anyhow};
6use inventory;
7use serde::Serialize;
8use serde_yaml_ng::Value;
9use std::collections::HashMap;
10use tera::Tera;
11
12pub mod actions;
13pub mod ruchy_bridge;
14pub mod strategies;
15
16type ActionList = Vec<HashMap<String, Value>>;
17
18#[derive(Debug, Serialize, Clone)]
19pub struct RenderContext {
20    pub includes: Vec<String>,
21    pub initializations: Vec<String>,
22    pub variables: Vec<String>,
23    pub setup_code: Vec<String>,
24    pub loop_code: Vec<String>,
25}
26
27pub struct ContextResolver {
28    tera: Tera,
29    parameter_strategies: HashMap<ParameterType, Box<dyn ParameterStrategy>>,
30    action_resolver: ActionResolver,
31}
32
33impl Default for ContextResolver {
34    fn default() -> Self {
35        Self::new()
36    }
37}
38
39impl ContextResolver {
40    pub fn new() -> Self {
41        let strategies = Self::load_registered_strategies();
42
43        Self {
44            tera: Tera::default(),
45            parameter_strategies: strategies,
46            action_resolver: ActionResolver::new(),
47        }
48    }
49
50    fn load_registered_strategies() -> HashMap<ParameterType, Box<dyn ParameterStrategy>> {
51        let mut strategies: HashMap<ParameterType, Box<dyn ParameterStrategy>> = HashMap::new();
52
53        for registration in inventory::iter::<StrategyRegistration> {
54            let (param_type, strategy) = (registration.factory)();
55            strategies.insert(param_type, strategy);
56        }
57
58        strategies
59    }
60
61    pub fn resolve(
62        &mut self,
63        config: &EspforgeConfiguration,
64        manifests: &HashMap<String, ComponentManifest>,
65    ) -> Result<RenderContext> {
66        let mut includes = Vec::new();
67        let components_map = self.resolve_components(config, manifests, &mut includes)?;
68        let devices_map = self.resolve_devices(config, manifests, &mut includes)?;
69
70        let variables_code = self.resolve_variables(config)?;
71
72        let (setup_actions, loop_actions) = self.extract_lifecycle_actions(config);
73
74        let setup_code = self.resolve_lifecycle_block(setup_actions, "setup", config, manifests)?;
75        let loop_code = self.resolve_lifecycle_block(loop_actions, "loop", config, manifests)?;
76
77        // Append initializations in order (components then devices)
78        let mut initializations: Vec<String> = components_map.values().cloned().collect();
79        initializations.extend(devices_map.values().cloned());
80
81        Ok(RenderContext {
82            includes,
83            initializations,
84            variables: variables_code,
85            setup_code,
86            loop_code,
87        })
88    }
89
90    fn resolve_variables(&self, config: &EspforgeConfiguration) -> Result<Vec<String>> {
91        let mut vars = Vec::new();
92        if let Some(app) = &config.app {
93            for (name, var_config) in &app.variables {
94                // Determine Rust type
95                let rust_type = match var_config.type_name.as_str() {
96                    "bool" => "bool",
97                    "int" => "i32",
98                    "u8" => "u8",
99                    "u32" => "u32",
100                    "float" => "f32",
101                    _ => "i32", // default
102                };
103
104                let init_val = match &var_config.initial {
105                    Value::Bool(b) => b.to_string(),
106                    Value::Number(n) => n.to_string(),
107                    _ => "0".to_string(),
108                };
109
110                vars.push(format!("let mut {} : {} = {};", name, rust_type, init_val));
111            }
112        }
113        Ok(vars)
114    }
115
116    fn extract_lifecycle_actions<'a>(
117        &self,
118        config: &'a EspforgeConfiguration,
119    ) -> (&'a ActionList, &'a ActionList) {
120        static EMPTY_VEC: Vec<HashMap<String, Value>> = Vec::new();
121
122        config
123            .app
124            .as_ref()
125            .map(|app| (&app.setup, &app.loop_fn))
126            .unwrap_or((&EMPTY_VEC, &EMPTY_VEC))
127    }
128
129    fn resolve_components(
130        &mut self,
131        config: &EspforgeConfiguration,
132        manifests: &HashMap<String, ComponentManifest>,
133        includes: &mut Vec<String>,
134    ) -> Result<HashMap<String, String>> {
135        let Some(components) = &config.components else {
136            return Ok(HashMap::new());
137        };
138
139        let resolution_ctx = ResolutionContext {
140            hardware: config.esp32.as_ref(),
141            platform: &config.espforge.platform,
142        };
143
144        let mut rendered_inits = HashMap::new();
145
146        for (name, instance) in components {
147            let rendered = self.resolve_single_component(
148                name,
149                instance,
150                manifests,
151                &resolution_ctx,
152                includes,
153            )?;
154            rendered_inits.insert(name.clone(), rendered);
155        }
156
157        Ok(rendered_inits)
158    }
159
160    fn resolve_single_component(
161        &mut self,
162        name: &str,
163        instance: &crate::config::ComponentConfig,
164        manifests: &HashMap<String, ComponentManifest>,
165        resolution_ctx: &ResolutionContext,
166        includes: &mut Vec<String>,
167    ) -> Result<String> {
168        let manifest = self.get_manifest(manifests, &instance.using)?;
169
170        let params_context = self
171            .resolve_parameters(manifest, &instance.with, resolution_ctx)
172            .with_context(|| format!("Failed to resolve parameters for component '{}'", name))?;
173
174        let init_code = self.render_component_template(name, &params_context, manifest)?;
175
176        includes.extend_from_slice(&manifest.requires);
177
178        Ok(init_code)
179    }
180
181    fn resolve_devices(
182        &mut self,
183        config: &EspforgeConfiguration,
184        manifests: &HashMap<String, ComponentManifest>,
185        includes: &mut Vec<String>,
186    ) -> Result<HashMap<String, String>> {
187        let Some(devices) = &config.devices else {
188            return Ok(HashMap::new());
189        };
190
191        let resolution_ctx = ResolutionContext {
192            hardware: config.esp32.as_ref(),
193            platform: &config.espforge.platform,
194        };
195
196        let mut rendered_inits = HashMap::new();
197
198        for (name, instance) in devices {
199            let rendered =
200                self.resolve_single_device(name, instance, manifests, &resolution_ctx, includes)?;
201            rendered_inits.insert(name.clone(), rendered);
202        }
203
204        Ok(rendered_inits)
205    }
206
207    fn resolve_single_device(
208        &mut self,
209        name: &str,
210        instance: &crate::config::DeviceConfig,
211        manifests: &HashMap<String, ComponentManifest>,
212        resolution_ctx: &ResolutionContext,
213        includes: &mut Vec<String>,
214    ) -> Result<String> {
215        let manifest = self.get_manifest(manifests, &instance.using)?;
216
217        let params_context = self
218            .resolve_parameters(manifest, &instance.with, resolution_ctx)
219            .with_context(|| format!("Failed to resolve parameters for device '{}'", name))?;
220
221        let init_code = self.render_component_template(name, &params_context, manifest)?;
222
223        includes.extend_from_slice(&manifest.requires);
224
225        Ok(init_code)
226    }
227
228    fn get_manifest<'a>(
229        &self,
230        manifests: &'a HashMap<String, ComponentManifest>,
231        component_type: &str,
232    ) -> Result<&'a ComponentManifest> {
233        manifests
234            .get(component_type)
235            .ok_or_else(|| anyhow!("Component type '{}' not found in manifests", component_type))
236    }
237
238    fn render_component_template(
239        &mut self,
240        name: &str,
241        params: &HashMap<String, Value>,
242        manifest: &ComponentManifest,
243    ) -> Result<String> {
244        let mut render_context = tera::Context::new();
245        render_context.insert("name", name);
246        render_context.insert("params", params);
247
248        self.tera
249            .render_str(&manifest.setup_template, &render_context)
250            .with_context(|| format!("Failed to render setup template for '{}'", name))
251    }
252
253    fn resolve_parameters(
254        &self,
255        manifest: &ComponentManifest,
256        user_params: &HashMap<String, Value>,
257        ctx: &ResolutionContext,
258    ) -> Result<HashMap<String, Value>> {
259        let mut resolved_params = HashMap::new();
260
261        for param_def in &manifest.parameters {
262            let value = user_params.get(&param_def.name);
263
264            self.validate_required_parameter(param_def.required, value, &param_def.name)?;
265
266            if let Some(val) = value {
267                let resolved = self.resolve_single_parameter(param_def, val, ctx)?;
268                resolved_params.insert(param_def.name.clone(), resolved);
269            }
270        }
271
272        Ok(resolved_params)
273    }
274
275    fn validate_required_parameter(
276        &self,
277        required: bool,
278        value: Option<&Value>,
279        name: &str,
280    ) -> Result<()> {
281        if required && value.is_none() {
282            return Err(anyhow!("Missing required parameter: {}", name));
283        }
284        Ok(())
285    }
286
287    fn resolve_single_parameter(
288        &self,
289        param_def: &ParameterDef,
290        value: &Value,
291        ctx: &ResolutionContext,
292    ) -> Result<Value> {
293        let strategy = self
294            .parameter_strategies
295            .get(&param_def.param_type)
296            .ok_or_else(|| {
297                anyhow!(
298                    "No strategy registered for parameter type: {:?}",
299                    param_def.param_type
300                )
301            })?;
302
303        strategy.resolve(value, ctx)
304    }
305
306    fn resolve_lifecycle_block(
307        &mut self,
308        actions: &[HashMap<String, Value>],
309        block_name: &str,
310        config: &EspforgeConfiguration,
311        manifests: &HashMap<String, ComponentManifest>,
312    ) -> Result<Vec<String>> {
313        actions
314            .iter()
315            .enumerate()
316            .map(|(index, action)| {
317                let (key, value) = action.iter().next().ok_or_else(|| {
318                    anyhow!("Empty action in {} block at index {}", block_name, index)
319                })?;
320
321                self.action_resolver
322                    .resolve(key, value, config, manifests, &mut self.tera)
323            })
324            .collect()
325    }
326}