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 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 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", };
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, ¶ms_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, ¶ms_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(¶m_def.name);
270
271 self.validate_required_parameter(param_def.required, value, ¶m_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(¶m_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}