agentmux_srv/backend/wconfig/
state.rs1use std::sync::{Arc, RwLock};
7
8use super::types::{FullConfigType, SettingsType};
9
10pub struct ConfigState {
26 config: RwLock<Arc<FullConfigType>>,
27}
28
29impl ConfigState {
30 pub fn new() -> Self {
32 Self {
33 config: RwLock::new(Arc::new(FullConfigType::default())),
34 }
35 }
36
37 pub fn with_config(config: FullConfigType) -> Self {
39 Self {
40 config: RwLock::new(Arc::new(config)),
41 }
42 }
43
44 pub fn get_full_config(&self) -> Arc<FullConfigType> {
46 self.config.read().unwrap().clone()
47 }
48
49 pub fn get_settings(&self) -> SettingsType {
51 self.config.read().unwrap().settings.clone()
52 }
53
54 pub fn update_settings(&self, settings: SettingsType) {
58 let mut current = self.config.write().unwrap();
59 let mut new_config = (**current).clone();
60 new_config.connections = settings.connections.clone();
61 new_config.settings = settings;
62 *current = Arc::new(new_config);
63 }
64
65 pub fn update_widgets(&self, widgets: std::collections::HashMap<String, super::WidgetConfigType>) {
68 let mut current = self.config.write().unwrap();
69 let mut new_config = (**current).clone();
70 new_config.widgets = widgets;
71 *current = Arc::new(new_config);
72 }
73
74 pub fn update_browser_start_page(&self, url: Option<String>) {
78 let mut current = self.config.write().unwrap();
79 let mut new_config = (**current).clone();
80 new_config.browser_start_page = url;
81 *current = Arc::new(new_config);
82 }
83}
84
85impl Default for ConfigState {
86 fn default() -> Self {
87 Self::new()
88 }
89}
90
91#[cfg(test)]
92mod tests {
93 use super::*;
94
95 #[test]
99 fn connection_settings_in_settings_json_reach_the_full_config() {
100 let settings: SettingsType = serde_json::from_value(serde_json::json!({
101 "term:theme": "default-dark",
102 "connections": {
103 "db1": { "term:durable": false, "display:name": "prod-db", "display:color": "#e5484d" }
104 }
105 }))
106 .unwrap();
107 assert!(!settings.extra.contains_key("connections"), "typed, not a catch-all key");
108
109 let state = ConfigState::new();
110 state.update_settings(settings);
111 let config = state.get_full_config();
112 let db1 = config.connections.get("db1").expect("db1's settings are loaded");
113 assert_eq!(db1.term_durable, Some(false));
114 assert_eq!(db1.display_name, "prod-db");
115 assert_eq!(db1.display_color, "#e5484d");
116 }
117
118 #[test]
119 fn removing_a_connection_from_settings_removes_its_settings() {
120 let state = ConfigState::new();
121 state.update_settings(
122 serde_json::from_value(serde_json::json!({ "connections": { "db1": { "display:pinned": true } } })).unwrap(),
123 );
124 state.update_settings(SettingsType::default());
125 assert!(state.get_full_config().connections.is_empty());
126 }
127
128 #[test]
131 fn a_bad_connection_entry_does_not_fail_the_settings() {
132 let settings: SettingsType = serde_json::from_value(serde_json::json!({
133 "term:theme": "default-dark",
134 "connections": {
135 "bad": { "term:durable": "yes" },
136 "good": { "display:pinned": true }
137 }
138 }))
139 .expect("the file still parses");
140 assert_eq!(settings.term_theme, "default-dark");
141 assert!(!settings.connections.contains_key("bad"));
142 assert_eq!(settings.connections["good"].display_pinned, Some(true));
143
144 let not_an_object: SettingsType =
145 serde_json::from_value(serde_json::json!({ "term:theme": "x", "connections": "nope" })).unwrap();
146 assert_eq!(not_an_object.term_theme, "x");
147 assert!(not_an_object.connections.is_empty());
148 }
149}