use crate::catalog::parameters::ParameterSubstitutionEngine;
use crate::error::{Error, Result};
use crate::types::catalogs::controllers::{CatalogController, ControllerCatalog};
use crate::types::catalogs::environments::{CatalogEnvironment, EnvironmentCatalog};
use crate::types::catalogs::routes::{CatalogRoute, RouteCatalog};
use crate::types::catalogs::trajectories::{CatalogTrajectory, TrajectoryCatalog};
use std::collections::{HashMap, HashSet};
pub struct ResolvedCatalog<T> {
pub entity: T,
pub metadata: ResolutionMetadata,
}
#[derive(Debug, Clone)]
pub struct ResolutionMetadata {
pub catalog_path: String,
pub entity_name: String,
pub parameter_substitutions: HashMap<String, String>,
}
pub trait CatalogResolvable<T> {
fn resolve(&self, entry_name: &str, catalogs: &CatalogManager) -> Result<ResolvedCatalog<T>>;
}
pub struct CatalogManager {
pub controller_catalogs: HashMap<String, ControllerCatalog>,
pub trajectory_catalogs: HashMap<String, TrajectoryCatalog>,
pub route_catalogs: HashMap<String, RouteCatalog>,
pub environment_catalogs: HashMap<String, EnvironmentCatalog>,
pub parameter_resolver: ParameterSubstitutionEngine,
}
pub struct CatalogResolver {
resolution_stack: HashSet<String>,
catalog_manager: Option<CatalogManager>,
}
impl Default for CatalogManager {
fn default() -> Self {
Self::new()
}
}
impl CatalogManager {
pub fn new() -> Self {
Self {
controller_catalogs: HashMap::new(),
trajectory_catalogs: HashMap::new(),
route_catalogs: HashMap::new(),
environment_catalogs: HashMap::new(),
parameter_resolver: ParameterSubstitutionEngine::new(),
}
}
pub fn add_controller_catalog(&mut self, name: String, catalog: ControllerCatalog) {
self.controller_catalogs.insert(name, catalog);
}
pub fn add_trajectory_catalog(&mut self, name: String, catalog: TrajectoryCatalog) {
self.trajectory_catalogs.insert(name, catalog);
}
pub fn add_route_catalog(&mut self, name: String, catalog: RouteCatalog) {
self.route_catalogs.insert(name, catalog);
}
pub fn add_environment_catalog(&mut self, name: String, catalog: EnvironmentCatalog) {
self.environment_catalogs.insert(name, catalog);
}
pub fn set_parameters(&mut self, parameters: HashMap<String, String>) -> Result<()> {
self.parameter_resolver.set_parameters(parameters)
}
pub fn set_parameter(&mut self, name: String, value: String) -> Result<()> {
self.parameter_resolver.set_parameter(name, value)
}
pub fn parameter_context(&self) -> &HashMap<String, String> {
self.parameter_resolver.context()
}
pub fn resolve_controller_reference(
&self,
catalog_name: &str,
entry_name: &str,
) -> Result<ResolvedCatalog<CatalogController>> {
self.resolve_controller_reference_with_params(catalog_name, entry_name, &HashMap::new())
}
pub fn resolve_controller_reference_with_params(
&self,
catalog_name: &str,
entry_name: &str,
params: &HashMap<String, String>,
) -> Result<ResolvedCatalog<CatalogController>> {
if let Some(catalog) = self.controller_catalogs.get(catalog_name) {
for controller in &catalog.controllers {
if controller.name == entry_name {
let resolved_controller =
if !params.is_empty() || !self.parameter_resolver.context().is_empty() {
let _param_engine = self
.parameter_resolver
.with_additional_context(params.clone());
controller.clone()
} else {
controller.clone()
};
return Ok(ResolvedCatalog::with_parameters(
resolved_controller,
format!("controller_catalog:{}", catalog_name),
entry_name.to_string(),
params.clone(),
));
}
}
let available: Vec<String> =
catalog.controllers.iter().map(|c| c.name.clone()).collect();
return Err(Error::catalog_entry_not_found(catalog_name, entry_name)
.with_context(&format!("Available controllers: {}", available.join(", "))));
}
let available: Vec<String> = self.controller_catalogs.keys().cloned().collect();
Err(Error::catalog_not_found(catalog_name, &available))
}
pub fn resolve_trajectory_reference(
&self,
catalog_name: &str,
entry_name: &str,
) -> Result<ResolvedCatalog<CatalogTrajectory>> {
self.resolve_trajectory_reference_with_params(catalog_name, entry_name, &HashMap::new())
}
pub fn resolve_trajectory_reference_with_params(
&self,
catalog_name: &str,
entry_name: &str,
params: &HashMap<String, String>,
) -> Result<ResolvedCatalog<CatalogTrajectory>> {
if let Some(catalog) = self.trajectory_catalogs.get(catalog_name) {
for trajectory in &catalog.trajectories {
if trajectory.name == entry_name {
return Ok(ResolvedCatalog::with_parameters(
trajectory.clone(),
format!("trajectory_catalog:{}", catalog_name),
entry_name.to_string(),
params.clone(),
));
}
}
let available: Vec<String> = catalog
.trajectories
.iter()
.map(|t| t.name.clone())
.collect();
return Err(Error::catalog_entry_not_found(catalog_name, entry_name)
.with_context(&format!("Available trajectories: {}", available.join(", "))));
}
let available: Vec<String> = self.trajectory_catalogs.keys().cloned().collect();
Err(Error::catalog_not_found(catalog_name, &available))
}
pub fn resolve_route_reference(
&self,
catalog_name: &str,
entry_name: &str,
) -> Result<ResolvedCatalog<CatalogRoute>> {
self.resolve_route_reference_with_params(catalog_name, entry_name, &HashMap::new())
}
pub fn resolve_route_reference_with_params(
&self,
catalog_name: &str,
entry_name: &str,
params: &HashMap<String, String>,
) -> Result<ResolvedCatalog<CatalogRoute>> {
if let Some(catalog) = self.route_catalogs.get(catalog_name) {
for route in &catalog.routes {
if route.name == entry_name {
return Ok(ResolvedCatalog::with_parameters(
route.clone(),
format!("route_catalog:{}", catalog_name),
entry_name.to_string(),
params.clone(),
));
}
}
let available: Vec<String> = catalog.routes.iter().map(|r| r.name.clone()).collect();
return Err(Error::catalog_entry_not_found(catalog_name, entry_name)
.with_context(&format!("Available routes: {}", available.join(", "))));
}
let available: Vec<String> = self.route_catalogs.keys().cloned().collect();
Err(Error::catalog_not_found(catalog_name, &available))
}
pub fn resolve_environment_reference(
&self,
catalog_name: &str,
entry_name: &str,
) -> Result<ResolvedCatalog<CatalogEnvironment>> {
self.resolve_environment_reference_with_params(catalog_name, entry_name, &HashMap::new())
}
pub fn resolve_environment_reference_with_params(
&self,
catalog_name: &str,
entry_name: &str,
params: &HashMap<String, String>,
) -> Result<ResolvedCatalog<CatalogEnvironment>> {
if let Some(catalog) = self.environment_catalogs.get(catalog_name) {
for environment in &catalog.environments {
if environment.name == entry_name {
return Ok(ResolvedCatalog::with_parameters(
environment.clone(),
format!("environment_catalog:{}", catalog_name),
entry_name.to_string(),
params.clone(),
));
}
}
let available: Vec<String> = catalog
.environments
.iter()
.map(|e| e.name.clone())
.collect();
return Err(Error::catalog_entry_not_found(catalog_name, entry_name)
.with_context(&format!("Available environments: {}", available.join(", "))));
}
let available: Vec<String> = self.environment_catalogs.keys().cloned().collect();
Err(Error::catalog_not_found(catalog_name, &available))
}
}
impl CatalogResolver {
pub fn new() -> Self {
Self {
resolution_stack: HashSet::new(),
catalog_manager: None,
}
}
pub fn with_catalog_manager(catalog_manager: CatalogManager) -> Self {
Self {
resolution_stack: HashSet::new(),
catalog_manager: Some(catalog_manager),
}
}
pub fn set_catalog_manager(&mut self, catalog_manager: CatalogManager) {
self.catalog_manager = Some(catalog_manager);
}
pub fn catalog_manager(&self) -> Option<&CatalogManager> {
self.catalog_manager.as_ref()
}
pub fn begin_resolution(&mut self, reference_key: &str) -> Result<()> {
if self.resolution_stack.contains(reference_key) {
return Err(Error::circular_dependency(reference_key));
}
self.resolution_stack.insert(reference_key.to_string());
Ok(())
}
pub fn end_resolution(&mut self, reference_key: &str) {
self.resolution_stack.remove(reference_key);
}
pub fn is_resolving(&self, reference_key: &str) -> bool {
self.resolution_stack.contains(reference_key)
}
pub fn clear(&mut self) {
self.resolution_stack.clear();
}
}
impl Default for CatalogResolver {
fn default() -> Self {
Self::new()
}
}
impl<T> ResolvedCatalog<T> {
pub fn new(entity: T, catalog_path: String, entity_name: String) -> Self {
Self {
entity,
metadata: ResolutionMetadata {
catalog_path,
entity_name,
parameter_substitutions: HashMap::new(),
},
}
}
pub fn with_parameters(
entity: T,
catalog_path: String,
entity_name: String,
parameters: HashMap<String, String>,
) -> Self {
Self {
entity,
metadata: ResolutionMetadata {
catalog_path,
entity_name,
parameter_substitutions: parameters,
},
}
}
pub fn into_entity(self) -> T {
self.entity
}
pub fn entity(&self) -> &T {
&self.entity
}
pub fn metadata(&self) -> &ResolutionMetadata {
&self.metadata
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_resolver_creation() {
let resolver = CatalogResolver::new();
assert!(!resolver.is_resolving("test"));
}
#[test]
fn test_circular_dependency_detection() {
let mut resolver = CatalogResolver::new();
assert!(resolver.begin_resolution("vehicle1").is_ok());
assert!(resolver.is_resolving("vehicle1"));
assert!(resolver.begin_resolution("vehicle1").is_err());
resolver.end_resolution("vehicle1");
assert!(!resolver.is_resolving("vehicle1"));
assert!(resolver.begin_resolution("vehicle1").is_ok());
resolver.end_resolution("vehicle1");
}
#[test]
fn test_resolved_catalog() {
let entity = "test_vehicle".to_string();
let resolved = ResolvedCatalog::new(
entity.clone(),
"/path/to/catalog.xosc".to_string(),
"TestVehicle".to_string(),
);
assert_eq!(resolved.entity(), &entity);
assert_eq!(resolved.metadata().catalog_path, "/path/to/catalog.xosc");
assert_eq!(resolved.metadata().entity_name, "TestVehicle");
assert!(resolved.metadata().parameter_substitutions.is_empty());
}
#[test]
fn test_resolved_catalog_with_parameters() {
let mut params = HashMap::new();
params.insert("MaxSpeed".to_string(), "60.0".to_string());
params.insert("Color".to_string(), "Red".to_string());
let resolved = ResolvedCatalog::with_parameters(
42u32,
"/catalogs/vehicles.xosc".to_string(),
"SportsCar".to_string(),
params.clone(),
);
assert_eq!(*resolved.entity(), 42u32);
assert_eq!(resolved.metadata().parameter_substitutions, params);
}
}