sentinel_modsec/transformations/
pipeline.rs1use super::{create_transformation, Transformation};
4use crate::error::Result;
5use std::borrow::Cow;
6use std::sync::Arc;
7
8#[derive(Clone)]
10pub struct TransformationPipeline {
11 transformations: Vec<Arc<dyn Transformation>>,
12}
13
14impl TransformationPipeline {
15 pub fn new() -> Self {
17 Self {
18 transformations: Vec::new(),
19 }
20 }
21
22 pub fn from_names(names: &[String]) -> Result<Self> {
24 let mut transformations = Vec::new();
25
26 for name in names {
27 if name.eq_ignore_ascii_case("none") {
29 transformations.clear();
30 continue;
31 }
32
33 let t = create_transformation(name)?;
34 transformations.push(t);
35 }
36
37 Ok(Self { transformations })
38 }
39
40 pub fn add(&mut self, transformation: Arc<dyn Transformation>) {
42 self.transformations.push(transformation);
43 }
44
45 pub fn apply<'a>(&self, input: &'a str) -> Cow<'a, str> {
47 if self.transformations.is_empty() {
48 return Cow::Borrowed(input);
49 }
50
51 let mut current: Cow<str> = Cow::Borrowed(input);
52
53 for t in &self.transformations {
54 current = match current {
55 Cow::Borrowed(s) => t.transform(s),
56 Cow::Owned(s) => {
57 let transformed = t.transform(&s);
58 match transformed {
59 Cow::Borrowed(_) => Cow::Owned(s),
60 Cow::Owned(new) => Cow::Owned(new),
61 }
62 }
63 };
64 }
65
66 current
67 }
68
69 pub fn is_empty(&self) -> bool {
71 self.transformations.is_empty()
72 }
73
74 pub fn len(&self) -> usize {
76 self.transformations.len()
77 }
78}
79
80impl Default for TransformationPipeline {
81 fn default() -> Self {
82 Self::new()
83 }
84}
85
86impl std::fmt::Debug for TransformationPipeline {
87 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
88 f.debug_struct("TransformationPipeline")
89 .field(
90 "transformations",
91 &self
92 .transformations
93 .iter()
94 .map(|t| t.name())
95 .collect::<Vec<_>>(),
96 )
97 .finish()
98 }
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104
105 #[test]
106 fn test_empty_pipeline() {
107 let pipeline = TransformationPipeline::new();
108 assert_eq!(pipeline.apply("hello"), "hello");
109 }
110
111 #[test]
112 fn test_single_transformation() {
113 let pipeline =
114 TransformationPipeline::from_names(&["lowercase".to_string()]).unwrap();
115 assert_eq!(pipeline.apply("HELLO"), "hello");
116 }
117
118 #[test]
119 fn test_multiple_transformations() {
120 let pipeline = TransformationPipeline::from_names(&[
121 "urlDecode".to_string(),
122 "lowercase".to_string(),
123 ])
124 .unwrap();
125 assert_eq!(pipeline.apply("HELLO%20WORLD"), "hello world");
126 }
127
128 #[test]
129 fn test_none_clears_pipeline() {
130 let pipeline = TransformationPipeline::from_names(&[
131 "lowercase".to_string(),
132 "none".to_string(),
133 "uppercase".to_string(),
134 ])
135 .unwrap();
136 assert_eq!(pipeline.apply("hello"), "HELLO");
138 }
139}