tier 0.1.17

Rust configuration library for layered TOML, env, and CLI settings
Documentation
use crate::{ConfigError, Layer};

use super::PatchLayerBuilder;

/// Sparse patch field wrapper used by typed override structs.
///
/// `Patch<T>` makes intent explicit:
///
/// - `Patch::Unset` means "do not touch this field"
/// - `Patch::Set(value)` means "override this field with `value`"
///
/// For optional config fields, use `Patch<Option<T>>` when the patch needs to
/// distinguish "unset" from "set this field to null".
///
/// # Examples
///
/// ```
/// use tier::Patch;
///
/// let port = Patch::set(8080);
/// assert!(port.is_set());
///
/// let untouched: Patch<u16> = Patch::Unset;
/// assert_eq!(untouched.into_option(), None);
/// ```
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub enum Patch<T> {
    /// Do not override this field.
    #[default]
    Unset,
    /// Override this field with the contained value.
    Set(T),
}

impl<T> Patch<T> {
    /// Creates a patch that overrides a field with `value`.
    #[must_use]
    pub fn set(value: T) -> Self {
        Self::Set(value)
    }

    /// Returns the contained value by reference when the patch is set.
    #[must_use]
    pub fn as_ref(&self) -> Option<&T> {
        match self {
            Self::Unset => None,
            Self::Set(value) => Some(value),
        }
    }

    /// Returns `true` when this patch carries an override value.
    #[must_use]
    pub fn is_set(&self) -> bool {
        matches!(self, Self::Set(_))
    }

    /// Consumes the patch and returns the override value, when present.
    #[must_use]
    pub fn into_option(self) -> Option<T> {
        match self {
            Self::Unset => None,
            Self::Set(value) => Some(value),
        }
    }
}

impl<T> From<T> for Patch<T> {
    fn from(value: T) -> Self {
        Self::Set(value)
    }
}

/// Trait implemented by typed sparse override structures.
///
/// The easiest way to implement this trait is `#[derive(TierPatch)]` with the
/// `derive` feature enabled. A `TierPatch` value can then be turned into a
/// [`Layer`] or applied directly to a [`crate::ConfigLoader`].
///
/// Fields are sparse by default:
///
/// - `Option<T>` fields only write when they are `Some(...)`
/// - nested patch structs can be connected with `#[tier(nested)]`
/// - CLI-only fields can be ignored with `#[tier(skip)]`
/// - enums can be used for subcommand-like patch models
/// - use [`Patch<Option<T>>`] when the patch must distinguish "unset" from
///   "explicitly clear this optional config field"
///
/// # Examples
///
/// ```no_run
/// # #[cfg(feature = "derive")] {
/// # fn main() -> Result<(), tier::ConfigError> {
/// use serde::{Deserialize, Serialize};
/// use tier::{Layer, Patch, TierPatch};
///
/// #[derive(Debug, Clone, Serialize, Deserialize)]
/// struct ServerConfig {
///     port: u16,
///     tls: TlsConfig,
/// }
///
/// #[derive(Debug, Clone, Serialize, Deserialize)]
/// struct TlsConfig {
///     cert: Option<String>,
/// }
///
/// #[derive(Debug, TierPatch, Default)]
/// struct ServerPatch {
///     port: Option<u16>,
///     #[tier(path_expr = tier::path!(ServerConfig.tls.cert))]
///     cert_path: Patch<Option<String>>,
/// }
///
/// let patch = ServerPatch {
///     port: Some(8443),
///     cert_path: Patch::set(None),
/// };
///
/// let _layer = Layer::from_patch("typed-cli", &patch)?;
/// # Ok(())
/// # }
/// # }
/// ```
pub trait TierPatch {
    /// Writes sparse overrides into the provided layer builder.
    fn write_layer(&self, builder: &mut PatchLayerBuilder, prefix: &str)
    -> Result<(), ConfigError>;

    /// Converts the patch into a custom configuration layer.
    ///
    /// This is useful when the patch is built separately from the loader and
    /// should be applied with [`crate::ConfigLoader::layer`].
    fn to_layer(&self, name: impl Into<String>) -> Result<Layer, ConfigError>
    where
        Self: Sized,
    {
        Layer::from_patch(name, self)
    }
}