settings_store.rs

  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}