cfgmatic-source 2.2.0

Configuration sources (file, env, remote) for cfgmatic framework
Documentation
//! # cfgmatic-source
//!
//! Configuration sources (file, env, remote) for cfgmatic framework.
//!
//! This crate provides a unified interface for loading configuration
//! from various sources: files, environment variables, and remote URLs.
//!
//! ## Architecture
//!
//! ```mermaid
//! graph TB
//!     subgraph "Public API"
//!         LIB[lib.rs Facade]
//!         PRELUDE[prelude]
//!         FUNC[Convenience Functions]
//!     end
//!
//!     subgraph "Config Layer"
//!         OPT[LoaderOptions]
//!         CTX[LoadContext]
//!     end
//!
//!     subgraph "Application Layer"
//!         LOADER[Loader]
//!         DETECTOR[FormatDetector]
//!     end
//!
//!     subgraph "Infrastructure Layer"
//!         FS[FileSource]
//!         ES[EnvSource]
//!         MS[MemorySource]
//!         CS[CompositeSource]
//!         REG[SourceRegistry]
//!     end
//!
//!     subgraph "Domain Layer"
//!         SRC[Source Trait]
//!         KIND[SourceKind]
//!         META[SourceMetadata]
//!         FMT[Format]
//!         RAW[RawContent]
//!         PARSED[ParsedContent]
//!         ERR[SourceError]
//!     end
//!
//!     LIB --> PRELUDE
//!     LIB --> FUNC
//!     LIB --> OPT
//!     LIB --> LOADER
//!     LIB --> SRC
//!
//!     FUNC --> LOADER
//!     LOADER --> REG
//!     REG --> FS
//!     REG --> ES
//!     REG --> MS
//!     REG --> CS
//!
//!     FS --> SRC
//!     ES --> SRC
//!     MS --> SRC
//!     CS --> SRC
//!
//!     SRC --> RAW
//!     RAW --> FMT
//!     FMT --> PARSED
//!     DETECTOR --> FMT
//!
//!     OPT --> CTX
//!     CTX --> LOADER
//! ```
//!
//! ## Layer Dependencies
//!
//! The architecture follows the Onion (Layered) pattern with dependencies
//! pointing inward:
//!
//! ```text
//! +------------------------------------------+
//! |              lib.rs (Facade)              |
//! +------------------------------------------+
//!                     |
//! +------------------------------------------+
//! |             Config Layer                  |
//! |  Options, Context, Builders               |
//! +------------------------------------------+
//!                     |
//! +------------------------------------------+
//! |          Infrastructure Layer             |
//! |  FileSource, EnvSource, MemorySource,     |
//! |  CompositeSource, Registry                |
//! +------------------------------------------+
//!                     |
//! +------------------------------------------+
//! |           Application Layer               |
//! |  Loader, FormatDetector                   |
//! +------------------------------------------+
//!                     |
//! +------------------------------------------+
//! |             Domain Layer                  |
//! |  Source, SourceKind, Format,              |
//! |  RawContent, ParsedContent, Error         |
//! +------------------------------------------+
//! ```
//!
//! ## Feature Flags
//!
//! | Feature   | Description                        | Default |
//! |-----------|------------------------------------|---------|
//! | `file`    | File-based sources                 | Yes     |
//! | `env`     | Environment variable sources       | No      |
//! | `remote`  | Remote HTTP/HTTPS sources          | No      |
//! | `toml`    | TOML format support                | Yes     |
//! | `json`    | JSON format support                | Yes     |
//! | `yaml`    | YAML format support                | No      |
//! | `async`   | Async source loading               | No      |
//! | `watch`   | File watching support              | No      |
//!
//! ## Quick Start
//!
//! ### Load from a file
//!
//! ```rust,ignore
//! use cfgmatic_source::prelude::*;
//!
//! let content = FileSource::new("config.toml")
//!     .load()?
//!     .parse_as::<MyConfig>()?;
//! ```
//!
//! ### Load from environment
//!
//! ```rust,ignore
//! use cfgmatic_source::prelude::*;
//!
//! let content = EnvSource::new("APP")
//!     .load()?
//!     .parse_as::<MyConfig>()?;
//! ```
//!
//! ### Combine multiple sources
//!
//! ```rust,ignore
//! use cfgmatic_source::prelude::*;
//!
//! let source = CompositeSource::new()
//!     .with_source(FileSource::new("config.toml"), SourcePriority::High)
//!     .with_source(EnvSource::new("APP"), SourcePriority::Low);
//!
//! let content = source.load()?.parse_as::<MyConfig>()?;
//! ```
//!
//! ### Use convenience functions
//!
//! ```rust,ignore
//! use cfgmatic_source::{load, load_from_file, load_from_env};
//!
//! // Auto-detect source and load
//! let config: MyConfig = load()?;
//!
//! // Load from specific file
//! let config: MyConfig = load_from_file("config.toml")?;
//!
//! // Load from environment
//! let config: MyConfig = load_from_env("APP")?;
//! ```

