statime-config 0.1.0

Full-featured implementation of NTP with NTS support
Documentation
//! Configuration parsing
//!
//! This crate provides a configuration parsing library and associated derive
//! macros for parsing configuration from multiple TOML files.
//!
//! As a user of this crate, you will typically define a set of structs, enums
//! and 'atomic' values that represent individual configuration settings. To
//! these you then apply the [`Configurable`] and [`ConfigurableAtomic`] derives.
//!
//! The root of the configuration tree is represented by the [`RootConfig`] type,
//! which is a [`Configurable`] struct that holds the configuration for the entire
//! application, but has an additional [`RootConfig::load`] method that loads
//! the configuration from TOML files.
//!
//! ```no_run
//! # use std::path::PathBuf;
//! # use serde::{Deserialize, Serialize};
//! use statime_config::{Configurable, ConfigurableAtomic, RootConfig, SystemConfigSetting};
//!
//! /// The root configuration needs to be a [`Configurable`] struct with [`RootConfig`]
//! /// implemented, which is used to load the configuration from TOML files.
//! /// It also needs to contain information which can identify whether and which
//! /// system configuration is to be used when loading the configuration.
//! #[derive(Debug, Clone, PartialEq, Eq, Configurable)]
//! pub struct Config {
//!     /// Whether to use the system configuration files, note the default
//!     /// attribute calls Default::default()
//!     #[config(default)]
//!     pub use_system_config: UseSystemConfig,
//!
//!     /// This section has no default value, if any value within it is not
//!     /// specified and has no default, that will be an error.
//!     pub observability: ObservabilityConfig,
//! }
//!
//! impl RootConfig for Config {
//!     fn use_system_config(partial: &Self::Partial) -> SystemConfigSetting {
//!         match partial.use_system_config.clone().into_option() {
//!             Some(UseSystemConfig::Enabled(false)) => SystemConfigSetting::None,
//!             Some(UseSystemConfig::Enabled(true)) => SystemConfigSetting::DefaultPath(UseSystemConfig::DEFAULT_SYSTEM_CONFIG.into()),
//!             Some(UseSystemConfig::Directory(path)) => SystemConfigSetting::UserSpecifiedPath(path),
//!             None => SystemConfigSetting::Unset,
//!         }
//!     }
//! }
//!
//! /// Only the Configurable derive is needed here, as this is not the root struct.
//! #[derive(Debug, Clone, PartialEq, Eq, Configurable)]
//! pub struct ObservabilityConfig {
//!     /// Example of using a custom enum as an atomic value, note how the
//!     /// default can also be any expression that evaluates to the type of
//!     /// the field.
//!     #[config(default = LogLevel::Info)]
//!     pub level: LogLevel,
//! }
//!
//! /// Example of a custom enum that is used as an atomic value, by deriving
//! /// the ConfigurableAtomic trait.
//! #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, ConfigurableAtomic)]
//! #[serde(rename_all = "kebab-case")]
//! pub enum LogLevel {
//!     Debug,
//!     Info,
//!     Warn,
//!     Error,
//! }
//!
//!
//! /// Which system configuration to layer underneath the main configuration, if
//! /// any.
//! #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, ConfigurableAtomic)]
//! #[serde(untagged)]
//! pub enum UseSystemConfig {
//!     /// `true` reads the fragments supplied by the distribution, `false` uses no
//!     /// system configuration at all.
//!     Enabled(bool),
//!
//!     /// A directory to read the fragments from, instead of the default one.
//!     Directory(PathBuf),
//! }
//!
//! impl UseSystemConfig {
//!     pub const DEFAULT_SYSTEM_CONFIG: &str = "/usr/lib/ntpd-rs/system-config";
//! }
//!
//! impl Default for UseSystemConfig {
//!     fn default() -> Self {
//!         Self::Enabled(false)
//!     }
//! }
//!
//! fn main() {
//!     let config = Config::load("/path/to/config.toml").unwrap();
//! }
//! ```

// so that the derive macro can name this crate even from inside it
extern crate self as statime_config;

mod error;
#[cfg(test)]
mod fixture;
mod load;
pub mod serde_util;
mod tree;

pub use error::ConfigError;
pub use load::{PartialTree, RootConfig, SystemConfigSetting};
// the traits and the derives that implement them share their names, so that
// one import brings both, as serde does
pub use statime_config_derive::{Configurable, ConfigurableAtomic};
// `Setting` and `Section` are what `Configurable::Node` resolves to, so they
// are part of the interface whether or not anyone names them directly
pub use tree::{
    ConfigPath, Configurable, ConfigurableAtomic, EnumSection, InfiniteOr, IsNonNegative,
    IsPositive, Merge, MergeContext, MergePolicy, NonNegative, Origin, OriginId, PartialValue,
    Positive, Section, Setting,
};

/// The machinery the derive macro's generated code reaches for, which is
/// everything it needs that is not already public. Not a stable interface.
#[doc(hidden)]
pub mod __private {
    pub use serde::{self, Deserialize, Serialize};

    pub use crate::tree::{EffectivelyUnset, is_effectively_unset};
}