Skip to main content

tellaro_query_language/mutators/
string_mutators.rs

1//! String manipulation mutators for TQL.
2//!
3//! Provides transformations like lowercase, uppercase, trim, split, length, and replace.
4
5use super::{get_param, Mutator, MutatorParams};
6use crate::error::{Result, TqlError};
7use serde_json::Value as JsonValue;
8
9/// Mutator that converts string values to lowercase
10pub struct LowercaseMutator {
11    _params: MutatorParams,
12}
13
14impl LowercaseMutator {
15    pub fn new(params: MutatorParams) -> Self {
16        Self { _params: params }
17    }
18}
19
20impl Mutator for LowercaseMutator {
21    fn apply(
22        &self,
23        _field_name: &str,
24        _record: &JsonValue,
25        value: &JsonValue,
26    ) -> Result<JsonValue> {
27        match value {
28            JsonValue::String(s) => Ok(JsonValue::String(s.to_lowercase())),
29            JsonValue::Array(arr) => {
30                let transformed: Vec<JsonValue> = arr
31                    .iter()
32                    .map(|item| {
33                        if let JsonValue::String(s) = item {
34                            JsonValue::String(s.to_lowercase())
35                        } else {
36                            item.clone()
37                        }
38                    })
39                    .collect();
40                Ok(JsonValue::Array(transformed))
41            }
42            _ => Ok(value.clone()),
43        }
44    }
45
46    fn name(&self) -> &str {
47        "lowercase"
48    }
49}
50
51/// Mutator that converts string values to uppercase
52pub struct UppercaseMutator {
53    _params: MutatorParams,
54}
55
56impl UppercaseMutator {
57    pub fn new(params: MutatorParams) -> Self {
58        Self { _params: params }
59    }
60}
61
62impl Mutator for UppercaseMutator {
63    fn apply(
64        &self,
65        _field_name: &str,
66        _record: &JsonValue,
67        value: &JsonValue,
68    ) -> Result<JsonValue> {
69        match value {
70            JsonValue::String(s) => Ok(JsonValue::String(s.to_uppercase())),
71            JsonValue::Array(arr) => {
72                let transformed: Vec<JsonValue> = arr
73                    .iter()
74                    .map(|item| {
75                        if let JsonValue::String(s) = item {
76                            JsonValue::String(s.to_uppercase())
77                        } else {
78                            item.clone()
79                        }
80                    })
81                    .collect();
82                Ok(JsonValue::Array(transformed))
83            }
84            _ => Ok(value.clone()),
85        }
86    }
87
88    fn name(&self) -> &str {
89        "uppercase"
90    }
91}
92
93/// Mutator that trims whitespace from string values
94pub struct TrimMutator {
95    _params: MutatorParams,
96}
97
98impl TrimMutator {
99    pub fn new(params: MutatorParams) -> Self {
100        Self { _params: params }
101    }
102}
103
104impl Mutator for TrimMutator {
105    fn apply(
106        &self,
107        _field_name: &str,
108        _record: &JsonValue,
109        value: &JsonValue,
110    ) -> Result<JsonValue> {
111        match value {
112            JsonValue::String(s) => Ok(JsonValue::String(s.trim().to_string())),
113            JsonValue::Array(arr) => {
114                let transformed: Vec<JsonValue> = arr
115                    .iter()
116                    .map(|item| {
117                        if let JsonValue::String(s) = item {
118                            JsonValue::String(s.trim().to_string())
119                        } else {
120                            item.clone()
121                        }
122                    })
123                    .collect();
124                Ok(JsonValue::Array(transformed))
125            }
126            _ => Ok(value.clone()),
127        }
128    }
129
130    fn name(&self) -> &str {
131        "trim"
132    }
133}
134
135/// Mutator that splits string values on a delimiter
136pub struct SplitMutator {
137    params: MutatorParams,
138}
139
140impl SplitMutator {
141    pub fn new(params: MutatorParams) -> Self {
142        Self { params }
143    }
144
145    fn get_delimiter(&self) -> String {
146        get_param(&self.params, "delimiter", 0)
147            .and_then(|v| v.as_str())
148            .unwrap_or(" ")
149            .to_string()
150    }
151}
152
153impl Mutator for SplitMutator {
154    fn apply(
155        &self,
156        _field_name: &str,
157        _record: &JsonValue,
158        value: &JsonValue,
159    ) -> Result<JsonValue> {
160        let delimiter = self.get_delimiter();
161
162        match value {
163            JsonValue::String(s) => {
164                let parts: Vec<JsonValue> = s
165                    .split(&delimiter)
166                    .map(|part| JsonValue::String(part.to_string()))
167                    .collect();
168                Ok(JsonValue::Array(parts))
169            }
170            JsonValue::Array(arr) => {
171                // Apply split to each string element
172                let transformed: Vec<JsonValue> = arr
173                    .iter()
174                    .flat_map(|item| {
175                        if let JsonValue::String(s) = item {
176                            s.split(&delimiter)
177                                .map(|part| JsonValue::String(part.to_string()))
178                                .collect::<Vec<JsonValue>>()
179                        } else {
180                            vec![item.clone()]
181                        }
182                    })
183                    .collect();
184                Ok(JsonValue::Array(transformed))
185            }
186            _ => Ok(value.clone()),
187        }
188    }
189
190    fn name(&self) -> &str {
191        "split"
192    }
193}
194
195/// Mutator that returns the length of a string or array
196pub struct LengthMutator {
197    _params: MutatorParams,
198}
199
200impl LengthMutator {
201    pub fn new(params: MutatorParams) -> Self {
202        Self { _params: params }
203    }
204}
205
206impl Mutator for LengthMutator {
207    fn apply(
208        &self,
209        _field_name: &str,
210        _record: &JsonValue,
211        value: &JsonValue,
212    ) -> Result<JsonValue> {
213        match value {
214            JsonValue::String(s) => Ok(JsonValue::Number(serde_json::Number::from(
215                s.chars().count(),
216            ))),
217            JsonValue::Array(arr) => Ok(JsonValue::Number(serde_json::Number::from(arr.len()))),
218            _ => Ok(JsonValue::Number(serde_json::Number::from(0))),
219        }
220    }
221
222    fn name(&self) -> &str {
223        "length"
224    }
225}
226
227/// Mutator that replaces substrings in a string value
228pub struct ReplaceMutator {
229    params: MutatorParams,
230}
231
232impl ReplaceMutator {
233    pub fn new(params: MutatorParams) -> Self {
234        Self { params }
235    }
236
237    fn get_find(&self) -> Result<String> {
238        get_param(&self.params, "find", 0)
239            .and_then(|v| v.as_str())
240            .map(|s| s.to_string())
241            .ok_or_else(|| {
242                TqlError::MutatorError("Replace mutator requires 'find' parameter".to_string())
243            })
244    }
245
246    fn get_replace(&self) -> String {
247        get_param(&self.params, "replace", 1)
248            .and_then(|v| v.as_str())
249            .unwrap_or("")
250            .to_string()
251    }
252}
253
254impl Mutator for ReplaceMutator {
255    fn apply(
256        &self,
257        _field_name: &str,
258        _record: &JsonValue,
259        value: &JsonValue,
260    ) -> Result<JsonValue> {
261        let find = self.get_find()?;
262        let replace = self.get_replace();
263
264        match value {
265            JsonValue::String(s) => Ok(JsonValue::String(s.replace(&find, &replace))),
266            JsonValue::Array(arr) => {
267                let transformed: Result<Vec<JsonValue>> = arr
268                    .iter()
269                    .map(|item| {
270                        if let JsonValue::String(s) = item {
271                            Ok(JsonValue::String(s.replace(&find, &replace)))
272                        } else {
273                            Ok(item.clone())
274                        }
275                    })
276                    .collect();
277                Ok(JsonValue::Array(transformed?))
278            }
279            _ => Ok(value.clone()),
280        }
281    }
282
283    fn name(&self) -> &str {
284        "replace"
285    }
286}
287
288#[cfg(test)]
289mod tests {
290    use super::*;
291    use serde_json::json;
292    use std::collections::HashMap;
293
294    #[test]
295    fn test_lowercase_mutator() {
296        let mutator = LowercaseMutator::new(HashMap::new());
297        let record = json!({});
298
299        // Test string
300        let value = json!("HELLO WORLD");
301        let result = mutator.apply("field", &record, &value).unwrap();
302        assert_eq!(result, json!("hello world"));
303
304        // Test array
305        let value = json!(["HELLO", "WORLD"]);
306        let result = mutator.apply("field", &record, &value).unwrap();
307        assert_eq!(result, json!(["hello", "world"]));
308
309        // Test non-string (should return as-is)
310        let value = json!(42);
311        let result = mutator.apply("field", &record, &value).unwrap();
312        assert_eq!(result, json!(42));
313    }
314
315    #[test]
316    fn test_uppercase_mutator() {
317        let mutator = UppercaseMutator::new(HashMap::new());
318        let record = json!({});
319
320        let value = json!("hello world");
321        let result = mutator.apply("field", &record, &value).unwrap();
322        assert_eq!(result, json!("HELLO WORLD"));
323    }
324
325    #[test]
326    fn test_trim_mutator() {
327        let mutator = TrimMutator::new(HashMap::new());
328        let record = json!({});
329
330        let value = json!("  hello world  ");
331        let result = mutator.apply("field", &record, &value).unwrap();
332        assert_eq!(result, json!("hello world"));
333
334        // Test array
335        let value = json!(["  hello  ", "  world  "]);
336        let result = mutator.apply("field", &record, &value).unwrap();
337        assert_eq!(result, json!(["hello", "world"]));
338    }
339
340    #[test]
341    fn test_split_mutator() {
342        let mut params = HashMap::new();
343        params.insert("delimiter".to_string(), json!(","));
344        let mutator = SplitMutator::new(params);
345        let record = json!({});
346
347        let value = json!("a,b,c");
348        let result = mutator.apply("field", &record, &value).unwrap();
349        assert_eq!(result, json!(["a", "b", "c"]));
350    }
351
352    #[test]
353    fn test_split_default_delimiter() {
354        let mutator = SplitMutator::new(HashMap::new());
355        let record = json!({});
356
357        let value = json!("hello world");
358        let result = mutator.apply("field", &record, &value).unwrap();
359        assert_eq!(result, json!(["hello", "world"]));
360    }
361
362    #[test]
363    fn test_length_mutator() {
364        let mutator = LengthMutator::new(HashMap::new());
365        let record = json!({});
366
367        // Test string length
368        let value = json!("hello");
369        let result = mutator.apply("field", &record, &value).unwrap();
370        assert_eq!(result, json!(5));
371
372        // Test array length
373        let value = json!(["a", "b", "c"]);
374        let result = mutator.apply("field", &record, &value).unwrap();
375        assert_eq!(result, json!(3));
376    }
377
378    #[test]
379    fn test_replace_mutator() {
380        let mut params = HashMap::new();
381        params.insert("find".to_string(), json!("world"));
382        params.insert("replace".to_string(), json!("universe"));
383        let mutator = ReplaceMutator::new(params);
384        let record = json!({});
385
386        let value = json!("hello world");
387        let result = mutator.apply("field", &record, &value).unwrap();
388        assert_eq!(result, json!("hello universe"));
389
390        // Test array
391        let value = json!(["hello world", "world peace"]);
392        let result = mutator.apply("field", &record, &value).unwrap();
393        assert_eq!(result, json!(["hello universe", "universe peace"]));
394    }
395
396    #[test]
397    fn test_replace_missing_find_parameter() {
398        let mutator = ReplaceMutator::new(HashMap::new());
399        let record = json!({});
400        let value = json!("hello");
401
402        let result = mutator.apply("field", &record, &value);
403        assert!(result.is_err());
404        assert!(result
405            .unwrap_err()
406            .to_string()
407            .contains("Replace mutator requires 'find' parameter"));
408    }
409}