1use super::types::{CompoundFilter, Filter, FilterExpr, Operator, Value};
57use miniserde::json::{Number, Value as JsonValue};
58use std::fmt;
59
60#[derive(Debug, Clone, PartialEq, Eq)]
62#[non_exhaustive]
63pub enum ParseError {
64 InvalidJson,
66 UnknownOperator(String),
68 ExpectedObject,
70 ExpectedArray,
72 ExpectedValue,
74 EmptyFieldName,
76 EmptyFilter,
78 InvalidOperatorValue {
80 op: String,
82 expected: &'static str,
84 },
85 NotRequiresOneCondition,
87}
88
89pub fn parse_filter(json_str: &str) -> Result<FilterExpr, ParseError> {
120 FilterExpr::parse(json_str)
121}
122
123impl fmt::Display for ParseError {
124 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
125 match self {
126 Self::InvalidJson => write!(f, "Invalid JSON syntax or encoding"),
127 Self::UnknownOperator(op) => write!(f, "Unknown operator '{op}'"),
128 Self::ExpectedObject => write!(f, "Expected JSON object"),
129 Self::ExpectedArray => write!(f, "Expected JSON array"),
130 Self::ExpectedValue => write!(f, "Expected a value"),
131 Self::EmptyFieldName => write!(f, "Field name cannot be empty"),
132 Self::EmptyFilter => write!(f, "Filter object cannot be empty"),
133 Self::InvalidOperatorValue { op, expected } => {
134 write!(f, "Operator '{op}' expects {expected}")
135 },
136 Self::NotRequiresOneCondition => {
137 write!(f, "$not requires exactly one condition")
138 },
139 }
140 }
141}
142
143impl std::error::Error for ParseError {}
144
145impl Operator {
146 #[must_use]
160 pub fn from_mongo(s: &str) -> Option<Self> {
161 let s = s.strip_prefix('$').unwrap_or(s);
163
164 match s {
165 "eq" => Some(Self::Eq),
166 "ne" => Some(Self::Ne),
167 "gt" => Some(Self::Gt),
168 "gte" => Some(Self::Gte),
169 "lt" => Some(Self::Lt),
170 "lte" => Some(Self::Lte),
171 "in" => Some(Self::In),
172 "nin" => Some(Self::NotIn),
173 "like" => Some(Self::Like),
174 "ilike" => Some(Self::ILike),
175 "regex" => Some(Self::Regex),
176 "startsWith" | "starts_with" => Some(Self::StartsWith),
177 "endsWith" | "ends_with" => Some(Self::EndsWith),
178 "contains" => Some(Self::Contains),
179 "between" => Some(Self::Between),
180 _ => None,
181 }
182 }
183}
184
185impl Value {
186 #[must_use]
201 pub fn from_json(json: &JsonValue) -> Option<Self> {
202 match json {
203 JsonValue::Null => Some(Self::Null),
204 JsonValue::Bool(b) => Some(Self::Bool(*b)),
205 JsonValue::Number(n) => match n {
206 Number::I64(i) => Some(Self::Int(*i)),
207 Number::U64(u) => i64::try_from(*u).ok().map(Self::Int),
208 Number::F64(f) => Some(Self::Float(*f)),
209 },
210 JsonValue::String(s) => Some(Self::String(s.clone())),
211 JsonValue::Array(arr) => {
212 let values: Option<Vec<Self>> = arr.iter().map(Self::from_json).collect();
213 values.map(Self::Array)
214 },
215 JsonValue::Object(_) => None, }
217 }
218}
219
220impl FilterExpr {
221 pub fn parse(json_str: &str) -> Result<Self, ParseError> {
237 let json: JsonValue =
238 miniserde::json::from_str(json_str).map_err(|_| ParseError::InvalidJson)?;
239 Self::from_json(&json)
240 }
241
242 pub fn parse_bytes(bytes: &[u8]) -> Result<Self, ParseError> {
259 let s = std::str::from_utf8(bytes).map_err(|_| ParseError::InvalidJson)?;
260 Self::parse(s)
261 }
262
263 pub fn from_json(json: &JsonValue) -> Result<Self, ParseError> {
272 let obj = match json {
273 JsonValue::Object(o) => o,
274 _ => return Err(ParseError::ExpectedObject),
275 };
276
277 if obj.is_empty() {
278 return Err(ParseError::EmptyFilter);
279 }
280
281 let mut filters = Vec::new();
282
283 for (key, value) in obj {
284 if key.is_empty() {
285 return Err(ParseError::EmptyFieldName);
286 }
287
288 if key.starts_with('$') {
290 match key.as_str() {
291 "$and" => {
292 let exprs = parse_filter_array(value)?;
293 filters.push(Self::Compound(CompoundFilter::and(exprs)));
294 },
295 "$or" => {
296 let exprs = parse_filter_array(value)?;
297 filters.push(Self::Compound(CompoundFilter::or(exprs)));
298 },
299 "$not" => {
300 let inner = Self::from_json(value)?;
301 filters.push(Self::Compound(CompoundFilter::not(inner)));
302 },
303 _ => return Err(ParseError::UnknownOperator(key.clone())),
304 }
305 } else {
306 let filter = parse_field_filter(key, value)?;
308 filters.push(filter);
309 }
310 }
311
312 Ok(match filters.len() {
314 0 => return Err(ParseError::EmptyFilter),
315 1 => filters.remove(0),
316 _ => Self::Compound(CompoundFilter::and(filters)),
317 })
318 }
319}
320
321fn parse_filter_array(json: &JsonValue) -> Result<Vec<FilterExpr>, ParseError> {
323 let arr = match json {
324 JsonValue::Array(a) => a,
325 _ => return Err(ParseError::ExpectedArray),
326 };
327
328 arr.iter().map(FilterExpr::from_json).collect()
329}
330
331fn parse_field_filter(field: &str, value: &JsonValue) -> Result<FilterExpr, ParseError> {
333 if let JsonValue::Object(obj) = value {
335 if let Some((op_key, op_value)) = obj.iter().next()
336 && op_key.starts_with('$')
337 {
338 let op = Operator::from_mongo(op_key)
339 .ok_or_else(|| ParseError::UnknownOperator(op_key.clone()))?;
340
341 let val = parse_operator_value(op, op_value)?;
342
343 return Ok(FilterExpr::Simple(Filter {
344 field: field.to_string(),
345 op,
346 value: val,
347 }));
348 }
349 return Err(ParseError::ExpectedValue);
351 }
352
353 let val = Value::from_json(value).ok_or(ParseError::ExpectedValue)?;
355 Ok(FilterExpr::Simple(Filter {
356 field: field.to_string(),
357 op: Operator::Eq,
358 value: val,
359 }))
360}
361
362fn parse_operator_value(op: Operator, value: &JsonValue) -> Result<Value, ParseError> {
364 match op {
365 Operator::In | Operator::NotIn => match value {
367 JsonValue::Array(arr) => {
368 let values: Option<Vec<Value>> = arr.iter().map(Value::from_json).collect();
369 values
370 .map(Value::Array)
371 .ok_or_else(|| ParseError::InvalidOperatorValue {
372 op: format!("${op:?}").to_lowercase(),
373 expected: "array of values",
374 })
375 },
376 _ => Err(ParseError::InvalidOperatorValue {
377 op: "$in/$nin".to_string(),
378 expected: "array",
379 }),
380 },
381
382 Operator::Between => match value {
384 JsonValue::Array(arr) if arr.len() == 2 => {
385 let values: Option<Vec<Value>> = arr.iter().map(Value::from_json).collect();
386 values
387 .map(Value::Array)
388 .ok_or_else(|| ParseError::InvalidOperatorValue {
389 op: "$between".to_string(),
390 expected: "array of 2 values",
391 })
392 },
393 JsonValue::Array(_) => Err(ParseError::InvalidOperatorValue {
394 op: "$between".to_string(),
395 expected: "array of exactly 2 values",
396 }),
397 _ => Err(ParseError::InvalidOperatorValue {
398 op: "$between".to_string(),
399 expected: "array of 2 values",
400 }),
401 },
402
403 _ => Value::from_json(value).ok_or(ParseError::ExpectedValue),
405 }
406}
407
408#[cfg(test)]
409mod tests {
410 use super::*;
411 use crate::LogicalOp;
412 use miniserde::json::{self, Array as JsonArray};
413
414 #[test]
419 fn test_operator_from_mongo_with_prefix() {
420 assert_eq!(Operator::from_mongo("$eq"), Some(Operator::Eq));
421 assert_eq!(Operator::from_mongo("$ne"), Some(Operator::Ne));
422 assert_eq!(Operator::from_mongo("$gt"), Some(Operator::Gt));
423 assert_eq!(Operator::from_mongo("$gte"), Some(Operator::Gte));
424 assert_eq!(Operator::from_mongo("$lt"), Some(Operator::Lt));
425 assert_eq!(Operator::from_mongo("$lte"), Some(Operator::Lte));
426 assert_eq!(Operator::from_mongo("$in"), Some(Operator::In));
427 assert_eq!(Operator::from_mongo("$nin"), Some(Operator::NotIn));
428 assert_eq!(Operator::from_mongo("$like"), Some(Operator::Like));
429 assert_eq!(Operator::from_mongo("$ilike"), Some(Operator::ILike));
430 assert_eq!(Operator::from_mongo("$regex"), Some(Operator::Regex));
431 assert_eq!(Operator::from_mongo("$between"), Some(Operator::Between));
432 }
433
434 #[test]
435 fn test_operator_from_mongo_without_prefix() {
436 assert_eq!(Operator::from_mongo("eq"), Some(Operator::Eq));
437 assert_eq!(Operator::from_mongo("gte"), Some(Operator::Gte));
438 }
439
440 #[test]
441 fn test_operator_from_mongo_camel_case() {
442 assert_eq!(
443 Operator::from_mongo("$startsWith"),
444 Some(Operator::StartsWith)
445 );
446 assert_eq!(
447 Operator::from_mongo("$starts_with"),
448 Some(Operator::StartsWith)
449 );
450 assert_eq!(Operator::from_mongo("$endsWith"), Some(Operator::EndsWith));
451 assert_eq!(Operator::from_mongo("$ends_with"), Some(Operator::EndsWith));
452 }
453
454 #[test]
455 fn test_operator_from_mongo_unknown() {
456 assert_eq!(Operator::from_mongo("$unknown"), None);
457 assert_eq!(Operator::from_mongo("$foo"), None);
458 }
459
460 #[test]
465 fn test_value_from_json_primitives() {
466 assert_eq!(Value::from_json(&JsonValue::Null), Some(Value::Null));
467 assert_eq!(
468 Value::from_json(&JsonValue::Bool(true)),
469 Some(Value::Bool(true))
470 );
471 assert_eq!(
472 Value::from_json(&JsonValue::Number(Number::I64(42))),
473 Some(Value::Int(42))
474 );
475 assert_eq!(
476 Value::from_json(&JsonValue::Number(Number::F64(2.5))),
477 Some(Value::Float(2.5))
478 );
479 assert_eq!(
480 Value::from_json(&JsonValue::String("hello".into())),
481 Some(Value::String("hello".into()))
482 );
483 }
484
485 #[test]
486 fn test_value_from_json_array() {
487 let mut arr = JsonArray::new();
488 arr.push(JsonValue::String("a".into()));
489 arr.push(JsonValue::String("b".into()));
490 let json_arr = JsonValue::Array(arr);
491 assert_eq!(
492 Value::from_json(&json_arr),
493 Some(Value::Array(vec![
494 Value::String("a".into()),
495 Value::String("b".into()),
496 ]))
497 );
498 }
499
500 #[test]
505 fn test_simple_equality() {
506 let json: JsonValue = json::from_str(r#"{"name": "Alice"}"#).unwrap();
507 let filter = FilterExpr::from_json(&json).unwrap();
508
509 assert!(matches!(
510 filter,
511 FilterExpr::Simple(Filter {
512 ref field,
513 op: Operator::Eq,
514 value: Value::String(ref s),
515 }) if field == "name" && s == "Alice"
516 ));
517 }
518
519 #[test]
520 fn test_explicit_operator() {
521 let json: JsonValue = json::from_str(r#"{"age": {"$gte": 18}}"#).unwrap();
522 let filter = FilterExpr::from_json(&json).unwrap();
523
524 assert!(matches!(
525 filter,
526 FilterExpr::Simple(Filter {
527 ref field,
528 op: Operator::Gte,
529 value: Value::Int(18),
530 }) if field == "age"
531 ));
532 }
533
534 #[test]
535 fn test_multiple_fields_implicit_and() {
536 let json: JsonValue = json::from_str(r#"{"name": "Alice", "age": 30}"#).unwrap();
537 let filter = FilterExpr::from_json(&json).unwrap();
538
539 assert!(matches!(
540 filter,
541 FilterExpr::Compound(CompoundFilter {
542 op: LogicalOp::And,
543 ..
544 })
545 ));
546 }
547
548 #[test]
549 fn test_explicit_and() {
550 let json: JsonValue =
551 json::from_str(r#"{"$and": [{"name": "Alice"}, {"age": 30}]}"#).unwrap();
552 let filter = FilterExpr::from_json(&json).unwrap();
553
554 assert!(matches!(
555 filter,
556 FilterExpr::Compound(CompoundFilter {
557 op: LogicalOp::And,
558 ..
559 })
560 ));
561 }
562
563 #[test]
564 fn test_explicit_or() {
565 let json: JsonValue =
566 json::from_str(r#"{"$or": [{"status": "active"}, {"status": "pending"}]}"#).unwrap();
567 let filter = FilterExpr::from_json(&json).unwrap();
568
569 assert!(matches!(
570 filter,
571 FilterExpr::Compound(CompoundFilter {
572 op: LogicalOp::Or,
573 ..
574 })
575 ));
576 }
577
578 #[test]
579 fn test_explicit_not() {
580 let json: JsonValue = json::from_str(r#"{"$not": {"deleted": true}}"#).unwrap();
581 let filter = FilterExpr::from_json(&json).unwrap();
582
583 assert!(matches!(
584 filter,
585 FilterExpr::Compound(CompoundFilter {
586 op: LogicalOp::Not,
587 ..
588 })
589 ));
590 }
591
592 #[test]
593 fn test_in_operator() {
594 let json: JsonValue = json::from_str(r#"{"status": {"$in": ["a", "b", "c"]}}"#).unwrap();
595 let filter = FilterExpr::from_json(&json).unwrap();
596
597 assert!(matches!(
598 filter,
599 FilterExpr::Simple(Filter {
600 op: Operator::In,
601 value: Value::Array(_),
602 ..
603 })
604 ));
605 }
606
607 #[test]
608 fn test_between_operator() {
609 let json: JsonValue = json::from_str(r#"{"age": {"$between": [18, 65]}}"#).unwrap();
610 let filter = FilterExpr::from_json(&json).unwrap();
611
612 assert!(matches!(
613 filter,
614 FilterExpr::Simple(Filter {
615 op: Operator::Between,
616 value: Value::Array(ref arr),
617 ..
618 }) if arr.len() == 2
619 ));
620 }
621
622 #[test]
623 fn test_nested_logical() {
624 let json: JsonValue = json::from_str(
625 r#"{"$and": [{"active": true}, {"$or": [{"role": "admin"}, {"role": "mod"}]}]}"#,
626 )
627 .unwrap();
628 let filter = FilterExpr::from_json(&json).unwrap();
629
630 assert!(matches!(
631 filter,
632 FilterExpr::Compound(CompoundFilter {
633 op: LogicalOp::And,
634 ..
635 })
636 ));
637 }
638
639 #[test]
644 fn test_error_not_object() {
645 let json: JsonValue = json::from_str(r"[1, 2, 3]").unwrap();
646 assert!(matches!(
647 FilterExpr::from_json(&json),
648 Err(ParseError::ExpectedObject)
649 ));
650 }
651
652 #[test]
653 fn test_error_empty_filter() {
654 let json: JsonValue = json::from_str(r"{}").unwrap();
655 assert!(matches!(
656 FilterExpr::from_json(&json),
657 Err(ParseError::EmptyFilter)
658 ));
659 }
660
661 #[test]
662 fn test_error_unknown_operator() {
663 let json: JsonValue = json::from_str(r#"{"field": {"$foo": 1}}"#).unwrap();
664 assert!(matches!(
665 FilterExpr::from_json(&json),
666 Err(ParseError::UnknownOperator(_))
667 ));
668 }
669
670 #[test]
671 fn test_error_between_wrong_count() {
672 let json: JsonValue = json::from_str(r#"{"age": {"$between": [18]}}"#).unwrap();
673 assert!(matches!(
674 FilterExpr::from_json(&json),
675 Err(ParseError::InvalidOperatorValue { .. })
676 ));
677 }
678
679 #[test]
680 fn test_error_in_not_array() {
681 let json: JsonValue = json::from_str(r#"{"status": {"$in": "not-array"}}"#).unwrap();
682 assert!(matches!(
683 FilterExpr::from_json(&json),
684 Err(ParseError::InvalidOperatorValue { .. })
685 ));
686 }
687}