mirror of
https://framagit.org/ppom/reaction
synced 2026-03-14 12:45:47 +01:00
Provide a correct implementation of user-configured match line parsing
This commit is contained in:
parent
3ed2ebd488
commit
da5c3afefb
2 changed files with 244 additions and 1 deletions
|
|
@ -53,6 +53,17 @@
|
|||
//! ERROR plugin test: Freeeee errrooooorrr
|
||||
//! ```
|
||||
//!
|
||||
//! ## Helpers
|
||||
//!
|
||||
//! Those helpers permits to easily maintain similar configuration interfaces accross plugins:
|
||||
//!
|
||||
//! - [`line::PatternLine`], to permit users to use templated lines (ie. "\<ip> bad password").
|
||||
//! - [`time::parse_duration`] to parse durations (ie. "6h", "3 days").
|
||||
//!
|
||||
//! Those helpers solve common issues for reaction plugins:
|
||||
//!
|
||||
//! - The [`shutdown`] module provides structures to ease the quitting process when having multiple tokio tasks.
|
||||
//!
|
||||
//! ## Starting template
|
||||
//!
|
||||
//! ```bash
|
||||
|
|
@ -101,6 +112,7 @@ use serde::{Deserialize, Serialize};
|
|||
use serde_json::{Number, Value as JValue};
|
||||
use tokio::io::{stdin, stdout};
|
||||
|
||||
pub mod line;
|
||||
pub mod shutdown;
|
||||
pub mod time;
|
||||
|
||||
|
|
@ -248,7 +260,7 @@ impl Into<JValue> for Value {
|
|||
|
||||
pub type Line = (String, DateTime<Local>);
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct StreamImpl {
|
||||
pub stream: rch::mpsc::Receiver<Line>,
|
||||
/// Whether this stream works standalone, or if it needs other streams to be fed.
|
||||
|
|
|
|||
231
plugins/reaction-plugin/src/line.rs
Normal file
231
plugins/reaction-plugin/src/line.rs
Normal file
|
|
@ -0,0 +1,231 @@
|
|||
#[derive(Debug, PartialEq, Eq)]
|
||||
enum SendItem {
|
||||
Index(usize),
|
||||
Str(String),
|
||||
}
|
||||
|
||||
impl SendItem {
|
||||
fn min_size(&self) -> usize {
|
||||
match self {
|
||||
Self::Index(_) => 0,
|
||||
Self::Str(s) => s.len(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper struct that permits to transform a template line with patterns into an instantiated line from a match.
|
||||
///
|
||||
/// Useful when you permit the user to reconstruct lines from an action, like in reaction's native actions and in the virtual plugin:
|
||||
/// ```yaml
|
||||
/// actions:
|
||||
/// native:
|
||||
/// cmd: ["iptables", "...", "<ip>"]
|
||||
///
|
||||
/// virtual:
|
||||
/// type: virtual
|
||||
/// options:
|
||||
/// send: "<ip>: bad password on user <user>"
|
||||
/// to: "my_virtual_stream"
|
||||
/// ```
|
||||
///
|
||||
/// Usage example:
|
||||
/// ```
|
||||
/// # use reaction_plugin::line::PatternLine;
|
||||
/// #
|
||||
/// let template = "<ip>: bad password on user <user>".to_string();
|
||||
/// let patterns = vec!["ip".to_string(), "user".to_string()];
|
||||
/// let pattern_line = PatternLine::new(template, patterns);
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// pattern_line.line(vec!["1.2.3.4".to_string(), "root".to_string()]),
|
||||
/// "1.2.3.4: bad password on user root".to_string(),
|
||||
/// );
|
||||
/// ```
|
||||
///
|
||||
/// You can find full examples in those plugins:
|
||||
/// `reaction-plugin-virtual`,
|
||||
/// `reaction-plugin-cluster`.
|
||||
///
|
||||
#[derive(Debug)]
|
||||
pub struct PatternLine {
|
||||
line: Vec<SendItem>,
|
||||
min_size: usize,
|
||||
}
|
||||
|
||||
impl PatternLine {
|
||||
/// Construct [`PatternLine`] from a template line and the list of patterns of the underlying [Filter](https://reaction.ppom.me/reference.html#filter).
|
||||
///
|
||||
/// This list of patterns comes from [`PluginInfo::action_impl`].
|
||||
pub fn new(template: String, patterns: Vec<String>) -> Self {
|
||||
let line = Self::_from(patterns, Vec::from([SendItem::Str(template)]));
|
||||
Self {
|
||||
min_size: line.iter().map(SendItem::min_size).sum(),
|
||||
line,
|
||||
}
|
||||
}
|
||||
fn _from(mut patterns: Vec<String>, acc: Vec<SendItem>) -> Vec<SendItem> {
|
||||
match patterns.pop() {
|
||||
None => acc,
|
||||
Some(pattern) => {
|
||||
let enclosed_pattern = format!("<{pattern}>");
|
||||
let acc = acc
|
||||
.into_iter()
|
||||
.flat_map(|item| match &item {
|
||||
SendItem::Index(_) => vec![item],
|
||||
SendItem::Str(str) => match str.find(&enclosed_pattern) {
|
||||
Some(i) => {
|
||||
let pattern_index = patterns.len();
|
||||
let mut ret = vec![];
|
||||
|
||||
let (left, mid) = str.split_at(i);
|
||||
if !left.is_empty() {
|
||||
ret.push(SendItem::Str(left.into()))
|
||||
}
|
||||
|
||||
ret.push(SendItem::Index(pattern_index));
|
||||
|
||||
if mid.len() > enclosed_pattern.len() {
|
||||
let (_, right) = mid.split_at(enclosed_pattern.len());
|
||||
ret.push(SendItem::Str(right.into()))
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
None => vec![item],
|
||||
},
|
||||
})
|
||||
.collect();
|
||||
Self::_from(patterns, acc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn line(&self, match_: Vec<String>) -> String {
|
||||
let mut res = String::with_capacity(self.min_size);
|
||||
for item in &self.line {
|
||||
match item {
|
||||
SendItem::Index(i) => {
|
||||
if let Some(element) = match_.get(*i) {
|
||||
res.push_str(element);
|
||||
}
|
||||
}
|
||||
SendItem::Str(str) => res.push_str(str),
|
||||
}
|
||||
}
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::line::{PatternLine, SendItem};
|
||||
|
||||
#[test]
|
||||
fn line_0_pattern() {
|
||||
let msg = "my message".to_string();
|
||||
let line = PatternLine::new(msg.clone(), vec![]);
|
||||
assert_eq!(line.line, vec![SendItem::Str(msg.clone())]);
|
||||
assert_eq!(line.min_size, msg.len());
|
||||
assert_eq!(line.line(vec![]), msg.clone());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn line_1_pattern() {
|
||||
let patterns = vec![
|
||||
"ignored".into(),
|
||||
"oh".into(),
|
||||
"ignored".into(),
|
||||
"my".into(),
|
||||
"test".into(),
|
||||
];
|
||||
|
||||
let matches = vec!["yay", "oh", "my", "test", "<oh>", "<my>", "<test>"];
|
||||
|
||||
let tests = [
|
||||
(
|
||||
"<oh> my test",
|
||||
1,
|
||||
vec![SendItem::Index(1), SendItem::Str(" my test".into())],
|
||||
vec![
|
||||
("yay", "yay my test"),
|
||||
("oh", "oh my test"),
|
||||
("my", "my my test"),
|
||||
("test", "test my test"),
|
||||
("<oh>", "<oh> my test"),
|
||||
("<my>", "<my> my test"),
|
||||
("<test>", "<test> my test"),
|
||||
],
|
||||
),
|
||||
(
|
||||
"oh <my> test",
|
||||
3,
|
||||
vec![
|
||||
SendItem::Str("oh ".into()),
|
||||
SendItem::Index(3),
|
||||
SendItem::Str(" test".into()),
|
||||
],
|
||||
vec![
|
||||
("yay", "oh yay test"),
|
||||
("oh", "oh oh test"),
|
||||
("my", "oh my test"),
|
||||
("test", "oh test test"),
|
||||
("<oh>", "oh <oh> test"),
|
||||
("<my>", "oh <my> test"),
|
||||
("<test>", "oh <test> test"),
|
||||
],
|
||||
),
|
||||
(
|
||||
"oh my <test>",
|
||||
4,
|
||||
vec![SendItem::Str("oh my ".into()), SendItem::Index(4)],
|
||||
vec![
|
||||
("yay", "oh my yay"),
|
||||
("oh", "oh my oh"),
|
||||
("my", "oh my my"),
|
||||
("test", "oh my test"),
|
||||
("<oh>", "oh my <oh>"),
|
||||
("<my>", "oh my <my>"),
|
||||
("<test>", "oh my <test>"),
|
||||
],
|
||||
),
|
||||
];
|
||||
|
||||
for (msg, index, expected_pl, lines) in tests {
|
||||
let pattern_line = PatternLine::new(msg.to_string(), patterns.clone());
|
||||
assert_eq!(pattern_line.line, expected_pl);
|
||||
|
||||
for (match_element, line) in lines {
|
||||
for match_default in &matches {
|
||||
let mut match_ = vec![
|
||||
match_default.to_string(),
|
||||
match_default.to_string(),
|
||||
match_default.to_string(),
|
||||
match_default.to_string(),
|
||||
match_default.to_string(),
|
||||
];
|
||||
match_[index] = match_element.to_string();
|
||||
assert_eq!(
|
||||
pattern_line.line(match_.clone()),
|
||||
line,
|
||||
"match: {match_:?}, pattern_line: {pattern_line:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn line_2_pattern() {
|
||||
let pattern_line = PatternLine::new("<a> ; <b>".into(), vec!["a".into(), "b".into()]);
|
||||
|
||||
let matches = ["a", "b", "ab", "<a>", "<b>"];
|
||||
for a in &matches {
|
||||
for b in &matches {
|
||||
assert_eq!(
|
||||
pattern_line.line(vec![a.to_string(), b.to_string()]),
|
||||
format!("{a} ; {b}"),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue