Skip to main content

configory/
lib.rs

1//! Batteries included application config management.
2//!
3//! Configory is a batteries included configuration management library which
4//! handles all the gory details of supporting configuration files while also
5//! supporting IPC access and overrides.
6//!
7//! # Example
8//!
9//! To get a configuration which is backed by a file that is automatically
10//! reloaded on change, you just need to create a config with [`Manager::new`]:
11//!
12//! ```rust
13//! use configory::Manager;
14//!
15//! let manager = Manager::new("configory", ()).unwrap();
16//! manager.set(&["option"], 3);
17//!
18//! assert_eq!(manager.get::<_, i32>(&["option"]), Ok(Some(3)));
19//! ```
20//!
21//! This will also automatically spawn an IPC server which allows configuration
22//! file access and modification through a socket file. If you want to disable
23//! this, you can use [`Manager::with_options`].
24//!
25//! The event handler passed to [`Manager::new`] or [`Manager::with_options`]
26//! can be used to handle configuration changes, custom IPC messages, and
27//! errors.
28//!
29//! ```rust
30//! use std::sync::Arc;
31//! use std::sync::atomic::{AtomicU8, Ordering};
32//!
33//! use configory::{Config, EventHandler, Manager};
34//!
35//! /// Event handler with a configuration change counter.
36//! struct MyEventHandler {
37//!     changes: Arc<AtomicU8>,
38//! }
39//!
40//! impl EventHandler for MyEventHandler {
41//!     type MessageData = ();
42//!
43//!     fn ipc_changed(&self, _config: &Config) {
44//!         self.changes.fetch_add(1, Ordering::Relaxed);
45//!     }
46//! }
47//!
48//! // Register our event handler, which increments a counter on change.
49//! let changes = Arc::new(AtomicU8::new(0));
50//! let event_handler = MyEventHandler { changes: changes.clone() };
51//! let manager = Manager::new("configory", event_handler).unwrap();
52//!
53//! // Update the config through direct access and IPC.
54//! manager.set(&["integer"], 3);
55//! manager.ipc().unwrap().set(&["text"], "demo");
56//!
57//! // Verify the config is correct and was changed once through IPC.
58//! assert_eq!(manager.get::<_, String>(&["text"]), Ok(Some("demo".into())));
59//! assert_eq!(manager.get::<_, i32>(&["integer"]), Ok(Some(3)));
60//! assert_eq!(changes.load(Ordering::Relaxed), 1);
61//! ```
62//!
63//! # IPC client
64//!
65//! The client side of the IPC interface is constructed from the socket path,
66//! which can be acquired using [`Manager::ipc`] and [`Ipc::socket_path`].
67//!
68//! ```rust
69//! use configory::Manager;
70//! use configory::ipc::Ipc;
71//!
72//! // This would typically happen in a separate process.
73//! let manager = Manager::new("configory", ()).unwrap();
74//! let socket_path = manager.ipc().unwrap().socket_path();
75//!
76//! // Set and retrieve a configuration value through the socket.
77//! let ipc = Ipc::client(socket_path);
78//! ipc.set(&["option"], 3).unwrap();
79//! let value = ipc.get::<_, i32>(&["option"]).unwrap();
80//! assert_eq!(value, Some(3));
81//! ```
82//!
83//! # Struct Deserialization
84//!
85//! If you prefer accessing configuration values through a struct rather than
86//! using the dynamic syntax of [`Config::get`], you can deserialize the
87//! toplevel value into your struct:
88//!
89//! ```rust
90//! use configory::Manager;
91//! use serde::Deserialize;
92//!
93//! #[derive(Deserialize, Default, PartialEq, Debug)]
94//! #[serde(default)]
95//! struct MyConfig {
96//!     field: String,
97//! }
98//!
99//! let manager = Manager::new("configory", ()).unwrap();
100//!
101//! // Without configuration file, the default will be empty.
102//! let my_config = manager.get::<&str, MyConfig>(&[]);
103//! assert_eq!(my_config, Ok(None));
104//!
105//! // Once changed wit the path syntax, the field will be uptaded.
106//! manager.set(&["field"], "demo");
107//! let my_config = manager.get::<&str, MyConfig>(&[]).unwrap().unwrap();
108//! assert_eq!(my_config.field, String::from("demo"));
109//! ```
110
111#![deny(missing_docs)]
112
113use std::fs;
114use std::io::{self, ErrorKind as IoErrorKind};
115use std::ops::Deref;
116use std::path::{Path, PathBuf};
117use std::sync::{Arc, RwLock};
118
119use serde::de::{Deserialize, DeserializeOwned};
120use tempfile::NamedTempFile;
121use toml::{Table, Value};
122
123use crate::ipc::{Ipc, Message};
124use crate::monitor::Watcher;
125
126#[cfg(feature = "docgen")]
127pub mod docgen;
128pub mod ipc;
129mod monitor;
130mod thread;
131
132/// Configuration manager.
133///
134/// # Example
135///
136/// ```rust
137/// use configory::Manager;
138///
139/// let manager = Manager::new("configory", ()).unwrap();
140/// manager.set(&["option"], 3);
141///
142/// assert_eq!(manager.get::<_, i32>(&["option"]), Ok(Some(3)));
143/// ```
144pub struct Manager<E> {
145    config: Config,
146    ipc: Option<Ipc>,
147
148    watcher: Option<Watcher>,
149    event_handler: Arc<E>,
150    notify: bool,
151}
152
153impl<E> Manager<E>
154where
155    E: EventHandler,
156{
157    /// Initialize the configuration manager.
158    ///
159    /// This will spawn multiple background threads and create socket files.
160    /// See [`Self::with_options`] to control the enabled features.
161    ///
162    /// # Example
163    ///
164    /// ```rust
165    /// use configory::Manager;
166    ///
167    /// let manager = Manager::new("configory", ()).unwrap();
168    /// #
169    /// # manager.set(&["option"], 3);
170    /// # assert_eq!(manager.get::<_, i32>(&["option"]), Ok(Some(3)));
171    /// ```
172    pub fn new<S: AsRef<str>>(namespace: S, event_handler: E) -> Result<Self, Error>
173    where
174        <E as EventHandler>::MessageData: DeserializeOwned,
175    {
176        let options = Options::new(namespace.as_ref()).notify(true).ipc(true);
177        Self::with_options(&options, event_handler)
178    }
179
180    /// Initialize the configuration manager with some features disabled.
181    ///
182    /// See [`Self::new`] if you want all the features available.
183    ///
184    /// # Example
185    ///
186    /// ```rust
187    /// use configory::{Manager, Options};
188    ///
189    /// // Create a config without IPC.
190    /// let options = Options::new("configory").notify(true);
191    /// let manager = Manager::with_options(&options, ()).unwrap();
192    /// #
193    /// # manager.set(&["option"], 3);
194    /// # assert_eq!(manager.get::<_, i32>(&["option"]), Ok(Some(3)));
195    /// ```
196    pub fn with_options(options: &Options, event_handler: E) -> Result<Self, Error>
197    where
198        <E as EventHandler>::MessageData: DeserializeOwned,
199    {
200        // Parse initial configuration file.
201        let mut path = dirs::config_dir()
202            .map(|dir| dir.join(options.namespace).join(format!("{}.toml", options.namespace)));
203        let config = match path.as_ref().map(|path| load_config(path)) {
204            Some(Ok(config_file)) => Config::from_config(config_file, path.clone()),
205            Some(Err(err)) => {
206                let config = Config::new(path.clone());
207                event_handler.file_error(&config, err);
208                config
209            },
210            None => Config::new(path.clone()),
211        };
212
213        let event_handler = Arc::new(event_handler);
214
215        // Spawn IPC thread.
216        let ipc = if options.ipc {
217            Some(Ipc::listen(config.clone(), options.namespace, event_handler.clone())?)
218        } else {
219            None
220        };
221
222        // Spawn file monitor.
223        let watcher = match path.take() {
224            Some(path) if options.notify => {
225                Watcher::new(config.clone(), event_handler.clone(), path)?
226            },
227            _ => None,
228        };
229
230        Ok(Self { notify: options.notify, event_handler, watcher, config, ipc })
231    }
232
233    /// Get an IPC handle.
234    ///
235    /// Will be [`None`] only if IPC was disabled using [`Self::with_options`].
236    ///
237    /// # Example
238    ///
239    /// ```rust
240    /// use configory::{Manager, Options};
241    ///
242    /// let manager = Manager::new("configory", ()).unwrap();
243    /// assert!(manager.ipc().is_some());
244    ///
245    /// let options = Options::new("configory");
246    /// let manager = Manager::with_options(&options, ()).unwrap();
247    /// assert!(manager.ipc().is_none());
248    /// ```
249    pub fn ipc(&self) -> Option<&Ipc> {
250        self.ipc.as_ref()
251    }
252
253    /// Persist the current runtime config to the config file.
254    ///
255    /// # Example
256    ///
257    /// ```rust
258    /// use configory::Manager;
259    ///
260    /// # let tempdir = tempfile::tempdir().unwrap();
261    /// # let fake_home = tempdir.path().join("configory-docs-persist");
262    /// # unsafe { std::env::set_var("XDG_CONFIG_HOME", &*fake_home.to_string_lossy()) };
263    ///
264    /// let mut manager = Manager::new("configory", ()).unwrap();
265    /// manager.set(&["option"], 3);
266    /// manager.persist().unwrap();
267    ///
268    /// # let config_path = fake_home.join("configory").join("configory.toml");
269    /// # let config = std::fs::read_to_string(&config_path).unwrap();
270    /// # assert_eq!(&config, "option = 3\n");
271    /// ```
272    pub fn persist(&mut self) -> Result<(), Error> {
273        let path = match &self.config.path {
274            Some(path) => path,
275            None => return Err(Error::NoConfigDir),
276        };
277
278        // Ensure config directory exists.
279        let parent = path.parent().unwrap();
280        fs::create_dir_all(parent)?;
281
282        // Write updated config to tempfile.
283        let tempfile = NamedTempFile::new_in(parent)?;
284        let mut values = self.config.values.write().unwrap();
285        let toml = toml::to_string_pretty(&values.merged)?;
286        fs::write(tempfile.path(), toml)?;
287
288        // Move config into place.
289        tempfile.persist(path)?;
290
291        // Clear runtime overrides, since we've just saved them.
292        values.runtime = Value::Table(Table::new());
293        values.file = values.merged.clone();
294        drop(values);
295
296        // Try to start config monitor, since this might have created the file.
297        if self.notify && self.watcher.is_none() {
298            self.watcher =
299                Watcher::new(self.config.clone(), self.event_handler.clone(), path.clone())?;
300
301            // Manually dispatch creation event.
302            self.event_handler.file_changed(&self.config);
303        }
304
305        Ok(())
306    }
307}
308
309impl<E> Deref for Manager<E> {
310    type Target = Config;
311
312    fn deref(&self) -> &Self::Target {
313        &self.config
314    }
315}
316
317/// Configuration value storage.
318#[derive(Clone)]
319pub struct Config {
320    pub(crate) values: Arc<RwLock<Values>>,
321    path: Option<PathBuf>,
322}
323
324impl Config {
325    /// Create a new empty store.
326    fn new(path: Option<PathBuf>) -> Self {
327        Self::from_config(Value::Table(Table::new()), path)
328    }
329
330    /// Create a new store from a parsed configuration file.
331    fn from_config(value: Value, path: Option<PathBuf>) -> Self {
332        Self { path, values: Arc::new(RwLock::new(Values::new(value))) }
333    }
334
335    /// Get the current value of a config option.
336    ///
337    /// This will return `Ok(None)` if the requested option does not exist, and
338    /// an error if it does not match the requested type. This will never error
339    /// if `T` is [`toml::Value`].
340    ///
341    /// # Example
342    ///
343    /// ```rust
344    /// use configory::Manager;
345    ///
346    /// let manager = Manager::new("configory", ()).unwrap();
347    /// manager.set(&["option"], 3);
348    ///
349    /// let existing_value = manager.get::<_, i32>(&["option"]);
350    /// let missing_value = manager.get::<_, i32>(&["missing"]);
351    ///
352    /// assert_eq!(existing_value, Ok(Some(3)));
353    /// assert_eq!(missing_value, Ok(None));
354    /// ```
355    pub fn get<'de, S, T>(&self, path: &[S]) -> Result<Option<T>, toml::de::Error>
356    where
357        S: AsRef<str>,
358        T: Deserialize<'de>,
359    {
360        let value = self.values.read().unwrap().get(path).cloned();
361        match value {
362            Some(value) => Ok(Some(value.try_into()?)),
363            None => Ok(None),
364        }
365    }
366
367    /// Set a runtime override for a config option.
368    ///
369    /// See [`Self::reset`] for clearing runtime overrides.
370    ///
371    /// # Example
372    ///
373    /// ```rust
374    /// use configory::Manager;
375    ///
376    /// let manager = Manager::new("configory", ()).unwrap();
377    ///
378    /// manager.set(&["option"], 3);
379    /// #
380    /// # assert_eq!(manager.get::<_, i32>(&["option"]), Ok(Some(3)));
381    /// ```
382    pub fn set<S, V>(&self, path: &[S], value: V)
383    where
384        S: AsRef<str>,
385        V: Into<Value>,
386    {
387        let value = value.into();
388        self.values.write().unwrap().set(path, value);
389    }
390
391    /// Clear the runtime override for a config option.
392    ///
393    /// See [`Self::set`] for setting runtime overrides.
394    ///
395    /// # Example
396    ///
397    /// ```rust
398    /// use configory::Manager;
399    ///
400    /// let manager = Manager::new("configory", ()).unwrap();
401    /// # manager.set(&["option"], 3);
402    ///
403    /// assert_eq!(manager.get::<_, i32>(&["option"]), Ok(Some(3)));
404    /// manager.reset(&["option"]);
405    /// assert_eq!(manager.get::<_, i32>(&["option"]), Ok(None));
406    /// ```
407    pub fn reset<S>(&self, path: &[S])
408    where
409        S: AsRef<str>,
410    {
411        self.values.write().unwrap().reset(path)
412    }
413}
414
415/// Configuration value store.
416#[derive(Debug)]
417struct Values {
418    /// Deserialized configuration file values.
419    file: Value,
420    /// Current runtime overrides, like IPC config.
421    runtime: Value,
422    /// File values merged with runtime overrides.
423    merged: Value,
424}
425
426impl Values {
427    /// Create a new config value store.
428    fn new(file: Value) -> Self {
429        let merged = file.clone();
430        Self { merged, file, runtime: Value::Table(Table::new()) }
431    }
432
433    /// Update configuration file values.
434    fn set_file(&mut self, file: Value) {
435        self.file = file;
436
437        // Apply config overrides to the file.
438        self.merged = self.file.clone();
439        toml_merge(&mut self.merged, self.runtime.clone());
440    }
441
442    /// Get the current value of a config option.
443    fn get<S>(&self, path: &[S]) -> Option<&toml::Value>
444    where
445        S: AsRef<str>,
446    {
447        toml_get(&self.merged, path)
448    }
449
450    /// Override the runtime portion of a configuration value.
451    fn set<S, V>(&mut self, path: &[S], value: V)
452    where
453        S: AsRef<str>,
454        V: Into<Value>,
455    {
456        let value = value.into();
457        toml_insert(&mut self.merged, path, value.clone());
458        toml_insert(&mut self.runtime, path, value);
459    }
460
461    /// Clear the runtime portion of a configuration value.
462    fn reset<S>(&mut self, path: &[S])
463    where
464        S: AsRef<str>,
465    {
466        // Reset merged value to configuration file if available.
467        match toml_get(&self.file, path) {
468            Some(value) => toml_insert(&mut self.merged, path, value.clone()),
469            None => toml_remove(&mut self.merged, path),
470        }
471        toml_remove(&mut self.runtime, path);
472    }
473}
474
475/// Event handler for configuration changes.
476///
477/// ```rust
478/// use std::sync::Arc;
479/// use std::sync::atomic::{AtomicU8, Ordering};
480///
481/// use configory::{Config, EventHandler, Manager};
482///
483/// /// Event handler with a configuration change counter.
484/// struct MyEventHandler {
485///     changes: Arc<AtomicU8>,
486/// }
487///
488/// impl EventHandler for MyEventHandler {
489///     type MessageData = ();
490///
491///     fn ipc_changed(&self, _config: &Config) {
492///         self.changes.fetch_add(1, Ordering::Relaxed);
493///     }
494/// }
495///
496/// // Register our event handler, which increments a counter on change.
497/// let changes = Arc::new(AtomicU8::new(0));
498/// let event_handler = MyEventHandler { changes: changes.clone() };
499/// let manager = Manager::new("configory", event_handler).unwrap();
500///
501/// // Update the config through direct access and IPC.
502/// manager.set(&["integer"], 3);
503/// manager.ipc().unwrap().set(&["text"], "demo");
504///
505/// // Verify the config is correct and was changed once through IPC.
506/// assert_eq!(manager.get::<_, String>(&["text"]), Ok(Some("demo".into())));
507/// assert_eq!(manager.get::<_, i32>(&["integer"]), Ok(Some(3)));
508/// assert_eq!(changes.load(Ordering::Relaxed), 1);
509/// ```
510pub trait EventHandler: Send + Sync + 'static {
511    /// Type for custom IPC messages.
512    type MessageData;
513
514    /// Handle configuration file changes.
515    fn file_changed(&self, _config: &Config) {}
516
517    /// Handle configuration file syntax errors.
518    fn file_error(&self, _config: &Config, _err: Error) {}
519
520    /// Handle configuration changes through IPC.
521    fn ipc_changed(&self, _config: &Config) {}
522
523    /// Handle user-defined ipc messages.
524    fn ipc_message(&self, _config: &Config, _message: Message<Self::MessageData>) {}
525}
526
527/// Dummy handler when update notifications are undesired.
528impl EventHandler for () {
529    type MessageData = ();
530}
531
532/// Configuration monitor options.
533///
534/// See [`Manager::with_options`].
535///
536/// # Example
537///
538/// ```rust
539/// use configory::Options;
540///
541/// // Disable all features.
542/// let options = Options::new("configory");
543///
544/// // Enable all features.
545/// let options = Options::new("configory").notify(true).ipc(true);
546/// ```
547pub struct Options<'a> {
548    namespace: &'a str,
549    notify: bool,
550    ipc: bool,
551}
552
553impl<'a> Options<'a> {
554    /// Create a new set of options with all features disabled.
555    ///
556    /// # Example
557    ///
558    /// ```rust
559    /// use configory::Options;
560    ///
561    /// let options = Options::new("configory");
562    /// ```
563    pub fn new(namespace: &'a str) -> Self {
564        Self { namespace, notify: false, ipc: false }
565    }
566
567    /// Enable or disable the file monitor.
568    ///
569    /// # Example
570    ///
571    /// ```rust
572    /// use configory::Options;
573    ///
574    /// let options = Options::new("configory").notify(true);
575    /// ```
576    pub fn notify(mut self, enabled: bool) -> Self {
577        self.notify = enabled;
578        self
579    }
580
581    /// Enable or disable IPC.
582    ///
583    /// # Example
584    ///
585    /// ```rust
586    /// use configory::Options;
587    ///
588    /// // Enable all features.
589    /// let options = Options::new("configory").ipc(true);
590    /// ```
591    pub fn ipc(mut self, enabled: bool) -> Self {
592        self.ipc = enabled;
593        self
594    }
595}
596
597/// Resolve a toml path key's value recursively.
598fn toml_get<'a, S>(value: &'a Value, path: &[S]) -> Option<&'a Value>
599where
600    S: AsRef<str>,
601{
602    match (value, path.first()) {
603        (Value::Table(table), Some(segment)) => {
604            let next_value = table.get(segment.as_ref())?;
605            toml_get(next_value, &path[1..])
606        },
607        (Value::Table(table), None) if table.is_empty() => None,
608        (value, _) => Some(value),
609    }
610}
611
612/// Insert a toml value, creating new tables if necessary.
613fn toml_insert<S>(value: &mut Value, path: &[S], inserting: Value)
614where
615    S: AsRef<str>,
616{
617    match (value, path.first()) {
618        (Value::Table(table), Some(segment)) => {
619            let next_value =
620                table.entry(segment.as_ref()).or_insert_with(|| Value::Table(Table::new()));
621            toml_insert(next_value, &path[1..], inserting)
622        },
623        (value, Some(segment)) => {
624            *value = Value::Table(Table::new());
625            let table = value.as_table_mut().unwrap();
626            let next_value =
627                table.entry(segment.as_ref()).or_insert_with(|| Value::Table(Table::new()));
628            toml_insert(next_value, path, inserting)
629        },
630        (value, None) => *value = inserting,
631    }
632}
633
634/// Remove a toml value.
635fn toml_remove<S>(value: &mut Value, path: &[S])
636where
637    S: AsRef<str>,
638{
639    // If the root is removed, just replace it with a new table.
640    if path.is_empty() {
641        *value = Value::Table(Table::new());
642        return;
643    }
644
645    // Values can only be removed from tables, so ignore everything else.
646    let table = match value {
647        Value::Table(table) => table,
648        _ => return,
649    };
650
651    // Remove value if it's in the current table.
652    if path.len() == 1 {
653        table.remove(path[0].as_ref());
654    }
655
656    // Recurse into the table, ignoring invalid paths.
657    if let Some(next_value) = table.get_mut(path[0].as_ref()) {
658        toml_remove(next_value, &path[1..]);
659    }
660}
661
662/// Merge two toml values together.
663fn toml_merge(base: &mut Value, new: Value) {
664    match (base, new) {
665        (Value::Table(base_table), Value::Table(new_table)) => {
666            for (key, new_value) in new_table.into_iter() {
667                match base_table.get_mut(&key) {
668                    Some(base_value) => toml_merge(base_value, new_value),
669                    None => _ = base_table.insert(key, new_value),
670                }
671            }
672        },
673        (Value::String(base_string), Value::String(new_string)) => *base_string = new_string,
674        (Value::Integer(base_int), Value::Integer(new_int)) => *base_int = new_int,
675        (Value::Float(base_float), Value::Float(new_float)) => *base_float = new_float,
676        (Value::Boolean(base_bool), Value::Boolean(new_bool)) => *base_bool = new_bool,
677        (Value::Datetime(base_date), Value::Datetime(new_date)) => *base_date = new_date,
678        (Value::Array(base_array), Value::Array(new_array)) => base_array.extend(new_array),
679        // On type mismatch, we just use the override.
680        (base, new) => *base = new,
681    }
682}
683
684/// Configuration errors.
685#[derive(thiserror::Error, Debug)]
686pub enum Error {
687    /// Failed to atomically replace config.
688    #[error("{0}")]
689    AtomicReplace(#[from] tempfile::PersistError),
690    /// Configuration file deserialization failed.
691    #[error("{0}")]
692    ConfigDeserialize(#[from] toml::de::Error),
693    /// Configuration file serialization failed.
694    #[error("{0}")]
695    ConfigSerialize(#[from] toml::ser::Error),
696    /// IPC message deserialization failed.
697    #[error("{0}")]
698    IpcDeserialize(#[from] serde_json::Error),
699    /// File monitor creation failed.
700    #[error("{0}")]
701    Notify(#[from] notify::Error),
702    /// IO error.
703    #[error("{0}")]
704    Io(#[from] io::Error),
705    /// Unable to resolve get system's config directory.
706    #[error("could not determine system config dir")]
707    NoConfigDir,
708}
709
710/// Deserialize a configuration file.
711pub(crate) fn load_config(path: &Path) -> Result<Value, Error> {
712    // Get file content and strip UTF-8 BOM.
713    let mut config_text = match fs::read_to_string(path) {
714        Ok(config_text) => config_text,
715        Err(err) if err.kind() == IoErrorKind::NotFound => {
716            return Ok(Value::Table(Table::new()));
717        },
718        Err(err) => return Err(err.into()),
719    };
720    if config_text.starts_with('\u{FEFF}') {
721        config_text = config_text.split_off(3);
722    }
723
724    // Deserialize configuration file.
725    let config: Value = toml::from_str(&config_text)?;
726
727    Ok(config)
728}
729
730#[cfg(test)]
731mod tests {
732    use std::env;
733
734    use serde::Deserialize;
735    use tempfile::tempdir;
736
737    use super::*;
738
739    #[test]
740    fn toml_get_simple() {
741        // Create test tree.
742        let mut table = Table::new();
743        table.insert("exists".into(), Value::Integer(3));
744        let value = Value::Table(table);
745
746        // Run tests against both implementations.
747        assert_eq!(toml_get(&value, &["exists"]), Some(&Value::Integer(3)));
748        assert_eq!(toml_get(&value, &["missing"]), None);
749    }
750
751    #[test]
752    fn toml_get_table() {
753        // Create test tree.
754        let mut nested_table = Table::new();
755        nested_table.insert("nested".into(), Value::Integer(3));
756        let mut table = Table::new();
757        table.insert("exists".into(), Value::Table(Table::new()));
758        table.insert("exists2".into(), Value::Table(nested_table));
759        let value = Value::Table(table);
760
761        // Run tests against both implementations.
762        assert_eq!(toml_get(&value, &["exists"]), None);
763        assert!(toml_get(&value, &["exists2"]).is_some());
764        assert_eq!(toml_get(&value, &["exists2", "nested"]), Some(&Value::Integer(3)));
765    }
766
767    #[test]
768    fn toml_insert_simple() {
769        // Create test tree.
770        let mut value = Value::Table(Table::new());
771
772        // Insert the new value.
773        toml_insert(&mut value, &["exists"], Value::Integer(3));
774
775        // Verify new tree structure.
776        assert_eq!(toml_get(&value, &["exists"]), Some(&Value::Integer(3)));
777    }
778
779    #[test]
780    fn toml_insert_replace() {
781        // Create test tree.
782        let mut table = Table::new();
783        table.insert("exists".into(), Value::Integer(0));
784        let mut value = Value::Table(table);
785        assert_eq!(toml_get(&value, &["exists"]), Some(&Value::Integer(0)));
786
787        // Insert the new value.
788        toml_insert(&mut value, &["exists"], Value::Integer(3));
789
790        // Verify new tree structure.
791        assert_eq!(toml_get(&value, &["exists"]), Some(&Value::Integer(3)));
792    }
793
794    #[test]
795    fn toml_insert_nested_replace() {
796        // Create test tree.
797        let mut nested_table = Table::new();
798        nested_table.insert("nested".into(), Value::Integer(0));
799        let mut table = Table::new();
800        table.insert("exists".into(), Value::Table(nested_table));
801        let mut value = Value::Table(table);
802        assert_eq!(toml_get(&value, &["exists", "nested"]), Some(&Value::Integer(0)));
803
804        // Insert the new value.
805        toml_insert(&mut value, &["exists", "nested"], Value::Integer(3));
806
807        // Verify new tree structure.
808        assert_eq!(toml_get(&value, &["exists", "nested"]), Some(&Value::Integer(3)));
809    }
810
811    #[test]
812    fn toml_insert_replace_table() {
813        // Create test tree.
814        let mut nested_table = Table::new();
815        nested_table.insert("nested".into(), Value::Integer(0));
816        nested_table.insert("nested2".into(), Value::Integer(1));
817        let mut table = Table::new();
818        table.insert("exists".into(), Value::Table(nested_table));
819        let mut value = Value::Table(table);
820        assert_eq!(toml_get(&value, &["exists", "nested"]), Some(&Value::Integer(0)));
821        assert_eq!(toml_get(&value, &["exists", "nested2"]), Some(&Value::Integer(1)));
822
823        // Insert the new value.
824        let mut new_nested_table = Table::new();
825        new_nested_table.insert("nested".into(), Value::Integer(3));
826        toml_insert(&mut value, &["exists"], Value::Table(new_nested_table));
827
828        // Verify new tree structure.
829        assert_eq!(toml_get(&value, &["exists", "nested"]), Some(&Value::Integer(3)));
830        assert_eq!(toml_get(&value, &["exists", "nested2"]), None);
831    }
832
833    #[test]
834    fn toml_insert_deep_new() {
835        // Create test tree.
836        let mut value = Value::Table(Table::new());
837
838        // Insert the new value.
839        toml_insert(&mut value, &["exists", "nested", "deep"], Value::Integer(3));
840
841        // Verify new tree structure.
842        assert_eq!(toml_get(&value, &["exists", "nested", "deep"]), Some(&Value::Integer(3)));
843    }
844
845    #[test]
846    fn toml_remove_all() {
847        let mut table = Table::new();
848        table.insert("aoeu".into(), Value::Integer(3));
849        let mut value = Value::Table(table);
850
851        toml_remove::<&str>(&mut value, &[]);
852
853        assert_eq!(value, Value::Table(Table::new()));
854    }
855
856    #[test]
857    fn toml_remove_simple() {
858        let mut table = Table::new();
859        table.insert("aoeu".into(), Value::Integer(3));
860        table.insert("bbb".into(), Value::Integer(9));
861        let mut value = Value::Table(table);
862
863        toml_remove(&mut value, &["bbb"]);
864
865        let mut expected_table = Table::new();
866        expected_table.insert("aoeu".into(), Value::Integer(3));
867        let expected = Value::Table(expected_table);
868
869        assert_eq!(value, expected);
870    }
871
872    #[test]
873    fn toml_merge_tables() {
874        let mut table_a = Table::new();
875        table_a.insert("yyy".into(), Value::Integer(1));
876        table_a.insert("aoeu".into(), Value::Integer(3));
877        let mut a = Value::Table(table_a);
878
879        let mut table_b = Table::new();
880        table_b.insert("aoeu".into(), Value::Integer(0));
881        table_b.insert("xxx".into(), Value::Integer(9));
882        let b = Value::Table(table_b);
883
884        toml_merge(&mut a, b);
885
886        let mut expected_table = Table::new();
887        expected_table.insert("yyy".into(), Value::Integer(1));
888        expected_table.insert("aoeu".into(), Value::Integer(0));
889        expected_table.insert("xxx".into(), Value::Integer(9));
890        let expected = Value::Table(expected_table);
891
892        assert_eq!(a, expected);
893    }
894
895    #[test]
896    fn toml_merge_array() {
897        let mut table_a = Table::new();
898        table_a.insert("a".into(), Value::Array(vec![Value::Integer(3)]));
899        let mut a = Value::Table(table_a);
900
901        let mut table_b = Table::new();
902        table_b.insert("a".into(), Value::Array(vec![Value::Integer(9)]));
903        let b = Value::Table(table_b);
904
905        toml_merge(&mut a, b);
906
907        let mut expected_table = Table::new();
908        expected_table.insert("a".into(), Value::Array(vec![Value::Integer(3), Value::Integer(9)]));
909        let expected = Value::Table(expected_table);
910
911        assert_eq!(a, expected);
912    }
913
914    #[test]
915    fn toml_merge_mismatched_types() {
916        let mut table_a = Table::new();
917        table_a.insert("a".into(), Value::Integer(0));
918        let mut a = Value::Table(table_a);
919
920        let mut table_b = Table::new();
921        table_b.insert("a".into(), Value::String("test".into()));
922        let b = Value::Table(table_b);
923
924        toml_merge(&mut a, b);
925
926        let mut expected_table = Table::new();
927        expected_table.insert("a".into(), Value::String("test".into()));
928        let expected = Value::Table(expected_table);
929
930        assert_eq!(a, expected);
931    }
932
933    #[test]
934    fn config_get_merged() {
935        let test_id = "configory_config_get_merged";
936
937        #[derive(Deserialize, PartialEq, Debug)]
938        struct Test {
939            integer: i32,
940            text: String,
941        }
942
943        // Create a temporary config with an initial value present.
944        let tempdir = tempdir().unwrap();
945        let fake_home = tempdir.path().join(test_id);
946        unsafe { env::set_var("XDG_CONFIG_HOME", &*fake_home.to_string_lossy()) };
947        let config_path = fake_home.join(test_id).join(format!("{test_id}.toml"));
948        fs::create_dir_all(config_path.parent().unwrap()).unwrap();
949        fs::write(&config_path, "integer = 13").unwrap();
950
951        // Load config and add a runtime option.
952        let manager = Manager::new(test_id, ()).unwrap();
953        manager.set(&["text"], "test");
954
955        // Ensure runtime and file values are merged in the root table.
956        let root = manager.get::<&str, Test>(&[]);
957        assert_eq!(root, Ok(Some(Test { integer: 13, text: "test".into() })));
958    }
959}