tier 0.1.17

Rust configuration library for layered TOML, env, and CLI settings
Documentation
use std::collections::{BTreeMap, BTreeSet};

use serde_json::Value;

use crate::path::{canonicalize_path_with_aliases, normalize_path};

use super::super::Layer;

pub(in crate::loader) fn canonicalize_layer_path(
    value: &Value,
    path: &str,
    aliases: &BTreeMap<String, String>,
) -> String {
    let runtime = canonicalize_runtime_path(value, path);
    canonicalize_path_with_aliases(&runtime, aliases)
}

pub(in crate::loader) fn canonicalize_runtime_path(value: &Value, path: &str) -> String {
    if path.is_empty() {
        return String::new();
    }

    let segments = path.split('.').collect::<Vec<_>>();
    let mut current = value;
    let mut canonical = Vec::new();
    let mut index = 0;
    while index < segments.len() {
        let segment = segments[index];
        match current {
            Value::Object(map) => {
                canonical.push(segment.to_owned());
                let Some(next) = map.get(segment) else {
                    append_remaining_segments(&mut canonical, &segments[index + 1..]);
                    break;
                };
                current = next;
            }
            Value::Array(values) => {
                let Ok(array_index) = segment.parse::<usize>() else {
                    canonical.push(segment.to_owned());
                    append_remaining_segments(&mut canonical, &segments[index + 1..]);
                    break;
                };
                canonical.push(array_index.to_string());
                let Some(next) = values.get(array_index) else {
                    append_remaining_segments(&mut canonical, &segments[index + 1..]);
                    break;
                };
                current = next;
            }
            _ => {
                canonical.push(segment.to_owned());
                append_remaining_segments(&mut canonical, &segments[index + 1..]);
                break;
            }
        }
        index += 1;
    }

    canonical.join(".")
}

pub(in crate::loader) fn canonicalize_secret_paths(
    secret_paths: &BTreeSet<String>,
    aliases: &BTreeMap<String, String>,
) -> BTreeSet<String> {
    secret_paths
        .iter()
        .map(|path| canonicalize_path_with_aliases(path, aliases))
        .collect()
}

pub(in crate::loader) fn canonicalize_secret_paths_against_layers(
    secret_paths: &BTreeSet<String>,
    layers: &[Layer],
    aliases: &BTreeMap<String, String>,
) -> BTreeSet<String> {
    secret_paths
        .iter()
        .map(|path| {
            layers.iter().fold(path.clone(), |current, layer| {
                canonicalize_layer_path(&layer.value, &current, aliases)
            })
        })
        .collect()
}

pub(in crate::loader) fn canonicalize_secret_paths_against_value(
    secret_paths: &BTreeSet<String>,
    value: &Value,
    aliases: &BTreeMap<String, String>,
) -> BTreeSet<String> {
    secret_paths
        .iter()
        .map(|path| canonicalize_layer_path(value, path, aliases))
        .collect()
}

pub(in crate::loader) fn canonicalize_runtime_paths_against_value<I>(
    paths: I,
    value: &Value,
) -> Vec<String>
where
    I: IntoIterator<Item = String>,
{
    let mut canonicalized = Vec::new();
    for path in paths {
        let canonical = canonicalize_runtime_path(value, &path);
        if canonical.is_empty() || canonicalized.contains(&canonical) {
            continue;
        }
        canonicalized.push(canonical);
    }
    canonicalized
}

pub(in crate::loader) fn canonicalize_runtime_paths_across_layers<I>(
    paths: I,
    layers: &[Layer],
) -> Vec<String>
where
    I: IntoIterator<Item = String>,
{
    let mut canonicalized = Vec::new();
    for path in paths {
        let canonical = canonicalize_runtime_path_across_layers(&path, layers);
        if canonical.is_empty() || canonicalized.contains(&canonical) {
            continue;
        }
        canonicalized.push(canonical);
    }
    canonicalized
}

pub(in crate::loader) fn canonicalize_runtime_path_across_layers(
    path: &str,
    layers: &[Layer],
) -> String {
    layers.iter().fold(normalize_path(path), |current, layer| {
        canonicalize_runtime_path(&layer.value, &current)
    })
}

pub(in crate::loader) fn canonicalize_paths_with_aliases<I>(
    paths: I,
    aliases: &BTreeMap<String, String>,
) -> Vec<String>
where
    I: IntoIterator<Item = String>,
{
    let mut canonicalized = Vec::new();
    for path in paths {
        let canonical = canonicalize_path_with_aliases(&path, aliases);
        if canonical.is_empty() || canonicalized.contains(&canonical) {
            continue;
        }
        canonicalized.push(canonical);
    }
    canonicalized
}

fn append_remaining_segments(canonical: &mut Vec<String>, segments: &[&str]) {
    canonical.extend(segments.iter().map(|segment| (*segment).to_owned()));
}