parse_rust_rest/
query_parse.rs1use parse_rust_core::{classify, ParseError};
12use parse_rust_storage::{Comparison, Constraint};
13use serde_json::Value as Json;
14
15pub fn parse_where(where_json: &Json) -> Result<Vec<Constraint>, ParseError> {
17 let Json::Object(map) = where_json else {
18 return Err(ParseError::invalid_query(
19 "where must be an object".to_string(),
20 ));
21 };
22
23 let mut out = Vec::new();
24 for (field, value) in map {
25 if field.starts_with('$') {
29 return Err(ParseError::invalid_query(format!(
30 "unsupported query operator: {field}"
31 )));
32 }
33
34 match value {
35 Json::Object(inner) if is_operator_document(inner) => {
37 for (op, operand) in inner {
38 let operand = classify(operand.clone())?;
39 out.push(Constraint {
40 field: field.clone(),
41 comparison: Comparison::from_operator(op, operand)?,
42 });
43 }
44 }
45 literal => out.push(Constraint {
46 field: field.clone(),
47 comparison: Comparison::Equal(classify(literal.clone())?),
48 }),
49 }
50 }
51 Ok(out)
52}
53
54fn is_operator_document(map: &serde_json::Map<String, Json>) -> bool {
59 !map.is_empty() && map.keys().all(|k| k.starts_with('$'))
60}
61
62#[cfg(test)]
63mod tests {
64 use super::*;
65 use parse_rust_core::ParseValue;
66
67 fn j(s: &str) -> Json {
68 serde_json::from_str(s).expect("test literal")
69 }
70
71 #[test]
72 fn a_bare_value_is_equality() {
73 let c = parse_where(&j(r#"{"title":"hello"}"#)).expect("parse");
74 assert_eq!(c.len(), 1);
75 assert_eq!(c[0].field, "title");
76 assert!(matches!(
77 &c[0].comparison,
78 Comparison::Equal(ParseValue::String(s)) if s == "hello"
79 ));
80 }
81
82 #[test]
83 fn several_operators_on_one_field_become_several_constraints() {
84 let c = parse_where(&j(r#"{"views":{"$gt":1,"$lt":9}}"#)).expect("parse");
87 assert_eq!(c.len(), 2);
88 assert!(c.iter().all(|x| x.field == "views"));
89 }
90
91 #[test]
92 fn a_tagged_value_is_a_literal_not_an_operator_document() {
93 let c = parse_where(&j(
94 r#"{"author":{"__type":"Pointer","className":"_User","objectId":"u1"}}"#,
95 ))
96 .expect("parse");
97 assert_eq!(c.len(), 1);
98 assert!(matches!(
99 &c[0].comparison,
100 Comparison::Equal(ParseValue::Pointer { object_id, .. }) if object_id == "u1"
101 ));
102 }
103
104 #[test]
105 fn an_unsupported_operator_is_refused() {
106 let e = parse_where(&j(r#"{"title":{"$regex":"^a"}}"#)).unwrap_err();
107 assert_eq!(e.code, parse_rust_core::ErrorCode::InvalidQuery);
108 assert!(e.message.contains("$regex"));
109 }
110
111 #[test]
114 fn a_top_level_operator_is_refused_rather_than_treated_as_a_field() {
115 let e = parse_where(&j(r#"{"$or":[{"a":1},{"a":2}]}"#)).unwrap_err();
116 assert_eq!(e.code, parse_rust_core::ErrorCode::InvalidQuery);
117 assert!(e.message.contains("$or"));
118 }
119
120 #[test]
121 fn in_and_exists_parse() {
122 let c =
123 parse_where(&j(r#"{"tag":{"$in":["a","b"]},"x":{"$exists":true}}"#)).expect("parse");
124 assert_eq!(c.len(), 2);
125 }
126
127 #[test]
128 fn an_empty_object_is_an_empty_query_not_an_operator_document() {
129 assert!(parse_where(&j("{}")).expect("parse").is_empty());
130 let c = parse_where(&j(r#"{"meta":{}}"#)).expect("parse");
132 assert!(matches!(
133 &c[0].comparison,
134 Comparison::Equal(ParseValue::Object(_))
135 ));
136 }
137
138 #[test]
139 fn where_must_be_an_object() {
140 assert!(parse_where(&j("[]")).is_err());
141 assert!(parse_where(&j("3")).is_err());
142 }
143}