use std::collections::BTreeSet;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DeltaField {
pub key: String,
pub old: String,
pub new: String,
}
#[derive(Debug, Clone, Copy)]
pub struct NearRefConfig {
pub max_delta_bytes: usize,
pub min_body_bytes: usize,
}
impl Default for NearRefConfig {
fn default() -> Self {
Self {
max_delta_bytes: 50,
min_body_bytes: 500,
}
}
}
pub fn extract_delta(
old_body: &str,
new_body: &str,
config: &NearRefConfig,
) -> Option<Vec<DeltaField>> {
if new_body.len() < config.min_body_bytes {
return None;
}
let old: serde_json::Value = serde_json::from_str(old_body.trim_start()).ok()?;
let new: serde_json::Value = serde_json::from_str(new_body.trim_start()).ok()?;
let old_obj = old.as_object()?;
let new_obj = new.as_object()?;
let old_keys: BTreeSet<&str> = old_obj.keys().map(|s| s.as_str()).collect();
let new_keys: BTreeSet<&str> = new_obj.keys().map(|s| s.as_str()).collect();
if old_keys != new_keys {
return None;
}
let mut deltas = Vec::new();
for (k, new_val) in new_obj {
let old_val = match old_obj.get(k) {
Some(v) => v,
None => unreachable!("key sets are equal"),
};
if old_val == new_val {
continue;
}
if !is_scalar(old_val) || !is_scalar(new_val) {
return None;
}
deltas.push(DeltaField {
key: k.clone(),
old: scalar_to_string(old_val),
new: scalar_to_string(new_val),
});
}
if rendered_delta_bytes(&deltas) > config.max_delta_bytes {
return None;
}
if deltas.is_empty() {
return None;
}
Some(deltas)
}
fn rendered_delta_bytes(deltas: &[DeltaField]) -> usize {
let mut frag = String::new();
for d in deltas {
frag.push_str(", ");
frag.push_str(&d.key);
frag.push_str(": ");
frag.push_str(&d.old);
frag.push('→');
frag.push_str(&d.new);
}
frag.len()
}
fn is_scalar(v: &serde_json::Value) -> bool {
matches!(
v,
serde_json::Value::Null
| serde_json::Value::Bool(_)
| serde_json::Value::Number(_)
| serde_json::Value::String(_)
)
}
fn scalar_to_string(v: &serde_json::Value) -> String {
match v {
serde_json::Value::Null => String::new(),
serde_json::Value::Bool(b) => b.to_string(),
serde_json::Value::Number(n) => n.to_string(),
serde_json::Value::String(s) => s.clone(),
_ => v.to_string(),
}
}
pub fn render_near_ref_hint(reference_id: &str, deltas: &[DeltaField]) -> String {
let mut out = String::new();
out.push_str("> [near-ref: ");
out.push_str(reference_id);
for d in deltas {
out.push_str(", ");
out.push_str(&d.key);
out.push_str(": ");
out.push_str(&d.old);
out.push('→');
out.push_str(&d.new);
}
out.push(']');
out
}
#[cfg(test)]
mod tests {
use super::*;
fn cfg() -> NearRefConfig {
NearRefConfig::default()
}
fn long_pipeline_body(status: &str, duration: u64) -> String {
format!(
r#"{{"id":42,"name":"{}","status":"{}","duration":{},"url":"https://example.com/pipelines/42","commit_sha":"deadbeefdeadbeefdeadbeefdeadbeef","ref":"refs/heads/main","author":"alice","triggered_by":"webhook","preview":"{}"}}"#,
"p".repeat(20),
status,
duration,
"x".repeat(400)
)
}
#[test]
fn extracts_status_and_duration_delta_for_pipeline_polling() {
let a = long_pipeline_body("pending", 12);
let b = long_pipeline_body("success", 34);
let deltas = extract_delta(&a, &b, &cfg()).unwrap();
assert_eq!(deltas.len(), 2);
let keys: BTreeSet<_> = deltas.iter().map(|d| d.key.as_str()).collect();
assert!(keys.contains("status"));
assert!(keys.contains("duration"));
}
#[test]
fn refuses_when_too_short() {
let a = r#"{"a":1,"b":2}"#;
let b = r#"{"a":1,"b":3}"#;
assert!(extract_delta(a, b, &cfg()).is_none());
}
#[test]
fn refuses_when_keys_differ() {
let a = format!(r#"{{"a":1,"long":"{}"}}"#, "x".repeat(600));
let b = format!(r#"{{"a":1,"different":"{}"}}"#, "x".repeat(600));
assert!(extract_delta(&a, &b, &cfg()).is_none());
}
#[test]
fn refuses_when_nested_value_changes() {
let a = format!(r#"{{"meta":{{"k":1}},"pad":"{}"}}"#, "x".repeat(600));
let b = format!(r#"{{"meta":{{"k":2}},"pad":"{}"}}"#, "x".repeat(600));
assert!(extract_delta(&a, &b, &cfg()).is_none());
}
#[test]
fn refuses_when_delta_blob_too_large() {
let a = format!(
r#"{{"x":"{}","pad":"{}"}}"#,
"a".repeat(80),
"p".repeat(600)
);
let b = format!(
r#"{{"x":"{}","pad":"{}"}}"#,
"b".repeat(80),
"p".repeat(600)
);
assert!(extract_delta(&a, &b, &cfg()).is_none());
}
#[test]
fn returns_none_for_byte_identical_inputs() {
let a = long_pipeline_body("ok", 10);
assert!(extract_delta(&a, &a, &cfg()).is_none());
}
#[test]
fn render_format_matches_paper_spec() {
let deltas = vec![
DeltaField {
key: "status".into(),
old: "pending".into(),
new: "success".into(),
},
DeltaField {
key: "duration".into(),
old: "12".into(),
new: "34".into(),
},
];
let s = render_near_ref_hint("tc_42", &deltas);
assert_eq!(
s,
"> [near-ref: tc_42, status: pending→success, duration: 12→34]"
);
}
#[test]
fn hint_size_under_paper_budget() {
let deltas = vec![
DeltaField {
key: "status".into(),
old: "pending".into(),
new: "success".into(),
},
DeltaField {
key: "duration".into(),
old: "12".into(),
new: "34".into(),
},
];
let s = render_near_ref_hint("tc_42", &deltas);
assert!(s.len() <= 70, "near-ref hint too long: {} bytes", s.len());
}
}