use std::collections::HashMap;
use rustrails_support::string_ext::StringExt;
use serde_json::Value;
pub struct Normalization {
pub attribute: &'static str,
pub normalizer: fn(Value) -> Value,
}
pub trait Normalizable {
fn normalizations() -> &'static [Normalization] {
&[]
}
fn normalize_attributes(attrs: &mut HashMap<String, Value>) {
for normalization in Self::normalizations() {
if let Some(value) = attrs.remove(normalization.attribute) {
attrs.insert(
normalization.attribute.to_owned(),
(normalization.normalizer)(value),
);
}
}
}
}
fn normalize_string(value: Value, normalizer: impl FnOnce(&str) -> String) -> Value {
match value {
Value::String(content) => Value::String(normalizer(&content)),
other => other,
}
}
#[must_use]
pub fn strip_normalizer(value: Value) -> Value {
normalize_string(value, |content| content.trim().to_owned())
}
#[must_use]
pub fn downcase_normalizer(value: Value) -> Value {
normalize_string(value, |content| content.to_lowercase())
}
#[must_use]
pub fn squish_normalizer(value: Value) -> Value {
normalize_string(value, StringExt::squish)
}
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use serde_json::{Value, json};
use super::{
Normalizable, Normalization, downcase_normalizer, squish_normalizer, strip_normalizer,
};
struct NormalizedUser;
impl Normalizable for NormalizedUser {
fn normalizations() -> &'static [Normalization] {
&[
Normalization {
attribute: "email",
normalizer: strip_normalizer,
},
Normalization {
attribute: "name",
normalizer: squish_normalizer,
},
Normalization {
attribute: "handle",
normalizer: downcase_normalizer,
},
]
}
}
#[test]
fn strip_normalizer_trims_whitespace() {
assert_eq!(strip_normalizer(json!(" hello ")), json!("hello"));
}
#[test]
fn downcase_normalizer_lowercases_strings() {
assert_eq!(downcase_normalizer(json!("MiXeD")), json!("mixed"));
}
#[test]
fn squish_normalizer_collapses_internal_whitespace() {
assert_eq!(
squish_normalizer(json!(" hello \n world ")),
json!("hello world")
);
}
#[test]
fn normalize_attributes_applies_normalizers_to_matching_keys() {
let mut attrs = HashMap::from([
("email".to_owned(), json!(" alice@example.com ")),
("name".to_owned(), json!(" Alice Example ")),
("handle".to_owned(), json!("AliceExample")),
]);
NormalizedUser::normalize_attributes(&mut attrs);
assert_eq!(attrs.get("email"), Some(&json!("alice@example.com")));
assert_eq!(attrs.get("name"), Some(&json!("Alice Example")));
assert_eq!(attrs.get("handle"), Some(&json!("aliceexample")));
}
#[test]
fn normalize_attributes_ignores_keys_without_normalizers() {
let mut attrs = HashMap::from([
("email".to_owned(), json!(" alice@example.com ")),
("role".to_owned(), json!(" Admin ")),
]);
NormalizedUser::normalize_attributes(&mut attrs);
assert_eq!(attrs.get("email"), Some(&json!("alice@example.com")));
assert_eq!(attrs.get("role"), Some(&json!(" Admin ")));
}
#[test]
fn normalizers_leave_non_string_values_unchanged() {
let value = json!(42);
assert_eq!(strip_normalizer(value.clone()), Value::from(42));
assert_eq!(downcase_normalizer(value.clone()), Value::from(42));
assert_eq!(squish_normalizer(value), Value::from(42));
}
}