1use serde_json::{json, Value as Json};
33
34#[derive(Debug, Clone, PartialEq)]
36pub enum FilterError {
37 PropertyNotString { op: String },
41}
42
43impl std::fmt::Display for FilterError {
44 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45 match self {
46 FilterError::PropertyNotString { op } => write!(
47 f,
48 "legacy filter operator {op:?} expects a string property name"
49 ),
50 }
51 }
52}
53
54impl std::error::Error for FilterError {}
55
56pub fn is_expression_filter(filter: &Json) -> bool {
63 if filter.is_boolean() {
64 return true;
65 }
66 let Some(arr) = filter.as_array() else {
67 return false;
68 };
69 if arr.is_empty() {
70 return false;
71 }
72
73 let op = arr[0].as_str();
76 let is_string = |v: Option<&Json>| v.is_some_and(Json::is_string);
77 let is_array = |v: Option<&Json>| v.is_some_and(Json::is_array);
78
79 match op {
80 Some("has") => arr.len() >= 2 && !matches!(arr[1].as_str(), Some("$id") | Some("$type")),
81
82 Some("in") => arr.len() >= 3 && (!is_string(arr.get(1)) || is_array(arr.get(2))),
83
84 Some("!in") | Some("!has") => false,
85
86 Some("==") | Some("!=") | Some(">") | Some(">=") | Some("<") | Some("<=") => {
87 arr.len() != 3 || is_array(arr.get(1)) || is_array(arr.get(2))
88 }
89
90 Some("none") => {
91 for f in &arr[1..] {
93 if f.is_boolean() {
94 continue;
95 }
96 if is_expression_filter(f) {
97 return true;
98 }
99 }
100 false
101 }
102
103 Some("any") | Some("all") => {
104 let mut has_legacy = false;
106 for f in &arr[1..] {
107 if f.is_boolean() {
108 continue;
109 }
110 if is_expression_filter(f) {
111 return true;
112 }
113 has_legacy = true;
114 }
115 !has_legacy
116 }
117
118 _ => true,
119 }
120}
121
122pub fn convert_legacy_filter(filter: &Json) -> Result<Json, FilterError> {
160 let mut expected = ExpectedTypes::new();
161 convert(filter, &mut expected)
162}
163
164struct ExpectedTypes {
168 entries: Vec<(String, &'static str)>,
169}
170
171impl ExpectedTypes {
172 fn new() -> ExpectedTypes {
173 ExpectedTypes {
174 entries: Vec::new(),
175 }
176 }
177
178 fn set(&mut self, property: &str, ty: &'static str) {
179 if let Some(slot) = self.entries.iter_mut().find(|(k, _)| k == property) {
180 slot.1 = ty;
181 } else {
182 self.entries.push((property.to_string(), ty));
183 }
184 }
185}
186
187fn convert(filter: &Json, expected: &mut ExpectedTypes) -> Result<Json, FilterError> {
188 if is_expression_filter(filter) {
189 return Ok(filter.clone());
190 }
191 if filter.is_null() {
193 return Ok(json!(true));
194 }
195 let Some(arr) = filter.as_array() else {
196 return Ok(json!(true));
198 };
199
200 let op = arr[0].as_str();
201 if arr.len() <= 1 {
202 return Ok(json!(op != Some("any")));
204 }
205
206 match op {
207 Some(cmp @ ("==" | "!=" | "<" | ">" | "<=" | ">=")) => {
208 convert_comparison_op(&arr[1], &arr[2], cmp, expected)
209 }
210 Some("any") => {
211 let mut children = vec![json!("any")];
212 for f in &arr[1..] {
213 let mut types = ExpectedTypes::new();
214 let child = convert(f, &mut types)?;
215 let checks = runtime_type_checks(&types);
216 if checks == json!(true) {
217 children.push(child);
218 } else {
219 children.push(json!(["case", checks, child, false]));
220 }
221 }
222 Ok(Json::Array(children))
223 }
224 Some("all") => {
225 let mut children = Vec::with_capacity(arr.len() - 1);
226 for f in &arr[1..] {
227 children.push(convert(f, expected)?);
228 }
229 if children.len() > 1 {
230 let mut out = vec![json!("all")];
231 out.extend(children);
232 Ok(Json::Array(out))
233 } else {
234 Ok(children.into_iter().next().unwrap())
236 }
237 }
238 Some("none") => {
239 let mut any = vec![json!("any")];
241 any.extend(arr[1..].iter().cloned());
242 let mut types = ExpectedTypes::new();
243 let inner = convert(&Json::Array(any), &mut types)?;
244 Ok(json!(["!", inner]))
245 }
246 Some("in") => convert_in_op(&arr[1], &arr[2..], false),
247 Some("!in") => convert_in_op(&arr[1], &arr[2..], true),
248 Some("has") => convert_has_op(&arr[1]),
249 Some("!has") => Ok(json!(["!", convert_has_op(&arr[1])?])),
250 _ => Ok(json!(true)),
251 }
252}
253
254fn runtime_type_checks(expected: &ExpectedTypes) -> Json {
255 let mut conditions: Vec<Json> = Vec::new();
256 for (property, ty) in &expected.entries {
257 let get = if property == "$id" {
258 json!(["id"])
259 } else {
260 json!(["get", property])
261 };
262 conditions.push(json!(["==", ["typeof", get], ty]));
263 }
264 match conditions.len() {
265 0 => json!(true),
266 1 => conditions.into_iter().next().unwrap(),
267 _ => {
268 let mut out = vec![json!("all")];
269 out.extend(conditions);
270 Json::Array(out)
271 }
272 }
273}
274
275fn convert_comparison_op(
276 property: &Json,
277 value: &Json,
278 op: &str,
279 expected: &mut ExpectedTypes,
280) -> Result<Json, FilterError> {
281 if property.as_str() == Some("$type") {
283 return Ok(json!([op, ["geometry-type"], value]));
284 }
285
286 let is_id = property.as_str() == Some("$id");
287 let get = if is_id {
288 json!(["id"])
289 } else {
290 json!(["get", property_str(property, op)?])
291 };
292
293 if !value.is_null() {
296 let key = if is_id {
297 "$id"
298 } else {
299 property_str(property, op)?
300 };
301 expected.set(key, js_typeof(value));
302 }
303
304 if op == "==" && !is_id && value.is_null() {
307 let p = property_str(property, op)?;
308 return Ok(json!(["all", ["has", p], ["==", get, Json::Null]]));
309 }
310 if op == "!=" && !is_id && value.is_null() {
311 let p = property_str(property, op)?;
312 return Ok(json!(["any", ["!", ["has", p]], ["!=", get, Json::Null]]));
313 }
314
315 Ok(json!([op, get, value]))
316}
317
318fn convert_in_op(property: &Json, values: &[Json], negate: bool) -> Result<Json, FilterError> {
319 if values.is_empty() {
320 return Ok(json!(negate));
321 }
322
323 let op = if negate { "!in" } else { "in" };
324 let get = match property.as_str() {
325 Some("$type") => json!(["geometry-type"]),
326 Some("$id") => json!(["id"]),
327 _ => json!(["get", property_str(property, op)?]),
328 };
329
330 let type0 = js_typeof(&values[0]);
333 let uniform = values.iter().all(|v| js_typeof(v) == type0);
334 if uniform && (type0 == "string" || type0 == "number") {
335 let unique = sort_and_dedupe(values);
336 return Ok(json!(["match", get, unique, !negate, negate]));
337 }
338
339 let (combiner, cmp) = if negate { ("all", "!=") } else { ("any", "==") };
340 let mut out = vec![json!(combiner)];
341 for v in values {
342 out.push(json!([cmp, get, v]));
343 }
344 Ok(Json::Array(out))
345}
346
347fn convert_has_op(property: &Json) -> Result<Json, FilterError> {
348 match property.as_str() {
349 Some("$type") => Ok(json!(true)),
350 Some("$id") => Ok(json!(["!=", ["id"], Json::Null])),
351 _ => Ok(json!(["has", property_str(property, "has")?])),
352 }
353}
354
355fn property_str<'a>(property: &'a Json, op: &str) -> Result<&'a str, FilterError> {
358 property
359 .as_str()
360 .ok_or_else(|| FilterError::PropertyNotString { op: op.to_string() })
361}
362
363fn js_typeof(v: &Json) -> &'static str {
366 match v {
367 Json::Bool(_) => "boolean",
368 Json::Number(_) => "number",
369 Json::String(_) => "string",
370 _ => "object",
372 }
373}
374
375fn sort_and_dedupe(values: &[Json]) -> Vec<Json> {
378 let mut sorted = values.to_vec();
379 sorted.sort_by_cached_key(js_string);
380 let mut unique: Vec<Json> = Vec::with_capacity(sorted.len());
381 for v in sorted {
382 if unique.last() != Some(&v) {
383 unique.push(v);
384 }
385 }
386 unique
387}
388
389fn js_string(v: &Json) -> String {
391 match v {
392 Json::String(s) => s.clone(),
393 Json::Number(n) => n.to_string(),
394 Json::Bool(b) => b.to_string(),
395 Json::Null => "null".to_string(),
396 other => other.to_string(),
397 }
398}