1use serde::{Deserialize, Deserializer};
8use serde_json::{Map, Value};
9
10#[derive(Debug, Clone, Default, PartialEq)]
14pub struct FilterUse {
15 pub name: String,
16 pub params: Map<String, Value>,
17}
18
19#[derive(Debug, Clone, Default, PartialEq)]
24pub struct FilterChain(pub Vec<FilterUse>);
25
26impl<'de> Deserialize<'de> for FilterChain {
27 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
28 Ok(chain_from_value(Value::deserialize(d)?))
29 }
30}
31
32pub const MAX_CHAIN_LEN: usize = u8::MAX as usize + 1;
38
39fn chain_from_value(value: Value) -> FilterChain {
40 let entries: Vec<Value> = match value {
41 Value::Array(entries) => entries,
42 obj @ Value::Object(_) => vec![obj],
43 other => {
44 warn_decode(
45 &other,
46 &format!("filter must be an object or array of objects, got {other}"),
47 );
48 return FilterChain::default();
49 }
50 };
51 if entries.len() > MAX_CHAIN_LEN {
52 crate::protocol::decode_warn(
55 "filterParams",
56 &format!("[array of {} entries]", entries.len()),
57 &format!(
58 "filter chain has {} entries, over the cap of {MAX_CHAIN_LEN}",
59 entries.len()
60 ),
61 );
62 return FilterChain::default();
63 }
64 let mut uses = Vec::with_capacity(entries.len());
65 for entry in entries {
66 match filter_use(entry) {
67 Ok(fu) => uses.push(fu),
68 Err((offending, message)) => {
71 warn_decode(&offending, &message);
72 return FilterChain::default();
73 }
74 }
75 }
76 FilterChain(uses)
77}
78
79fn filter_use(value: Value) -> Result<FilterUse, (Value, String)> {
83 let mut obj = match value {
84 Value::Object(obj) => obj,
85 other => {
86 let message = format!("filter entry must be an object, got {other}");
87 return Err((other, message));
88 }
89 };
90 let name = match obj.remove("name") {
91 Some(Value::String(name)) => name,
92 Some(other) => {
93 let message = format!("filter name must be a string, got {other}");
94 return Err((other, message));
95 }
96 None => {
97 let entry = Value::Object(obj);
98 let message = format!("filter entry {entry} is missing \"name\"");
99 return Err((entry, message));
100 }
101 };
102 let params = match obj.remove("params") {
103 None | Some(Value::Null) => Map::new(),
106 Some(Value::Object(params)) => params,
107 Some(other) => {
108 let message = format!("filter params must be an object, got {other}");
109 return Err((other, message));
110 }
111 };
112 Ok(FilterUse { name, params })
113}
114
115fn warn_decode(value: &Value, message: &str) {
116 crate::protocol::decode_warn("filterParams", &value.to_string(), message);
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123
124 fn chain(json: &str) -> FilterChain {
126 serde_json::from_str(json).expect("FilterChain decode must not error")
127 }
128
129 fn blur_use(radius: u64) -> FilterUse {
130 let mut params = Map::new();
131 params.insert("radius".into(), Value::from(radius));
132 FilterUse {
133 name: "blur".into(),
134 params,
135 }
136 }
137
138 #[test]
139 fn single_object_decodes_to_one_element_chain() {
140 assert_eq!(
141 chain(r#"{"name":"blur","params":{"radius":4}}"#),
142 FilterChain(vec![blur_use(4)]),
143 );
144 }
145
146 #[test]
147 fn array_decodes_preserving_order() {
148 assert_eq!(
149 chain(
150 r#"[
151 {"name":"blur","params":{"radius":4}},
152 {"name":"grayscale","params":{"amount":1}}
153 ]"#
154 ),
155 FilterChain(vec![blur_use(4), {
156 let mut params = Map::new();
157 params.insert("amount".into(), Value::from(1u64));
158 FilterUse {
159 name: "grayscale".into(),
160 params,
161 }
162 }]),
163 );
164 }
165
166 #[test]
167 fn missing_params_decodes_to_empty_map() {
168 #[cfg(all(feature = "devtools", debug_assertions))]
169 let _ = crate::diag::take_decode_warnings();
170 let expected = FilterChain(vec![FilterUse {
171 name: "invert".into(),
172 params: Map::new(),
173 }]);
174 assert_eq!(chain(r#"{"name":"invert"}"#), expected);
175 assert_eq!(chain(r#"{"name":"invert","params":null}"#), expected);
177 #[cfg(all(feature = "devtools", debug_assertions))]
178 assert!(crate::diag::take_decode_warnings().is_empty());
179 }
180
181 #[test]
184 fn malformed_entry_degrades_whole_value_to_empty_chain() {
185 assert_eq!(chain(r#"[{"name":"blur"},3]"#), FilterChain::default());
187 assert_eq!(chain(r#"{"params":{}}"#), FilterChain::default());
189 assert_eq!(chain(r#"{"name":7}"#), FilterChain::default());
191 assert_eq!(
193 chain(r#"{"name":"blur","params":3}"#),
194 FilterChain::default()
195 );
196 }
197
198 #[test]
201 fn over_long_chain_degrades_to_empty_chain() {
202 #[cfg(all(feature = "devtools", debug_assertions))]
203 let _ = crate::diag::take_decode_warnings();
204 let at_cap = format!(
205 "[{}]",
206 vec![r#"{"name":"invert"}"#; MAX_CHAIN_LEN].join(",")
207 );
208 assert_eq!(chain(&at_cap).0.len(), MAX_CHAIN_LEN);
209 let over_cap = format!(
210 "[{}]",
211 vec![r#"{"name":"invert"}"#; MAX_CHAIN_LEN + 1].join(",")
212 );
213 assert_eq!(chain(&over_cap), FilterChain::default());
214 #[cfg(all(feature = "devtools", debug_assertions))]
215 {
216 let warns = crate::diag::take_decode_warnings();
217 assert_eq!(warns.len(), 1);
218 assert_eq!(warns[0].kind, "filterParams");
219 assert!(
220 warns[0].message.contains("over the cap"),
221 "{}",
222 warns[0].message
223 );
224 }
225 }
226
227 #[test]
228 fn garbage_top_level_value_degrades_to_empty_chain() {
229 assert_eq!(chain("42"), FilterChain::default());
230 assert_eq!(chain("true"), FilterChain::default());
231 assert_eq!(chain(r#""blur""#), FilterChain::default());
232 }
233
234 #[test]
237 fn malformed_chain_does_not_abort_containing_struct() {
238 #[derive(Deserialize)]
239 struct Holder {
240 filter: FilterChain,
241 width: f32,
242 }
243 let h: Holder =
244 serde_json::from_str(r#"{"filter":42,"width":16.0}"#).expect("holder decodes");
245 assert_eq!(h.filter, FilterChain::default());
246 assert_eq!(h.width, 16.0);
247 }
248
249 #[cfg(all(feature = "devtools", debug_assertions))]
252 #[test]
253 fn malformed_values_report_decode_warnings() {
254 let _ = crate::diag::take_decode_warnings();
255 let _ = chain(r#"[{"name":"blur"},3]"#);
256 let _ = chain("true");
257 let warns = crate::diag::take_decode_warnings();
258 let brief: Vec<_> = warns.iter().map(|w| (w.kind, w.value.as_str())).collect();
259 assert_eq!(brief, vec![("filterParams", "3"), ("filterParams", "true")]);
260 assert!(warns.iter().all(|w| !w.message.is_empty()));
261 }
262
263 #[cfg(all(feature = "devtools", debug_assertions))]
265 #[test]
266 fn valid_values_report_nothing() {
267 let _ = crate::diag::take_decode_warnings();
268 let _ = chain(r#"{"name":"blur"}"#);
269 assert!(crate::diag::take_decode_warnings().is_empty());
270 assert_eq!(chain("[]"), FilterChain::default());
273 assert!(crate::diag::take_decode_warnings().is_empty());
274 }
275}