1use crate::catalog::parameters::ParameterSubstitutionEngine;
10use crate::error::{Error, Result};
11use crate::types::catalogs::controllers::{CatalogController, ControllerCatalog};
12use crate::types::catalogs::environments::{CatalogEnvironment, EnvironmentCatalog};
13use crate::types::catalogs::routes::{CatalogRoute, RouteCatalog};
14use crate::types::catalogs::trajectories::{CatalogTrajectory, TrajectoryCatalog};
15use std::collections::{HashMap, HashSet};
16
17pub struct ResolvedCatalog<T> {
19 pub entity: T,
21 pub metadata: ResolutionMetadata,
23}
24
25#[derive(Debug, Clone)]
27pub struct ResolutionMetadata {
28 pub catalog_path: String,
30 pub entity_name: String,
32 pub parameter_substitutions: HashMap<String, String>,
34}
35
36pub trait CatalogResolvable<T> {
38 fn resolve(&self, entry_name: &str, catalogs: &CatalogManager) -> Result<ResolvedCatalog<T>>;
40}
41
42pub struct CatalogManager {
44 pub controller_catalogs: HashMap<String, ControllerCatalog>,
46 pub trajectory_catalogs: HashMap<String, TrajectoryCatalog>,
48 pub route_catalogs: HashMap<String, RouteCatalog>,
50 pub environment_catalogs: HashMap<String, EnvironmentCatalog>,
52 pub parameter_resolver: ParameterSubstitutionEngine,
54}
55
56pub struct CatalogResolver {
58 resolution_stack: HashSet<String>,
60 catalog_manager: Option<CatalogManager>,
62}
63
64impl Default for CatalogManager {
65 fn default() -> Self {
66 Self::new()
67 }
68}
69
70impl CatalogManager {
71 pub fn new() -> Self {
73 Self {
74 controller_catalogs: HashMap::new(),
75 trajectory_catalogs: HashMap::new(),
76 route_catalogs: HashMap::new(),
77 environment_catalogs: HashMap::new(),
78 parameter_resolver: ParameterSubstitutionEngine::new(),
79 }
80 }
81
82 pub fn add_controller_catalog(&mut self, name: String, catalog: ControllerCatalog) {
84 self.controller_catalogs.insert(name, catalog);
85 }
86
87 pub fn add_trajectory_catalog(&mut self, name: String, catalog: TrajectoryCatalog) {
89 self.trajectory_catalogs.insert(name, catalog);
90 }
91
92 pub fn add_route_catalog(&mut self, name: String, catalog: RouteCatalog) {
94 self.route_catalogs.insert(name, catalog);
95 }
96
97 pub fn add_environment_catalog(&mut self, name: String, catalog: EnvironmentCatalog) {
99 self.environment_catalogs.insert(name, catalog);
100 }
101
102 pub fn set_parameters(&mut self, parameters: HashMap<String, String>) -> Result<()> {
104 self.parameter_resolver.set_parameters(parameters)
105 }
106
107 pub fn set_parameter(&mut self, name: String, value: String) -> Result<()> {
109 self.parameter_resolver.set_parameter(name, value)
110 }
111
112 pub fn parameter_context(&self) -> &HashMap<String, String> {
114 self.parameter_resolver.context()
115 }
116
117 pub fn resolve_controller_reference(
119 &self,
120 catalog_name: &str,
121 entry_name: &str,
122 ) -> Result<ResolvedCatalog<CatalogController>> {
123 self.resolve_controller_reference_with_params(catalog_name, entry_name, &HashMap::new())
124 }
125
126 pub fn resolve_controller_reference_with_params(
128 &self,
129 catalog_name: &str,
130 entry_name: &str,
131 params: &HashMap<String, String>,
132 ) -> Result<ResolvedCatalog<CatalogController>> {
133 if let Some(catalog) = self.controller_catalogs.get(catalog_name) {
134 for controller in &catalog.controllers {
135 if controller.name == entry_name {
136 let resolved_controller =
137 if !params.is_empty() || !self.parameter_resolver.context().is_empty() {
138 let _param_engine = self
139 .parameter_resolver
140 .with_additional_context(params.clone());
141 controller.clone()
142 } else {
143 controller.clone()
144 };
145
146 return Ok(ResolvedCatalog::with_parameters(
147 resolved_controller,
148 format!("controller_catalog:{}", catalog_name),
149 entry_name.to_string(),
150 params.clone(),
151 ));
152 }
153 }
154 let available: Vec<String> =
155 catalog.controllers.iter().map(|c| c.name.clone()).collect();
156 return Err(Error::catalog_entry_not_found(catalog_name, entry_name)
157 .with_context(&format!("Available controllers: {}", available.join(", "))));
158 }
159 let available: Vec<String> = self.controller_catalogs.keys().cloned().collect();
160 Err(Error::catalog_not_found(catalog_name, &available))
161 }
162
163 pub fn resolve_trajectory_reference(
165 &self,
166 catalog_name: &str,
167 entry_name: &str,
168 ) -> Result<ResolvedCatalog<CatalogTrajectory>> {
169 self.resolve_trajectory_reference_with_params(catalog_name, entry_name, &HashMap::new())
170 }
171
172 pub fn resolve_trajectory_reference_with_params(
174 &self,
175 catalog_name: &str,
176 entry_name: &str,
177 params: &HashMap<String, String>,
178 ) -> Result<ResolvedCatalog<CatalogTrajectory>> {
179 if let Some(catalog) = self.trajectory_catalogs.get(catalog_name) {
180 for trajectory in &catalog.trajectories {
181 if trajectory.name == entry_name {
182 return Ok(ResolvedCatalog::with_parameters(
183 trajectory.clone(),
184 format!("trajectory_catalog:{}", catalog_name),
185 entry_name.to_string(),
186 params.clone(),
187 ));
188 }
189 }
190 let available: Vec<String> = catalog
191 .trajectories
192 .iter()
193 .map(|t| t.name.clone())
194 .collect();
195 return Err(Error::catalog_entry_not_found(catalog_name, entry_name)
196 .with_context(&format!("Available trajectories: {}", available.join(", "))));
197 }
198 let available: Vec<String> = self.trajectory_catalogs.keys().cloned().collect();
199 Err(Error::catalog_not_found(catalog_name, &available))
200 }
201
202 pub fn resolve_route_reference(
204 &self,
205 catalog_name: &str,
206 entry_name: &str,
207 ) -> Result<ResolvedCatalog<CatalogRoute>> {
208 self.resolve_route_reference_with_params(catalog_name, entry_name, &HashMap::new())
209 }
210
211 pub fn resolve_route_reference_with_params(
213 &self,
214 catalog_name: &str,
215 entry_name: &str,
216 params: &HashMap<String, String>,
217 ) -> Result<ResolvedCatalog<CatalogRoute>> {
218 if let Some(catalog) = self.route_catalogs.get(catalog_name) {
219 for route in &catalog.routes {
220 if route.name == entry_name {
221 return Ok(ResolvedCatalog::with_parameters(
222 route.clone(),
223 format!("route_catalog:{}", catalog_name),
224 entry_name.to_string(),
225 params.clone(),
226 ));
227 }
228 }
229 let available: Vec<String> = catalog.routes.iter().map(|r| r.name.clone()).collect();
230 return Err(Error::catalog_entry_not_found(catalog_name, entry_name)
231 .with_context(&format!("Available routes: {}", available.join(", "))));
232 }
233 let available: Vec<String> = self.route_catalogs.keys().cloned().collect();
234 Err(Error::catalog_not_found(catalog_name, &available))
235 }
236
237 pub fn resolve_environment_reference(
239 &self,
240 catalog_name: &str,
241 entry_name: &str,
242 ) -> Result<ResolvedCatalog<CatalogEnvironment>> {
243 self.resolve_environment_reference_with_params(catalog_name, entry_name, &HashMap::new())
244 }
245
246 pub fn resolve_environment_reference_with_params(
248 &self,
249 catalog_name: &str,
250 entry_name: &str,
251 params: &HashMap<String, String>,
252 ) -> Result<ResolvedCatalog<CatalogEnvironment>> {
253 if let Some(catalog) = self.environment_catalogs.get(catalog_name) {
254 for environment in &catalog.environments {
255 if environment.name == entry_name {
256 return Ok(ResolvedCatalog::with_parameters(
257 environment.clone(),
258 format!("environment_catalog:{}", catalog_name),
259 entry_name.to_string(),
260 params.clone(),
261 ));
262 }
263 }
264 let available: Vec<String> = catalog
265 .environments
266 .iter()
267 .map(|e| e.name.clone())
268 .collect();
269 return Err(Error::catalog_entry_not_found(catalog_name, entry_name)
270 .with_context(&format!("Available environments: {}", available.join(", "))));
271 }
272 let available: Vec<String> = self.environment_catalogs.keys().cloned().collect();
273 Err(Error::catalog_not_found(catalog_name, &available))
274 }
275}
276
277impl CatalogResolver {
278 pub fn new() -> Self {
280 Self {
281 resolution_stack: HashSet::new(),
282 catalog_manager: None,
283 }
284 }
285
286 pub fn with_catalog_manager(catalog_manager: CatalogManager) -> Self {
288 Self {
289 resolution_stack: HashSet::new(),
290 catalog_manager: Some(catalog_manager),
291 }
292 }
293
294 pub fn set_catalog_manager(&mut self, catalog_manager: CatalogManager) {
296 self.catalog_manager = Some(catalog_manager);
297 }
298
299 pub fn catalog_manager(&self) -> Option<&CatalogManager> {
301 self.catalog_manager.as_ref()
302 }
303
304 pub fn begin_resolution(&mut self, reference_key: &str) -> Result<()> {
306 if self.resolution_stack.contains(reference_key) {
307 return Err(Error::circular_dependency(reference_key));
308 }
309 self.resolution_stack.insert(reference_key.to_string());
310 Ok(())
311 }
312
313 pub fn end_resolution(&mut self, reference_key: &str) {
315 self.resolution_stack.remove(reference_key);
316 }
317
318 pub fn is_resolving(&self, reference_key: &str) -> bool {
320 self.resolution_stack.contains(reference_key)
321 }
322
323 pub fn clear(&mut self) {
325 self.resolution_stack.clear();
326 }
327}
328
329impl Default for CatalogResolver {
330 fn default() -> Self {
331 Self::new()
332 }
333}
334
335impl<T> ResolvedCatalog<T> {
336 pub fn new(entity: T, catalog_path: String, entity_name: String) -> Self {
338 Self {
339 entity,
340 metadata: ResolutionMetadata {
341 catalog_path,
342 entity_name,
343 parameter_substitutions: HashMap::new(),
344 },
345 }
346 }
347
348 pub fn with_parameters(
350 entity: T,
351 catalog_path: String,
352 entity_name: String,
353 parameters: HashMap<String, String>,
354 ) -> Self {
355 Self {
356 entity,
357 metadata: ResolutionMetadata {
358 catalog_path,
359 entity_name,
360 parameter_substitutions: parameters,
361 },
362 }
363 }
364
365 pub fn into_entity(self) -> T {
367 self.entity
368 }
369
370 pub fn entity(&self) -> &T {
372 &self.entity
373 }
374
375 pub fn metadata(&self) -> &ResolutionMetadata {
377 &self.metadata
378 }
379}
380
381#[cfg(test)]
382mod tests {
383 use super::*;
384
385 #[test]
386 fn test_resolver_creation() {
387 let resolver = CatalogResolver::new();
388 assert!(!resolver.is_resolving("test"));
389 }
390
391 #[test]
392 fn test_circular_dependency_detection() {
393 let mut resolver = CatalogResolver::new();
394
395 assert!(resolver.begin_resolution("vehicle1").is_ok());
397 assert!(resolver.is_resolving("vehicle1"));
398
399 assert!(resolver.begin_resolution("vehicle1").is_err());
401
402 resolver.end_resolution("vehicle1");
404 assert!(!resolver.is_resolving("vehicle1"));
405
406 assert!(resolver.begin_resolution("vehicle1").is_ok());
408 resolver.end_resolution("vehicle1");
409 }
410
411 #[test]
412 fn test_resolved_catalog() {
413 let entity = "test_vehicle".to_string();
414 let resolved = ResolvedCatalog::new(
415 entity.clone(),
416 "/path/to/catalog.xosc".to_string(),
417 "TestVehicle".to_string(),
418 );
419
420 assert_eq!(resolved.entity(), &entity);
421 assert_eq!(resolved.metadata().catalog_path, "/path/to/catalog.xosc");
422 assert_eq!(resolved.metadata().entity_name, "TestVehicle");
423 assert!(resolved.metadata().parameter_substitutions.is_empty());
424 }
425
426 #[test]
427 fn test_resolved_catalog_with_parameters() {
428 let mut params = HashMap::new();
429 params.insert("MaxSpeed".to_string(), "60.0".to_string());
430 params.insert("Color".to_string(), "Red".to_string());
431
432 let resolved = ResolvedCatalog::with_parameters(
433 42u32,
434 "/catalogs/vehicles.xosc".to_string(),
435 "SportsCar".to_string(),
436 params.clone(),
437 );
438
439 assert_eq!(*resolved.entity(), 42u32);
440 assert_eq!(resolved.metadata().parameter_substitutions, params);
441 }
442}