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 log::info;
11use serde_json::{Map, Value};
12use tauri::Manager;
13
14use crate::{
15	ApplicationState::DTO::MergedConfigurationStateDTO::MergedConfigurationStateDTO,
16	Environment::Utility,
17	RunTime::ApplicationRunTime::RuntimeStruct::ApplicationRunTime,
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	info!("[ConfigurationProvider] Re-initializing and merging all configurations...");
42
43	let default_config = collect_default_configurations(&environment.ApplicationState)?;
44
45	let user_settings_path = environment
46		.ApplicationHandle
47		.path()
48		.app_config_dir()
49		.map(|p| p.join("settings.json"))
50		.ok();
51
52	let workspace_settings_path = environment
53		.ApplicationState
54		.Workspace
55		.WorkspaceConfigurationPath
56		.lock()
57		.map_err(Utility::MapApplicationStateLockErrorToCommonError)?
58		.clone();
59
60	let user_config = read_and_parse_configuration_file(environment, &user_settings_path).await?;
61
62	let workspace_config = read_and_parse_configuration_file(environment, &workspace_settings_path).await?;
63
64	// A true deep merge is required here. The merge order matches the cascade:
65	// Default (base) → User (overrides default) → Workspace (overrides user)
66	let mut merged = default_config.as_object().cloned().unwrap_or_default();
67
68	if let Some(user_map) = user_config.as_object() {
69		for (key, value) in user_map {
70			// Deep merge nested objects, shallow merge at root level
71			if value.is_object() && merged.get(key.as_str()).is_some_and(|v| v.is_object()) {
72				if let (Some(user_value), Some(_base_value)) =
73					(value.as_object(), merged.get(key.as_str()).and_then(|v| v.as_object()))
74				{
75					for (inner_key, inner_value) in user_value {
76						merged.get_mut(key.as_str()).and_then(|v| v.as_object_mut()).map(|m| {
77							m.insert(inner_key.clone(), inner_value.clone());
78						});
79					}
80				}
81			} else {
82				merged.insert(key.clone(), value.clone());
83			}
84		}
85	}
86
87	if let Some(workspace_map) = workspace_config.as_object() {
88		for (key, value) in workspace_map {
89			if value.is_object() && merged.get(key.as_str()).is_some_and(|v| v.is_object()) {
90				if let (Some(workspace_value), Some(_base_value)) =
91					(value.as_object(), merged.get(key.as_str()).and_then(|v| v.as_object()))
92				{
93					for (inner_key, inner_value) in workspace_value {
94						merged.get_mut(key.as_str()).and_then(|v| v.as_object_mut()).map(|m| {
95							m.insert(inner_key.clone(), inner_value.clone());
96						});
97					}
98				}
99			} else {
100				merged.insert(key.clone(), value.clone());
101			}
102		}
103	}
104
105	let configuration_size = merged.len();
106	let final_config = MergedConfigurationStateDTO::Create(Value::Object(merged));
107
108	*environment
109		.ApplicationState
110		.Configuration
111		.GlobalConfiguration
112		.lock()
113		.map_err(Utility::MapApplicationStateLockErrorToCommonError)? = final_config.Data;
114
115	info!(
116		"[ConfigurationProvider] Configuration merged successfully with {} top-level keys.",
117		configuration_size
118	);
119
120	Ok(())
121}
122
123/// Collects default configurations from all installed extensions.
124pub(super) fn collect_default_configurations(
125	application_state:&crate::ApplicationState::ApplicationState,
126) -> Result<Value, CommonError> {
127	let mut default_config = Map::new();
128
129	// Collect configurations from all extensions' contributes.configuration
130	for extension in application_state
131		.Extension
132		.ScannedExtensions
133		.ScannedExtensions
134		.lock()
135		.map_err(Utility::MapApplicationStateLockErrorToCommonError)?
136		.values()
137	{
138		if let Some(contributes) = &extension.Contributes {
139			if let Some(config_array) = contributes.get("configuration").and_then(|c| c.as_array()) {
140				for config_value in config_array {
141					// Each config contribution may have "key" and "value"
142					if let (Some(key), Some(value)) =
143						(config_value.get("key").and_then(|k| k.as_str()), config_value.get("value"))
144					{
145						if let Some(value_obj) = value.as_object() {
146							default_config.insert(key.to_string(), Value::Object(value_obj.clone()));
147						}
148					}
149				}
150			}
151		}
152	}
153
154	Ok(Value::Object(default_config))
155}