Skip to main content

tellaro_query_language/mutators/
encoding.rs

1//! Encoding and decoding mutators for TQL.
2//!
3//! Provides transformations for base64, URL, and hex encoding/decoding.
4
5use super::{Mutator, MutatorParams};
6use crate::error::{Result, TqlError};
7use base64::{engine::general_purpose, Engine as _};
8use serde_json::Value as JsonValue;
9
10/// Mutator that encodes string values to Base64
11pub struct Base64EncodeMutator {
12    _params: MutatorParams,
13}
14
15impl Base64EncodeMutator {
16    pub fn new(params: MutatorParams) -> Self {
17        Self { _params: params }
18    }
19}
20
21impl Mutator for Base64EncodeMutator {
22    fn apply(
23        &self,
24        _field_name: &str,
25        _record: &JsonValue,
26        value: &JsonValue,
27    ) -> Result<JsonValue> {
28        match value {
29            JsonValue::String(s) => {
30                let encoded = general_purpose::STANDARD.encode(s.as_bytes());
31                Ok(JsonValue::String(encoded))
32            }
33            JsonValue::Array(arr) => {
34                let transformed: Vec<JsonValue> = arr
35                    .iter()
36                    .map(|item| {
37                        if let JsonValue::String(s) = item {
38                            JsonValue::String(general_purpose::STANDARD.encode(s.as_bytes()))
39                        } else {
40                            item.clone()
41                        }
42                    })
43                    .collect();
44                Ok(JsonValue::Array(transformed))
45            }
46            _ => Ok(value.clone()),
47        }
48    }
49
50    fn name(&self) -> &str {
51        "b64encode"
52    }
53}
54
55/// Mutator that decodes Base64-encoded string values
56pub struct Base64DecodeMutator {
57    _params: MutatorParams,
58}
59
60impl Base64DecodeMutator {
61    pub fn new(params: MutatorParams) -> Self {
62        Self { _params: params }
63    }
64}
65
66impl Mutator for Base64DecodeMutator {
67    fn apply(
68        &self,
69        _field_name: &str,
70        _record: &JsonValue,
71        value: &JsonValue,
72    ) -> Result<JsonValue> {
73        match value {
74            JsonValue::String(s) => {
75                let decoded_bytes = general_purpose::STANDARD
76                    .decode(s.as_bytes())
77                    .map_err(|e| TqlError::MutatorError(format!("Base64 decode error: {}", e)))?;
78
79                let decoded_str = String::from_utf8(decoded_bytes)
80                    .map_err(|e| TqlError::MutatorError(format!("UTF-8 decode error: {}", e)))?;
81
82                Ok(JsonValue::String(decoded_str))
83            }
84            JsonValue::Array(arr) => {
85                let transformed: Result<Vec<JsonValue>> = arr
86                    .iter()
87                    .map(|item| {
88                        if let JsonValue::String(s) = item {
89                            let decoded_bytes = general_purpose::STANDARD
90                                .decode(s.as_bytes())
91                                .map_err(|e| {
92                                    TqlError::MutatorError(format!("Base64 decode error: {}", e))
93                                })?;
94
95                            let decoded_str = String::from_utf8(decoded_bytes).map_err(|e| {
96                                TqlError::MutatorError(format!("UTF-8 decode error: {}", e))
97                            })?;
98
99                            Ok(JsonValue::String(decoded_str))
100                        } else {
101                            Ok(item.clone())
102                        }
103                    })
104                    .collect();
105                Ok(JsonValue::Array(transformed?))
106            }
107            _ => Ok(value.clone()),
108        }
109    }
110
111    fn name(&self) -> &str {
112        "b64decode"
113    }
114}
115
116/// Mutator that decodes URL-encoded string values
117pub struct URLDecodeMutator {
118    _params: MutatorParams,
119}
120
121impl URLDecodeMutator {
122    pub fn new(params: MutatorParams) -> Self {
123        Self { _params: params }
124    }
125}
126
127impl Mutator for URLDecodeMutator {
128    fn apply(
129        &self,
130        _field_name: &str,
131        _record: &JsonValue,
132        value: &JsonValue,
133    ) -> Result<JsonValue> {
134        match value {
135            JsonValue::String(s) => {
136                // Use percent_decode for URL decoding
137                let decoded = percent_encoding::percent_decode_str(s)
138                    .decode_utf8()
139                    .map_err(|e| TqlError::MutatorError(format!("URL decode error: {}", e)))?
140                    .to_string();
141                Ok(JsonValue::String(decoded))
142            }
143            JsonValue::Array(arr) => {
144                let transformed: Result<Vec<JsonValue>> = arr
145                    .iter()
146                    .map(|item| {
147                        if let JsonValue::String(s) = item {
148                            let decoded = percent_encoding::percent_decode_str(s)
149                                .decode_utf8()
150                                .map_err(|e| {
151                                    TqlError::MutatorError(format!("URL decode error: {}", e))
152                                })?
153                                .to_string();
154                            Ok(JsonValue::String(decoded))
155                        } else {
156                            Ok(item.clone())
157                        }
158                    })
159                    .collect();
160                Ok(JsonValue::Array(transformed?))
161            }
162            _ => Ok(value.clone()),
163        }
164    }
165
166    fn name(&self) -> &str {
167        "urldecode"
168    }
169}
170
171/// Mutator that encodes string values to hexadecimal
172pub struct HexEncodeMutator {
173    _params: MutatorParams,
174}
175
176impl HexEncodeMutator {
177    pub fn new(params: MutatorParams) -> Self {
178        Self { _params: params }
179    }
180}
181
182impl Mutator for HexEncodeMutator {
183    fn apply(
184        &self,
185        _field_name: &str,
186        _record: &JsonValue,
187        value: &JsonValue,
188    ) -> Result<JsonValue> {
189        match value {
190            JsonValue::String(s) => {
191                let encoded = hex::encode(s.as_bytes());
192                Ok(JsonValue::String(encoded))
193            }
194            JsonValue::Array(arr) => {
195                let transformed: Vec<JsonValue> = arr
196                    .iter()
197                    .map(|item| {
198                        if let JsonValue::String(s) = item {
199                            JsonValue::String(hex::encode(s.as_bytes()))
200                        } else {
201                            item.clone()
202                        }
203                    })
204                    .collect();
205                Ok(JsonValue::Array(transformed))
206            }
207            _ => Ok(value.clone()),
208        }
209    }
210
211    fn name(&self) -> &str {
212        "hexencode"
213    }
214}
215
216/// Mutator that decodes hexadecimal string values
217pub struct HexDecodeMutator {
218    _params: MutatorParams,
219}
220
221impl HexDecodeMutator {
222    pub fn new(params: MutatorParams) -> Self {
223        Self { _params: params }
224    }
225}
226
227impl Mutator for HexDecodeMutator {
228    fn apply(
229        &self,
230        _field_name: &str,
231        _record: &JsonValue,
232        value: &JsonValue,
233    ) -> Result<JsonValue> {
234        match value {
235            JsonValue::String(s) => {
236                let decoded_bytes = hex::decode(s)
237                    .map_err(|e| TqlError::MutatorError(format!("Hex decode error: {}", e)))?;
238
239                let decoded_str = String::from_utf8(decoded_bytes)
240                    .map_err(|e| TqlError::MutatorError(format!("UTF-8 decode error: {}", e)))?;
241
242                Ok(JsonValue::String(decoded_str))
243            }
244            JsonValue::Array(arr) => {
245                let transformed: Result<Vec<JsonValue>> = arr
246                    .iter()
247                    .map(|item| {
248                        if let JsonValue::String(s) = item {
249                            let decoded_bytes = hex::decode(s).map_err(|e| {
250                                TqlError::MutatorError(format!("Hex decode error: {}", e))
251                            })?;
252
253                            let decoded_str = String::from_utf8(decoded_bytes).map_err(|e| {
254                                TqlError::MutatorError(format!("UTF-8 decode error: {}", e))
255                            })?;
256
257                            Ok(JsonValue::String(decoded_str))
258                        } else {
259                            Ok(item.clone())
260                        }
261                    })
262                    .collect();
263                Ok(JsonValue::Array(transformed?))
264            }
265            _ => Ok(value.clone()),
266        }
267    }
268
269    fn name(&self) -> &str {
270        "hexdecode"
271    }
272}
273
274/// Mutator that calculates MD5 hash of string values
275pub struct MD5Mutator {
276    _params: MutatorParams,
277}
278
279impl MD5Mutator {
280    pub fn new(params: MutatorParams) -> Self {
281        Self { _params: params }
282    }
283}
284
285impl Mutator for MD5Mutator {
286    fn apply(
287        &self,
288        _field_name: &str,
289        _record: &JsonValue,
290        value: &JsonValue,
291    ) -> Result<JsonValue> {
292        match value {
293            JsonValue::String(s) => {
294                let digest = md5::compute(s.as_bytes());
295                Ok(JsonValue::String(format!("{:x}", digest)))
296            }
297            JsonValue::Array(arr) => {
298                let transformed: Vec<JsonValue> = arr
299                    .iter()
300                    .map(|item| {
301                        if let JsonValue::String(s) = item {
302                            let digest = md5::compute(s.as_bytes());
303                            JsonValue::String(format!("{:x}", digest))
304                        } else {
305                            item.clone()
306                        }
307                    })
308                    .collect();
309                Ok(JsonValue::Array(transformed))
310            }
311            _ => Ok(value.clone()),
312        }
313    }
314
315    fn name(&self) -> &str {
316        "md5"
317    }
318}
319
320/// Mutator that calculates SHA256 hash of string values
321pub struct SHA256Mutator {
322    _params: MutatorParams,
323}
324
325impl SHA256Mutator {
326    pub fn new(params: MutatorParams) -> Self {
327        Self { _params: params }
328    }
329}
330
331impl Mutator for SHA256Mutator {
332    fn apply(
333        &self,
334        _field_name: &str,
335        _record: &JsonValue,
336        value: &JsonValue,
337    ) -> Result<JsonValue> {
338        use sha2::{Digest, Sha256};
339
340        match value {
341            JsonValue::String(s) => {
342                let mut hasher = Sha256::new();
343                hasher.update(s.as_bytes());
344                let result = hasher.finalize();
345                Ok(JsonValue::String(format!("{:x}", result)))
346            }
347            JsonValue::Array(arr) => {
348                let transformed: Vec<JsonValue> = arr
349                    .iter()
350                    .map(|item| {
351                        if let JsonValue::String(s) = item {
352                            let mut hasher = Sha256::new();
353                            hasher.update(s.as_bytes());
354                            let result = hasher.finalize();
355                            JsonValue::String(format!("{:x}", result))
356                        } else {
357                            item.clone()
358                        }
359                    })
360                    .collect();
361                Ok(JsonValue::Array(transformed))
362            }
363            _ => Ok(value.clone()),
364        }
365    }
366
367    fn name(&self) -> &str {
368        "sha256"
369    }
370}
371
372#[cfg(test)]
373mod tests {
374    use super::*;
375    use serde_json::json;
376    use std::collections::HashMap;
377
378    #[test]
379    fn test_base64_encode_mutator() {
380        let mutator = Base64EncodeMutator::new(HashMap::new());
381        let record = json!({});
382
383        // Test string encoding
384        let value = json!("hello world");
385        let result = mutator.apply("field", &record, &value).unwrap();
386        assert_eq!(result, json!("aGVsbG8gd29ybGQ="));
387
388        // Test array encoding
389        let value = json!(["hello", "world"]);
390        let result = mutator.apply("field", &record, &value).unwrap();
391        assert_eq!(result, json!(["aGVsbG8=", "d29ybGQ="]));
392    }
393
394    #[test]
395    fn test_base64_decode_mutator() {
396        let mutator = Base64DecodeMutator::new(HashMap::new());
397        let record = json!({});
398
399        // Test string decoding
400        let value = json!("aGVsbG8gd29ybGQ=");
401        let result = mutator.apply("field", &record, &value).unwrap();
402        assert_eq!(result, json!("hello world"));
403
404        // Test array decoding
405        let value = json!(["aGVsbG8=", "d29ybGQ="]);
406        let result = mutator.apply("field", &record, &value).unwrap();
407        assert_eq!(result, json!(["hello", "world"]));
408    }
409
410    #[test]
411    fn test_base64_encode_decode_round_trip() {
412        let encode_mutator = Base64EncodeMutator::new(HashMap::new());
413        let decode_mutator = Base64DecodeMutator::new(HashMap::new());
414        let record = json!({});
415
416        let original = json!("test string 123");
417        let encoded = encode_mutator.apply("field", &record, &original).unwrap();
418        let decoded = decode_mutator.apply("field", &record, &encoded).unwrap();
419        assert_eq!(decoded, original);
420    }
421
422    #[test]
423    fn test_base64_decode_invalid() {
424        let mutator = Base64DecodeMutator::new(HashMap::new());
425        let record = json!({});
426
427        let value = json!("invalid!!!base64");
428        let result = mutator.apply("field", &record, &value);
429        assert!(result.is_err());
430        assert!(result
431            .unwrap_err()
432            .to_string()
433            .contains("Base64 decode error"));
434    }
435
436    #[test]
437    fn test_url_decode_mutator() {
438        let mutator = URLDecodeMutator::new(HashMap::new());
439        let record = json!({});
440
441        // Test simple URL decoding
442        let value = json!("hello%20world");
443        let result = mutator.apply("field", &record, &value).unwrap();
444        assert_eq!(result, json!("hello world"));
445
446        // Test special characters
447        let value = json!("test%2Fpath%3Fquery%3Dvalue");
448        let result = mutator.apply("field", &record, &value).unwrap();
449        assert_eq!(result, json!("test/path?query=value"));
450
451        // Test array decoding
452        let value = json!(["hello%20world", "test%2Fpath"]);
453        let result = mutator.apply("field", &record, &value).unwrap();
454        assert_eq!(result, json!(["hello world", "test/path"]));
455    }
456
457    #[test]
458    fn test_encoding_mutators_with_non_strings() {
459        let b64_encode = Base64EncodeMutator::new(HashMap::new());
460        let url_decode = URLDecodeMutator::new(HashMap::new());
461        let record = json!({});
462
463        // Numbers should pass through unchanged
464        let value = json!(42);
465        assert_eq!(
466            b64_encode.apply("field", &record, &value).unwrap(),
467            json!(42)
468        );
469        assert_eq!(
470            url_decode.apply("field", &record, &value).unwrap(),
471            json!(42)
472        );
473
474        // Booleans should pass through unchanged
475        let value = json!(true);
476        assert_eq!(
477            b64_encode.apply("field", &record, &value).unwrap(),
478            json!(true)
479        );
480        assert_eq!(
481            url_decode.apply("field", &record, &value).unwrap(),
482            json!(true)
483        );
484    }
485
486    #[test]
487    fn test_hex_encode_mutator() {
488        let mutator = HexEncodeMutator::new(HashMap::new());
489        let record = json!({});
490
491        let value = json!("hello");
492        let result = mutator.apply("field", &record, &value).unwrap();
493        assert_eq!(result, json!("68656c6c6f"));
494    }
495
496    #[test]
497    fn test_hex_decode_mutator() {
498        let mutator = HexDecodeMutator::new(HashMap::new());
499        let record = json!({});
500
501        let value = json!("68656c6c6f");
502        let result = mutator.apply("field", &record, &value).unwrap();
503        assert_eq!(result, json!("hello"));
504    }
505
506    #[test]
507    fn test_md5_mutator() {
508        let mutator = MD5Mutator::new(HashMap::new());
509        let record = json!({});
510
511        let value = json!("hello");
512        let result = mutator.apply("field", &record, &value).unwrap();
513        assert_eq!(result, json!("5d41402abc4b2a76b9719d911017c592"));
514    }
515
516    #[test]
517    fn test_sha256_mutator() {
518        let mutator = SHA256Mutator::new(HashMap::new());
519        let record = json!({});
520
521        let value = json!("hello");
522        let result = mutator.apply("field", &record, &value).unwrap();
523        assert_eq!(
524            result,
525            json!("2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824")
526        );
527    }
528}