Mountain/ApplicationState/DTO/
MergedConfigurationStateDTO.rs

1//! # MergedConfigurationStateDTO
2//!
3//! # RESPONSIBILITY
4//! - Data transfer object for merged application configuration
5//! - Serializable format for gRPC/IPC transmission
6//! - Used by Mountain to provide final effective configuration to UI features
7//!
8//! # FIELDS
9//! - Data: Merged configuration JSON object from all sources
10
11use serde::{Deserialize, Serialize};
12use serde_json::Value;
13
14/// Maximum configuration depth to prevent stack overflow from deeply nested
15/// paths
16const MAX_CONFIGURATION_DEPTH:usize = 50;
17
18/// Represents the final, effective configuration after merging settings from
19/// all sources (default, user, workspace, folder). This merged view is what
20/// is queried by application features.
21#[derive(Serialize, Deserialize, Clone, Debug, Default)]
22#[serde(rename_all = "PascalCase")]
23pub struct MergedConfigurationStateDTO {
24	/// Merged configuration data from all sources
25	pub Data:Value,
26}
27
28impl MergedConfigurationStateDTO {
29	/// Creates a new `MergedConfigurationStateDTO` from a `serde_json::Value`.
30	///
31	/// # Arguments
32	/// * `Data` - The merged configuration JSON value
33	///
34	/// # Returns
35	/// New MergedConfigurationStateDTO instance
36	pub fn Create(Data:Value) -> Self { Self { Data } }
37
38	/// Gets a specific value from the configuration using a dot-separated path.
39	/// If the section is `None`, it returns the entire configuration object.
40	///
41	/// # Arguments
42	/// * `Section` - Optional dot-separated path (e.g., "editor.fontSize")
43	///
44	/// # Returns
45	/// The configuration value at the path, or Null if not found
46	pub fn GetValue(&self, Section:Option<&str>) -> Value {
47		if let Some(Path) = Section {
48			let Depth = Path.matches('.').count();
49			if Depth > MAX_CONFIGURATION_DEPTH {
50				log::warn!(
51					"Configuration path depth {} exceeds maximum of {}",
52					Depth,
53					MAX_CONFIGURATION_DEPTH
54				);
55				return Value::Null;
56			}
57
58			Path.split('.')
59				.try_fold(&self.Data, |Node, Key| Node.get(Key))
60				.unwrap_or(&Value::Null)
61				.clone()
62		} else {
63			self.Data.clone()
64		}
65	}
66
67	/// Sets a value in the configuration using a dot-separated path.
68	/// Creates nested objects as needed.
69	///
70	/// # Arguments
71	/// * `Section` - Dot-separated path
72	/// * `Value` - Value to set
73	///
74	/// # Returns
75	/// Result indicating success or error if path too deep
76	pub fn SetValue(&mut self, Section:&str, Value:Value) -> Result<(), String> {
77		let Depth = Section.matches('.').count();
78		if Depth > MAX_CONFIGURATION_DEPTH {
79			return Err(format!(
80				"Configuration path depth {} exceeds maximum of {}",
81				Depth, MAX_CONFIGURATION_DEPTH
82			));
83		}
84
85		let Keys:Vec<&str> = Section.split('.').collect();
86
87		if Keys.is_empty() {
88			return Err("Section path cannot be empty".to_string());
89		}
90
91		// Navigate or create nested structure
92		let MutData = &mut self.Data;
93		Self::SetValueRecursive(MutData, &Keys, 0, Value);
94		Ok(())
95	}
96
97	/// Recursively navigates and sets values in nested structure.
98	fn SetValueRecursive(Data:&mut Value, Keys:&[&str], Index:usize, Value:Value) {
99		if Index == Keys.len() - 1 {
100			// At final key, set the value
101			*Data = Value;
102		} else if let Some(Map) = Data.as_object_mut() {
103			// Get or create nested object
104			Map.entry(Keys[Index]).or_insert_with(|| Value::Object(serde_json::Map::new()));
105			if let Some(Nested) = Map.get_mut(Keys[Index]) {
106				Self::SetValueRecursive(Nested, Keys, Index + 1, Value);
107			}
108		}
109	}
110
111	/// Gets a boolean value from configuration with default fallback.
112	///
113	/// # Arguments
114	/// * `Section` - Dot-separated path
115	/// * `Default` - Default value if path doesn't exist or isn't a boolean
116	///
117	/// # Returns
118	/// Boolean value or default
119	pub fn GetBool(&self, Section:&str, Default:bool) -> bool {
120		self.GetValue(Some(Section)).as_bool().unwrap_or(Default)
121	}
122
123	/// Gets a numeric value from configuration with default fallback.
124	///
125	/// # Arguments
126	/// * `Section` - Dot-separated path
127	/// * `Default` - Default value if path doesn't exist or isn't a number
128	///
129	/// # Returns
130	/// f64 value or default
131	pub fn GetNumber(&self, Section:&str, Default:f64) -> f64 {
132		self.GetValue(Some(Section)).as_f64().unwrap_or(Default)
133	}
134
135	/// Gets a string value from configuration with default fallback.
136	///
137	/// # Arguments
138	/// * `Section` - Dot-separated path
139	/// * `Default` - Default value if path doesn't exist or isn't a string
140	///
141	/// # Returns
142	/// String value or default
143	pub fn GetString(&self, Section:&str, Default:&str) -> String {
144		self.GetValue(Some(Section)).as_str().unwrap_or(Default).to_string()
145	}
146}