use std::borrow::Cow;
#[cfg(feature = "secrecy")]
use secrecy::ExposeSecret;
use crate::FilterValue;
use super::Function;
pub(crate) struct Trim;
impl Function for Trim {
fn name(&self) -> &str {
"trim"
}
fn arity(&self) -> usize {
1
}
fn call<'a>(&self, args: &[Cow<'a, FilterValue<'a>>]) -> Cow<'a, FilterValue<'a>> {
match args[0].as_ref() {
FilterValue::String(Cow::Borrowed(s)) => {
Cow::Owned(FilterValue::String(Cow::Borrowed(s.trim())))
}
FilterValue::String(Cow::Owned(s)) => {
Cow::Owned(FilterValue::String(Cow::Owned(s.trim().to_string())))
}
#[cfg(feature = "secrecy")]
FilterValue::Secret(s) => Cow::Owned(FilterValue::secret(s.expose_secret().trim())),
_ => Cow::Owned(FilterValue::Null),
}
}
}
#[cfg(test)]
mod tests {
use std::borrow::Cow;
use crate::{FilterValue, functions::Function};
use super::Trim;
#[test]
fn name_and_arity() {
assert_eq!(Trim.name(), "trim");
assert_eq!(Trim.arity(), 1);
}
#[test]
fn borrowed_strings_trim_to_a_borrowed_sub_slice() {
let result = Trim.call(&[Cow::Owned(FilterValue::String(Cow::Borrowed(" hi ")))]);
let FilterValue::String(cow) = result.as_ref() else {
panic!("expected a string, got {:?}", result.as_ref());
};
assert!(
matches!(cow, Cow::Borrowed(_)),
"the trimmed string should stay borrowed"
);
assert_eq!(cow.as_ref(), "hi");
}
#[test]
fn owned_strings_are_trimmed() {
let result = Trim.call(&[Cow::Owned(FilterValue::String(Cow::Owned(
" hi there ".to_string(),
)))]);
assert_eq!(result.as_ref(), &FilterValue::String("hi there".into()));
}
#[test]
fn non_strings_yield_null() {
let result = Trim.call(&[Cow::Owned(FilterValue::Number(1.0))]);
assert_eq!(result.as_ref(), &FilterValue::Null);
}
#[cfg(feature = "secrecy")]
#[test]
fn secrets_stay_wrapped_and_redacted() {
let result = Trim.call(&[Cow::Owned(FilterValue::secret(" hunter2 "))]);
let result = result.as_ref();
assert!(matches!(result, FilterValue::Secret(_)));
assert_eq!(result.to_string(), "[REDACTED]");
assert_eq!(result, &FilterValue::String("hunter2".into()));
}
}