1use anyhow::{anyhow, Result};
2use collections::{hash_map, BTreeMap, HashMap, HashSet};
3use schemars::JsonSchema;
4use serde::{de::DeserializeOwned, Serialize};
5use serde_json::value::RawValue;
6use smallvec::SmallVec;
7use std::{
8 any::{type_name, Any, TypeId},
9 cmp::Ordering,
10 fmt::Debug,
11 mem,
12 path::Path,
13 sync::Arc,
14};
15use util::{merge_non_null_json_value_into, ResultExt as _};
16
17/// A value that can be defined as a user setting.
18///
19/// Settings can be loaded from a combination of multiple JSON files.
20pub trait Setting: 'static + Debug {
21 /// The name of a field within the JSON file from which this setting should
22 /// be deserialized. If this is `None`, then the setting will be deserialized
23 /// from the root object.
24 const FIELD_NAME: Option<&'static str> = None;
25
26 /// The type that is stored in an individual JSON file.
27 type FileContent: DeserializeOwned + JsonSchema;
28
29 /// The logic for combining together values from one or more JSON files into the
30 /// final value for this setting.
31 ///
32 /// The user values are ordered from least specific (the global settings file)
33 /// to most specific (the innermost local settings file).
34 fn load(default_value: &Self::FileContent, user_values: &[&Self::FileContent]) -> Self;
35
36 fn load_via_json_merge(
37 default_value: &Self::FileContent,
38 user_values: &[&Self::FileContent],
39 ) -> Self
40 where
41 Self: DeserializeOwned,
42 Self::FileContent: Serialize,
43 {
44 let mut merged = serde_json::Value::Null;
45 for value in [default_value].iter().chain(user_values) {
46 merge_non_null_json_value_into(serde_json::to_value(value).unwrap(), &mut merged);
47 }
48 serde_json::from_value(merged).unwrap()
49 }
50}
51
52/// A set of strongly-typed setting values defined via multiple JSON files.
53#[derive(Default)]
54pub struct SettingsStore {
55 setting_keys: Vec<(Option<&'static str>, TypeId)>,
56 setting_values: HashMap<TypeId, Box<dyn AnySettingValue>>,
57 default_deserialized_settings: DeserializedSettingMap,
58 user_deserialized_settings: Option<DeserializedSettingMap>,
59 local_deserialized_settings: BTreeMap<Arc<Path>, DeserializedSettingMap>,
60 changed_setting_types: HashSet<TypeId>,
61}
62
63#[derive(Debug)]
64struct SettingValue<T> {
65 global_value: Option<T>,
66 local_values: Vec<(Arc<Path>, T)>,
67}
68
69trait AnySettingValue: Debug {
70 fn setting_type_name(&self) -> &'static str;
71 fn deserialize_setting(&self, json: &str) -> Result<DeserializedSetting>;
72 fn load_setting(
73 &self,
74 default_value: &DeserializedSetting,
75 custom: &[&DeserializedSetting],
76 ) -> Box<dyn Any>;
77 fn value_for_path(&self, path: Option<&Path>) -> &dyn Any;
78 fn set_global_value(&mut self, value: Box<dyn Any>);
79 fn set_local_value(&mut self, path: Arc<Path>, value: Box<dyn Any>);
80}
81
82struct DeserializedSetting(Box<dyn Any>);
83
84type DeserializedSettingMap = HashMap<TypeId, DeserializedSetting>;
85
86impl SettingsStore {
87 /// Add a new type of setting to the store.
88 ///
89 /// This should be done before any settings are loaded.
90 pub fn register_setting<T: Setting>(&mut self) {
91 let type_id = TypeId::of::<T>();
92
93 let entry = self.setting_values.entry(type_id);
94 if matches!(entry, hash_map::Entry::Occupied(_)) {
95 panic!("duplicate setting type: {}", type_name::<T>());
96 }
97 entry.or_insert(Box::new(SettingValue::<T> {
98 global_value: None,
99 local_values: Vec::new(),
100 }));
101
102 match self
103 .setting_keys
104 .binary_search_by_key(&T::FIELD_NAME, |e| e.0)
105 {
106 Ok(ix) | Err(ix) => self.setting_keys.insert(ix, (T::FIELD_NAME, type_id)),
107 }
108 }
109
110 /// Get the value of a setting.
111 ///
112 /// Panics if settings have not yet been loaded, or there is no default
113 /// value for this setting.
114 pub fn get<T: Setting>(&self, path: Option<&Path>) -> &T {
115 self.setting_values
116 .get(&TypeId::of::<T>())
117 .unwrap()
118 .value_for_path(path)
119 .downcast_ref::<T>()
120 .unwrap()
121 }
122
123 /// Set the default settings via a JSON string.
124 ///
125 /// The string should contain a JSON object with a default value for every setting.
126 pub fn set_default_settings(&mut self, default_settings_content: &str) -> Result<()> {
127 self.default_deserialized_settings = self.load_setting_map(default_settings_content)?;
128 if self.default_deserialized_settings.len() != self.setting_keys.len() {
129 return Err(anyhow!(
130 "default settings file is missing fields: {:?}",
131 self.setting_keys
132 .iter()
133 .filter(|(_, type_id)| !self
134 .default_deserialized_settings
135 .contains_key(type_id))
136 .map(|(name, _)| *name)
137 .collect::<Vec<_>>()
138 ));
139 }
140 self.recompute_values(false, None, None);
141 Ok(())
142 }
143
144 /// Set the user settings via a JSON string.
145 pub fn set_user_settings(&mut self, user_settings_content: &str) -> Result<()> {
146 let user_settings = self.load_setting_map(user_settings_content)?;
147 let old_user_settings =
148 mem::replace(&mut self.user_deserialized_settings, Some(user_settings));
149 self.recompute_values(true, None, old_user_settings);
150 Ok(())
151 }
152
153 /// Add or remove a set of local settings via a JSON string.
154 pub fn set_local_settings(
155 &mut self,
156 path: Arc<Path>,
157 settings_content: Option<&str>,
158 ) -> Result<()> {
159 let removed_map = if let Some(settings_content) = settings_content {
160 self.local_deserialized_settings
161 .insert(path.clone(), self.load_setting_map(settings_content)?);
162 None
163 } else {
164 self.local_deserialized_settings.remove(&path)
165 };
166 self.recompute_values(true, Some(&path), removed_map);
167 Ok(())
168 }
169
170 fn recompute_values(
171 &mut self,
172 user_settings_changed: bool,
173 changed_local_path: Option<&Path>,
174 old_settings_map: Option<DeserializedSettingMap>,
175 ) {
176 // Identify all of the setting types that have changed.
177 let new_settings_map = if let Some(changed_path) = changed_local_path {
178 &self.local_deserialized_settings.get(changed_path).unwrap()
179 } else if user_settings_changed {
180 self.user_deserialized_settings.as_ref().unwrap()
181 } else {
182 &self.default_deserialized_settings
183 };
184 self.changed_setting_types.clear();
185 self.changed_setting_types.extend(new_settings_map.keys());
186 if let Some(previous_settings_map) = old_settings_map {
187 self.changed_setting_types
188 .extend(previous_settings_map.keys());
189 }
190
191 // Reload the global and local values for every changed setting.
192 let mut user_values_stack = Vec::<&DeserializedSetting>::new();
193 for setting_type_id in self.changed_setting_types.iter() {
194 let setting_value = self.setting_values.get_mut(setting_type_id).unwrap();
195
196 // Build the prioritized list of deserialized values to pass to the setting's
197 // load function.
198 user_values_stack.clear();
199 if let Some(user_settings) = &self.user_deserialized_settings {
200 if let Some(user_value) = user_settings.get(setting_type_id) {
201 user_values_stack.push(&user_value);
202 }
203 }
204
205 // If the global settings file changed, reload the global value for the field.
206 if changed_local_path.is_none() {
207 let global_value = setting_value.load_setting(
208 &self.default_deserialized_settings[setting_type_id],
209 &user_values_stack,
210 );
211 setting_value.set_global_value(global_value);
212 }
213
214 // Reload the local values for the setting.
215 let user_value_stack_len = user_values_stack.len();
216 for (path, deserialized_values) in &self.local_deserialized_settings {
217 // If a local settings file changed, then avoid recomputing local
218 // settings for any path outside of that directory.
219 if changed_local_path.map_or(false, |changed_local_path| {
220 !path.starts_with(changed_local_path)
221 }) {
222 continue;
223 }
224
225 // Ignore recomputing settings for any path that hasn't customized that setting.
226 let Some(deserialized_value) = deserialized_values.get(setting_type_id) else {
227 continue;
228 };
229
230 // Build a stack of all of the local values for that setting.
231 user_values_stack.truncate(user_value_stack_len);
232 for (preceding_path, preceding_deserialized_values) in
233 &self.local_deserialized_settings
234 {
235 if preceding_path >= path {
236 break;
237 }
238 if !path.starts_with(preceding_path) {
239 continue;
240 }
241
242 if let Some(preceding_deserialized_value) =
243 preceding_deserialized_values.get(setting_type_id)
244 {
245 user_values_stack.push(&*preceding_deserialized_value);
246 }
247 }
248 user_values_stack.push(&*deserialized_value);
249
250 // Load the local value for the field.
251 let local_value = setting_value.load_setting(
252 &self.default_deserialized_settings[setting_type_id],
253 &user_values_stack,
254 );
255 setting_value.set_local_value(path.clone(), local_value);
256 }
257 }
258 }
259
260 /// Deserialize the given JSON string into a map keyed by setting type.
261 ///
262 /// Returns an error if the string doesn't contain a valid JSON object.
263 fn load_setting_map(&self, json: &str) -> Result<DeserializedSettingMap> {
264 let mut map = DeserializedSettingMap::default();
265 let settings_content_by_key: BTreeMap<&str, &RawValue> = serde_json::from_str(json)?;
266 let mut setting_types_by_key = self.setting_keys.iter().peekable();
267
268 // Load all of the fields that don't have a key.
269 while let Some((setting_key, setting_type_id)) = setting_types_by_key.peek() {
270 if setting_key.is_some() {
271 break;
272 }
273 setting_types_by_key.next();
274 if let Some(deserialized_value) = self
275 .setting_values
276 .get(setting_type_id)
277 .unwrap()
278 .deserialize_setting(json)
279 .log_err()
280 {
281 map.insert(*setting_type_id, deserialized_value);
282 }
283 }
284
285 // For each key in the file, load all of the settings that belong to that key.
286 for (key, key_content) in settings_content_by_key {
287 while let Some((setting_key, setting_type_id)) = setting_types_by_key.peek() {
288 let setting_key = setting_key.expect("setting names are ordered");
289 match setting_key.cmp(key) {
290 Ordering::Less => {
291 setting_types_by_key.next();
292 continue;
293 }
294 Ordering::Greater => break,
295 Ordering::Equal => {
296 if let Some(deserialized_value) = self
297 .setting_values
298 .get(setting_type_id)
299 .unwrap()
300 .deserialize_setting(key_content.get())
301 .log_err()
302 {
303 map.insert(*setting_type_id, deserialized_value);
304 }
305 setting_types_by_key.next();
306 }
307 }
308 }
309 }
310 Ok(map)
311 }
312}
313
314impl<T: Setting> AnySettingValue for SettingValue<T> {
315 fn setting_type_name(&self) -> &'static str {
316 type_name::<T>()
317 }
318
319 fn load_setting(
320 &self,
321 default_value: &DeserializedSetting,
322 user_values: &[&DeserializedSetting],
323 ) -> Box<dyn Any> {
324 let default_value = default_value.0.downcast_ref::<T::FileContent>().unwrap();
325 let values: SmallVec<[&T::FileContent; 6]> = user_values
326 .iter()
327 .map(|value| value.0.downcast_ref().unwrap())
328 .collect();
329 Box::new(T::load(default_value, &values))
330 }
331
332 fn deserialize_setting(&self, json: &str) -> Result<DeserializedSetting> {
333 let value = serde_json::from_str::<T::FileContent>(json)?;
334 Ok(DeserializedSetting(Box::new(value)))
335 }
336
337 fn value_for_path(&self, path: Option<&Path>) -> &dyn Any {
338 if let Some(path) = path {
339 for (settings_path, value) in self.local_values.iter().rev() {
340 if path.starts_with(&settings_path) {
341 return value;
342 }
343 }
344 }
345 self.global_value.as_ref().unwrap()
346 }
347
348 fn set_global_value(&mut self, value: Box<dyn Any>) {
349 self.global_value = Some(*value.downcast().unwrap());
350 }
351
352 fn set_local_value(&mut self, path: Arc<Path>, value: Box<dyn Any>) {
353 let value = *value.downcast().unwrap();
354 match self.local_values.binary_search_by_key(&&path, |e| &e.0) {
355 Ok(ix) => self.local_values[ix].1 = value,
356 Err(ix) => self.local_values.insert(ix, (path, value)),
357 }
358 }
359}
360
361impl Debug for SettingsStore {
362 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
363 return f
364 .debug_struct("SettingsStore")
365 .field(
366 "setting_value_sets_by_type",
367 &self
368 .setting_values
369 .values()
370 .map(|set| (set.setting_type_name(), set))
371 .collect::<HashMap<_, _>>(),
372 )
373 .finish_non_exhaustive();
374 }
375}
376
377#[cfg(test)]
378mod tests {
379 use super::*;
380 use serde_derive::Deserialize;
381
382 #[test]
383 fn test_settings_store() {
384 let mut store = SettingsStore::default();
385 store.register_setting::<UserSettings>();
386 store.register_setting::<TurboSetting>();
387 store.register_setting::<MultiKeySettings>();
388
389 // error - missing required field in default settings
390 store
391 .set_default_settings(
392 r#"{
393 "user": {
394 "name": "John Doe",
395 "age": 30,
396 "staff": false
397 }
398 }"#,
399 )
400 .unwrap_err();
401
402 // error - type error in default settings
403 store
404 .set_default_settings(
405 r#"{
406 "turbo": "the-wrong-type",
407 "user": {
408 "name": "John Doe",
409 "age": 30,
410 "staff": false
411 }
412 }"#,
413 )
414 .unwrap_err();
415
416 // valid default settings.
417 store
418 .set_default_settings(
419 r#"{
420 "turbo": false,
421 "user": {
422 "name": "John Doe",
423 "age": 30,
424 "staff": false
425 }
426 }"#,
427 )
428 .unwrap();
429
430 assert_eq!(store.get::<TurboSetting>(None), &TurboSetting(false));
431 assert_eq!(
432 store.get::<UserSettings>(None),
433 &UserSettings {
434 name: "John Doe".to_string(),
435 age: 30,
436 staff: false,
437 }
438 );
439 assert_eq!(
440 store.get::<MultiKeySettings>(None),
441 &MultiKeySettings {
442 key1: String::new(),
443 key2: String::new(),
444 }
445 );
446
447 store
448 .set_user_settings(
449 r#"{
450 "turbo": true,
451 "user": { "age": 31 },
452 "key1": "a"
453 }"#,
454 )
455 .unwrap();
456
457 assert_eq!(store.get::<TurboSetting>(None), &TurboSetting(true));
458 assert_eq!(
459 store.get::<UserSettings>(None),
460 &UserSettings {
461 name: "John Doe".to_string(),
462 age: 31,
463 staff: false
464 }
465 );
466
467 store
468 .set_local_settings(
469 Path::new("/root1").into(),
470 Some(r#"{ "user": { "staff": true } }"#),
471 )
472 .unwrap();
473 store
474 .set_local_settings(
475 Path::new("/root1/subdir").into(),
476 Some(r#"{ "user": { "name": "Jane Doe" } }"#),
477 )
478 .unwrap();
479
480 store
481 .set_local_settings(
482 Path::new("/root2").into(),
483 Some(r#"{ "user": { "age": 42 }, "key2": "b" }"#),
484 )
485 .unwrap();
486
487 assert_eq!(
488 store.get::<UserSettings>(Some(Path::new("/root1/something"))),
489 &UserSettings {
490 name: "John Doe".to_string(),
491 age: 31,
492 staff: true
493 }
494 );
495 assert_eq!(
496 store.get::<UserSettings>(Some(Path::new("/root1/subdir/something"))),
497 &UserSettings {
498 name: "Jane Doe".to_string(),
499 age: 31,
500 staff: true
501 }
502 );
503 assert_eq!(
504 store.get::<UserSettings>(Some(Path::new("/root2/something"))),
505 &UserSettings {
506 name: "John Doe".to_string(),
507 age: 42,
508 staff: false
509 }
510 );
511 assert_eq!(
512 store.get::<MultiKeySettings>(Some(Path::new("/root2/something"))),
513 &MultiKeySettings {
514 key1: "a".to_string(),
515 key2: "b".to_string(),
516 }
517 );
518 }
519
520 #[derive(Debug, PartialEq, Deserialize)]
521 struct UserSettings {
522 name: String,
523 age: u32,
524 staff: bool,
525 }
526
527 #[derive(Serialize, Deserialize, JsonSchema)]
528 struct UserSettingsJson {
529 name: Option<String>,
530 age: Option<u32>,
531 staff: Option<bool>,
532 }
533
534 impl Setting for UserSettings {
535 const FIELD_NAME: Option<&'static str> = Some("user");
536 type FileContent = UserSettingsJson;
537
538 fn load(default_value: &UserSettingsJson, user_values: &[&UserSettingsJson]) -> Self {
539 Self::load_via_json_merge(default_value, user_values)
540 }
541 }
542
543 #[derive(Debug, Deserialize, PartialEq)]
544 struct TurboSetting(bool);
545
546 impl Setting for TurboSetting {
547 const FIELD_NAME: Option<&'static str> = Some("turbo");
548 type FileContent = Option<bool>;
549
550 fn load(default_value: &Option<bool>, user_values: &[&Option<bool>]) -> Self {
551 Self::load_via_json_merge(default_value, user_values)
552 }
553 }
554
555 #[derive(Clone, Debug, PartialEq, Deserialize)]
556 struct MultiKeySettings {
557 #[serde(default)]
558 key1: String,
559 #[serde(default)]
560 key2: String,
561 }
562
563 #[derive(Serialize, Deserialize, JsonSchema)]
564 struct MultiKeySettingsJson {
565 key1: Option<String>,
566 key2: Option<String>,
567 }
568
569 impl Setting for MultiKeySettings {
570 type FileContent = MultiKeySettingsJson;
571
572 fn load(
573 default_value: &MultiKeySettingsJson,
574 user_values: &[&MultiKeySettingsJson],
575 ) -> Self {
576 Self::load_via_json_merge(default_value, user_values)
577 }
578 }
579
580 #[derive(Debug, Deserialize)]
581 struct JournalSettings {
582 pub path: String,
583 pub hour_format: HourFormat,
584 }
585
586 #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
587 #[serde(rename_all = "snake_case")]
588 enum HourFormat {
589 Hour12,
590 Hour24,
591 }
592
593 #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
594 struct JournalSettingsJson {
595 pub path: Option<String>,
596 pub hour_format: Option<HourFormat>,
597 }
598
599 impl Setting for JournalSettings {
600 const FIELD_NAME: Option<&'static str> = Some("journal");
601
602 type FileContent = JournalSettingsJson;
603
604 fn load(default_value: &JournalSettingsJson, user_values: &[&JournalSettingsJson]) -> Self {
605 Self::load_via_json_merge(default_value, user_values)
606 }
607 }
608}