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}