Skip to main content

Mountain/Environment/ConfigurationProvider/
Loading.rs

1//! Configuration loading and merging utilities.
2
3use std::{path::PathBuf, sync::Arc};
4
5use CommonLibrary::{
6	Effect::ApplicationRunTime::ApplicationRunTime as _,
7	Error::CommonError::CommonError,
8	FileSystem::ReadFile::ReadFile,
9};
10use serde_json::{Map, Value};
11use tauri::Manager;
12
13use crate::{
14	ApplicationState::DTO::MergedConfigurationStateDTO::MergedConfigurationStateDTO,
15	Environment::Utility,
16	RunTime::ApplicationRunTime::RuntimeStruct::ApplicationRunTime,
17	dev_log,
18};
19
20/// An internal helper to read and parse a single JSON configuration file.
21pub(super) async fn read_and_parse_configuration_file(
22	environment:&crate::Environment::MountainEnvironment::MountainEnvironment,
23	path:&Option<PathBuf>,
24) -> Result<Value, CommonError> {
25	if let Some(p) = path {
26		let runtime = environment.ApplicationHandle.state::<Arc<ApplicationRunTime>>().inner().clone();
27
28		if let Ok(bytes) = runtime.Run(ReadFile(p.clone())).await {
29			return Ok(serde_json::from_slice(&bytes).unwrap_or_else(|_| Value::Object(Map::new())));
30		}
31	}
32
33	Ok(Value::Object(Map::new()))
34}
35
36/// Logic to load and merge all configuration files into the effective
37/// configuration stored in `ApplicationState`.
38pub async fn initialize_and_merge_configurations(
39	environment:&crate::Environment::MountainEnvironment::MountainEnvironment,
40) -> Result<(), CommonError> {
41	dev_log!(
42		"config",
43		"[ConfigurationProvider] Re-initializing and merging all configurations..."
44	);
45
46	let default_config = collect_default_configurations(&environment.ApplicationState)?;
47
48	let user_settings_path = environment
49		.ApplicationHandle
50		.path()
51		.app_config_dir()
52		.map(|p| p.join("settings.json"))
53		.ok();
54
55	let workspace_settings_path = environment
56		.ApplicationState
57		.Workspace
58		.WorkspaceConfigurationPath
59		.lock()
60		.map_err(Utility::MapApplicationStateLockErrorToCommonError)?
61		.clone();
62
63	let user_config = read_and_parse_configuration_file(environment, &user_settings_path).await?;
64
65	let workspace_config = read_and_parse_configuration_file(environment, &workspace_settings_path).await?;
66
67	// A true deep merge is required here. The merge order matches the cascade:
68	// Default (base) → User (overrides default) → Workspace (overrides user)
69	let mut merged = default_config.as_object().cloned().unwrap_or_default();
70
71	if let Some(user_map) = user_config.as_object() {
72		for (key, value) in user_map {
73			// Deep merge nested objects, shallow merge at root level
74			if value.is_object() && merged.get(key.as_str()).is_some_and(|v| v.is_object()) {
75				if let (Some(user_value), Some(_base_value)) =
76					(value.as_object(), merged.get(key.as_str()).and_then(|v| v.as_object()))
77				{
78					for (inner_key, inner_value) in user_value {
79						merged.get_mut(key.as_str()).and_then(|v| v.as_object_mut()).map(|m| {
80							m.insert(inner_key.clone(), inner_value.clone());
81						});
82					}
83				}
84			} else {
85				merged.insert(key.clone(), value.clone());
86			}
87		}
88	}
89
90	if let Some(workspace_map) = workspace_config.as_object() {
91		for (key, value) in workspace_map {
92			if value.is_object() && merged.get(key.as_str()).is_some_and(|v| v.is_object()) {
93				if let (Some(workspace_value), Some(_base_value)) =
94					(value.as_object(), merged.get(key.as_str()).and_then(|v| v.as_object()))
95				{
96					for (inner_key, inner_value) in workspace_value {
97						merged.get_mut(key.as_str()).and_then(|v| v.as_object_mut()).map(|m| {
98							m.insert(inner_key.clone(), inner_value.clone());
99						});
100					}
101				}
102			} else {
103				merged.insert(key.clone(), value.clone());
104			}
105		}
106	}
107
108	let configuration_size = merged.len();
109	let final_config = MergedConfigurationStateDTO::Create(Value::Object(merged));
110
111	*environment
112		.ApplicationState
113		.Configuration
114		.GlobalConfiguration
115		.lock()
116		.map_err(Utility::MapApplicationStateLockErrorToCommonError)? = final_config.Data;
117
118	dev_log!(
119		"config",
120		"[ConfigurationProvider] Configuration merged successfully with {} top-level keys.",
121		configuration_size
122	);
123
124	Ok(())
125}
126
127/// Collects default configurations from all installed extensions.
128pub(super) fn collect_default_configurations(
129	application_state:&crate::ApplicationState::ApplicationState,
130) -> Result<Value, CommonError> {
131	let mut default_config = Map::new();
132
133	// Collect configurations from all extensions' contributes.configuration
134	for extension in application_state
135		.Extension
136		.ScannedExtensions
137		.ScannedExtensions
138		.lock()
139		.map_err(Utility::MapApplicationStateLockErrorToCommonError)?
140		.values()
141	{
142		if let Some(contributes) = &extension.Contributes {
143			if let Some(config_array) = contributes.get("configuration").and_then(|c| c.as_array()) {
144				for config_value in config_array {
145					// Each config contribution may have "key" and "value"
146					if let (Some(key), Some(value)) =
147						(config_value.get("key").and_then(|k| k.as_str()), config_value.get("value"))
148					{
149						if let Some(value_obj) = value.as_object() {
150							default_config.insert(key.to_string(), Value::Object(value_obj.clone()));
151						}
152					}
153				}
154			}
155		}
156	}
157
158	Ok(Value::Object(default_config))
159}