config-lib 0.9.0

Enterprise-grade multi-format configuration library supporting 8 formats (CONF, INI, Properties, JSON, XML, HCL, TOML, NOML) with sub-50ns caching, hot reloading, and comprehensive validation.
Documentation
//! # NOML Format Parser
//!
//! Integration with the NOML library for advanced configuration features.
//! This parser leverages the full NOML library to provide:
//!
//! - Environment variable resolution
//! - File includes  
//! - Variable interpolation
//! - Native types (@size, @duration, etc.)
//! - Format preservation for round-trip editing

#[cfg(not(feature = "noml"))]
use crate::error::Error;
use crate::error::Result;
use crate::value::Value;
use std::collections::BTreeMap;

/// Parse NOML format configuration using the noml library
#[cfg(feature = "noml")]
pub fn parse(source: &str) -> Result<Value> {
    // Parse the document
    let document = noml::parse_string(source, None)?;

    // Resolve dynamic features (env vars, includes, etc.)
    let mut resolver = noml::Resolver::new();
    let resolved = resolver.resolve(&document)?;

    convert_noml_value(resolved)
}

/// Parse NOML format configuration (fallback when NOML is not available)
#[cfg(not(feature = "noml"))]
pub fn parse(_source: &str) -> Result<Value> {
    Err(Error::general(
        "NOML parsing requires the 'noml' feature to be enabled",
    ))
}

/// Convert NOML Value to config-lib Value
#[cfg(feature = "noml")]
fn convert_noml_value(noml_value: noml::Value) -> Result<Value> {
    match noml_value {
        noml::Value::Null => Ok(Value::Null),
        noml::Value::Bool(b) => Ok(Value::Bool(b)),
        noml::Value::Integer(i) => Ok(Value::Integer(i)),
        noml::Value::Float(f) => Ok(Value::Float(f)),
        noml::Value::String(s) => Ok(Value::String(s)),
        noml::Value::Array(arr) => {
            let converted: Result<Vec<Value>> = arr.into_iter().map(convert_noml_value).collect();
            Ok(Value::Array(converted?))
        }
        noml::Value::Table(table) => {
            let mut converted = BTreeMap::new();
            for (key, value) in table {
                converted.insert(key, convert_noml_value(value)?);
            }
            Ok(Value::Table(converted))
        }
        #[cfg(feature = "chrono")]
        noml::Value::DateTime(_) => Ok(Value::String("datetime_value".to_string())),
        // Handle NOML-specific types by converting to basic types
        noml::Value::Binary(_data) => {
            // Convert binary data to base64 string for compatibility
            #[cfg(feature = "base64")]
            {
                use base64::{engine::general_purpose, Engine as _};
                Ok(Value::String(general_purpose::STANDARD.encode(_data)))
            }
            #[cfg(not(feature = "base64"))]
            Ok(Value::String("<binary data>".to_string()))
        }
        noml::Value::Size(size) => {
            // Convert size to integer (bytes)
            Ok(Value::Integer(size as i64))
        }
        noml::Value::Duration(duration) => {
            // Convert duration to float (seconds)
            Ok(Value::Float(duration))
        }
    }
}

/// Parse NOML with format preservation for round-trip editing
pub fn parse_with_preservation(source: &str) -> Result<(Value, noml::Document)> {
    // Parse to get the AST document for format preservation
    let document = noml::parse_string(source, None)?;

    // Resolve to get the actual values
    let mut resolver = noml::Resolver::new();
    let resolved = resolver.resolve(&document)?;

    // Convert to config-lib Value
    let value = convert_noml_value(resolved)?;

    Ok((value, document))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_basic_noml() {
        let config = parse(
            r#"
            name = "test"
            port = 8080
            debug = true
        "#,
        )
        .unwrap();

        assert_eq!(config.get("name").unwrap().as_string().unwrap(), "test");
        assert_eq!(config.get("port").unwrap().as_integer().unwrap(), 8080);
        assert!(config.get("debug").unwrap().as_bool().unwrap());
    }

    #[test]
    fn test_noml_features() {
        std::env::set_var("TEST_VAR", "hello");

        let config = parse(
            r#"
            greeting = env("TEST_VAR", "world")
            size = @size("10MB")
            timeout = @duration("30s")
        "#,
        )
        .unwrap();

        assert_eq!(
            config.get("greeting").unwrap().as_string().unwrap(),
            "hello"
        );
        // Size converted to bytes
        assert_eq!(config.get("size").unwrap().as_integer().unwrap(), 10485760);
        // Duration converted to seconds
        assert_eq!(config.get("timeout").unwrap().as_float().unwrap(), 30.0);
    }
}