parse_rust_storage/
query.rs1use parse_rust_core::{ParseError, ParseValue};
15
16#[derive(Debug, Clone)]
18pub enum Comparison {
19 Equal(ParseValue),
21 NotEqual(ParseValue),
22 GreaterThan(ParseValue),
23 GreaterThanOrEqual(ParseValue),
24 LessThan(ParseValue),
25 LessThanOrEqual(ParseValue),
26 In(Vec<ParseValue>),
27 NotIn(Vec<ParseValue>),
28 Exists(bool),
29}
30
31impl Comparison {
32 pub fn from_operator(op: &str, value: ParseValue) -> Result<Self, ParseError> {
37 Ok(match op {
38 "$ne" => Comparison::NotEqual(value),
39 "$gt" => Comparison::GreaterThan(value),
40 "$gte" => Comparison::GreaterThanOrEqual(value),
41 "$lt" => Comparison::LessThan(value),
42 "$lte" => Comparison::LessThanOrEqual(value),
43 "$in" | "$nin" => {
44 let items = match value {
45 ParseValue::Array(items) => items,
46 _ => {
47 return Err(ParseError::invalid_query(format!(
48 "bad {op} value: expected an array"
49 )))
50 }
51 };
52 if op == "$in" {
53 Comparison::In(items)
54 } else {
55 Comparison::NotIn(items)
56 }
57 }
58 "$exists" => match value {
59 ParseValue::Bool(b) => Comparison::Exists(b),
60 _ => {
61 return Err(ParseError::invalid_query(
62 "bad $exists value: expected a boolean".to_string(),
63 ))
64 }
65 },
66 other => {
67 return Err(ParseError::invalid_query(format!(
68 "unsupported query operator: {other}"
69 )))
70 }
71 })
72 }
73}
74
75#[derive(Debug, Clone)]
77pub struct Constraint {
78 pub field: String,
79 pub comparison: Comparison,
80}
81
82impl Constraint {
83 pub fn equal(field: impl Into<String>, value: ParseValue) -> Self {
84 Self {
85 field: field.into(),
86 comparison: Comparison::Equal(value),
87 }
88 }
89}
90
91#[derive(Debug, Clone, Copy, PartialEq, Eq)]
93pub enum SortDirection {
94 Ascending,
95 Descending,
96}
97
98pub const DEFAULT_LIMIT: u32 = 100;
104
105#[derive(Debug, Clone)]
107pub struct QueryOptions {
108 pub limit: Option<u32>,
109 pub skip: Option<u32>,
110 pub order: Vec<(String, SortDirection)>,
111 pub keys: Option<Vec<String>>,
116}
117
118impl Default for QueryOptions {
119 fn default() -> Self {
120 Self {
121 limit: Some(DEFAULT_LIMIT),
122 skip: None,
123 order: Vec::new(),
124 keys: None,
125 }
126 }
127}
128
129impl QueryOptions {
130 pub fn parse_order(order: &str) -> Vec<(String, SortDirection)> {
132 order
133 .split(',')
134 .map(str::trim)
135 .filter(|s| !s.is_empty())
136 .map(|k| match k.strip_prefix('-') {
137 Some(rest) => (rest.to_string(), SortDirection::Descending),
138 None => (k.to_string(), SortDirection::Ascending),
139 })
140 .collect()
141 }
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147
148 #[test]
149 fn supported_operators_map() {
150 for op in ["$ne", "$gt", "$gte", "$lt", "$lte"] {
151 assert!(
152 Comparison::from_operator(op, ParseValue::Number(1.0)).is_ok(),
153 "{op}"
154 );
155 }
156 assert!(Comparison::from_operator("$in", ParseValue::Array(vec![])).is_ok());
157 assert!(Comparison::from_operator("$nin", ParseValue::Array(vec![])).is_ok());
158 assert!(Comparison::from_operator("$exists", ParseValue::Bool(true)).is_ok());
159 }
160
161 #[test]
163 fn an_unsupported_operator_is_an_error_not_a_no_op() {
164 for op in [
165 "$regex",
166 "$select",
167 "$inQuery",
168 "$all",
169 "$nearSphere",
170 "$text",
171 ] {
172 let e = Comparison::from_operator(op, ParseValue::Null).unwrap_err();
173 assert_eq!(e.code, parse_rust_core::ErrorCode::InvalidQuery, "{op}");
174 assert!(e.message.contains(op), "the message must name the operator");
175 }
176 }
177
178 #[test]
179 fn in_requires_an_array_and_exists_requires_a_boolean() {
180 assert!(Comparison::from_operator("$in", ParseValue::Number(1.0)).is_err());
181 assert!(Comparison::from_operator("$exists", ParseValue::Number(1.0)).is_err());
182 }
183
184 #[test]
185 fn the_default_limit_is_a_hundred_not_unlimited() {
186 assert_eq!(QueryOptions::default().limit, Some(DEFAULT_LIMIT));
187 assert_eq!(DEFAULT_LIMIT, 100);
188 }
189
190 #[test]
191 fn order_parsing_handles_the_minus_prefix() {
192 assert_eq!(
193 QueryOptions::parse_order("name,-createdAt, score"),
194 vec![
195 ("name".to_string(), SortDirection::Ascending),
196 ("createdAt".to_string(), SortDirection::Descending),
197 ("score".to_string(), SortDirection::Ascending),
198 ]
199 );
200 assert!(QueryOptions::parse_order("").is_empty());
201 }
202}