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