use parse_rust_core::{classify, ParseError};
use parse_rust_storage::{Comparison, Constraint};
use serde_json::Value as Json;
pub fn parse_where(where_json: &Json) -> Result<Vec<Constraint>, ParseError> {
let Json::Object(map) = where_json else {
return Err(ParseError::invalid_query(
"where must be an object".to_string(),
));
};
let mut out = Vec::new();
for (field, value) in map {
if field.starts_with('$') {
return Err(ParseError::invalid_query(format!(
"unsupported query operator: {field}"
)));
}
match value {
Json::Object(inner) if is_operator_document(inner) => {
for (op, operand) in inner {
let operand = classify(operand.clone())?;
out.push(Constraint {
field: field.clone(),
comparison: Comparison::from_operator(op, operand)?,
});
}
}
literal => out.push(Constraint {
field: field.clone(),
comparison: Comparison::Equal(classify(literal.clone())?),
}),
}
}
Ok(out)
}
fn is_operator_document(map: &serde_json::Map<String, Json>) -> bool {
!map.is_empty() && map.keys().all(|k| k.starts_with('$'))
}
#[cfg(test)]
mod tests {
use super::*;
use parse_rust_core::ParseValue;
fn j(s: &str) -> Json {
serde_json::from_str(s).expect("test literal")
}
#[test]
fn a_bare_value_is_equality() {
let c = parse_where(&j(r#"{"title":"hello"}"#)).expect("parse");
assert_eq!(c.len(), 1);
assert_eq!(c[0].field, "title");
assert!(matches!(
&c[0].comparison,
Comparison::Equal(ParseValue::String(s)) if s == "hello"
));
}
#[test]
fn several_operators_on_one_field_become_several_constraints() {
let c = parse_where(&j(r#"{"views":{"$gt":1,"$lt":9}}"#)).expect("parse");
assert_eq!(c.len(), 2);
assert!(c.iter().all(|x| x.field == "views"));
}
#[test]
fn a_tagged_value_is_a_literal_not_an_operator_document() {
let c = parse_where(&j(
r#"{"author":{"__type":"Pointer","className":"_User","objectId":"u1"}}"#,
))
.expect("parse");
assert_eq!(c.len(), 1);
assert!(matches!(
&c[0].comparison,
Comparison::Equal(ParseValue::Pointer { object_id, .. }) if object_id == "u1"
));
}
#[test]
fn an_unsupported_operator_is_refused() {
let e = parse_where(&j(r#"{"title":{"$regex":"^a"}}"#)).unwrap_err();
assert_eq!(e.code, parse_rust_core::ErrorCode::InvalidQuery);
assert!(e.message.contains("$regex"));
}
#[test]
fn a_top_level_operator_is_refused_rather_than_treated_as_a_field() {
let e = parse_where(&j(r#"{"$or":[{"a":1},{"a":2}]}"#)).unwrap_err();
assert_eq!(e.code, parse_rust_core::ErrorCode::InvalidQuery);
assert!(e.message.contains("$or"));
}
#[test]
fn in_and_exists_parse() {
let c =
parse_where(&j(r#"{"tag":{"$in":["a","b"]},"x":{"$exists":true}}"#)).expect("parse");
assert_eq!(c.len(), 2);
}
#[test]
fn an_empty_object_is_an_empty_query_not_an_operator_document() {
assert!(parse_where(&j("{}")).expect("parse").is_empty());
let c = parse_where(&j(r#"{"meta":{}}"#)).expect("parse");
assert!(matches!(
&c[0].comparison,
Comparison::Equal(ParseValue::Object(_))
));
}
#[test]
fn where_must_be_an_object() {
assert!(parse_where(&j("[]")).is_err());
assert!(parse_where(&j("3")).is_err());
}
}