use diffo::{diff, diff_with, DiffConfig, ValueExt};
use serde::Serialize;
use std::rc::Rc;
#[derive(Serialize)]
struct Event {
id: u64,
name: String,
timestamp: u64,
}
#[derive(Serialize)]
struct User {
id: u64,
name: String,
email: String,
updated_at: u64,
}
#[derive(Serialize)]
struct Config {
url: String,
timeout: u32,
}
fn main() {
println!("=== Custom Comparators Demo ===\n");
println!("1. Ignoring Timestamps");
let old_event = Event {
id: 1,
name: "login".into(),
timestamp: 1000,
};
let new_event = Event {
id: 1,
name: "login".into(),
timestamp: 2000, };
let d = diff(&old_event, &new_event).unwrap();
println!(" Without comparator: {} changes", d.len());
let config = DiffConfig::new().comparator(
"",
Rc::new(|old, new| {
old.get_field("id") == new.get_field("id")
&& old.get_field("name") == new.get_field("name")
}),
);
let d = diff_with(&old_event, &new_event, &config).unwrap();
println!(" With comparator: {} changes", d.len());
println!();
println!("2. Comparing Users by ID Only");
let old_user = User {
id: 42,
name: "Alice".into(),
email: "alice@old.com".into(),
updated_at: 1000,
};
let new_user = User {
id: 42,
name: "Alice Smith".into(),
email: "alice@new.com".into(),
updated_at: 2000,
};
let config = DiffConfig::new().comparator(
"",
Rc::new(|old, new| {
old.get_field("id") == new.get_field("id")
}),
);
let d = diff_with(&old_user, &new_user, &config).unwrap();
println!(" Changes when comparing by ID only: {}", d.len());
println!();
println!("3. Case-Insensitive URL Comparison");
let old_config = Config {
url: "https://API.example.com".into(),
timeout: 30,
};
let new_config = Config {
url: "https://api.example.com".into(), timeout: 30,
};
let d = diff(&old_config, &new_config).unwrap();
println!(" Without comparator: {} changes", d.len());
let config = DiffConfig::new().comparator(
"url",
Rc::new(|old, new| {
if let (Some(old_str), Some(new_str)) = (old.as_string(), new.as_string()) {
old_str.to_lowercase() == new_str.to_lowercase()
} else {
old == new
}
}),
);
let d = diff_with(&old_config, &new_config, &config).unwrap();
println!(" With case-insensitive comparator: {} changes", d.len());
println!();
println!("4. Ignoring Fields with Patterns");
#[derive(Serialize)]
struct Data {
stable: String,
temp_cache: String,
temp_buffer: String,
}
let old_data = Data {
stable: "same".into(),
temp_cache: "old_cache".into(),
temp_buffer: "old_buffer".into(),
};
let new_data = Data {
stable: "same".into(),
temp_cache: "new_cache".into(),
temp_buffer: "new_buffer".into(),
};
let config = DiffConfig::new()
.comparator(
"temp_cache",
Rc::new(|_old, _new| true), )
.comparator(
"temp_buffer",
Rc::new(|_old, _new| true), );
let d = diff_with(&old_data, &new_data, &config).unwrap();
println!(" Changes after ignoring temp fields: {}", d.len());
println!();
println!("5. Using ValueExt Helper Methods");
#[derive(Serialize)]
struct Product {
id: u64,
name: String,
price: f64,
}
let old_product = Product {
id: 1,
name: "Widget".into(),
price: 9.99,
};
let new_product = Product {
id: 1,
name: "Widget".into(),
price: 10.99, };
let config = DiffConfig::new().comparator(
"",
Rc::new(|old, new| {
let id_match = old.get_field("id") == new.get_field("id");
let name_match = old.get_field("name") == new.get_field("name");
id_match && name_match
}),
);
let d = diff_with(&old_product, &new_product, &config).unwrap();
println!(" Changes when ignoring price: {}", d.len());
println!("\n=== Summary ===");
println!("Custom comparators allow you to:");
println!(" - Ignore specific fields (timestamps, caches)");
println!(" - Compare by key fields only (ID-based equality)");
println!(" - Apply custom logic (case-insensitive, normalized URLs)");
println!(" - Use ValueExt helpers for convenient field access");
println!("\nNote: Array element wildcards (e.g., [*]) have limitations due to glob");
println!("pattern syntax. Use explicit patterns like ?0?, ?1? for small arrays.");
}