cfgmatic 1.1.0

High-level configuration management framework for Rust with derive macros and validation
Documentation
//! High-level configuration management framework for Rust with derive macros.
#![allow(
    clippy::missing_errors_doc,
    clippy::missing_docs_in_private_items,
    clippy::mixed_attributes_style,
    clippy::must_use_candidate
)]
//!
//! This crate provides a unified, easy-to-use API for configuration management
//! with support for:
//!
//! - File-based configuration (TOML, JSON, YAML)
//! - Environment variables with NPM-style naming (double underscore separator)
//! - Derive macros for declarative configuration
//! - Built-in validation via `validator` crate
//!
//! # Quick Start
//!
//! ```
//! use cfgmatic::{Config, ConfigLoader};
//! use serde::Deserialize;
//!
//! #[derive(Debug, Default, Deserialize, Config)]
//! #[config(prefix = "MYAPP")]
//! struct AppConfig {
//!     #[config(default = "8080")]
//!     port: u16,
//!     #[config(default = "localhost")]
//!     host: String,
//! }
//!
//! // Load from environment
//! let config = AppConfig::from_env().unwrap();
//! ```
//!
//! # NPM-style Environment Variables
//!
//! Environment variables follow NPM's naming convention using double underscore (`__`)
//! as a separator for nested fields:
//!
//! ```bash
//! # For struct with prefix "MYAPP" and field "database.url":
//! export MYAPP__DATABASE__URL="postgres://localhost/db"
//!
//! # For nested structs:
//! export MYAPP__SERVER__PORT="3000"
//! export MYAPP__SERVER__HOST="0.0.0.0"
//! ```
//!
//! # Validation
//!
//! Use the `validator` crate's derive macro for validation:
//!
//! ```
//! use cfgmatic::{Config, ConfigLoader};
//! use serde::Deserialize;
//! use validator::Validate;
//!
//! #[derive(Debug, Default, Deserialize, Config, Validate)]
//! struct DatabaseConfig {
//!     #[validate(url)]
//!     url: String,
//!     #[validate(range(min = 1, max = 100))]
//!     pool_size: u32,
//! }
//! ```
//!
//! # Reactive Configuration (Future Feature)
//!
//! ```ignore
//! use cfgmatic::reactive::{ConfigStream, FieldQuery};
//!
//! let mut stream = manager
//!     .subscribe(FieldQuery::new(|c: &Config| c.server.port))
//!     .await?;
//!
//! while let Some(change) = stream.next().await {
//!     println!("Port: {} -> {}", change.old, change.new);
//! }
//! ```

// Re-exports from macros
#[cfg(feature = "env")]
pub use cfgmatic_macros::{Config, ConfigEnv};

// Re-exports from sub-crates
#[cfg(feature = "files")]
pub use cfgmatic_files::{FileError, FileFinder, Format, MergeBehavior, MergeOptions};
pub use cfgmatic_paths::{
    ConfigCandidate, ConfigDiscovery, ConfigFileRule, ConfigRuleSet, ConfigTier, DiscoveryOptions,
    FilePattern, FragmentRule, PathStatus, PathsBuilder, SourceType, TierSearchMode,
};

// Reactive module (feature-gated)
#[cfg(feature = "reactive")]
pub mod reactive;
#[cfg(feature = "reactive")]
pub use reactive::{ChangeSource, ConfigChange, ConfigStream};

// Re-export validator for convenience
pub use validator::{Validate, ValidationError, ValidationErrors};

mod env;
mod error;
mod loader;
mod manager;
mod multi;

pub use env::{ConfigLoader, EnvConfig, EnvLoader};
pub use error::{ConfigError, ConfigFields, Result};
pub use loader::{LoadBuilder, load_with};
pub use manager::{ConfigLoadResult, ConfigManager};
pub use multi::{MultiFileConfigLoader, load_multi};

/// Common imports for convenience.
pub mod prelude {
    //! Common imports for working with configuration.

    #[cfg(feature = "env")]
    pub use crate::{Config, ConfigEnv, ConfigLoader};
    pub use crate::{
        ConfigCandidate, ConfigDiscovery, ConfigError, ConfigFileRule, ConfigLoadResult,
        ConfigManager, ConfigRuleSet, ConfigTier, DiscoveryOptions, FilePattern, FragmentRule,
        MergeBehavior, MergeOptions, MultiFileConfigLoader, PathStatus, Result, SourceType,
        TierSearchMode,
    };

    #[cfg(feature = "files")]
    pub use crate::{FileFinder, Format};

    pub use validator::Validate;
}