#![warn(missing_docs)]
// Allow some pedantic lints that are too noisy
#![allow(clippy::missing_const_for_fn)]
#![allow(clippy::doc_markdown)]
#![allow(clippy::needless_pass_by_value)]
#![allow(clippy::option_if_let_else)]
#![allow(clippy::use_self)]
#![allow(clippy::derivable_impls)]
#![allow(clippy::trivially_copy_pass_by_ref)]
#![allow(clippy::redundant_closure_for_method_calls)]
#![allow(clippy::unused_self)]
#![allow(clippy::cast_precision_loss)]
#![allow(clippy::unnecessary_lazy_evaluations)]
#![allow(clippy::uninlined_format_args)]
#![allow(clippy::single_match_else)]
#![allow(clippy::should_implement_trait)]
#![allow(clippy::needless_continue)]
#![allow(clippy::missing_fields_in_debug)]
#![allow(clippy::struct_excessive_bools)]
#![allow(clippy::cast_possible_truncation)]
#![allow(clippy::map_unwrap_or)]
#![allow(clippy::collapsible_if)]

pub mod constants;
pub mod domain;
pub mod infrastructure;

// Placeholder modules for future implementation
/// Config layer - options and context.
pub mod config;

/// Application layer - services.
pub mod application;

// Re-exports from domain layer
pub use domain::{
    Format, ParsedContent, RawContent, Result, Source, SourceError, SourceKind, SourceMetadata,
};

// Re-exports from infrastructure layer
pub use infrastructure::{
    CompositeSource, CompositeSourceBuilder, MemorySource, MemorySourceBuilder, SourcePriority,
};

#[cfg(feature = "file")]
pub use infrastructure::{FileSource, FileSourceBuilder};

#[cfg(feature = "env")]
pub use infrastructure::{EnvConfig, EnvSource, EnvSourceBuilder};

// Re-exports from config layer
pub use config::{
    LoadOptions, LoadOptionsBuilder, MergeStrategy, SourceConfig, SourceConfigBuilder,
};

// Re-exports from application layer
pub use application::{
    LoadResult, Loader, LoaderBuilder, SourceCoordinator, SourceCoordinatorBuilder,
};

// Re-exports from constants
pub use constants::{
    DEFAULT_APP_NAME, DEFAULT_CONFIG_BASE_NAME, DEFAULT_DEBOUNCE_MS, DEFAULT_ENV_PREFIX,
    DEFAULT_EXTENSIONS, DEFAULT_REMOTE_TIMEOUT_SECS, ENV_KEY_SEPARATOR, JSON_POINTER_ROOT,
    MAX_REMOTE_RETRIES, MAX_SEARCH_DEPTH,
};

pub mod prelude {
    //! Common imports for cfgmatic-source.
    //!
    //! This module re-exports the most commonly used types and traits
    //! for convenience.

    // Domain layer
    pub use crate::domain::{
        Format, ParsedContent, RawContent, Result, Source, SourceError, SourceKind, SourceMetadata,
    };

    // Infrastructure layer
    pub use crate::infrastructure::{
        CompositeSource, CompositeSourceBuilder, MemorySource, MemorySourceBuilder, SourcePriority,
    };

    #[cfg(feature = "file")]
    pub use crate::infrastructure::{FileSource, FileSourceBuilder};

    #[cfg(feature = "env")]
    pub use crate::infrastructure::{EnvConfig, EnvSource, EnvSourceBuilder};

