Skip to main content

toon/encode/
folding.rs

1use std::borrow::Cow;
2use std::collections::HashSet;
3
4use crate::JsonValue;
5use crate::encode::normalize::{is_empty_object, is_json_object};
6use crate::options::{KeyFoldingMode, ResolvedEncodeOptions};
7use crate::shared::constants::DOT;
8use crate::shared::validation::is_identifier_segment;
9
10#[derive(Debug, Clone)]
11pub struct FoldResult {
12    pub folded_key: String,
13    pub remainder: Option<JsonValue>,
14    pub leaf_value: JsonValue,
15    pub segment_count: usize,
16}
17
18#[must_use]
19#[allow(clippy::implicit_hasher)]
20pub fn try_fold_key_chain(
21    key: &str,
22    value: &JsonValue,
23    siblings: &[&str],
24    options: &ResolvedEncodeOptions,
25    root_literal_keys: Option<&HashSet<String>>,
26    path_prefix: Option<&str>,
27    flatten_depth: usize,
28) -> Option<FoldResult> {
29    if options.key_folding != KeyFoldingMode::Safe {
30        return None;
31    }
32
33    if !is_json_object(value) {
34        return None;
35    }
36
37    let effective_depth = flatten_depth;
38    if effective_depth < 2 {
39        return None;
40    }
41
42    let (segments, tail, leaf_value) = collect_single_key_chain(key, value, effective_depth);
43
44    if segments.len() < 2 {
45        return None;
46    }
47
48    if !segments.iter().all(|seg| is_identifier_segment(seg)) {
49        return None;
50    }
51
52    let mut folded_key =
53        String::with_capacity(segments.iter().map(String::len).sum::<usize>() + segments.len());
54    for (i, seg) in segments.iter().enumerate() {
55        if i > 0 {
56            folded_key.push(DOT);
57        }
58        folded_key.push_str(seg);
59    }
60
61    if siblings.iter().any(|sibling| *sibling == folded_key) {
62        return None;
63    }
64
65    let absolute_path: Cow<str> = path_prefix.map_or_else(
66        || Cow::Borrowed(folded_key.as_str()),
67        |prefix| Cow::Owned(format!("{prefix}{DOT}{folded_key}")),
68    );
69
70    if let Some(root_keys) = root_literal_keys
71        && root_keys.contains(absolute_path.as_ref())
72    {
73        return None;
74    }
75
76    Some(FoldResult {
77        folded_key,
78        remainder: tail,
79        leaf_value,
80        segment_count: segments.len(),
81    })
82}
83
84fn collect_single_key_chain(
85    start_key: &str,
86    start_value: &JsonValue,
87    max_depth: usize,
88) -> (Vec<String>, Option<JsonValue>, JsonValue) {
89    let mut segments = vec![start_key.to_string()];
90    let mut current_value = start_value.clone();
91
92    while segments.len() < max_depth {
93        let JsonValue::Object(ref obj) = current_value else {
94            break;
95        };
96
97        if obj.len() != 1 {
98            break;
99        }
100
101        let (next_key, next_value) = obj[0].clone();
102        segments.push(next_key);
103        current_value = next_value;
104    }
105
106    match current_value {
107        JsonValue::Object(entries) if is_empty_object(&entries) => {
108            let obj = JsonValue::Object(entries);
109            (segments, None, obj)
110        }
111        JsonValue::Object(entries) => {
112            let remainder = JsonValue::Object(entries);
113            (segments, Some(remainder.clone()), remainder)
114        }
115        other => (segments, None, other),
116    }
117}