tier 0.1.17

Rust configuration library for layered TOML, env, and CLI settings
Documentation
use super::super::path::invalid_path_key_message;

pub(super) struct DerivedEnvPathError {
    pub(super) path: String,
    pub(super) message: String,
}

pub(super) fn path_for_env_var(
    key: &str,
    prefix: Option<&str>,
    separator: &str,
    lowercase_segments: bool,
) -> Result<Option<String>, DerivedEnvPathError> {
    let remainder = if let Some(prefix) = prefix {
        let normalized = crate::env_name::normalize_env_prefix(prefix, separator);
        if normalized.is_empty() {
            key
        } else {
            if key == normalized {
                return Ok(None);
            }
            let Some(remainder) = key.strip_prefix(&normalized) else {
                return Ok(None);
            };
            let boundary = if prefix.ends_with(separator) && !separator.is_empty() {
                PrefixBoundary::SeparatorOnly
            } else {
                PrefixBoundary::Flexible
            };
            let Some(remainder) = parse_prefixed_env_remainder(remainder, separator, boundary)
            else {
                return Ok(None);
            };
            remainder
        }
    } else {
        key
    };

    if remainder.is_empty() {
        return Ok(None);
    }

    let mut segments = Vec::new();
    for segment in remainder.split(separator) {
        if segment.is_empty() {
            return Ok(None);
        }
        let segment = if lowercase_segments {
            segment.to_ascii_lowercase()
        } else {
            segment.to_owned()
        };
        if let Some(message) = invalid_path_key_message(&segment) {
            let mut path = segments.join(".");
            if !path.is_empty() {
                path.push('.');
            }
            path.push_str(&segment);
            return Err(DerivedEnvPathError {
                path,
                message: format!(
                    "environment variable segments must not contain reserved path syntax: {message}"
                ),
            });
        }
        segments.push(segment);
    }

    Ok(Some(segments.join(".")))
}

#[derive(Clone, Copy)]
enum PrefixBoundary {
    SeparatorOnly,
    Flexible,
}

fn parse_prefixed_env_remainder<'a>(
    remainder: &'a str,
    separator: &str,
    boundary: PrefixBoundary,
) -> Option<&'a str> {
    let remainder = match boundary {
        PrefixBoundary::SeparatorOnly => remainder.strip_prefix(separator)?,
        PrefixBoundary::Flexible => {
            if let Some(stripped) = remainder.strip_prefix(separator) {
                stripped
            } else if separator == "__" {
                remainder.strip_prefix('_')?
            } else {
                return None;
            }
        }
    };

    (!remainder.is_empty()).then_some(remainder)
}