zer_blocking/keys/
token.rs1use zer_core::{record::Record, schema::Schema};
2
3use super::BlockingKey;
4use crate::normalize::normalize_text;
5
6pub struct AddressInitialKey {
10 address_field: String,
11 first_name_field: String,
12}
13
14impl AddressInitialKey {
15 pub fn new(address_field: &str, first_name_field: &str) -> Self {
16 Self {
17 address_field: address_field.into(),
18 first_name_field: first_name_field.into(),
19 }
20 }
21}
22
23impl BlockingKey for AddressInitialKey {
24 fn name(&self) -> &str {
25 "addr_initial"
26 }
27
28 fn extract(&self, record: &Record, _schema: &Schema) -> Vec<String> {
29 let addr_cow = record.field_as_str(&self.address_field);
30 let addr_raw = match addr_cow.as_deref() {
31 Some(s) => s,
32 None => return vec![],
33 };
34 let name_cow = record.field_as_str(&self.first_name_field);
35 let name_raw = match name_cow.as_deref() {
36 Some(s) => s,
37 None => return vec![],
38 };
39
40 let addr_norm = normalize_text(addr_raw);
41 let name_norm = normalize_text(name_raw);
42
43 let addr_token = addr_norm
44 .split_whitespace()
45 .next()
46 .unwrap_or("")
47 .to_string();
48 let initial = name_norm.chars().next().unwrap_or(' ');
49
50 if addr_token.is_empty() || !initial.is_ascii_alphabetic() {
51 return vec![];
52 }
53
54 vec![format!("{}:{}", addr_token, initial)]
55 }
56}
57
58#[cfg(test)]
59mod tests {
60 use super::*;
61 use zer_core::{
62 record::FieldValue,
63 schema::{FieldKind, SchemaBuilder},
64 };
65
66 fn schema() -> Schema {
67 SchemaBuilder::new()
68 .field("address", FieldKind::Address)
69 .field("first_name", FieldKind::Name)
70 .build()
71 .unwrap()
72 }
73
74 #[test]
75 fn extracts_first_token_and_initial() {
76 let k = AddressInitialKey::new("address", "first_name");
77 let r = Record::new(1)
78 .insert("address", FieldValue::Text("123 Main Street".into()))
79 .insert("first_name", FieldValue::Text("John".into()));
80 assert_eq!(k.extract(&r, &schema()), vec!["123:J"]);
81 }
82
83 #[test]
84 fn same_address_different_first_name_no_collision() {
85 let k = AddressInitialKey::new("address", "first_name");
86 let s = schema();
87 let r1 = Record::new(1)
88 .insert("address", FieldValue::Text("Singel 191".into()))
89 .insert("first_name", FieldValue::Text("Alice".into()));
90 let r2 = Record::new(2)
91 .insert("address", FieldValue::Text("Singel 191".into()))
92 .insert("first_name", FieldValue::Text("Bob".into()));
93 assert_ne!(k.extract(&r1, &s), k.extract(&r2, &s));
94 }
95
96 #[test]
97 fn missing_field_returns_empty() {
98 let k = AddressInitialKey::new("address", "first_name");
99 let r = Record::new(1).insert("address", FieldValue::Text("Singel 191".into()));
100 assert!(k.extract(&r, &schema()).is_empty());
101 }
102}