Skip to main content

routee_compass/app/compass/
compass_app_config.rs

1use std::{path::Path, sync::Arc};
2
3use config::Config;
4use routee_compass_core::{
5    algorithm::search::SearchAlgorithmConfig,
6    config::{ConfigJsonExtensions, OneOrMany},
7    model::{
8        constraint::ConstraintModelService, cost::CostModelConfig, map::MapModelConfig,
9        network::GraphConfig, state::StateVariableConfig, termination::TerminationModel,
10        traversal::TraversalModelService,
11    },
12};
13use serde::{Deserialize, Serialize};
14use serde_json::Value;
15
16use crate::{
17    app::compass::{CompassAppError, CompassAppSystemParameters, CompassBuilderInventory},
18    plugin::PluginConfig,
19};
20
21/// high-level application configuration that orchestrates together
22/// configuration requirements for the various components making up a
23/// [`CompassApp`].
24#[derive(Serialize, Deserialize, Clone, Debug)]
25pub struct CompassAppConfig {
26    pub algorithm: SearchAlgorithmConfig,
27    pub state: Option<Vec<(String, StateVariableConfig)>>,
28    pub cost: CostModelConfig,
29    pub label: Value,
30    pub mapping: MapModelConfig,
31    pub graph: GraphConfig,
32    /// section containing a single search config or an array of search configs (OneOrMany).
33    pub search: OneOrMany<SearchConfig>,
34    pub plugin: PluginConfig,
35    pub termination: TerminationModel,
36    pub system: CompassAppSystemParameters,
37    pub map_matching: Value,
38}
39
40/// sub-section of [`CompassAppConfig`] where the [`TraversalModelService`], [`AccessModelService`], and [`ConstraintModelService`] components
41/// for an [`EdgeList`] are specified.
42#[derive(Serialize, Deserialize, Clone, Debug)]
43pub struct SearchConfig {
44    pub traversal: Value,
45    pub constraint: Value,
46}
47
48impl CompassAppConfig {
49    /// reads a stringified configuration file with provided format and constructs a [`CompassAppConfig`]
50    pub fn from_str(
51        config: &str,
52        config_path: &str,
53        format: config::FileFormat,
54    ) -> Result<CompassAppConfig, CompassAppError> {
55        let default_config = config::File::from_str(
56            include_str!("config.default.toml"),
57            config::FileFormat::Toml,
58        );
59
60        let user_config = config::File::from_str(config, format);
61
62        let config = Config::builder()
63            .add_source(default_config)
64            .add_source(user_config)
65            .build()?;
66
67        let config_json = config
68            .clone()
69            .try_deserialize::<serde_json::Value>()?
70            .normalize_file_paths(Path::new(config_path), None)?;
71        let compass_config: CompassAppConfig = serde_json::from_value(config_json)?;
72
73        Ok(compass_config)
74    }
75}
76
77impl TryFrom<&Path> for CompassAppConfig {
78    type Error = CompassAppError;
79
80    fn try_from(config_path: &Path) -> Result<Self, Self::Error> {
81        let default_config = config::File::from_str(
82            include_str!("config.default.toml"),
83            config::FileFormat::Toml,
84        );
85
86        let config = Config::builder()
87            .add_source(default_config)
88            .add_source(config::File::from(config_path))
89            .build()?;
90
91        let config_json = config
92            .clone()
93            .try_deserialize::<serde_json::Value>()?
94            .normalize_file_paths(config_path, None)?;
95        let compass_config: CompassAppConfig =
96            serde_json::from_value(config_json).map_err(|e| {
97                let filename = config_path.to_str().unwrap_or("<config path>");
98                CompassAppError::BuildFailure(format!("while reading {filename}: {e}"))
99            })?;
100
101        Ok(compass_config)
102    }
103}
104
105impl CompassAppConfig {
106    /// Returns a pretty-printed JSON representation of this config.
107    /// Useful for debugging and logging the exact configuration being used.
108    pub fn to_pretty_string(&self) -> Result<String, CompassAppError> {
109        serde_json::to_string_pretty(self).map_err(|e| {
110            CompassAppError::BuildFailure(format!("Failed to serialize config: {}", e))
111        })
112    }
113
114    pub fn build_traversal_model_services(
115        &self,
116        builders: &CompassBuilderInventory,
117    ) -> Result<Vec<Arc<dyn TraversalModelService>>, CompassAppError> {
118        let result = self
119            .search
120            .iter()
121            .map(|el| builders.build_traversal_model_service(&el.traversal))
122            .collect::<Result<Vec<_>, _>>()?;
123        Ok(result)
124    }
125
126    pub fn build_constraint_model_services(
127        &self,
128        builders: &CompassBuilderInventory,
129    ) -> Result<Vec<Arc<dyn ConstraintModelService>>, CompassAppError> {
130        let result = self
131            .search
132            .iter()
133            .map(|el| builders.build_constraint_model_service(&el.constraint))
134            .collect::<Result<Vec<_>, _>>()?;
135        Ok(result)
136    }
137}