1pub mod tokens;
12extern crate derive_more;
13
14use std::fmt;
15
16use lazy_static::lazy_static;
17use string_interner::DefaultSymbol;
18use tracing::{debug, instrument};
19use uuid::Uuid;
20
21use self::tokens::{Offset, Token, TokenStream}; use crate::drains::simple::INTERNER;
23
24lazy_static! {
25 pub static ref ASTERISK: DefaultSymbol = INTERNER.write().get_or_intern_static("<*>");
26}
27#[derive(Clone, Debug)]
32pub struct Record {
33 pub(crate) inner: TokenStream,
34 pub uid: Uuid,
35}
36impl Record {
37 #[instrument(name = "Create new record", level = "trace", skip(line))]
50 pub fn new(line: String) -> Self {
51 let now = chrono::Utc::now();
56 let context = uuid::NoContext; let timestamp = uuid::v1::Timestamp::from_unix(
58 context, now.timestamp() as u64,
60 now.timestamp_subsec_nanos(),
61 );
62 const NODE_ID: &[u8; 6] = b"drainf";
64 Self {
65 inner: TokenStream::from_unicode_line(&line),
66 uid: Uuid::new_v1(timestamp, NODE_ID),
67 }
68 }
69
70 #[instrument(
84 name = "Calculate similarity score",
85 level = "trace",
86 skip(candidate, self)
87 )]
88 pub fn calc_sim_score(&self, candidate: &Record) -> u64 {
89 self.inner
94 .inner
95 .iter() .zip(candidate.inner.inner.iter()) .filter(|((_, template_token), (_, candidate_token))| {
98 match template_token {
99 Token::Wildcard => {
100 debug!(
101 "template token is Wildcard, matches candidate token {:?}",
102 candidate_token
103 );
104 true }
106 _ => {
107 if template_token == candidate_token {
111 debug!(
112 "template token {:?} matches candidate token {:?}",
113 template_token, candidate_token
114 );
115 true
116 } else {
117 debug!(
118 "template token {:?} does NOT match candidate token {:?}",
119 template_token, candidate_token
120 );
121 false
122 }
123 }
124 }
125 })
126 .count() as u64 }
128
129 #[instrument(level = "trace", skip(self))]
135 pub fn first(&self) -> Option<DefaultSymbol> {
136 self.inner.first().map(std::convert::Into::into)
137 }
138
139 #[instrument(level = "trace", skip(self))]
145 pub fn len(&self) -> usize {
146 self.inner.len()
147 }
148
149 #[instrument(level = "trace", skip(self))]
155 pub fn is_empty(&self) -> bool {
156 self.inner.len() == 0
157 }
158
159 #[instrument(level = "trace")]
173 pub fn resolve(sym: DefaultSymbol) -> Option<String> {
174 INTERNER
175 .read()
176 .resolve(sym)
177 .map(std::borrow::ToOwned::to_owned)
178 }
179}
180
181pub struct IntoIter {
183 record: Record,
184 index: usize,
185}
186
187impl Iterator for IntoIter {
188 type Item = String;
189
190 fn next(&mut self) -> Option<String> {
191 if self.index >= self.record.len() {
192 return None;
193 }
194 let token_display = self
196 .record
197 .inner
198 .get_token_at_index(self.index)
199 .map(|t| t.to_string());
200
201 self.index += 1;
202 token_display
203 }
204}
205
206impl IntoIterator for Record {
207 type IntoIter = IntoIter;
208 type Item = String; fn into_iter(self) -> Self::IntoIter {
211 IntoIter {
212 record: self, index: 0,
214 }
215 }
216}
217
218impl<'a> IntoIterator for &'a Record {
219 type Item = &'a Token; type IntoIter =
221 std::iter::Map<std::slice::Iter<'a, (Offset, Token)>, fn(&(Offset, Token)) -> &Token>;
222
223 fn into_iter(self) -> Self::IntoIter {
224 self.inner.inner.iter().map(|(_, token)| token)
225 }
226}
227
228impl fmt::Display for Record {
229 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
242 write!(f, "{}", self.inner)
243 }
244}
245#[cfg(test)]
246mod should {
247 use joinery::{Joinable, JoinableIterator};
248 use proptest::{prelude::*, string::string_regex};
249 use spectral::prelude::*;
250
251 use crate::{drains::simple::INTERNER, record::Record};
252
253 prop_compose! {
254 fn gen_word()(s in "[[:alpha:]]+") -> String {
255 s
256 }
257 }
258
259 fn gen_variable_string() -> impl Strategy<Value = String> {
260 prop_oneof![
261 string_regex(r"[A-Fa-f0-9]{8}-(?:[A-Fa-f0-9]{4}-){3}[A-Fa-f0-9]{12}").unwrap(),
263 string_regex(r"(?:(?:[A-Fa-f0-9]{2}:){5}[A-Fa-f0-9]{2})").unwrap(),
265 string_regex(r"((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))").unwrap(),
267 string_regex(r"(?:[+-]?(?:[0-9]+))").unwrap(),
269 ]
270 }
271
272 fn gen_phrase(len: usize) -> impl Strategy<Value = String> {
273 prop::collection::vec(gen_word(), len)
274 .prop_flat_map(|vec| Just(vec.iter().join_with(" ").to_string()))
275 }
276
277 fn gen_vars(len: usize) -> impl Strategy<Value = String> {
278 prop::collection::vec(gen_variable_string(), len)
279 .prop_flat_map(|vec| Just(vec.iter().join_with(" ").to_string()))
280 }
281
282 fn gen_complex(base: usize, variable: usize) -> impl Strategy<Value = String> {
283 let base = gen_phrase(base);
284 let vars = gen_vars(variable);
285 (base, vars).prop_map(|(b, v)| [b, v].join_with(" ").to_string())
286 }
287
288 prop_compose! {
289 fn gen_matching_lines(base_len: usize, var_count: usize, num_lines: usize)(base_phrase in gen_phrase(base_len), var_set in prop::collection::vec(gen_vars(var_count), num_lines)) -> Vec<String> {
290 var_set.iter().map(|v| {[base_phrase.clone(), v.to_string()].join_with(" ").to_string()}).collect::<Vec<String>>()
291 }
292 }
293
294 proptest! {
295 #[test]
296 fn test_proptest_base_record_new(phrase in gen_phrase(5)) {
297 let rec = Record::new(phrase.clone());
298 prop_assert_eq!(phrase, rec.to_string());
299 }
300 }
301
302 proptest! {
303 #[test]
304 fn test_proptest_variable_record_new(line in gen_complex(7, 3)) {
305 let rec = Record::new(line.clone());
308 let rec2 = Record::new(rec.to_string());
309 prop_assert_eq!(rec.to_string(), rec2.to_string());
310
311 let reconstituted = rec.to_string();
313 prop_assert!(line.contains(&reconstituted));
314 }
315 }
316
317 proptest! {
318 #[test]
319 fn test_matching_records(lines in gen_matching_lines(7, 3, 3)) {
320 let recs = lines
321 .iter()
322 .map(|l| Record::new(l.clone()))
323 .collect::<Vec<Record>>();
324 let base = recs[0].clone();
325 let score1 = base.calc_sim_score(&recs[1].clone());
326 let score2 = base.calc_sim_score(&recs[2].clone());
327
328 prop_assert!(score1 >= 7 && score2 >= 7);
332 }
333 }
334
335 #[test]
336 fn test_record_first() {
337 let input = "Message send failed to remote host: foo.bar.com".to_string();
338 let rec = Record::new(input);
339 let val = rec.first().unwrap();
340 assert_eq!(INTERNER.read().resolve(val).unwrap(), "Message");
341 }
342
343 #[test]
344 fn test_record_len() {
345 let input = "Message send failed to remote host: foo.bar.com".to_string();
346 let rec = Record::new(input);
347 assert_eq!(rec.len(), 7);
348 }
349
350 #[test]
351 fn test_consuming_iter() {
352 let input = "Message send failed to remote host: foo.bar.com".to_string();
353 let rec = Record::new(input.clone());
354 let tokens = rec.into_iter().collect::<Vec<String>>();
355 let words = &input
356 .split(|c: char| c.is_whitespace())
357 .map(|s| s.to_owned())
358 .collect::<Vec<String>>();
359 assert_that(&tokens.iter()).contains_all_of(&words.iter());
360 }
361
362 #[test]
363 fn test_non_consuming_iter() {
364 let input = "Message send failed to remote host: foo.bar.com".to_string();
365 let rec = Record::new(input);
366 let tokens = (&rec).into_iter().collect::<Vec<_>>();
367 assert_that(&tokens.len()).is_equal_to(7);
369 }
370}