use serde_json::Value;
use std::collections::BTreeMap;
pub fn transform(
name: &str,
items: Vec<Value>,
attrs: &BTreeMap<String, String>,
) -> Option<Vec<Value>> {
match name {
"filter" => Some(filter(items, attrs)),
"sort" => Some(sort(items, attrs)),
"slice" => Some(slice(items, attrs)),
_ => None,
}
}
fn filter(items: Vec<Value>, attrs: &BTreeMap<String, String>) -> Vec<Value> {
let where_key = attrs.get("where").map(String::as_str).unwrap_or("");
items
.into_iter()
.filter(|item| item.get(where_key).is_some_and(is_truthy))
.collect()
}
fn sort(mut items: Vec<Value>, attrs: &BTreeMap<String, String>) -> Vec<Value> {
let by = attrs.get("by").cloned().unwrap_or_default();
let desc = attrs.get("order").is_some_and(|o| o == "desc");
items.sort_by(|a, b| {
let (ka, kb) = (sort_key(a.get(&by)), sort_key(b.get(&by)));
if desc {
kb.cmp(&ka)
} else {
ka.cmp(&kb)
}
});
items
}
fn slice(items: Vec<Value>, attrs: &BTreeMap<String, String>) -> Vec<Value> {
let start = num(attrs, &["start", "from"]);
let count = num(attrs, &["count", "to"]);
let end = if count > 0 {
(start + count).min(items.len())
} else {
items.len()
};
items
.into_iter()
.skip(start)
.take(end.saturating_sub(start))
.collect()
}
fn is_truthy(val: &Value) -> bool {
match val {
Value::Null => false,
Value::Bool(b) => *b,
Value::String(s) => !s.is_empty(),
Value::Array(a) => !a.is_empty(),
Value::Number(_) | Value::Object(_) => true,
}
}
fn num(attrs: &BTreeMap<String, String>, keys: &[&str]) -> usize {
keys.iter()
.find_map(|k| attrs.get(*k))
.and_then(|v| v.parse().ok())
.unwrap_or(0)
}
fn sort_key(val: Option<&Value>) -> String {
match val {
Some(Value::Number(n)) => format!("{:020}", n.as_u64().unwrap_or(0)),
Some(Value::String(s)) => s.clone(),
Some(Value::Bool(b)) => (if *b { "1" } else { "0" }).to_string(),
_ => String::new(),
}
}