Skip to main content

Mountain/Environment/ConfigurationProvider/
GetValue.rs

1//! Configuration value retrieval.
2//!
3//! Implements `GetConfigurationValue` for `MountainEnvironment`. Reads
4//! from the pre-merged `ApplicationState::Configuration::GlobalConfiguration`
5//! cache - no disk I/O on the hot path.
6//!
7//! If `section` is `None`, the entire merged object is returned. If it
8//! is `Some("a.b.c")`, the key is split on `.` and the function walks
9//! the nested JSON tree one segment at a time, returning `Value::Null`
10//! (not an error) for any missing intermediate or leaf node. This
11//! matches VS Code's behaviour where `getConfiguration('a.b').get('c')`
12//! returns `undefined` rather than throwing.
13
14use CommonLibrary::{
15	Configuration::DTO::ConfigurationOverridesDTO::ConfigurationOverridesDTO,
16	Error::CommonError::CommonError,
17};
18use serde_json::Value;
19
20use crate::dev_log;
21
22/// Retrieves a configuration value from the cached, merged configuration.
23/// When `overrides.OverrideIdentifier` is set, language-scoped values
24/// from `[<language>]` blocks in settings.json take precedence over base
25/// values.
26pub(super) async fn get_configuration_value(
27	environment:&crate::Environment::MountainEnvironment::MountainEnvironment,
28
29	section:Option<String>,
30
31	overrides:ConfigurationOverridesDTO,
32) -> Result<Value, CommonError> {
33	dev_log!(
34		"config",
35		"[ConfigurationProvider] Getting configuration for section: {:?} (language: {:?})",
36		section,
37		overrides.OverrideIdentifier
38	);
39
40	let configuration_guard = environment
41		.ApplicationState
42		.Configuration
43		.GlobalConfiguration
44		.lock()
45		.map_err(|e| CommonError::StateLockPoisoned { Context:format!("Failed to lock configuration: {}", e) })?;
46
47	// Base value from merged config.
48	let base_value = match section.as_deref() {
49		None => (*configuration_guard).clone(),
50
51		Some(section_path) => {
52			let mut current = &*configuration_guard;
53
54			for key in section_path.split('.') {
55				current = match current.get(key) {
56					Some(value) => value,
57
58					None => {
59						dev_log!(
60							"config",
61							"warn: [ConfigurationProvider] Configuration section '{}' not found in path: {:?}",
62							key,
63							section_path
64						);
65
66						return Ok(Value::Null);
67					},
68				};
69			}
70
71			current.clone()
72		},
73	};
74
75	// If a language override is requested, check for `[<language>]` blocks in
76	// the merged config and overlay any matching keys on top of the base value.
77	// VS Code uses `[rust]`, `[typescript]`, etc. as top-level keys.
78	let configuration_value = if let Some(ref lang_id) = overrides.OverrideIdentifier {
79		let lang = lang_id.as_str();
80		let lang_block_key = format!("[{}]", lang);
81		if let Some(lang_block) = configuration_guard.get(&lang_block_key).and_then(|v| v.as_object()) {
82			match section.as_deref() {
83				None => {
84					// Return the whole merged config with language block applied.
85					let mut merged = if let Some(obj) = base_value.as_object() {
86						obj.clone()
87					} else {
88						return Ok(base_value);
89					};
90					for (k, v) in lang_block {
91						merged.insert(k.clone(), v.clone());
92					}
93					Value::Object(merged)
94				},
95				Some(section_path) => {
96					// Check if the language block overrides this specific section key.
97					let top_key = section_path.split('.').next().unwrap_or(section_path);
98					if let Some(lang_value) = lang_block.get(top_key) {
99						let remainder:Vec<&str> = section_path.splitn(2, '.').skip(1).collect();
100						if remainder.is_empty() {
101							lang_value.clone()
102						} else {
103							let mut cur = lang_value;
104							for k in remainder[0].split('.') {
105								match cur.get(k) {
106									Some(v) => cur = v,
107									None => return Ok(base_value),
108								}
109							}
110							cur.clone()
111						}
112					} else {
113						base_value
114					}
115				},
116			}
117		} else {
118			base_value
119		}
120	} else {
121		base_value
122	};
123
124	// Validate that the configuration value exists
125	if configuration_value.is_null() {
126		dev_log!(
127			"config",
128			"warn: [ConfigurationProvider] Configuration section not found: {:?}",
129			section
130		);
131	}
132
133	Ok(configuration_value)
134}