/// Load configuration for an application.
///
/// This is a convenience function for simple use cases.
/// Requires `Config` trait which is implemented by the `Config` derive macro.
///
/// # Example
///
/// ```ignore
/// use cfgmatic::{Config, load};
/// use serde::Deserialize;
///
/// #[derive(Debug, Default, Deserialize, Config)]
/// struct MyConfig {
///     name: String,
/// }
///
/// match load::<MyConfig>("myapp") {
///     Ok(config) => println!("Loaded: {:?}", config),
///     Err(e) => eprintln!("Failed to load: {}", e),
/// }
/// ```
///
/// # Errors
///
/// Returns an error if no configuration is found or if parsing fails.
#[cfg(feature = "files")]
pub fn load<T>(app_name: impl Into<String>) -> Result<T>
where
    T: serde::de::DeserializeOwned + ConfigFields + Default,
{
    ConfigManager::new(app_name).load()
}

/// Check if configuration exists for an application.
///
/// # Example
///
/// ```
/// use cfgmatic::exists;
///
/// if exists("myapp") {
///     println!("Configuration found!");
/// }
/// ```
#[cfg(feature = "files")]
pub fn exists(app_name: impl Into<String>) -> bool {
    ConfigManager::new(app_name).exists()
}

/// Load configuration from environment variables only.
///
/// # Example
///
/// ```
/// use cfgmatic::{load_from_env, Config, ConfigLoader};
/// use serde::Deserialize;
///
/// #[derive(Debug, Deserialize, Config)]
/// #[config(prefix = "MYAPP")]
/// struct Config {
///     port: u16,
/// }
///
/// // std::env::set_var("MYAPP__PORT", "8080");
/// // let config: Config = load_from_env().unwrap();
/// ```
///
/// # Errors
///
/// Returns an error if environment variables cannot be parsed.
#[cfg(feature = "env")]
pub fn load_from_env<T>() -> Result<T>
where
    T: ConfigLoader + Default,
{
    T::from_env()
}

/// Parse a value from an environment variable string.
///
/// Supports common types like strings, numbers, bools.
///
/// # Example
///
/// ```
/// use cfgmatic::parse_env_value;
///
/// let port: u16 = parse_env_value("8080").unwrap();
/// let debug: bool = parse_env_value("true").unwrap();
/// ```
///
/// # Errors
///
/// Returns an error if the value cannot be parsed into the target type.
pub fn parse_env_value<T: std::str::FromStr>(value: &str) -> Result<T>
where
    T::Err: std::fmt::Display,
{
    value
        .parse()
        .map_err(|e| ConfigError::Parse(format!("Failed to parse value '{value}': {e}")))
}

/// Build environment variable name from path components.
///
/// Uses double underscore (`__`) as separator, following NPM convention.
///
/// # Example
///
/// ```
/// use cfgmatic::build_env_name;
///
/// let name = build_env_name("MYAPP", &["database", "url"]);
/// assert_eq!(name, "MYAPP__DATABASE__URL");
/// ```
#[must_use]
pub fn build_env_name(prefix: &str, path: &[&str]) -> String {
    let mut parts = Vec::with_capacity(path.len() + 1);
    if !prefix.is_empty() {
        parts.push(prefix.to_uppercase());
    }
    parts.extend(path.iter().map(|s| s.to_uppercase()));
    parts.join("__")
}

/// Discover configuration locations for an application.
///
/// Returns comprehensive diagnostic information about configuration
/// locations without actually loading the configuration.
///
/// # Example
///
/// ```
/// use cfgmatic::discover_config;
///
/// let discovery = discover_config("myapp");
///
/// println!("Config would be at: {}", discovery.preferred_path.display());
/// if let Some(found) = &discovery.found_path {
///     println!("Found existing config at: {}", found.display());
/// }
///
/// for candidate in discovery.candidates {
///     println!("  - {:?}: {} ({:?})",
///         candidate.tier,
///         candidate.path.display(),
///         candidate.status
///     );
/// }
/// ```
#[must_use]
pub fn discover_config(app_name: impl Into<String>) -> ConfigDiscovery {
    PathsBuilder::new(app_name).build().discover_config()
}

/// Get the preferred configuration path for an application.
///
/// Returns the path where configuration would be created,
/// without checking if it exists.
///
/// # Example
///
/// ```
/// use cfgmatic::config_path;
///
/// let path = config_path("myapp");
/// println!("Config location: {}", path.display());
/// ```
#[must_use]
pub fn config_path(app_name: impl Into<String>) -> std::path::PathBuf {
    PathsBuilder::new(app_name).build().preferred_config_path()
}

/// Get the preferred configuration file path for an application.
///
/// # Example
///
/// ```
/// use cfgmatic::config_file_path;
///
/// let path = config_file_path("myapp", "config.toml");
/// println!("Config file: {}", path.display());
/// ```
#[must_use]
pub fn config_file_path(
    app_name: impl Into<String>,
    filename: impl AsRef<std::path::Path>,
) -> std::path::PathBuf {
    PathsBuilder::new(app_name)
        .build()
        .preferred_config_file(filename)
}