218 lines
4.6 KiB
Rust
218 lines
4.6 KiB
Rust
use pile_config::Label;
|
|
use smartstring::{LazyCompact, SmartString};
|
|
use std::sync::Arc;
|
|
|
|
use crate::{
|
|
extract::traits::{ExtractState, ObjectExtractor},
|
|
value::PileValue,
|
|
};
|
|
|
|
pub struct StringExtractor {
|
|
item: Arc<SmartString<LazyCompact>>,
|
|
}
|
|
|
|
impl StringExtractor {
|
|
pub fn new(item: &Arc<SmartString<LazyCompact>>) -> Self {
|
|
Self { item: item.clone() }
|
|
}
|
|
}
|
|
|
|
#[async_trait::async_trait]
|
|
impl ObjectExtractor for StringExtractor {
|
|
async fn field(
|
|
&self,
|
|
_state: &ExtractState,
|
|
name: &Label,
|
|
args: Option<&str>,
|
|
) -> Result<Option<PileValue>, std::io::Error> {
|
|
Ok(match (name.as_str(), args) {
|
|
("trim", None) => Some(PileValue::String(Arc::new(
|
|
self.item.as_str().trim().into(),
|
|
))),
|
|
|
|
("upper", None) => Some(PileValue::String(Arc::new(
|
|
self.item.as_str().to_lowercase().into(),
|
|
))),
|
|
|
|
("lower", None) => Some(PileValue::String(Arc::new(
|
|
self.item.as_str().to_uppercase().into(),
|
|
))),
|
|
|
|
("nonempty", None) => Some(match self.item.is_empty() {
|
|
true => PileValue::Null,
|
|
false => PileValue::String(self.item.clone()),
|
|
}),
|
|
|
|
("trimprefix", Some(prefix)) => Some(PileValue::String(Arc::new(
|
|
self.item
|
|
.as_str()
|
|
.strip_prefix(prefix)
|
|
.unwrap_or(self.item.as_str())
|
|
.into(),
|
|
))),
|
|
|
|
("trimsuffix", Some(suffix)) => Some(PileValue::String(Arc::new(
|
|
self.item
|
|
.as_str()
|
|
.strip_suffix(suffix)
|
|
.unwrap_or(self.item.as_str())
|
|
.into(),
|
|
))),
|
|
|
|
("split", Some(by)) => Some(PileValue::Array(Arc::new(
|
|
self.item
|
|
.as_str()
|
|
.split(by)
|
|
.map(|s| PileValue::String(Arc::new(s.into())))
|
|
.collect(),
|
|
))),
|
|
|
|
_ => None,
|
|
})
|
|
}
|
|
|
|
#[expect(clippy::unwrap_used)]
|
|
async fn fields(&self) -> Result<Vec<Label>, std::io::Error> {
|
|
return Ok(vec![
|
|
Label::new("trim").unwrap(),
|
|
Label::new("upper").unwrap(),
|
|
Label::new("lower").unwrap(),
|
|
Label::new("nonempty").unwrap(),
|
|
]);
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
#[expect(clippy::expect_used)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
fn extractor(s: &str) -> StringExtractor {
|
|
StringExtractor::new(&Arc::new(s.into()))
|
|
}
|
|
|
|
#[expect(clippy::unwrap_used)]
|
|
async fn field(ext: &StringExtractor, name: &str, args: Option<&str>) -> Option<PileValue> {
|
|
let state = ExtractState { ignore_mime: false };
|
|
ext.field(&state, &Label::new(name).unwrap(), args)
|
|
.await
|
|
.unwrap()
|
|
}
|
|
|
|
fn string(v: Option<PileValue>) -> Option<String> {
|
|
match v? {
|
|
PileValue::String(s) => Some(s.as_str().to_owned()),
|
|
_ => panic!("expected string"),
|
|
}
|
|
}
|
|
|
|
fn array(v: Option<PileValue>) -> Vec<String> {
|
|
match v.expect("expected Some") {
|
|
PileValue::Array(arr) => arr
|
|
.iter()
|
|
.map(|v| match v {
|
|
PileValue::String(s) => s.as_str().to_owned(),
|
|
_ => panic!("expected string element"),
|
|
})
|
|
.collect(),
|
|
_ => panic!("expected array"),
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn trim() {
|
|
assert_eq!(
|
|
string(field(&extractor(" hi "), "trim", None).await),
|
|
Some("hi".into())
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn trim_no_args() {
|
|
assert!(field(&extractor("x"), "trim", Some("foo")).await.is_none());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn nonempty_with_content() {
|
|
assert!(matches!(
|
|
field(&extractor("hello"), "nonempty", None).await,
|
|
Some(PileValue::String(_))
|
|
));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn nonempty_empty_string() {
|
|
assert!(matches!(
|
|
field(&extractor(""), "nonempty", None).await,
|
|
Some(PileValue::Null)
|
|
));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn trimprefix_present() {
|
|
assert_eq!(
|
|
string(field(&extractor("foobar"), "trimprefix", Some("foo")).await),
|
|
Some("bar".into())
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn trimprefix_absent() {
|
|
assert_eq!(
|
|
string(field(&extractor("foobar"), "trimprefix", Some("baz")).await),
|
|
Some("foobar".into())
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn trimprefix_no_args() {
|
|
assert!(
|
|
field(&extractor("foobar"), "trimprefix", None)
|
|
.await
|
|
.is_none()
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn trimsuffix_present() {
|
|
assert_eq!(
|
|
string(field(&extractor("foobar"), "trimsuffix", Some("bar")).await),
|
|
Some("foo".into())
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn trimsuffix_absent() {
|
|
assert_eq!(
|
|
string(field(&extractor("foobar"), "trimsuffix", Some("baz")).await),
|
|
Some("foobar".into())
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn split_basic() {
|
|
assert_eq!(
|
|
array(field(&extractor("a,b,c"), "split", Some(",")).await),
|
|
vec!["a", "b", "c"]
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn split_no_match() {
|
|
assert_eq!(
|
|
array(field(&extractor("abc"), "split", Some(",")).await),
|
|
vec!["abc"]
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn split_no_args() {
|
|
assert!(field(&extractor("abc"), "split", None).await.is_none());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn unknown_field() {
|
|
assert!(field(&extractor("abc"), "bogus", None).await.is_none());
|
|
}
|
|
}
|