Skip to main content

toon/encode/
folding.rs

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