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