hierconf-core 0.2.0

Core functionality for hierconf configuration management
Documentation
//! Extension attributes for hierconf
//!
//! This module defines custom facet attributes for hierconf configuration structs.

use crate::error::HierConfError;
use facet::Facet;

/// Attribute keys for hierconf attributes.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AttrKey {
    /// Application name attribute
    AppName,
    /// Synopsis attribute (mangen feature only)
    #[cfg(feature = "mangen")]
    Synopsis,
    /// Date attribute (mangen feature only)
    #[cfg(feature = "mangen")]
    Date,
}

#[cfg(not(feature = "mangen"))]
facet::define_attr_grammar! {
    ns "hierconf";
    crate_path ::hierconf_core::attrs;

    /// Hierconf extension attributes for configuration structs.
    pub enum Attr {
        /// Set application name used for config paths and man pages.
        ///
        /// Usage: `#[facet(hierconf::app_name("myapp"))]`
        AppName(&'static str),
    }
}

#[cfg(feature = "mangen")]
facet::define_attr_grammar! {
    ns "hierconf";
    crate_path ::hierconf_core::attrs;

    /// Hierconf extension attributes for configuration structs.
    pub enum Attr {
        /// Set application name used for config paths and man pages.
        ///
        /// Usage: `#[facet(hierconf::app_name("myapp"))]`
        AppName(&'static str),

        /// Override the synopsis text for the man page. Defaults to the rustdoc comment of the struct.
        ///
        /// Usage: `#[facet(hierconf::synopsis("Configuration format for myapp"))]`
        Synopsis(&'static str),

        /// Override the date for the man page. Defaults to the current date.
        ///
        /// Usage: `#[facet(hierconf::date("2025-01-15"))]`
        Date(&'static str),
    }
}

impl AttrKey {
    fn key_str(&self) -> &'static str {
        match self {
            AttrKey::AppName => "app_name",
            #[cfg(feature = "mangen")]
            AttrKey::Synopsis => "synopsis",
            #[cfg(feature = "mangen")]
            AttrKey::Date => "date",
        }
    }

    fn attr_name(&self) -> &'static str {
        match self {
            AttrKey::AppName => "hierconf::app_name",
            #[cfg(feature = "mangen")]
            AttrKey::Synopsis => "hierconf::synopsis",
            #[cfg(feature = "mangen")]
            AttrKey::Date => "hierconf::date",
        }
    }
}

/// Extract a string attribute from facet's Shape attributes.
///
/// Looks for `#[facet(hierconf::{key}("..."))]` attributes on the struct.
pub fn extract_attr<T>(key: AttrKey) -> Result<String, HierConfError>
where
    T: Facet<'static>,
{
    let key_str = key.key_str();
    let attr_name = key.attr_name();

    for attr in T::SHAPE.attributes {
        if attr.ns == Some("hierconf") && attr.key == key_str {
            if let Some(value) = attr.get_as::<&'static str>() {
                return Ok((*value).to_string());
            }
            return Err(HierConfError::AttributeShapeMismatch {
                shape: attr.data.shape(),
                namespace: attr.ns,
                key: attr.key,
                attr: attr_name,
            });
        }
    }

    Err(HierConfError::MissingAttribute(format!(
        "{} must be specified via #[facet({}(\"...\"))] attribute on the struct",
        key_str, attr_name
    )))
}

/// Extract an optional string attribute from facet's Shape attributes.
///
/// Looks for `#[facet(hierconf::{key}("..."))]` attributes on the struct.
pub fn extract_optional_attr<T>(key: AttrKey) -> Result<Option<String>, HierConfError>
where
    T: Facet<'static>,
{
    match extract_attr::<T>(key) {
        Ok(value) => Ok(Some(value)),
        Err(HierConfError::MissingAttribute(_)) => Ok(None),
        Err(e) => Err(e),
    }
}