use serde_json::{Map, Value};
use lean_ctx_patterns::json_crush::{self, CrushOpts, CrushResult};
const MARKER: &str = "_lc_yaml_crush";
const MIN_SAVE_RATIO_NUM: usize = 3;
const MIN_SAVE_RATIO_DEN: usize = 4;
fn beneficial(compact: &str, raw: &str) -> bool {
compact.len().saturating_mul(MIN_SAVE_RATIO_DEN) <= raw.len().saturating_mul(MIN_SAVE_RATIO_NUM)
}
pub fn crush_text_if_beneficial(text: &str) -> Option<String> {
let res = crush(text, 1.0)?;
(res.lossless && beneficial(&res.text, text)).then_some(res.text)
}
pub fn crush_text_lossy_if_beneficial(text: &str, drop_entropy: f64) -> Option<CrushResult> {
let res = crush(text, drop_entropy.clamp(0.0, 1.0))?;
(!res.lossless && beneficial(&res.text, text)).then_some(res)
}
fn crush(text: &str, drop_entropy: f64) -> Option<CrushResult> {
let value: Value = yaml_serde::from_str(text).ok()?;
if !matches!(value, Value::Object(_) | Value::Array(_)) {
return None;
}
if matches!(&value, Value::Object(map) if map.contains_key(MARKER)) {
return None;
}
let (inner, lossless) = if drop_entropy < 1.0 {
match json_crush::crush_lossy(&value, &CrushOpts::lossy(drop_entropy)) {
Some(res) => (serde_json::from_str(&res.text).ok()?, res.lossless),
None => (value, true),
}
} else {
match json_crush::crush_lossless(&value) {
Some(res) => (serde_json::from_str(&res.text).ok()?, true),
None => (value, true),
}
};
let mut env = Map::new();
env.insert(MARKER.to_string(), inner);
let text_out = serde_json::to_string(&Value::Object(env)).ok()?;
Some(CrushResult {
text: text_out,
lossless,
})
}
pub fn reconstruct(text: &str) -> Option<Value> {
let v: Value = serde_json::from_str(text).ok()?;
let inner = v.as_object()?.get(MARKER)?;
let inner_text = serde_json::to_string(inner).ok()?;
json_crush::reconstruct(&inner_text)
}
#[cfg(test)]
pub mod tests {
use super::*;
fn manifest_yaml() -> String {
let mut s = String::from("items:\n");
for i in 0..16 {
s.push_str(&format!(
" - apiVersion: v1\n kind: Pod\n namespace: prod\n name: pod-{i}\n"
));
}
s
}
fn parsed(text: &str) -> Value {
yaml_serde::from_str(text).expect("valid yaml")
}
#[test]
fn lossless_compacts_and_factors() {
let yaml = manifest_yaml();
let crushed = crush_text_if_beneficial(&yaml).expect("should crush");
assert!(crushed.starts_with("{\"_lc_yaml_crush\":"));
assert_eq!(crushed.matches("\"Pod\"").count(), 1);
assert_eq!(crushed.matches("\"prod\"").count(), 1);
assert!(crushed.len() < yaml.len(), "must never inflate");
}
#[test]
fn lossless_roundtrips_to_the_parsed_value() {
let yaml = manifest_yaml();
let crushed = crush_text_if_beneficial(&yaml).unwrap();
assert_eq!(reconstruct(&crushed).unwrap(), parsed(&yaml));
}
#[test]
fn conversion_pays_for_scalar_sequences_without_object_factoring() {
let mut yaml = String::from("ports:\n");
for i in 0..60 {
yaml.push_str(&format!(" - {}\n", 8000 + i));
}
let crushed = crush_text_if_beneficial(&yaml).expect("conversion pays");
assert_eq!(reconstruct(&crushed).unwrap(), parsed(&yaml));
assert!(crushed.len() < yaml.len());
}
#[test]
fn flat_string_map_does_not_inflate() {
let mut yaml = String::from("config:\n");
for i in 0..40 {
yaml.push_str(&format!(" setting_{i}: value_{i}\n"));
}
assert!(crush_text_if_beneficial(&yaml).is_none());
}
#[test]
fn output_is_byte_stable_across_calls() {
let yaml = manifest_yaml();
let run = || crush_text_if_beneficial(&yaml).unwrap();
assert_eq!(run(), run(), "crush output must be deterministic (#498)");
}
#[test]
fn second_pass_is_a_noop_via_marker_guard() {
let yaml = manifest_yaml();
let crushed = crush_text_if_beneficial(&yaml).unwrap();
assert!(crush_text_if_beneficial(&crushed).is_none());
}
#[test]
fn lossy_drops_high_entropy_columns_and_flags_lossy() {
let mut s = String::from("items:\n");
for i in 0..40 {
s.push_str(&format!(
" - kind: Pod\n namespace: prod\n uid: uid-{i:08}-abcdef\n"
));
}
let res = crush_text_lossy_if_beneficial(&s, 0.9).expect("lossy gate fires");
assert!(!res.lossless, "dropping a column must report lossy");
assert!(res.text.contains("_dropped"));
assert!(
!res.text.contains("uid-00000000"),
"dropped values are gone from the text"
);
assert!(beneficial(&res.text, &s));
assert!(crush_text_lossy_if_beneficial(&s, 1.0).is_none());
}
#[test]
fn lossy_is_byte_stable() {
let mut s = String::from("items:\n");
for i in 0..40 {
s.push_str(&format!(" - kind: Pod\n uid: uid-{i:08}-abcdef\n"));
}
let run = || crush_text_lossy_if_beneficial(&s, 0.9).unwrap().text;
assert_eq!(run(), run());
}
#[test]
fn skips_scalars_small_and_non_yaml_input() {
assert!(crush_text_if_beneficial("just a sentence, not a document").is_none());
assert!(crush_text_if_beneficial("a: 1\nb: 2\n").is_none()); assert!(crush_text_if_beneficial("").is_none());
assert!(crush_text_if_beneficial("{\"a\":1,\"b\":2}").is_none());
assert!(crush_text_if_beneficial("_lc_yaml_crush:\n a: 1\n").is_none());
}
}