1use std::{
2 collections::{HashMap, HashSet},
3 sync::{Arc, Mutex},
4};
5
6use grok::Grok;
7use serde::Serialize;
8
9use crate::script::{IfCondition, ScriptLocation, ScriptRunContext};
10
11#[derive(Clone, Debug, PartialEq, Eq)]
12pub struct Line {
13 pub number: usize,
14 pub text: String,
15}
16
17#[derive(Clone)]
18pub struct Lines {
19 lines: Arc<Vec<String>>,
20 current_line: usize,
21 ignored_patterns: Vec<Arc<Vec<OutputPattern>>>,
22 negative_disabled: bool,
23 rejected_patterns: Vec<Arc<Vec<OutputPattern>>>,
24}
25
26impl<'s> IntoIterator for &'s Lines {
27 type Item = &'s String;
28 type IntoIter = std::slice::Iter<'s, String>;
29
30 fn into_iter(self) -> Self::IntoIter {
31 self.lines.iter()
32 }
33}
34
35impl std::fmt::Display for Lines {
36 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37 write!(f, "{}", self.lines[self.current_line..].join("\n"))
38 }
39}
40
41impl Lines {
42 pub fn new(lines: Vec<String>) -> Self {
43 Self {
44 lines: Arc::new(lines),
45 current_line: 0,
46 ignored_patterns: Default::default(),
47 negative_disabled: false,
48 rejected_patterns: Default::default(),
49 }
50 }
51
52 pub fn is_exhausted(&self) -> bool {
53 self.current_line >= self.lines.len()
54 }
55
56 pub fn next(
57 &self,
58 context: OutputMatchContext,
59 ) -> Result<(Option<Line>, Lines), OutputPatternMatchFailure> {
60 let mut next = self.clone();
61 'outer: while next.current_line < next.lines.len() {
62 if !self.negative_disabled {
63 let ignore_check = next.without_negatives();
64 for ignored in &next.ignored_patterns {
65 for ignored_pattern in ignored.iter() {
66 if let Ok(next_next) =
67 ignored_pattern.matches(context.ignore(), ignore_check.clone())
68 {
69 next = next_next.with_negatives();
70 continue 'outer;
71 }
72 }
73 }
74 for rejected in &next.rejected_patterns {
75 for rejected_pattern in rejected.iter() {
76 if let Ok(_) =
77 rejected_pattern.matches(context.ignore(), ignore_check.clone())
78 {
79 return Err(OutputPatternMatchFailure {
80 location: rejected_pattern.location.clone(),
81 pattern_type: "reject",
82 output_line: None,
83 });
84 }
85 }
86 }
87 }
88 let line = Line {
89 number: next.current_line,
90 text: next.lines[next.current_line].clone(),
91 };
92 next.current_line += 1;
93 return Ok((Some(line), next));
94 }
95 Ok((None, next))
96 }
97
98 pub fn with_ignore(&self, ignore: &Arc<Vec<OutputPattern>>) -> Self {
99 let mut ignored_patterns = self.ignored_patterns.clone();
100 ignored_patterns.push(ignore.clone());
101 Self {
102 ignored_patterns,
103 ..self.clone()
104 }
105 }
106
107 pub fn with_reject(&self, reject: &Arc<Vec<OutputPattern>>) -> Self {
108 let mut rejected_patterns = self.rejected_patterns.clone();
109 rejected_patterns.push(reject.clone());
110 Self {
111 rejected_patterns,
112 ..self.clone()
113 }
114 }
115
116 fn without_negatives(&self) -> Self {
117 Self {
118 negative_disabled: true,
119 ..self.clone()
120 }
121 }
122
123 fn with_negatives(&self) -> Self {
124 Self {
125 negative_disabled: false,
126 ..self.clone()
127 }
128 }
129
130 pub fn into_inner(self) -> Vec<String> {
131 Arc::unwrap_or_clone(self.lines).split_off(self.current_line)
132 }
133
134 pub fn is_empty(&self) -> bool {
135 self.lines.is_empty()
136 }
137}
138
139#[derive(Clone)]
140pub struct OutputPattern {
141 pub location: ScriptLocation,
142 pub pattern: OutputPatternType,
143 pub ignore: Arc<Vec<OutputPattern>>,
144 pub reject: Arc<Vec<OutputPattern>>,
145}
146
147impl Serialize for OutputPattern {
148 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
149 where
150 S: serde::Serializer,
151 {
152 self.pattern.serialize(serializer)
153 }
154}
155
156impl std::fmt::Debug for OutputPattern {
157 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
158 write!(f, "{:?}", self.pattern)
159 }
160}
161
162impl OutputPattern {
163 pub fn new_sequence(location: ScriptLocation, mut patterns: Vec<OutputPattern>) -> Self {
164 if patterns.len() == 1 {
165 patterns.remove(0)
166 } else {
167 Self {
168 pattern: OutputPatternType::Sequence(patterns),
169 ignore: Default::default(),
170 reject: Default::default(),
171 location: location.clone(),
172 }
173 }
174 }
175
176 pub fn matches(
177 &self,
178 context: OutputMatchContext,
179 output: Lines,
180 ) -> Result<Lines, OutputPatternMatchFailure> {
181 if self.ignore.is_empty() && self.reject.is_empty() {
182 self.pattern.matches(&self.location, context, output)
183 } else {
184 let output = output.with_ignore(&self.ignore).with_reject(&self.reject);
185 self.pattern.matches(&self.location, context, output)
186 }
187 }
188
189 pub fn min_matches(&self) -> usize {
191 self.pattern.min_matches()
192 }
193
194 pub fn max_matches(&self) -> usize {
196 self.pattern.max_matches()
197 }
198}
199
200#[derive(Clone)]
201pub enum OutputPatternType {
202 End,
204 None,
206 Any(Box<OutputPattern>),
208 Literal(String),
210 Pattern(Arc<GrokPattern>),
212 Repeat(Box<OutputPattern>),
214 Optional(Box<OutputPattern>),
216 Unordered(Vec<OutputPattern>),
218 Choice(Vec<OutputPattern>),
220 Sequence(Vec<OutputPattern>),
222 If(IfCondition, Box<OutputPattern>),
224}
225
226impl Serialize for OutputPatternType {
227 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
228 where
229 S: serde::Serializer,
230 {
231 match self {
232 OutputPatternType::Literal(literal) => {
233 serializer.serialize_str(&format!("! {literal}"))
234 }
235 OutputPatternType::Pattern(pattern) => {
236 serializer.serialize_str(&format!("? {}", pattern.pattern))
237 }
238 OutputPatternType::Repeat(pattern) => {
239 HashMap::from([("repeat", &pattern)]).serialize(serializer)
240 }
241 OutputPatternType::Optional(pattern) => {
242 HashMap::from([("optional", &pattern)]).serialize(serializer)
243 }
244 OutputPatternType::Unordered(patterns) => {
245 HashMap::from([("unordered", &patterns)]).serialize(serializer)
246 }
247 OutputPatternType::Choice(patterns) => {
248 HashMap::from([("choice", &patterns)]).serialize(serializer)
249 }
250 OutputPatternType::Sequence(patterns) => {
251 HashMap::from([("sequence", &patterns)]).serialize(serializer)
252 }
253 OutputPatternType::Any(pattern) => {
254 HashMap::from([("any", &pattern)]).serialize(serializer)
255 }
256 OutputPatternType::If(condition, pattern) => {
257 #[derive(Serialize)]
258 struct If<'a> {
259 condition: &'a IfCondition,
260 pattern: &'a OutputPattern,
261 }
262 If { condition, pattern }.serialize(serializer)
263 }
264 OutputPatternType::End => serializer.serialize_str("end"),
265 OutputPatternType::None => serializer.serialize_str("none"),
266 }
267 }
268}
269
270impl OutputPatternType {
271 pub fn min_matches(&self) -> usize {
273 match self {
274 OutputPatternType::None => 0,
275 OutputPatternType::Literal(_) => 1,
276 OutputPatternType::Pattern(_) => 1,
277 OutputPatternType::Repeat(pattern) => pattern.min_matches(),
278 OutputPatternType::Optional(_) => 0,
279 OutputPatternType::Unordered(patterns) => {
280 patterns.iter().map(|p| p.min_matches()).sum()
281 }
282 OutputPatternType::Choice(patterns) => {
283 patterns.iter().map(|p| p.min_matches()).min().unwrap_or(0)
284 }
285 OutputPatternType::Sequence(patterns) => patterns.iter().map(|p| p.min_matches()).sum(),
286 OutputPatternType::Any(pattern) => pattern.min_matches(),
287 OutputPatternType::If(_, _) => 0,
288 OutputPatternType::End => 0,
289 }
290 }
291
292 pub fn max_matches(&self) -> usize {
294 fn saturating_iter_sum<I>(iter: I) -> usize
295 where
296 I: IntoIterator<Item = usize>,
297 {
298 iter.into_iter()
299 .reduce(|n, i| n.saturating_add(i))
300 .unwrap_or(0)
301 }
302
303 match self {
304 OutputPatternType::None => 0,
305 OutputPatternType::Literal(_) => 1,
306 OutputPatternType::Pattern(_) => 1,
307 OutputPatternType::Repeat(pattern) => {
308 if pattern.max_matches() == 0 {
309 0
310 } else {
311 usize::MAX
312 }
313 }
314 OutputPatternType::Optional(pattern) => pattern.max_matches(),
315 OutputPatternType::Unordered(patterns) => {
316 saturating_iter_sum(patterns.iter().map(|p| p.max_matches()))
317 }
318 OutputPatternType::Choice(patterns) => {
319 patterns.iter().map(|p| p.max_matches()).max().unwrap_or(0)
320 }
321 OutputPatternType::Sequence(patterns) => {
322 saturating_iter_sum(patterns.iter().map(|p| p.max_matches()))
323 }
324 OutputPatternType::Any(_) => usize::MAX,
325 OutputPatternType::If(_, pattern) => pattern.max_matches(),
326 OutputPatternType::End => 0,
327 }
328 }
329}
330
331impl Default for OutputPatternType {
332 fn default() -> Self {
333 Self::Sequence(vec![])
334 }
335}
336
337impl std::fmt::Debug for OutputPatternType {
338 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
339 match self {
340 OutputPatternType::Literal(literal) => write!(f, "Literal({literal})"),
341 OutputPatternType::Pattern(pattern) => write!(f, "Pattern({pattern:?})"),
342 OutputPatternType::Repeat(pattern) => write!(f, "Repeat({pattern:?})"),
343 OutputPatternType::Optional(pattern) => write!(f, "Optional({pattern:?})"),
344 OutputPatternType::Unordered(patterns) => write!(f, "Unordered({patterns:?})"),
345 OutputPatternType::Choice(patterns) => write!(f, "Choice({patterns:?})"),
346 OutputPatternType::Sequence(patterns) => write!(f, "Sequence({patterns:?})"),
347 OutputPatternType::Any(until) => write!(f, "Any({until:?})"),
348 OutputPatternType::If(condition, pattern) => {
349 write!(f, "If({condition:?}, {pattern:?})")
350 }
351 OutputPatternType::End => write!(f, "End"),
352 OutputPatternType::None => write!(f, "None"),
353 }
354 }
355}
356
357#[derive(Serialize, derive_more::Debug)]
358#[debug("/{pattern:?}/")]
359pub struct GrokPattern {
360 pattern: String,
361 #[serde(skip)]
362 grok: grok::Pattern,
363}
364
365impl GrokPattern {
366 pub fn compile(
367 grok: &mut Grok,
368 line: &str,
369 escape_non_grok: bool,
370 ) -> Result<Self, grok::Error> {
371 if escape_non_grok {
372 const GROK_PATTERN: &str = r"%\{(?<name>(?<pattern>[A-z0-9]+)(?::(?<alias>[A-z0-9_:;\/\s\.]+))?)(?:=(?<definition>(?:(?:[^{}]+|\.+)+)+))?\}";
374 let re = onig::Regex::new(GROK_PATTERN).expect("Failed to compile Grok metapattern");
375 let mut prev_start = 0;
376
377 let mut escaped_string = String::with_capacity(line.len() * 2);
380 for re_match in re.find_iter(line) {
381 let text = &line[prev_start..re_match.0];
382 text.chars().for_each(|c| {
383 if c.is_ascii() && !c.is_alphanumeric() {
384 escaped_string.push('\\');
385 escaped_string.push(c);
386 } else {
387 escaped_string.push(c);
388 }
389 });
390 escaped_string.push_str(&line[re_match.0..re_match.1]);
391 prev_start = re_match.1;
392 }
393 let text = &line[prev_start..];
394 text.chars().for_each(|c| {
395 if c.is_ascii() && !c.is_alphanumeric() {
396 escaped_string.push('\\');
397 escaped_string.push(c);
398 } else {
399 escaped_string.push(c);
400 }
401 });
402 let eol = format!("{escaped_string}$");
403 Ok(Self {
404 pattern: escaped_string,
405 grok: grok.compile(&eol, false)?,
406 })
407 } else {
408 let eol = format!("{line}$");
409 Ok(Self {
410 pattern: line.to_string(),
411 grok: grok.compile(&eol, false)?,
412 })
413 }
414 }
415}
416
417#[derive(Clone, Debug, thiserror::Error, derive_more::Display, PartialEq, Eq)]
418#[display("pattern {pattern_type} at line {location} does not match output line {:?}", output_line.as_ref().map(|l| l.text.clone()).unwrap_or("<eof>".to_string()))]
419pub struct OutputPatternMatchFailure {
420 pub location: ScriptLocation,
421 pub pattern_type: &'static str,
422 pub output_line: Option<Line>,
423}
424
425#[derive(Debug, Clone)]
426pub struct OutputMatchContext<'s> {
427 depth: usize,
428 trace: Arc<Mutex<Vec<String>>>,
429 ignore: bool,
430 script_context: &'s ScriptRunContext,
431}
432
433impl<'s> OutputMatchContext<'s> {
434 pub fn new(script_context: &'s ScriptRunContext) -> Self {
435 Self {
436 depth: 0,
437 trace: Default::default(),
438 ignore: false,
439 script_context,
440 }
441 }
442
443 pub fn descend(&self) -> Self {
444 Self {
445 depth: self.depth + 1,
446 trace: self.trace.clone(),
447 ignore: self.ignore,
448 script_context: self.script_context,
449 }
450 }
451
452 pub fn ignore(&self) -> Self {
453 Self {
454 depth: self.depth,
455 trace: self.trace.clone(),
456 ignore: true,
457 script_context: self.script_context,
458 }
459 }
460
461 pub fn trace(&self, line: &str) {
462 let ignore = if self.ignore { "-" } else { "" };
463 self.trace.lock().unwrap().push(format!(
464 "{:indent$}{ignore}{}",
465 "",
466 line,
467 indent = self.depth * 2
468 ));
469 }
470
471 pub fn traces(&self) -> Vec<String> {
472 std::mem::take(&mut self.trace.lock().unwrap())
473 }
474}
475
476impl OutputPatternType {
477 pub fn matches<'s>(
478 &self,
479 location: &ScriptLocation,
480 context: OutputMatchContext,
481 mut output: Lines,
482 ) -> Result<Lines, OutputPatternMatchFailure> {
483 context.trace(&format!("matching {:?}", self));
484 match self {
485 OutputPatternType::None => Ok(output),
486 OutputPatternType::Literal(literal) => {
487 let (line, next) = output.next(context.clone())?;
488 if let Some(line) = line {
489 if &line.text.trim_end() == literal {
490 context.trace(&format!("literal match: {:?} == {literal:?}", line.text));
491 Ok(next)
492 } else {
493 context.trace(&format!(
494 "literal FAILED match: {:?} == {literal:?}",
495 line.text
496 ));
497 Err(OutputPatternMatchFailure {
498 location: location.clone(),
499 pattern_type: "literal",
500 output_line: Some(line),
501 })
502 }
503 } else {
504 Err(OutputPatternMatchFailure {
505 location: location.clone(),
506 pattern_type: "literal",
507 output_line: None,
508 })
509 }
510 }
511 OutputPatternType::Pattern(pattern) => {
512 let (line, next) = output.next(context.clone())?;
513 if let Some(line) = line {
514 let text = line.text.clone();
515 let res = match std::panic::catch_unwind(|| pattern.grok.match_against(&text)) {
517 Ok(res) => res,
518 Err(_) => {
519 return Err(OutputPatternMatchFailure {
520 location: location.clone(),
521 pattern_type: "pattern",
522 output_line: Some(line),
523 });
524 }
525 };
526 if let Some(matches) = res {
527 context.trace(&format!("pattern match: {:?} =~ {pattern:?}", line.text));
528 Ok(next)
529 } else {
530 context.trace(&format!(
531 "pattern FAILED match: {:?} =~ {pattern:?}",
532 line.text
533 ));
534 Err(OutputPatternMatchFailure {
535 location: location.clone(),
536 pattern_type: "pattern",
537 output_line: Some(line),
538 })
539 }
540 } else {
541 Err(OutputPatternMatchFailure {
542 location: location.clone(),
543 pattern_type: "pattern",
544 output_line: None,
545 })
546 }
547 }
548 OutputPatternType::Sequence(patterns) => {
549 for pattern in patterns {
550 match pattern.matches(context.descend(), output) {
551 Ok(v) => {
552 output = v;
553 }
554 Err(e) => {
555 return Err(e);
556 }
557 }
558 }
559 Ok(output)
560 }
561 OutputPatternType::Repeat(pattern) => {
562 let mut output = pattern.matches(context.descend(), output)?;
564 loop {
566 match pattern.matches(context.descend(), output.clone()) {
567 Ok(new_rest) => {
568 output = new_rest;
569 }
570 Err(_) => break Ok(output),
571 }
572 }
573 }
574 OutputPatternType::Optional(pattern) => {
575 match pattern.matches(context.descend(), output.clone()) {
577 Ok(v) => Ok(v),
578 Err(_) => Ok(output),
579 }
580 }
581 OutputPatternType::Unordered(patterns) => {
582 let mut not_found = (0..patterns.len()).collect::<HashSet<_>>();
584 'outer: while !not_found.is_empty() {
585 for pattern in ¬_found {
586 let pattern = *pattern;
587 match patterns[pattern].matches(context.descend(), output.clone()) {
588 Ok(v) => {
589 not_found.remove(&pattern);
590 output = v;
591 continue 'outer;
592 }
593 Err(_) => {
594 continue;
595 }
596 }
597 }
598 return Err(OutputPatternMatchFailure {
599 location: location.clone(),
600 pattern_type: "unordered",
601 output_line: None,
602 });
603 }
604 Ok(output)
605 }
606 OutputPatternType::Choice(patterns) => {
607 for pattern in patterns {
608 if let Ok(v) = pattern.matches(context.descend(), output.clone()) {
609 return Ok(v);
610 }
611 }
612 Err(OutputPatternMatchFailure {
613 location: location.clone(),
614 pattern_type: "choice",
615 output_line: None,
616 })
617 }
618 OutputPatternType::Any(until) => {
619 loop {
620 match until.matches(context.descend(), output.clone()) {
621 Ok(v) => {
622 output = v;
623 break Ok(output);
624 }
625 Err(e) => {
626 let (line, next) = output.next(context.clone())?;
628 if line.is_some() {
629 output = next;
630 continue;
631 } else {
632 break Err(e);
633 }
634 }
635 }
636 }
637 }
638 OutputPatternType::If(condition, pattern) => {
639 if condition.matches(context.script_context) {
640 context.trace(&format!("if match: {:?}", condition));
641 pattern.matches(context.clone(), output.clone())
642 } else {
643 context.trace(&format!("if FAILED match: {:?}", condition));
644 Ok(output)
645 }
646 }
647 OutputPatternType::End => {
648 let (line, next) = output.next(context)?;
649 if let Some(line) = line {
650 Err(OutputPatternMatchFailure {
651 location: location.clone(),
652 pattern_type: "end",
653 output_line: Some(line),
654 })
655 } else {
656 Ok(next)
657 }
658 }
659 }
660 }
661}
662
663#[derive(Debug)]
664pub enum PatternResult {
665 Matches,
666 MatchesFailure,
667 ExpectedFailure,
668 Mismatch(OutputPatternMatchFailure, String),
669}