1use std::collections::HashMap;
13
14use parse_rust_core::ParseError;
15use parse_rust_rest::{parse_include, parse_where, FindOptions, ParsedWhere};
16use parse_rust_storage::{QueryOptions, DEFAULT_LIMIT};
17use serde_json::Value as Json;
18
19#[derive(Debug, Clone, Default)]
21pub struct Params(HashMap<String, String>);
22
23const FIND_KEYS: [&str; 16] = [
25 "skip",
26 "limit",
27 "order",
28 "count",
29 "keys",
30 "excludeKeys",
31 "include",
32 "includeAll",
33 "redirectClassNameForKey",
34 "where",
35 "readPreference",
36 "includeReadPreference",
37 "subqueryReadPreference",
38 "hint",
39 "explain",
40 "comment",
41];
42
43const GET_KEYS: [&str; 6] = [
45 "keys",
46 "include",
47 "excludeKeys",
48 "readPreference",
49 "includeReadPreference",
50 "subqueryReadPreference",
51];
52
53const UNIMPLEMENTED_KEYS: [&str; 5] = [
60 "includeAll",
61 "redirectClassNameForKey",
62 "hint",
63 "explain",
64 "comment",
65];
66
67impl Params {
68 pub fn from_map(map: HashMap<String, String>) -> Self {
69 Self(map)
70 }
71
72 pub fn from_json(value: Option<&Json>) -> Self {
78 let mut map = HashMap::new();
79 if let Some(Json::Object(object)) = value {
80 for (key, value) in object {
81 let text = match value {
82 Json::String(s) => s.clone(),
83 other => other.to_string(),
84 };
85 map.insert(key.clone(), text);
86 }
87 }
88 Self(map)
89 }
90
91 pub fn get(&self, key: &str) -> Option<&str> {
92 self.0.get(key).map(String::as_str)
93 }
94
95 pub fn reject_unknown_find_keys(&self) -> Result<(), ParseError> {
97 for key in self.0.keys() {
98 if !FIND_KEYS.contains(&key.as_str()) {
99 return Err(ParseError::invalid_query(format!(
100 "Invalid parameter for query: {key}"
101 )));
102 }
103 }
104 self.reject_unimplemented()
105 }
106
107 pub fn reject_unknown_get_keys(&self) -> Result<(), ParseError> {
110 for key in self.0.keys() {
111 if !GET_KEYS.contains(&key.as_str()) {
112 return Err(ParseError::invalid_query("Improper encode of parameter"));
113 }
114 }
115 self.reject_unimplemented()
116 }
117
118 fn reject_unimplemented(&self) -> Result<(), ParseError> {
119 for key in UNIMPLEMENTED_KEYS {
120 if self.0.contains_key(key) {
121 return Err(ParseError::new(
122 parse_rust_core::ErrorCode::CommandUnavailable,
123 format!("The {key} query parameter is not supported yet."),
124 ));
125 }
126 }
127 Ok(())
128 }
129
130 pub fn parse_where(&self) -> Result<ParsedWhere, ParseError> {
135 let Some(raw) = self.get("where") else {
136 return Ok(ParsedWhere::default());
137 };
138 let value: Json = serde_json::from_str(raw)
139 .map_err(|_| ParseError::invalid_json("where parameter is not valid JSON"))?;
140 parse_where(&value)
141 }
142
143 pub fn wants_count(&self) -> bool {
144 !matches!(
146 self.get("count"),
147 None | Some("0") | Some("false") | Some("")
148 )
149 }
150
151 pub fn find_options(&self) -> Result<FindOptions, ParseError> {
153 Ok(FindOptions {
154 limit: Some(
158 self.get("limit")
159 .and_then(|v| v.parse::<u32>().ok())
160 .unwrap_or(DEFAULT_LIMIT),
161 ),
162 skip: self.get("skip").and_then(|v| v.parse().ok()),
163 order: self
164 .get("order")
165 .map(QueryOptions::parse_order)
166 .unwrap_or_default(),
167 keys: self.csv("keys"),
168 exclude_keys: self.csv("excludeKeys"),
169 include: match self.get("include") {
170 Some(raw) => parse_include(raw)?,
171 None => Vec::new(),
172 },
173 })
174 }
175
176 pub fn get_options(&self) -> Result<FindOptions, ParseError> {
178 Ok(FindOptions {
179 limit: Some(1),
180 skip: None,
181 order: Vec::new(),
182 keys: self.csv("keys"),
183 exclude_keys: self.csv("excludeKeys"),
184 include: match self.get("include") {
185 Some(raw) => parse_include(raw)?,
186 None => Vec::new(),
187 },
188 })
189 }
190
191 fn csv(&self, key: &str) -> Option<Vec<String>> {
192 self.get(key).map(|raw| {
193 raw.split(',')
194 .map(str::trim)
195 .filter(|s| !s.is_empty())
196 .map(str::to_string)
197 .collect()
198 })
199 }
200}
201
202#[cfg(test)]
203mod tests {
204 use super::*;
205
206 fn params(pairs: &[(&str, &str)]) -> Params {
207 Params::from_map(
208 pairs
209 .iter()
210 .map(|(k, v)| ((*k).to_string(), (*v).to_string()))
211 .collect(),
212 )
213 }
214
215 #[test]
216 fn an_unknown_find_parameter_is_named_in_the_error() {
217 let e = params(&[("nonsense", "1")])
218 .reject_unknown_find_keys()
219 .unwrap_err();
220 assert_eq!(e.code, parse_rust_core::ErrorCode::InvalidQuery);
221 assert_eq!(e.message, "Invalid parameter for query: nonsense");
222 }
223
224 #[test]
225 fn an_unknown_get_parameter_is_not_named() {
226 let e = params(&[("limit", "1")])
228 .reject_unknown_get_keys()
229 .unwrap_err();
230 assert_eq!(e.message, "Improper encode of parameter");
231 }
232
233 #[test]
235 fn unimplemented_parameters_are_refused_rather_than_ignored() {
236 for key in UNIMPLEMENTED_KEYS {
237 let e = params(&[(key, "1")])
238 .reject_unknown_find_keys()
239 .unwrap_err();
240 assert_eq!(
241 e.code,
242 parse_rust_core::ErrorCode::CommandUnavailable,
243 "{key}"
244 );
245 }
246 assert!(params(&[("readPreference", "SECONDARY")])
248 .reject_unknown_find_keys()
249 .is_ok());
250 }
251
252 #[test]
253 fn a_batch_sub_request_carries_its_parameters_as_json() {
254 let body: Json = serde_json::from_str(r#"{"where":{"a":1},"limit":5}"#).expect("literal");
255 let p = Params::from_json(Some(&body));
256 assert_eq!(p.get("where"), Some(r#"{"a":1}"#));
257 assert_eq!(p.get("limit"), Some("5"));
258 assert_eq!(p.find_options().expect("options").limit, Some(5));
259 }
260
261 #[test]
262 fn a_malformed_where_reports_upstreams_message() {
263 let e = params(&[("where", "{oops")]).parse_where().unwrap_err();
264 assert_eq!(e.code, parse_rust_core::ErrorCode::InvalidJson);
265 assert_eq!(e.message, "where parameter is not valid JSON");
266 }
267
268 #[test]
269 fn count_is_a_truthiness_test() {
270 assert!(params(&[("count", "1")]).wants_count());
271 assert!(params(&[("count", "true")]).wants_count());
272 assert!(!params(&[("count", "0")]).wants_count());
273 assert!(!params(&[]).wants_count());
274 }
275
276 #[test]
277 fn limit_falls_back_to_the_parse_default_and_zero_is_honoured() {
278 assert_eq!(params(&[]).find_options().expect("o").limit, Some(100));
279 assert_eq!(
280 params(&[("limit", "0")]).find_options().expect("o").limit,
281 Some(0)
282 );
283 }
284}