    // Config layer
    pub use crate::config::{
        LoadOptions, LoadOptionsBuilder, MergeStrategy, SourceConfig, SourceConfigBuilder,
    };

    // Application layer
    pub use crate::application::{
        LoadResult, Loader, LoaderBuilder, SourceCoordinator, SourceCoordinatorBuilder,
    };

    // Constants
    pub use crate::constants::{
        DEFAULT_APP_NAME, DEFAULT_CONFIG_BASE_NAME, DEFAULT_DEBOUNCE_MS, DEFAULT_ENV_PREFIX,
        DEFAULT_EXTENSIONS, DEFAULT_REMOTE_TIMEOUT_SECS, ENV_KEY_SEPARATOR, JSON_POINTER_ROOT,
        MAX_REMOTE_RETRIES, MAX_SEARCH_DEPTH,
    };
}

// =============================================================================
// Convenience Functions
// =============================================================================

/// Load configuration from auto-detected source.
///
/// This function attempts to load configuration from the following sources
/// in order of priority:
///
/// 1. Environment variables (if `env` feature is enabled)
/// 2. Configuration file in current directory (if `file` feature is enabled)
///
/// # Errors
///
/// Returns [`SourceError`] if no configuration source can be found or
/// if loading fails.
///
/// # Example
///
/// ```rust,ignore
/// use cfgmatic_source::load;
/// use serde::Deserialize;
///
/// #[derive(Deserialize)]
/// struct Config {
///     name: String,
///     port: u16,
/// }
///
/// let config: Config = load()?;
/// ```
#[cfg(feature = "file")]
pub fn load<T>() -> crate::domain::Result<T>
where
    T: serde::de::DeserializeOwned,
{
    // Try to find and load config file
    let source = FileSource::new("config.toml");
    let raw = source.load_raw()?;
    let content = raw
        .as_str()
        .map_err(|e| SourceError::serialization(&e.to_string()))?;
    let format = source.detect_format().unwrap_or(Format::Toml);
    format.parse_as(content.as_ref())
}

/// Load configuration from a specific file.
///
/// # Errors
///
/// Returns [`SourceError`] if the file cannot be read or parsed.
///
/// # Example
///
/// ```rust,ignore
/// use cfgmatic_source::load_from_file;
///
/// let config: MyConfig = load_from_file("config.toml")?;
/// ```
#[cfg(feature = "file")]
pub fn load_from_file<T, P>(path: P) -> crate::domain::Result<T>
where
    T: serde::de::DeserializeOwned,
    P: AsRef<std::path::Path> + Into<std::path::PathBuf>,
{
    let source = FileSource::new(path);
    let raw = source.load_raw()?;
    let content = raw
        .as_str()
        .map_err(|e| SourceError::serialization(&e.to_string()))?;
    let format = source.detect_format().unwrap_or(Format::Toml);
    format.parse_as(content.as_ref())
}

/// Load configuration from environment variables.
///
/// # Errors
///
/// Returns [`SourceError`] if environment variables cannot be read or parsed.
///
/// # Example
///
/// ```rust,ignore
/// use cfgmatic_source::load_from_env;
///
/// // Reads APP_NAME, APP_PORT, etc.
/// let config: MyConfig = load_from_env("APP")?;
/// ```
#[cfg(feature = "env")]
pub fn load_from_env<T>(prefix: &str) -> crate::domain::Result<T>
where
    T: serde::de::DeserializeOwned,
{
    let source = EnvSource::with_prefix(prefix);
    let raw = source.load_raw()?;
    let content = raw
        .as_str()
        .map_err(|e| SourceError::serialization(&e.to_string()))?;
    // EnvSource always produces JSON
    Format::Json.parse_as(content.as_ref())
}

/// Load configuration from memory (useful for testing).
///
/// # Errors
///
/// Returns [`SourceError`] if the content cannot be parsed.
///
/// # Example
///
/// ```rust,ignore
/// use cfgmatic_source::load_from_memory;
///
/// let toml_content = r#"
/// name = "test"
/// port = 8080
/// "#;
///
/// let config: MyConfig = load_from_memory(toml_content, Format::Toml)?;
/// ```
pub fn load_from_memory<T>(content: &str, format: Format) -> crate::domain::Result<T>
where
    T: serde::de::DeserializeOwned,
{
    format.parse_as(content)
}