use super::{Mutator, MutatorParams};
use crate::error::{Result, TqlError};
use base64::{engine::general_purpose, Engine as _};
use serde_json::Value as JsonValue;
pub struct Base64EncodeMutator {
_params: MutatorParams,
}
impl Base64EncodeMutator {
pub fn new(params: MutatorParams) -> Self {
Self { _params: params }
}
}
impl Mutator for Base64EncodeMutator {
fn apply(
&self,
_field_name: &str,
_record: &JsonValue,
value: &JsonValue,
) -> Result<JsonValue> {
match value {
JsonValue::String(s) => {
let encoded = general_purpose::STANDARD.encode(s.as_bytes());
Ok(JsonValue::String(encoded))
}
JsonValue::Array(arr) => {
let transformed: Vec<JsonValue> = arr
.iter()
.map(|item| {
if let JsonValue::String(s) = item {
JsonValue::String(general_purpose::STANDARD.encode(s.as_bytes()))
} else {
item.clone()
}
})
.collect();
Ok(JsonValue::Array(transformed))
}
_ => Ok(value.clone()),
}
}
fn name(&self) -> &str {
"b64encode"
}
}
pub struct Base64DecodeMutator {
_params: MutatorParams,
}
impl Base64DecodeMutator {
pub fn new(params: MutatorParams) -> Self {
Self { _params: params }
}
}
impl Mutator for Base64DecodeMutator {
fn apply(
&self,
_field_name: &str,
_record: &JsonValue,
value: &JsonValue,
) -> Result<JsonValue> {
match value {
JsonValue::String(s) => {
let decoded_bytes = general_purpose::STANDARD
.decode(s.as_bytes())
.map_err(|e| TqlError::MutatorError(format!("Base64 decode error: {}", e)))?;
let decoded_str = String::from_utf8(decoded_bytes)
.map_err(|e| TqlError::MutatorError(format!("UTF-8 decode error: {}", e)))?;
Ok(JsonValue::String(decoded_str))
}
JsonValue::Array(arr) => {
let transformed: Result<Vec<JsonValue>> = arr
.iter()
.map(|item| {
if let JsonValue::String(s) = item {
let decoded_bytes = general_purpose::STANDARD
.decode(s.as_bytes())
.map_err(|e| {
TqlError::MutatorError(format!("Base64 decode error: {}", e))
})?;
let decoded_str = String::from_utf8(decoded_bytes).map_err(|e| {
TqlError::MutatorError(format!("UTF-8 decode error: {}", e))
})?;
Ok(JsonValue::String(decoded_str))
} else {
Ok(item.clone())
}
})
.collect();
Ok(JsonValue::Array(transformed?))
}
_ => Ok(value.clone()),
}
}
fn name(&self) -> &str {
"b64decode"
}
}
pub struct URLDecodeMutator {
_params: MutatorParams,
}
impl URLDecodeMutator {
pub fn new(params: MutatorParams) -> Self {
Self { _params: params }
}
}
impl Mutator for URLDecodeMutator {
fn apply(
&self,
_field_name: &str,
_record: &JsonValue,
value: &JsonValue,
) -> Result<JsonValue> {
match value {
JsonValue::String(s) => {
let decoded = percent_encoding::percent_decode_str(s)
.decode_utf8()
.map_err(|e| TqlError::MutatorError(format!("URL decode error: {}", e)))?
.to_string();
Ok(JsonValue::String(decoded))
}
JsonValue::Array(arr) => {
let transformed: Result<Vec<JsonValue>> = arr
.iter()
.map(|item| {
if let JsonValue::String(s) = item {
let decoded = percent_encoding::percent_decode_str(s)
.decode_utf8()
.map_err(|e| {
TqlError::MutatorError(format!("URL decode error: {}", e))
})?
.to_string();
Ok(JsonValue::String(decoded))
} else {
Ok(item.clone())
}
})
.collect();
Ok(JsonValue::Array(transformed?))
}
_ => Ok(value.clone()),
}
}
fn name(&self) -> &str {
"urldecode"
}
}
pub struct HexEncodeMutator {
_params: MutatorParams,
}
impl HexEncodeMutator {
pub fn new(params: MutatorParams) -> Self {
Self { _params: params }
}
}
impl Mutator for HexEncodeMutator {
fn apply(
&self,
_field_name: &str,
_record: &JsonValue,
value: &JsonValue,
) -> Result<JsonValue> {
match value {
JsonValue::String(s) => {
let encoded = hex::encode(s.as_bytes());
Ok(JsonValue::String(encoded))
}
JsonValue::Array(arr) => {
let transformed: Vec<JsonValue> = arr
.iter()
.map(|item| {
if let JsonValue::String(s) = item {
JsonValue::String(hex::encode(s.as_bytes()))
} else {
item.clone()
}
})
.collect();
Ok(JsonValue::Array(transformed))
}
_ => Ok(value.clone()),
}
}
fn name(&self) -> &str {
"hexencode"
}
}
pub struct HexDecodeMutator {
_params: MutatorParams,
}
impl HexDecodeMutator {
pub fn new(params: MutatorParams) -> Self {
Self { _params: params }
}
}
impl Mutator for HexDecodeMutator {
fn apply(
&self,
_field_name: &str,
_record: &JsonValue,
value: &JsonValue,
) -> Result<JsonValue> {
match value {
JsonValue::String(s) => {
let decoded_bytes = hex::decode(s)
.map_err(|e| TqlError::MutatorError(format!("Hex decode error: {}", e)))?;
let decoded_str = String::from_utf8(decoded_bytes)
.map_err(|e| TqlError::MutatorError(format!("UTF-8 decode error: {}", e)))?;
Ok(JsonValue::String(decoded_str))
}
JsonValue::Array(arr) => {
let transformed: Result<Vec<JsonValue>> = arr
.iter()
.map(|item| {
if let JsonValue::String(s) = item {
let decoded_bytes = hex::decode(s).map_err(|e| {
TqlError::MutatorError(format!("Hex decode error: {}", e))
})?;
let decoded_str = String::from_utf8(decoded_bytes).map_err(|e| {
TqlError::MutatorError(format!("UTF-8 decode error: {}", e))
})?;
Ok(JsonValue::String(decoded_str))
} else {
Ok(item.clone())
}
})
.collect();
Ok(JsonValue::Array(transformed?))
}
_ => Ok(value.clone()),
}
}
fn name(&self) -> &str {
"hexdecode"
}
}
pub struct MD5Mutator {
_params: MutatorParams,
}
impl MD5Mutator {
pub fn new(params: MutatorParams) -> Self {
Self { _params: params }
}
}
impl Mutator for MD5Mutator {
fn apply(
&self,
_field_name: &str,
_record: &JsonValue,
value: &JsonValue,
) -> Result<JsonValue> {
match value {
JsonValue::String(s) => {
let digest = md5::compute(s.as_bytes());
Ok(JsonValue::String(format!("{:x}", digest)))
}
JsonValue::Array(arr) => {
let transformed: Vec<JsonValue> = arr
.iter()
.map(|item| {
if let JsonValue::String(s) = item {
let digest = md5::compute(s.as_bytes());
JsonValue::String(format!("{:x}", digest))
} else {
item.clone()
}
})
.collect();
Ok(JsonValue::Array(transformed))
}
_ => Ok(value.clone()),
}
}
fn name(&self) -> &str {
"md5"
}
}
pub struct SHA256Mutator {
_params: MutatorParams,
}
impl SHA256Mutator {
pub fn new(params: MutatorParams) -> Self {
Self { _params: params }
}
}
impl Mutator for SHA256Mutator {
fn apply(
&self,
_field_name: &str,
_record: &JsonValue,
value: &JsonValue,
) -> Result<JsonValue> {
use sha2::{Digest, Sha256};
match value {
JsonValue::String(s) => {
let mut hasher = Sha256::new();
hasher.update(s.as_bytes());
let result = hasher.finalize();
Ok(JsonValue::String(format!("{:x}", result)))
}
JsonValue::Array(arr) => {
let transformed: Vec<JsonValue> = arr
.iter()
.map(|item| {
if let JsonValue::String(s) = item {
let mut hasher = Sha256::new();
hasher.update(s.as_bytes());
let result = hasher.finalize();
JsonValue::String(format!("{:x}", result))
} else {
item.clone()
}
})
.collect();
Ok(JsonValue::Array(transformed))
}
_ => Ok(value.clone()),
}
}
fn name(&self) -> &str {
"sha256"
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
use std::collections::HashMap;
#[test]
fn test_base64_encode_mutator() {
let mutator = Base64EncodeMutator::new(HashMap::new());
let record = json!({});
let value = json!("hello world");
let result = mutator.apply("field", &record, &value).unwrap();
assert_eq!(result, json!("aGVsbG8gd29ybGQ="));
let value = json!(["hello", "world"]);
let result = mutator.apply("field", &record, &value).unwrap();
assert_eq!(result, json!(["aGVsbG8=", "d29ybGQ="]));
}
#[test]
fn test_base64_decode_mutator() {
let mutator = Base64DecodeMutator::new(HashMap::new());
let record = json!({});
let value = json!("aGVsbG8gd29ybGQ=");
let result = mutator.apply("field", &record, &value).unwrap();
assert_eq!(result, json!("hello world"));
let value = json!(["aGVsbG8=", "d29ybGQ="]);
let result = mutator.apply("field", &record, &value).unwrap();
assert_eq!(result, json!(["hello", "world"]));
}
#[test]
fn test_base64_encode_decode_round_trip() {
let encode_mutator = Base64EncodeMutator::new(HashMap::new());
let decode_mutator = Base64DecodeMutator::new(HashMap::new());
let record = json!({});
let original = json!("test string 123");
let encoded = encode_mutator.apply("field", &record, &original).unwrap();
let decoded = decode_mutator.apply("field", &record, &encoded).unwrap();
assert_eq!(decoded, original);
}
#[test]
fn test_base64_decode_invalid() {
let mutator = Base64DecodeMutator::new(HashMap::new());
let record = json!({});
let value = json!("invalid!!!base64");
let result = mutator.apply("field", &record, &value);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Base64 decode error"));
}
#[test]
fn test_url_decode_mutator() {
let mutator = URLDecodeMutator::new(HashMap::new());
let record = json!({});
let value = json!("hello%20world");
let result = mutator.apply("field", &record, &value).unwrap();
assert_eq!(result, json!("hello world"));
let value = json!("test%2Fpath%3Fquery%3Dvalue");
let result = mutator.apply("field", &record, &value).unwrap();
assert_eq!(result, json!("test/path?query=value"));
let value = json!(["hello%20world", "test%2Fpath"]);
let result = mutator.apply("field", &record, &value).unwrap();
assert_eq!(result, json!(["hello world", "test/path"]));
}
#[test]
fn test_encoding_mutators_with_non_strings() {
let b64_encode = Base64EncodeMutator::new(HashMap::new());
let url_decode = URLDecodeMutator::new(HashMap::new());
let record = json!({});
let value = json!(42);
assert_eq!(
b64_encode.apply("field", &record, &value).unwrap(),
json!(42)
);
assert_eq!(
url_decode.apply("field", &record, &value).unwrap(),
json!(42)
);
let value = json!(true);
assert_eq!(
b64_encode.apply("field", &record, &value).unwrap(),
json!(true)
);
assert_eq!(
url_decode.apply("field", &record, &value).unwrap(),
json!(true)
);
}
#[test]
fn test_hex_encode_mutator() {
let mutator = HexEncodeMutator::new(HashMap::new());
let record = json!({});
let value = json!("hello");
let result = mutator.apply("field", &record, &value).unwrap();
assert_eq!(result, json!("68656c6c6f"));
}
#[test]
fn test_hex_decode_mutator() {
let mutator = HexDecodeMutator::new(HashMap::new());
let record = json!({});
let value = json!("68656c6c6f");
let result = mutator.apply("field", &record, &value).unwrap();
assert_eq!(result, json!("hello"));
}
#[test]
fn test_md5_mutator() {
let mutator = MD5Mutator::new(HashMap::new());
let record = json!({});
let value = json!("hello");
let result = mutator.apply("field", &record, &value).unwrap();
assert_eq!(result, json!("5d41402abc4b2a76b9719d911017c592"));
}
#[test]
fn test_sha256_mutator() {
let mutator = SHA256Mutator::new(HashMap::new());
let record = json!({});
let value = json!("hello");
let result = mutator.apply("field", &record, &value).unwrap();
assert_eq!(
result,
json!("2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824")
);
}
}