use serde::{Deserialize, Deserializer};
use serde_json::{Map, Value};
#[derive(Debug, Clone, Default, PartialEq)]
pub struct FilterUse {
pub name: String,
pub params: Map<String, Value>,
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct FilterChain(pub Vec<FilterUse>);
impl<'de> Deserialize<'de> for FilterChain {
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
Ok(chain_from_value(Value::deserialize(d)?))
}
}
pub const MAX_CHAIN_LEN: usize = u8::MAX as usize + 1;
fn chain_from_value(value: Value) -> FilterChain {
let entries: Vec<Value> = match value {
Value::Array(entries) => entries,
obj @ Value::Object(_) => vec![obj],
other => {
warn_decode(
&other,
&format!("filter must be an object or array of objects, got {other}"),
);
return FilterChain::default();
}
};
if entries.len() > MAX_CHAIN_LEN {
crate::protocol::decode_warn(
"filterParams",
&format!("[array of {} entries]", entries.len()),
&format!(
"filter chain has {} entries, over the cap of {MAX_CHAIN_LEN}",
entries.len()
),
);
return FilterChain::default();
}
let mut uses = Vec::with_capacity(entries.len());
for entry in entries {
match filter_use(entry) {
Ok(fu) => uses.push(fu),
Err((offending, message)) => {
warn_decode(&offending, &message);
return FilterChain::default();
}
}
}
FilterChain(uses)
}
fn filter_use(value: Value) -> Result<FilterUse, (Value, String)> {
let mut obj = match value {
Value::Object(obj) => obj,
other => {
let message = format!("filter entry must be an object, got {other}");
return Err((other, message));
}
};
let name = match obj.remove("name") {
Some(Value::String(name)) => name,
Some(other) => {
let message = format!("filter name must be a string, got {other}");
return Err((other, message));
}
None => {
let entry = Value::Object(obj);
let message = format!("filter entry {entry} is missing \"name\"");
return Err((entry, message));
}
};
let params = match obj.remove("params") {
None | Some(Value::Null) => Map::new(),
Some(Value::Object(params)) => params,
Some(other) => {
let message = format!("filter params must be an object, got {other}");
return Err((other, message));
}
};
Ok(FilterUse { name, params })
}
fn warn_decode(value: &Value, message: &str) {
crate::protocol::decode_warn("filterParams", &value.to_string(), message);
}
#[cfg(test)]
mod tests {
use super::*;
fn chain(json: &str) -> FilterChain {
serde_json::from_str(json).expect("FilterChain decode must not error")
}
fn blur_use(radius: u64) -> FilterUse {
let mut params = Map::new();
params.insert("radius".into(), Value::from(radius));
FilterUse {
name: "blur".into(),
params,
}
}
#[test]
fn single_object_decodes_to_one_element_chain() {
assert_eq!(
chain(r#"{"name":"blur","params":{"radius":4}}"#),
FilterChain(vec![blur_use(4)]),
);
}
#[test]
fn array_decodes_preserving_order() {
assert_eq!(
chain(
r#"[
{"name":"blur","params":{"radius":4}},
{"name":"grayscale","params":{"amount":1}}
]"#
),
FilterChain(vec![blur_use(4), {
let mut params = Map::new();
params.insert("amount".into(), Value::from(1u64));
FilterUse {
name: "grayscale".into(),
params,
}
}]),
);
}
#[test]
fn missing_params_decodes_to_empty_map() {
#[cfg(all(feature = "devtools", debug_assertions))]
let _ = crate::diag::take_decode_warnings();
let expected = FilterChain(vec![FilterUse {
name: "invert".into(),
params: Map::new(),
}]);
assert_eq!(chain(r#"{"name":"invert"}"#), expected);
assert_eq!(chain(r#"{"name":"invert","params":null}"#), expected);
#[cfg(all(feature = "devtools", debug_assertions))]
assert!(crate::diag::take_decode_warnings().is_empty());
}
#[test]
fn malformed_entry_degrades_whole_value_to_empty_chain() {
assert_eq!(chain(r#"[{"name":"blur"},3]"#), FilterChain::default());
assert_eq!(chain(r#"{"params":{}}"#), FilterChain::default());
assert_eq!(chain(r#"{"name":7}"#), FilterChain::default());
assert_eq!(
chain(r#"{"name":"blur","params":3}"#),
FilterChain::default()
);
}
#[test]
fn over_long_chain_degrades_to_empty_chain() {
#[cfg(all(feature = "devtools", debug_assertions))]
let _ = crate::diag::take_decode_warnings();
let at_cap = format!(
"[{}]",
vec![r#"{"name":"invert"}"#; MAX_CHAIN_LEN].join(",")
);
assert_eq!(chain(&at_cap).0.len(), MAX_CHAIN_LEN);
let over_cap = format!(
"[{}]",
vec![r#"{"name":"invert"}"#; MAX_CHAIN_LEN + 1].join(",")
);
assert_eq!(chain(&over_cap), FilterChain::default());
#[cfg(all(feature = "devtools", debug_assertions))]
{
let warns = crate::diag::take_decode_warnings();
assert_eq!(warns.len(), 1);
assert_eq!(warns[0].kind, "filterParams");
assert!(
warns[0].message.contains("over the cap"),
"{}",
warns[0].message
);
}
}
#[test]
fn garbage_top_level_value_degrades_to_empty_chain() {
assert_eq!(chain("42"), FilterChain::default());
assert_eq!(chain("true"), FilterChain::default());
assert_eq!(chain(r#""blur""#), FilterChain::default());
}
#[test]
fn malformed_chain_does_not_abort_containing_struct() {
#[derive(Deserialize)]
struct Holder {
filter: FilterChain,
width: f32,
}
let h: Holder =
serde_json::from_str(r#"{"filter":42,"width":16.0}"#).expect("holder decodes");
assert_eq!(h.filter, FilterChain::default());
assert_eq!(h.width, 16.0);
}
#[cfg(all(feature = "devtools", debug_assertions))]
#[test]
fn malformed_values_report_decode_warnings() {
let _ = crate::diag::take_decode_warnings();
let _ = chain(r#"[{"name":"blur"},3]"#);
let _ = chain("true");
let warns = crate::diag::take_decode_warnings();
let brief: Vec<_> = warns.iter().map(|w| (w.kind, w.value.as_str())).collect();
assert_eq!(brief, vec![("filterParams", "3"), ("filterParams", "true")]);
assert!(warns.iter().all(|w| !w.message.is_empty()));
}
#[cfg(all(feature = "devtools", debug_assertions))]
#[test]
fn valid_values_report_nothing() {
let _ = crate::diag::take_decode_warnings();
let _ = chain(r#"{"name":"blur"}"#);
assert!(crate::diag::take_decode_warnings().is_empty());
assert_eq!(chain("[]"), FilterChain::default());
assert!(crate::diag::take_decode_warnings().is_empty());
}
}