use crate::types::{
is_identifier_segment,
JsonValue as Value,
KeyFoldingMode,
};
pub struct FoldableChain {
pub folded_key: String,
pub leaf_value: Value,
pub depth_folded: usize,
}
fn is_single_key_object(value: &Value) -> Option<(&String, &Value)> {
if let Value::Object(obj) = value {
if obj.len() == 1 {
return obj.iter().next();
}
}
None
}
pub fn analyze_foldable_chain(
key: &str,
value: &Value,
flatten_depth: usize,
existing_keys: &[&String],
) -> Option<FoldableChain> {
if !is_identifier_segment(key) {
return None;
}
let mut segments = vec![key.to_string()];
let mut current_value = value;
while let Some((next_key, next_value)) = is_single_key_object(current_value) {
if segments.len() >= flatten_depth {
break;
}
if !is_identifier_segment(next_key) {
break;
}
segments.push(next_key.clone());
current_value = next_value;
}
if segments.len() < 2 {
return None;
}
let folded_key = segments.join(".");
if existing_keys.contains(&&folded_key) {
return None;
}
Some(FoldableChain {
folded_key,
leaf_value: current_value.clone(),
depth_folded: segments.len(),
})
}
pub fn should_fold(mode: KeyFoldingMode, chain: &Option<FoldableChain>) -> bool {
match mode {
KeyFoldingMode::Off => false,
KeyFoldingMode::Safe => chain.is_some(),
}
}
#[cfg(test)]
mod tests {
use serde_json::json;
use super::*;
#[test]
fn test_is_single_key_object() {
let val = Value::from(json!({"a": 1}));
assert!(is_single_key_object(&val).is_some());
let val = Value::from(json!({"a": 1, "b": 2}));
assert!(is_single_key_object(&val).is_none());
let val = Value::from(json!(42));
assert!(is_single_key_object(&val).is_none());
}
#[test]
fn test_analyze_simple_chain() {
let val = Value::from(json!({"b": {"c": 1}}));
let existing: Vec<&String> = vec![];
let result = analyze_foldable_chain("a", &val, usize::MAX, &existing);
assert!(result.is_some());
let chain = result.unwrap();
assert_eq!(chain.folded_key, "a.b.c");
assert_eq!(chain.depth_folded, 3);
assert_eq!(chain.leaf_value, Value::from(json!(1)));
}
#[test]
fn test_analyze_with_flatten_depth() {
let val = Value::from(json!({"b": {"c": {"d": 1}}}));
let existing: Vec<&String> = vec![];
let result = analyze_foldable_chain("a", &val, 2, &existing);
assert!(result.is_some());
let chain = result.unwrap();
assert_eq!(chain.folded_key, "a.b");
assert_eq!(chain.depth_folded, 2);
}
#[test]
fn test_analyze_stops_at_multi_key() {
let val = Value::from(json!({"b": {"c": 1, "d": 2}}));
let existing: Vec<&String> = vec![];
let result = analyze_foldable_chain("a", &val, usize::MAX, &existing);
assert!(result.is_some());
let chain = result.unwrap();
assert_eq!(chain.folded_key, "a.b");
assert_eq!(chain.depth_folded, 2);
}
#[test]
fn test_analyze_rejects_non_identifier() {
let val = Value::from(json!({"c": 1}));
let existing: Vec<&String> = vec![];
let result = analyze_foldable_chain("bad-key", &val, usize::MAX, &existing);
assert!(result.is_none());
}
#[test]
fn test_analyze_detects_collision() {
let val = Value::from(json!({"b": 1}));
let existing_key = String::from("a.b");
let existing: Vec<&String> = vec![&existing_key];
let result = analyze_foldable_chain("a", &val, usize::MAX, &existing);
assert!(result.is_none());
}
#[test]
fn test_analyze_too_short_chain() {
let val = Value::from(json!(42));
let existing: Vec<&String> = vec![];
let result = analyze_foldable_chain("a", &val, usize::MAX, &existing);
assert!(result.is_none());
}
}