tru 0.2.3

TOON reference implementation in Rust (JSON <-> TOON)
Documentation
use std::borrow::Cow;
use std::collections::HashSet;

use crate::JsonValue;
use crate::encode::normalize::{is_empty_object, is_json_object};
use crate::options::{KeyFoldingMode, ResolvedEncodeOptions};
use crate::shared::constants::DOT;
use crate::shared::validation::is_identifier_segment;

#[derive(Debug, Clone)]
pub struct FoldResult {
    pub folded_key: String,
    pub remainder: Option<JsonValue>,
    pub leaf_value: JsonValue,
    pub segment_count: usize,
}

#[must_use]
#[allow(clippy::implicit_hasher)]
pub fn try_fold_key_chain(
    key: &str,
    value: &JsonValue,
    siblings: &[&str],
    options: &ResolvedEncodeOptions,
    root_literal_keys: Option<&HashSet<String>>,
    path_prefix: Option<&str>,
    flatten_depth: usize,
) -> Option<FoldResult> {
    if options.key_folding != KeyFoldingMode::Safe {
        return None;
    }

    if !is_json_object(value) {
        return None;
    }

    let effective_depth = flatten_depth;
    if effective_depth < 2 {
        return None;
    }

    let (segments, tail, leaf_value) = collect_single_key_chain(key, value, effective_depth);

    if segments.len() < 2 {
        return None;
    }

    if !segments.iter().all(|seg| is_identifier_segment(seg)) {
        return None;
    }

    let mut folded_key =
        String::with_capacity(segments.iter().map(String::len).sum::<usize>() + segments.len());
    for (i, seg) in segments.iter().enumerate() {
        if i > 0 {
            folded_key.push(DOT);
        }
        folded_key.push_str(seg);
    }

    if siblings.iter().any(|sibling| *sibling == folded_key) {
        return None;
    }

    let absolute_path: Cow<str> = path_prefix.map_or_else(
        || Cow::Borrowed(folded_key.as_str()),
        |prefix| Cow::Owned(format!("{prefix}{DOT}{folded_key}")),
    );

    if let Some(root_keys) = root_literal_keys
        && root_keys.contains(absolute_path.as_ref())
    {
        return None;
    }

    Some(FoldResult {
        folded_key,
        remainder: tail,
        leaf_value,
        segment_count: segments.len(),
    })
}

fn collect_single_key_chain(
    start_key: &str,
    start_value: &JsonValue,
    max_depth: usize,
) -> (Vec<String>, Option<JsonValue>, JsonValue) {
    let mut segments = vec![start_key.to_string()];
    let mut current_value = start_value.clone();

    while segments.len() < max_depth {
        let JsonValue::Object(ref obj) = current_value else {
            break;
        };

        if obj.len() != 1 {
            break;
        }

        let (next_key, next_value) = obj[0].clone();
        segments.push(next_key);
        current_value = next_value;
    }

    match current_value {
        JsonValue::Object(entries) if is_empty_object(&entries) => {
            let obj = JsonValue::Object(entries);
            (segments, None, obj)
        }
        JsonValue::Object(entries) => {
            let remainder = JsonValue::Object(entries);
            (segments, Some(remainder.clone()), remainder)
        }
        other => (segments, None, other),
    }
}