Skip to main content

statime_config/
lib.rs

1//! Configuration parsing
2//!
3//! This crate provides a configuration parsing library and associated derive
4//! macros for parsing configuration from multiple TOML files.
5//!
6//! As a user of this crate, you will typically define a set of structs, enums
7//! and 'atomic' values that represent individual configuration settings. To
8//! these you then apply the [`Configurable`] and [`ConfigurableAtomic`] derives.
9//!
10//! The root of the configuration tree is represented by the [`RootConfig`] type,
11//! which is a [`Configurable`] struct that holds the configuration for the entire
12//! application, but has an additional [`RootConfig::load`] method that loads
13//! the configuration from TOML files.
14//!
15//! ```no_run
16//! # use std::path::PathBuf;
17//! # use serde::{Deserialize, Serialize};
18//! use statime_config::{Configurable, ConfigurableAtomic, RootConfig, SystemConfigSetting};
19//!
20//! /// The root configuration needs to be a [`Configurable`] struct with [`RootConfig`]
21//! /// implemented, which is used to load the configuration from TOML files.
22//! /// It also needs to contain information which can identify whether and which
23//! /// system configuration is to be used when loading the configuration.
24//! #[derive(Debug, Clone, PartialEq, Eq, Configurable)]
25//! pub struct Config {
26//!     /// Whether to use the system configuration files, note the default
27//!     /// attribute calls Default::default()
28//!     #[config(default)]
29//!     pub use_system_config: UseSystemConfig,
30//!
31//!     /// This section has no default value, if any value within it is not
32//!     /// specified and has no default, that will be an error.
33//!     pub observability: ObservabilityConfig,
34//! }
35//!
36//! impl RootConfig for Config {
37//!     fn use_system_config(partial: &Self::Partial) -> SystemConfigSetting {
38//!         match partial.use_system_config.clone().into_option() {
39//!             Some(UseSystemConfig::Enabled(false)) => SystemConfigSetting::None,
40//!             Some(UseSystemConfig::Enabled(true)) => SystemConfigSetting::DefaultPath(UseSystemConfig::DEFAULT_SYSTEM_CONFIG.into()),
41//!             Some(UseSystemConfig::Directory(path)) => SystemConfigSetting::UserSpecifiedPath(path),
42//!             None => SystemConfigSetting::Unset,
43//!         }
44//!     }
45//! }
46//!
47//! /// Only the Configurable derive is needed here, as this is not the root struct.
48//! #[derive(Debug, Clone, PartialEq, Eq, Configurable)]
49//! pub struct ObservabilityConfig {
50//!     /// Example of using a custom enum as an atomic value, note how the
51//!     /// default can also be any expression that evaluates to the type of
52//!     /// the field.
53//!     #[config(default = LogLevel::Info)]
54//!     pub level: LogLevel,
55//! }
56//!
57//! /// Example of a custom enum that is used as an atomic value, by deriving
58//! /// the ConfigurableAtomic trait.
59//! #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, ConfigurableAtomic)]
60//! #[serde(rename_all = "kebab-case")]
61//! pub enum LogLevel {
62//!     Debug,
63//!     Info,
64//!     Warn,
65//!     Error,
66//! }
67//!
68//!
69//! /// Which system configuration to layer underneath the main configuration, if
70//! /// any.
71//! #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, ConfigurableAtomic)]
72//! #[serde(untagged)]
73//! pub enum UseSystemConfig {
74//!     /// `true` reads the fragments supplied by the distribution, `false` uses no
75//!     /// system configuration at all.
76//!     Enabled(bool),
77//!
78//!     /// A directory to read the fragments from, instead of the default one.
79//!     Directory(PathBuf),
80//! }
81//!
82//! impl UseSystemConfig {
83//!     pub const DEFAULT_SYSTEM_CONFIG: &str = "/usr/lib/ntpd-rs/system-config";
84//! }
85//!
86//! impl Default for UseSystemConfig {
87//!     fn default() -> Self {
88//!         Self::Enabled(false)
89//!     }
90//! }
91//!
92//! fn main() {
93//!     let config = Config::load("/path/to/config.toml").unwrap();
94//! }
95//! ```
96
97// so that the derive macro can name this crate even from inside it
98extern crate self as statime_config;
99
100mod error;
101#[cfg(test)]
102mod fixture;
103mod load;
104pub mod serde_util;
105mod tree;
106
107pub use error::ConfigError;
108pub use load::{PartialTree, RootConfig, SystemConfigSetting};
109// the traits and the derives that implement them share their names, so that
110// one import brings both, as serde does
111pub use statime_config_derive::{Configurable, ConfigurableAtomic};
112// `Setting` and `Section` are what `Configurable::Node` resolves to, so they
113// are part of the interface whether or not anyone names them directly
114pub use tree::{
115    ConfigPath, Configurable, ConfigurableAtomic, EnumSection, InfiniteOr, IsNonNegative,
116    IsPositive, Merge, MergeContext, MergePolicy, NonNegative, Origin, OriginId, PartialValue,
117    Positive, Section, Setting,
118};
119
120/// The machinery the derive macro's generated code reaches for, which is
121/// everything it needs that is not already public. Not a stable interface.
122#[doc(hidden)]
123pub mod __private {
124    pub use serde::{self, Deserialize, Serialize};
125
126    pub use crate::tree::{EffectivelyUnset, is_effectively_unset};
127}