1use std::{
2 fmt,
3 hash::{Hash, Hasher},
4};
5
6use super::{LogEvent, ObjectMap, Value};
7
8#[derive(Debug, Clone)]
20pub struct Discriminant {
21 values: Vec<Option<Value>>,
22}
23
24impl Discriminant {
25 pub fn from_log_event(event: &LogEvent, discriminant_fields: &[impl AsRef<str>]) -> Self {
28 let values: Vec<Option<Value>> = discriminant_fields
29 .iter()
30 .map(|discriminant_field| {
31 event
32 .parse_path_and_get_value(discriminant_field.as_ref())
33 .ok()
34 .flatten()
35 .cloned()
36 })
37 .collect();
38 Self { values }
39 }
40}
41
42impl PartialEq for Discriminant {
43 fn eq(&self, other: &Self) -> bool {
44 self.values
45 .iter()
46 .zip(other.values.iter())
47 .all(|(this, other)| match (this, other) {
48 (None, None) => true,
49 (Some(this), Some(other)) => value_eq(this, other),
50 _ => false,
51 })
52 }
53}
54
55impl Eq for Discriminant {}
56
57fn value_eq(this: &Value, other: &Value) -> bool {
59 match (this, other) {
60 (Value::Bytes(this), Value::Bytes(other)) => this.eq(other),
62 (Value::Boolean(this), Value::Boolean(other)) => this.eq(other),
63 (Value::Integer(this), Value::Integer(other)) => this.eq(other),
64 (Value::Timestamp(this), Value::Timestamp(other)) => this.eq(other),
65 (Value::Null, Value::Null) => true,
66 (Value::Float(this), Value::Float(other)) => f64_eq(this.into_inner(), other.into_inner()),
68 (Value::Array(this), Value::Array(other)) => array_eq(this, other),
69 (Value::Object(this), Value::Object(other)) => map_eq(this, other),
70 _ => false,
72 }
73}
74
75fn f64_eq(this: f64, other: f64) -> bool {
77 if this.is_nan() && other.is_nan() {
78 return true;
79 }
80 if this != other {
81 return false;
82 }
83 if (this.is_sign_positive() && other.is_sign_negative())
84 || (this.is_sign_negative() && other.is_sign_positive())
85 {
86 return false;
87 }
88 true
89}
90
91fn array_eq(this: &[Value], other: &[Value]) -> bool {
92 if this.len() != other.len() {
93 return false;
94 }
95
96 this.iter()
97 .zip(other.iter())
98 .all(|(first, second)| value_eq(first, second))
99}
100
101fn map_eq(this: &ObjectMap, other: &ObjectMap) -> bool {
102 if this.len() != other.len() {
103 return false;
104 }
105
106 this.iter()
107 .zip(other.iter())
108 .all(|((key1, value1), (key2, value2))| key1 == key2 && value_eq(value1, value2))
109}
110
111impl Hash for Discriminant {
112 fn hash<H: Hasher>(&self, state: &mut H) {
113 for value in &self.values {
114 match value {
115 Some(value) => {
116 state.write_u8(1);
117 hash_value(state, value);
118 }
119 None => state.write_u8(0),
120 }
121 }
122 }
123}
124
125fn hash_value<H: Hasher>(hasher: &mut H, value: &Value) {
127 match value {
128 Value::Bytes(val) => val.hash(hasher),
130 Value::Regex(val) => val.as_bytes_slice().hash(hasher),
131 Value::Boolean(val) => val.hash(hasher),
132 Value::Integer(val) => val.hash(hasher),
133 Value::Timestamp(val) => val.hash(hasher),
134 Value::Float(val) => hash_f64(hasher, val.into_inner()),
136 Value::Array(val) => hash_array(hasher, val),
137 Value::Object(val) => hash_map(hasher, val),
138 Value::Null => hash_null(hasher),
139 }
140}
141
142fn hash_f64<H: Hasher>(hasher: &mut H, value: f64) {
144 hasher.write(&value.to_ne_bytes());
145}
146
147fn hash_array<H: Hasher>(hasher: &mut H, array: &[Value]) {
148 for val in array {
149 hash_value(hasher, val);
150 }
151}
152
153fn hash_map<H: Hasher>(hasher: &mut H, map: &ObjectMap) {
154 for (key, val) in map {
155 hasher.write(key.as_bytes());
156 hash_value(hasher, val);
157 }
158}
159
160fn hash_null<H: Hasher>(hasher: &mut H) {
161 hasher.write_u8(0);
162}
163
164impl fmt::Display for Discriminant {
165 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
166 for (i, value) in self.values.iter().enumerate() {
167 if i != 0 {
168 write!(fmt, "-")?;
169 }
170 if let Some(value) = value {
171 value.fmt(fmt)?;
172 } else {
173 fmt.write_str("none")?;
174 }
175 }
176 Ok(())
177 }
178}
179
180#[cfg(test)]
181mod tests {
182 use std::collections::{HashMap, hash_map::DefaultHasher};
183
184 use super::*;
185 use crate::event::LogEvent;
186 use vrl::event_path;
187
188 fn hash<H: Hash>(hash: H) -> u64 {
189 let mut hasher = DefaultHasher::new();
190 hash.hash(&mut hasher);
191 hasher.finish()
192 }
193
194 #[test]
195 fn equal() {
196 let mut event_1 = LogEvent::default();
197 event_1.insert(event_path!("hostname"), "localhost");
198 event_1.insert(event_path!("irrelevant"), "not even used");
199 let mut event_2 = event_1.clone();
200 event_2.insert(
201 event_path!("irrelevant"),
202 "does not matter if it's different",
203 );
204
205 let discriminant_fields = vec!["hostname".to_string(), "container_id".to_string()];
206
207 let discriminant_1 = Discriminant::from_log_event(&event_1, &discriminant_fields);
208 let discriminant_2 = Discriminant::from_log_event(&event_2, &discriminant_fields);
209
210 assert_eq!(discriminant_1, discriminant_2);
211 assert_eq!(hash(discriminant_1), hash(discriminant_2));
212 }
213
214 #[test]
215 fn not_equal() {
216 let mut event_1 = LogEvent::default();
217 event_1.insert(event_path!("hostname"), "localhost");
218 event_1.insert(event_path!("container_id"), "abc");
219 let mut event_2 = event_1.clone();
220 event_2.insert(event_path!("container_id"), "def");
221
222 let discriminant_fields = vec!["hostname".to_string(), "container_id".to_string()];
223
224 let discriminant_1 = Discriminant::from_log_event(&event_1, &discriminant_fields);
225 let discriminant_2 = Discriminant::from_log_event(&event_2, &discriminant_fields);
226
227 assert_ne!(discriminant_1, discriminant_2);
228 assert_ne!(hash(discriminant_1), hash(discriminant_2));
229 }
230
231 #[test]
232 fn field_order() {
233 let mut event_1 = LogEvent::default();
234 event_1.insert(event_path!("a"), "a");
235 event_1.insert(event_path!("b"), "b");
236 let mut event_2 = LogEvent::default();
237 event_2.insert(event_path!("b"), "b");
238 event_2.insert(event_path!("a"), "a");
239
240 let discriminant_fields = vec!["a".to_string(), "b".to_string()];
241
242 let discriminant_1 = Discriminant::from_log_event(&event_1, &discriminant_fields);
243 let discriminant_2 = Discriminant::from_log_event(&event_2, &discriminant_fields);
244
245 assert_eq!(discriminant_1, discriminant_2);
246 assert_eq!(hash(discriminant_1), hash(discriminant_2));
247 }
248
249 #[test]
250 fn map_values_key_order() {
251 let mut event_1 = LogEvent::default();
252 event_1.insert(event_path!("nested", "a"), "a");
253 event_1.insert(event_path!("nested", "b"), "b");
254 let mut event_2 = LogEvent::default();
255 event_2.insert(event_path!("nested", "b"), "b");
256 event_2.insert(event_path!("nested", "a"), "a");
257
258 let discriminant_fields = vec!["nested".to_string()];
259
260 let discriminant_1 = Discriminant::from_log_event(&event_1, &discriminant_fields);
261 let discriminant_2 = Discriminant::from_log_event(&event_2, &discriminant_fields);
262
263 assert_eq!(discriminant_1, discriminant_2);
264 assert_eq!(hash(discriminant_1), hash(discriminant_2));
265 }
266
267 #[test]
268 fn array_values_insertion_order() {
269 let mut event_1 = LogEvent::default();
270 event_1.insert(event_path!("array", 0isize), "a");
271 event_1.insert(event_path!("array", 1isize), "b");
272 let mut event_2 = LogEvent::default();
273 event_2.insert(event_path!("array", 1isize), "b");
274 event_2.insert(event_path!("array", 0isize), "a");
275
276 let discriminant_fields = vec!["array".to_string()];
277
278 let discriminant_1 = Discriminant::from_log_event(&event_1, &discriminant_fields);
279 let discriminant_2 = Discriminant::from_log_event(&event_2, &discriminant_fields);
280
281 assert_eq!(discriminant_1, discriminant_2);
282 assert_eq!(hash(discriminant_1), hash(discriminant_2));
283 }
284
285 #[test]
286 fn map_values_matter_1() {
287 let mut event_1 = LogEvent::default();
288 event_1.insert(event_path!("nested", "a"), "a"); let event_2 = LogEvent::default(); let discriminant_fields = vec!["nested".to_string()];
292
293 let discriminant_1 = Discriminant::from_log_event(&event_1, &discriminant_fields);
294 let discriminant_2 = Discriminant::from_log_event(&event_2, &discriminant_fields);
295
296 assert_ne!(discriminant_1, discriminant_2);
297 assert_ne!(hash(discriminant_1), hash(discriminant_2));
298 }
299
300 #[test]
301 fn map_values_matter_2() {
302 let mut event_1 = LogEvent::default();
303 event_1.insert(event_path!("nested", "a"), "a"); let mut event_2 = LogEvent::default();
305 event_2.insert(event_path!("nested"), "x"); let discriminant_fields = vec!["nested".to_string()];
308
309 let discriminant_1 = Discriminant::from_log_event(&event_1, &discriminant_fields);
310 let discriminant_2 = Discriminant::from_log_event(&event_2, &discriminant_fields);
311
312 assert_ne!(discriminant_1, discriminant_2);
313 assert_ne!(hash(discriminant_1), hash(discriminant_2));
314 }
315
316 #[test]
317 fn with_hash_map() {
318 #[allow(clippy::mutable_key_type)]
319 let mut map: HashMap<Discriminant, usize> = HashMap::new();
320
321 let event_stream_1 = {
322 let mut event = LogEvent::default();
323 event.insert(event_path!("hostname"), "a.test");
324 event.insert(event_path!("container_id"), "abc");
325 event
326 };
327
328 let event_stream_2 = {
329 let mut event = LogEvent::default();
330 event.insert(event_path!("hostname"), "b.test");
331 event.insert(event_path!("container_id"), "def");
332 event
333 };
334
335 let event_stream_3 = {
336 LogEvent::default()
338 };
339
340 let discriminant_fields = vec!["hostname".to_string(), "container_id".to_string()];
341
342 let mut process_event = |event| {
343 let discriminant = Discriminant::from_log_event(&event, &discriminant_fields);
344 *map.entry(discriminant).and_modify(|e| *e += 1).or_insert(0)
345 };
346
347 {
348 let mut event = event_stream_1.clone();
349 event.insert(event_path!("message"), "a");
350 assert_eq!(process_event(event), 0);
351 }
352
353 {
354 let mut event = event_stream_1.clone();
355 event.insert(event_path!("message"), "b");
356 event.insert(event_path!("irrelevant"), "c");
357 assert_eq!(process_event(event), 1);
358 }
359
360 {
361 let mut event = event_stream_2.clone();
362 event.insert(event_path!("message"), "d");
363 assert_eq!(process_event(event), 0);
364 }
365
366 {
367 let mut event = event_stream_2.clone();
368 event.insert(event_path!("message"), "e");
369 event.insert(event_path!("irrelevant"), "d");
370 assert_eq!(process_event(event), 1);
371 }
372
373 {
374 let mut event = event_stream_3.clone();
375 event.insert(event_path!("message"), "f");
376 assert_eq!(process_event(event), 0);
377 }
378
379 {
380 let mut event = event_stream_3.clone();
381 event.insert(event_path!("message"), "g");
382 event.insert(event_path!("irrelevant"), "d");
383 assert_eq!(process_event(event), 1);
384 }
385
386 assert_eq!(process_event(event_stream_1), 2);
388 assert_eq!(process_event(event_stream_2), 2);
389 assert_eq!(process_event(event_stream_3), 2);
390 }
391
392 #[test]
393 fn test_display() {
394 let mut event = LogEvent::default();
395 event.insert(event_path!("hostname"), "localhost");
396 event.insert(event_path!("container_id"), 1);
397
398 let discriminant = Discriminant::from_log_event(
399 &event,
400 &["hostname".to_string(), "container_id".to_string()],
401 );
402 assert_eq!(format!("{discriminant}"), "\"localhost\"-1");
403
404 let discriminant =
405 Discriminant::from_log_event(&event, &["hostname".to_string(), "service".to_string()]);
406 assert_eq!(format!("{discriminant}"), "\"localhost\"-none");
407 }
408}