use serde_json::Value;
use std::collections::HashSet;
pub fn deduplicate_work_items(items: Vec<Value>) -> Vec<Value> {
let mut seen_ids: HashSet<String> = HashSet::new();
let mut deduped = Vec::new();
for item in items {
let item_id = extract_item_id(&item);
if !item_id.is_empty() && seen_ids.insert(item_id) {
deduped.push(item);
}
}
deduped
}
fn extract_item_id(item: &Value) -> String {
let id_value = item
.get("id")
.or_else(|| item.get("item_id"))
.or_else(|| item.get("_id"));
match id_value {
Some(v) => {
if let Some(s) = v.as_str() {
s.to_string()
}
else if let Some(n) = v.as_i64() {
n.to_string()
}
else if let Some(n) = v.as_u64() {
n.to_string()
} else {
String::new()
}
}
None => String::new(),
}
}
pub fn count_duplicates(items: &[Value]) -> usize {
let total = items.len();
let unique = deduplicate_work_items(items.to_vec()).len();
total.saturating_sub(unique)
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_deduplicate_empty_list() {
let items: Vec<Value> = vec![];
let result = deduplicate_work_items(items);
assert_eq!(result.len(), 0);
}
#[test]
fn test_deduplicate_no_duplicates() {
let items = vec![
json!({"id": "1", "data": "a"}),
json!({"id": "2", "data": "b"}),
json!({"id": "3", "data": "c"}),
];
let result = deduplicate_work_items(items.clone());
assert_eq!(result.len(), 3);
assert_eq!(result[0], json!({"id": "1", "data": "a"}));
assert_eq!(result[1], json!({"id": "2", "data": "b"}));
assert_eq!(result[2], json!({"id": "3", "data": "c"}));
}
#[test]
fn test_deduplicate_with_duplicates() {
let items = vec![
json!({"id": "1", "data": "first"}),
json!({"id": "2", "data": "second"}),
json!({"id": "1", "data": "duplicate"}), json!({"id": "3", "data": "third"}),
];
let result = deduplicate_work_items(items);
assert_eq!(result.len(), 3);
assert_eq!(result[0]["id"], "1");
assert_eq!(result[0]["data"], "first"); assert_eq!(result[1]["id"], "2");
assert_eq!(result[2]["id"], "3");
}
#[test]
fn test_deduplicate_preserves_order() {
let items = vec![
json!({"id": "3", "data": "third"}),
json!({"id": "1", "data": "first"}),
json!({"id": "2", "data": "second"}),
];
let result = deduplicate_work_items(items.clone());
assert_eq!(result.len(), 3);
assert_eq!(result[0]["id"], "3");
assert_eq!(result[1]["id"], "1");
assert_eq!(result[2]["id"], "2");
}
#[test]
fn test_deduplicate_missing_ids_skipped() {
let items = vec![
json!({"id": "1", "data": "a"}),
json!({"data": "no_id"}), json!({"id": "2", "data": "b"}),
];
let result = deduplicate_work_items(items);
assert_eq!(result.len(), 2);
assert_eq!(result[0]["id"], "1");
assert_eq!(result[1]["id"], "2");
}
#[test]
fn test_deduplicate_large_dataset() {
use std::time::Instant;
let mut items = Vec::new();
for i in 0..5000 {
items.push(json!({"id": i.to_string(), "data": "test"}));
items.push(json!({"id": i.to_string(), "data": "duplicate"}));
}
let start = Instant::now();
let result = deduplicate_work_items(items);
let duration = start.elapsed();
assert_eq!(result.len(), 5000);
assert!(
duration.as_millis() < 200,
"Should complete in <200ms, took {}ms",
duration.as_millis()
);
}
#[test]
fn test_count_duplicates() {
let items = vec![
json!({"id": "1"}),
json!({"id": "2"}),
json!({"id": "1"}), json!({"id": "3"}),
json!({"id": "2"}), ];
assert_eq!(count_duplicates(&items), 2);
}
#[test]
fn test_count_duplicates_no_duplicates() {
let items = vec![json!({"id": "1"}), json!({"id": "2"}), json!({"id": "3"})];
assert_eq!(count_duplicates(&items), 0);
}
#[test]
fn test_extract_item_id_variants() {
let item1 = json!({"id": "test-1"});
assert_eq!(extract_item_id(&item1), "test-1");
let item2 = json!({"item_id": "test-2"});
assert_eq!(extract_item_id(&item2), "test-2");
let item3 = json!({"_id": "test-3"});
assert_eq!(extract_item_id(&item3), "test-3");
let item4 = json!({"data": "no-id"});
assert_eq!(extract_item_id(&item4), "");
let item5 = json!({"id": 42});
assert_eq!(extract_item_id(&item5), "42");
let item6 = json!({"id": 123u64});
assert_eq!(extract_item_id(&item6), "123");
}
}