toon_format/encode/
folding.rs1use crate::types::{
2 is_identifier_segment,
3 JsonValue as Value,
4 KeyFoldingMode,
5};
6
7pub struct FoldableChain {
9 pub folded_key: String,
11 pub leaf_value: Value,
13 pub depth_folded: usize,
15}
16
17fn is_single_key_object(value: &Value) -> Option<(&String, &Value)> {
19 if let Value::Object(obj) = value {
20 if obj.len() == 1 {
21 return obj.iter().next();
22 }
23 }
24 None
25}
26
27pub fn analyze_foldable_chain(
29 key: &str,
30 value: &Value,
31 flatten_depth: usize,
32 existing_keys: &[&String],
33) -> Option<FoldableChain> {
34 if !is_identifier_segment(key) {
35 return None;
36 }
37
38 let mut segments = vec![key.to_string()];
39 let mut current_value = value;
40
41 while let Some((next_key, next_value)) = is_single_key_object(current_value) {
43 if segments.len() >= flatten_depth {
44 break;
45 }
46
47 if !is_identifier_segment(next_key) {
48 break;
49 }
50
51 segments.push(next_key.clone());
52 current_value = next_value;
53 }
54
55 if segments.len() < 2 {
57 return None;
58 }
59
60 let folded_key = segments.join(".");
61
62 if existing_keys.contains(&&folded_key) {
64 return None;
65 }
66
67 Some(FoldableChain {
68 folded_key,
69 leaf_value: current_value.clone(),
70 depth_folded: segments.len(),
71 })
72}
73
74pub fn should_fold(mode: KeyFoldingMode, chain: &Option<FoldableChain>) -> bool {
75 match mode {
76 KeyFoldingMode::Off => false,
77 KeyFoldingMode::Safe => chain.is_some(),
78 }
79}
80
81#[cfg(test)]
82mod tests {
83 use serde_json::json;
84
85 use super::*;
86
87 #[test]
88 fn test_is_single_key_object() {
89 let val = Value::from(json!({"a": 1}));
90 assert!(is_single_key_object(&val).is_some());
91
92 let val = Value::from(json!({"a": 1, "b": 2}));
93 assert!(is_single_key_object(&val).is_none());
94
95 let val = Value::from(json!(42));
96 assert!(is_single_key_object(&val).is_none());
97 }
98
99 #[test]
100 fn test_analyze_simple_chain() {
101 let val = Value::from(json!({"b": {"c": 1}}));
102 let existing: Vec<&String> = vec![];
103
104 let result = analyze_foldable_chain("a", &val, usize::MAX, &existing);
105 assert!(result.is_some());
106
107 let chain = result.unwrap();
108 assert_eq!(chain.folded_key, "a.b.c");
109 assert_eq!(chain.depth_folded, 3);
110 assert_eq!(chain.leaf_value, Value::from(json!(1)));
111 }
112
113 #[test]
114 fn test_analyze_with_flatten_depth() {
115 let val = Value::from(json!({"b": {"c": {"d": 1}}}));
116 let existing: Vec<&String> = vec![];
117
118 let result = analyze_foldable_chain("a", &val, 2, &existing);
119 assert!(result.is_some());
120
121 let chain = result.unwrap();
122 assert_eq!(chain.folded_key, "a.b");
123 assert_eq!(chain.depth_folded, 2);
124 }
125
126 #[test]
127 fn test_analyze_stops_at_multi_key() {
128 let val = Value::from(json!({"b": {"c": 1, "d": 2}}));
129 let existing: Vec<&String> = vec![];
130
131 let result = analyze_foldable_chain("a", &val, usize::MAX, &existing);
132 assert!(result.is_some());
133
134 let chain = result.unwrap();
135 assert_eq!(chain.folded_key, "a.b");
136 assert_eq!(chain.depth_folded, 2);
137 }
138
139 #[test]
140 fn test_analyze_rejects_non_identifier() {
141 let val = Value::from(json!({"c": 1}));
142 let existing: Vec<&String> = vec![];
143
144 let result = analyze_foldable_chain("bad-key", &val, usize::MAX, &existing);
145 assert!(result.is_none());
146 }
147
148 #[test]
149 fn test_analyze_detects_collision() {
150 let val = Value::from(json!({"b": 1}));
151 let existing_key = String::from("a.b");
152 let existing: Vec<&String> = vec![&existing_key];
153
154 let result = analyze_foldable_chain("a", &val, usize::MAX, &existing);
155 assert!(result.is_none());
156 }
157
158 #[test]
159 fn test_analyze_too_short_chain() {
160 let val = Value::from(json!(42));
161 let existing: Vec<&String> = vec![];
162
163 let result = analyze_foldable_chain("a", &val, usize::MAX, &existing);
164 assert!(result.is_none());
165 }
166}