tier 0.1.17

Rust configuration library for layered TOML, env, and CLI settings
Documentation
use serde_json::{Map, Value};

pub(crate) fn insert_path(root: &mut Value, segments: &[&str], value: Value) -> Result<(), String> {
    if segments.is_empty() {
        return Err("configuration path cannot be empty".to_owned());
    }

    insert_path_recursive(root, segments, value)
}

fn insert_path_recursive(
    current: &mut Value,
    segments: &[&str],
    value: Value,
) -> Result<(), String> {
    let segment = segments[0];
    if segment.is_empty() {
        return Err("configuration path contains an empty segment".to_owned());
    }

    let is_last = segments.len() == 1;
    match current {
        Value::Object(map) => {
            if is_last {
                map.insert(segment.to_owned(), value);
                return Ok(());
            }

            let next_is_index = segments[1].parse::<usize>().is_ok();
            let child = map.entry(segment.to_owned()).or_insert_with(|| {
                if next_is_index {
                    Value::Array(Vec::new())
                } else {
                    Value::Object(Map::new())
                }
            });

            match child {
                Value::Object(_) if !next_is_index => {}
                Value::Array(_) if next_is_index => {}
                _ => {
                    return Err(format!(
                        "path segment {segment} conflicts with an existing non-container value"
                    ));
                }
            }

            insert_path_recursive(child, &segments[1..], value)
        }
        Value::Array(values) => {
            let index = segment.parse::<usize>().map_err(|_| {
                format!("path segment {segment} must be an array index at this position")
            })?;

            if is_last {
                if values.len() <= index {
                    values.resize(index + 1, Value::Null);
                }
                values[index] = value;
                return Ok(());
            }

            let next_is_index = segments[1].parse::<usize>().is_ok();
            if values.len() <= index {
                values.resize_with(index + 1, || {
                    if next_is_index {
                        Value::Array(Vec::new())
                    } else {
                        Value::Object(Map::new())
                    }
                });
            }

            let child = &mut values[index];
            if child.is_null() {
                *child = if next_is_index {
                    Value::Array(Vec::new())
                } else {
                    Value::Object(Map::new())
                };
            }

            match child {
                Value::Object(_) if !next_is_index => {}
                Value::Array(_) if next_is_index => {}
                _ => {
                    return Err(format!(
                        "path segment {segment} conflicts with an existing non-container value"
                    ));
                }
            }

            insert_path_recursive(child, &segments[1..], value)
        }
        _ => Err(format!(
            "path segment {segment} conflicts with an existing non-container value"
        )),
    }
}