parse_rs/
query.rs

1// src/query.rs
2
3use serde::{de::DeserializeOwned, Deserialize, Serialize};
4use serde_json::{json, Map, Value};
5
6use crate::{client::Parse, error::ParseError, Pointer};
7
8/// Represents a query to be performed against a Parse Server class.
9#[derive(Debug, Clone)]
10pub struct ParseQuery {
11    class_name: String,
12    conditions: Map<String, Value>,
13    limit: Option<isize>, // Parse uses signed int for limit, -1 for no limit (though we might handle Some(0) as no limit client side)
14    skip: Option<usize>,
15    order: Option<String>,
16    include: Option<String>,
17    keys: Option<String>, // For selecting specific fields
18    // count_flag: bool, // To indicate if this is a count query, managed by the count() method call
19    // read_preference: Option<String>, // For advanced MongoDB read preferences, future
20    // include_all: bool, // Future
21    use_master_key: bool, // Whether the query should be executed with the master key
22}
23
24impl ParseQuery {
25    /// Creates a new `ParseQuery` for the specified class name.
26    ///
27    /// # Arguments
28    /// * `class_name` - The name of the Parse class to query.
29    pub fn new(class_name: &str) -> Self {
30        Self {
31            class_name: class_name.to_string(),
32            conditions: Map::new(),
33            limit: None,
34            skip: None,
35            order: None,
36            include: None,
37            keys: None,
38            // count_flag: false,
39            use_master_key: false, // Default to false
40        }
41    }
42
43    /// Returns the class name this query targets.
44    pub fn class_name(&self) -> &str {
45        &self.class_name
46    }
47
48    /// Checks if this query is configured to use the master key.
49    pub fn uses_master_key(&self) -> bool {
50        self.use_master_key
51    }
52
53    /// Sets whether this query should be executed using the master key.
54    pub fn set_master_key(&mut self, use_key: bool) -> &mut Self {
55        self.use_master_key = use_key;
56        self
57    }
58
59    // Helper to add a simple condition like "field": "value"
60    fn add_simple_condition(&mut self, key: &str, value: Value) -> &mut Self {
61        self.conditions.insert(key.to_string(), value);
62        self
63    }
64
65    // Helper to add an operator condition like "field": {"$op": "value"}
66    fn add_operator_condition(&mut self, key: &str, operator: &str, value: Value) -> &mut Self {
67        let mut op_map = Map::new();
68        op_map.insert(operator.to_string(), value);
69        self.conditions
70            .insert(key.to_string(), Value::Object(op_map));
71        self
72    }
73
74    /// Adds a constraint to the query that a field must be equal to a specified value.
75    pub fn equal_to<V: Serialize>(&mut self, key: &str, value: V) -> &mut Self {
76        match serde_json::to_value(value) {
77            Ok(json_val) => self.add_simple_condition(key, json_val),
78            Err(_) => {
79                /* Handle error or log, for now, do nothing or panic */
80                self
81            }
82        }
83    }
84
85    /// Adds a constraint to the query that a field must not be equal to a specified value.
86    pub fn not_equal_to<V: Serialize>(&mut self, key: &str, value: V) -> &mut Self {
87        match serde_json::to_value(value) {
88            Ok(json_val) => self.add_operator_condition(key, "$ne", json_val),
89            Err(_) => self,
90        }
91    }
92
93    /// Adds a constraint to the query that a field must exist.
94    pub fn exists(&mut self, key: &str) -> &mut Self {
95        self.add_operator_condition(key, "$exists", serde_json::Value::Bool(true))
96    }
97
98    /// Adds a constraint to the query that a field must not exist.
99    pub fn does_not_exist(&mut self, key: &str) -> &mut Self {
100        self.add_operator_condition(key, "$exists", serde_json::Value::Bool(false))
101    }
102
103    /// Adds a constraint for finding objects where a field's value is greater than the provided value.
104    pub fn greater_than<V: Serialize>(&mut self, key: &str, value: V) -> &mut Self {
105        match serde_json::to_value(value) {
106            Ok(json_val) => self.add_operator_condition(key, "$gt", json_val),
107            Err(_) => self,
108        }
109    }
110
111    /// Adds a constraint for finding objects where a field's value is greater than or equal to the provided value.
112    pub fn greater_than_or_equal_to<V: Serialize>(&mut self, key: &str, value: V) -> &mut Self {
113        match serde_json::to_value(value) {
114            Ok(json_val) => self.add_operator_condition(key, "$gte", json_val),
115            Err(_) => self,
116        }
117    }
118
119    /// Adds a constraint for finding objects where a field's value is less than the provided value.
120    pub fn less_than<V: Serialize>(&mut self, key: &str, value: V) -> &mut Self {
121        match serde_json::to_value(value) {
122            Ok(json_val) => self.add_operator_condition(key, "$lt", json_val),
123            Err(_) => self,
124        }
125    }
126
127    /// Adds a constraint for finding objects where a field's value is less than or equal to the provided value.
128    pub fn less_than_or_equal_to<V: Serialize>(&mut self, key: &str, value: V) -> &mut Self {
129        match serde_json::to_value(value) {
130            Ok(json_val) => self.add_operator_condition(key, "$lte", json_val),
131            Err(_) => self,
132        }
133    }
134
135    /// Adds a constraint for finding objects where a field's value is contained in the provided list of values.
136    pub fn contained_in<V: Serialize>(&mut self, key: &str, values: Vec<V>) -> &mut Self {
137        match serde_json::to_value(values) {
138            Ok(json_val_array) => self.add_operator_condition(key, "$in", json_val_array),
139            Err(_) => self,
140        }
141    }
142
143    /// Adds a constraint for finding objects where a field's value is not contained in the provided list of values.
144    pub fn not_contained_in<V: Serialize>(&mut self, key: &str, values: Vec<V>) -> &mut Self {
145        match serde_json::to_value(values) {
146            Ok(json_val_array) => self.add_operator_condition(key, "$nin", json_val_array),
147            Err(_) => self,
148        }
149    }
150
151    /// Adds a constraint for finding objects where a field contains all of the provided values (for array fields).
152    pub fn contains_all<V: Serialize>(&mut self, key: &str, values: Vec<V>) -> &mut Self {
153        match serde_json::to_value(values) {
154            Ok(json_val_array) => self.add_operator_condition(key, "$all", json_val_array),
155            Err(_) => self,
156        }
157    }
158
159    /// Adds a constraint for finding objects where a string field starts with a given prefix.
160    pub fn starts_with(&mut self, key: &str, prefix: &str) -> &mut Self {
161        self.add_operator_condition(
162            key,
163            "$regex",
164            Value::String(format!("^{}", regex::escape(prefix))),
165        )
166    }
167
168    /// Adds a constraint for finding objects where a string field ends with a given suffix.
169    pub fn ends_with(&mut self, key: &str, suffix: &str) -> &mut Self {
170        self.add_operator_condition(
171            key,
172            "$regex",
173            Value::String(format!("{}$", regex::escape(suffix))),
174        )
175    }
176
177    /// Adds a constraint for finding objects where a string field contains a given substring.
178    /// This uses a regex `.*substring.*`.
179    pub fn contains(&mut self, key: &str, substring: &str) -> &mut Self {
180        self.add_operator_condition(
181            key,
182            "$regex",
183            Value::String(format!(".*{}.*", regex::escape(substring))),
184        )
185    }
186
187    /// Adds a constraint for finding objects where a string field matches a given regex pattern.
188    /// Modifiers can be 'i' for case-insensitive, 'm' for multiline, etc.
189    pub fn matches_regex(
190        &mut self,
191        key: &str,
192        regex_pattern: &str,
193        modifiers: Option<&str>,
194    ) -> &mut Self {
195        let mut regex_map = Map::new();
196        regex_map.insert(
197            "$regex".to_string(),
198            Value::String(regex_pattern.to_string()),
199        );
200        if let Some(mods) = modifiers {
201            regex_map.insert("$options".to_string(), Value::String(mods.to_string()));
202        }
203        self.conditions
204            .insert(key.to_string(), Value::Object(regex_map));
205        self
206    }
207
208    /// Adds a constraint for full-text search on a field.
209    /// Requires a text index to be configured on the field in MongoDB.
210    ///
211    /// # Arguments
212    /// * `key` - The field to perform the text search on.
213    /// * `term` - The search term.
214    /// * `language` - Optional: The language for the search (e.g., "en", "es").
215    /// * `case_sensitive` - Optional: Whether the search should be case-sensitive.
216    /// * `diacritic_sensitive` - Optional: Whether the search should be diacritic-sensitive.
217    pub fn search(
218        &mut self,
219        key: &str,
220        term: &str,
221        language: Option<&str>,
222        case_sensitive: Option<bool>,
223        diacritic_sensitive: Option<bool>,
224    ) -> &mut Self {
225        let mut search_params_map = Map::new();
226        search_params_map.insert("$term".to_string(), Value::String(term.to_string()));
227
228        if let Some(lang) = language {
229            search_params_map.insert("$language".to_string(), Value::String(lang.to_string()));
230        }
231        if let Some(cs) = case_sensitive {
232            search_params_map.insert("$caseSensitive".to_string(), Value::Bool(cs));
233        }
234        if let Some(ds) = diacritic_sensitive {
235            search_params_map.insert("$diacriticSensitive".to_string(), Value::Bool(ds));
236        }
237
238        let mut search_op = Map::new();
239        search_op.insert("$search".to_string(), Value::Object(search_params_map));
240
241        let mut text_op = Map::new();
242        text_op.insert("$text".to_string(), Value::Object(search_op));
243
244        self.conditions
245            .insert(key.to_string(), Value::Object(text_op));
246        self
247    }
248
249    /// Adds a constraint to the query that objects must be related to a given parent object
250    /// through a specific relation field.
251    ///
252    /// # Arguments
253    /// * `parent_object` - A `Pointer` to the parent object.
254    /// * `key_on_parent_object` - The name of the relation field on the `parent_object`.
255    ///
256    /// Example: Querying for "Comment" objects related to a "Post" object via the "comments" relation field on "Post":
257    /// ```
258    /// // let post_pointer = Pointer::new("Post", "postId123");
259    /// // let mut comment_query = ParseQuery::new("Comment");
260    /// // comment_query.related_to(&post_pointer, "comments");
261    /// ```
262    /// This will find all "Comment" objects that are part of the "comments" relation of the specified "Post".
263    pub fn related_to(&mut self, parent_object: &Pointer, key_on_parent_object: &str) -> &mut Self {
264        let mut related_to_map = Map::new();
265        match serde_json::to_value(parent_object) {
266            Ok(parent_ptr_json) => {
267                related_to_map.insert("object".to_string(), parent_ptr_json);
268                related_to_map.insert(
269                    "key".to_string(),
270                    Value::String(key_on_parent_object.to_string()),
271                );
272                self.conditions
273                    .insert("$relatedTo".to_string(), Value::Object(related_to_map));
274            }
275            Err(_) => {
276                // Handle or log serialization error for parent_object
277                // For now, effectively a no-op if serialization fails, which is not ideal.
278                // Consider returning Result<&mut Self, Error> or panicking for critical errors.
279            }
280        }
281        self
282    }
283
284    // --- Pagination and Sorting ---
285
286    /// Sets the maximum number of results to return.
287    pub fn limit(&mut self, count: isize) -> &mut Self {
288        self.limit = Some(count);
289        self
290    }
291
292    /// Sets the number of results to skip before returning.
293    pub fn skip(&mut self, count: usize) -> &mut Self {
294        self.skip = Some(count);
295        self
296    }
297
298    /// Sets the order of the results. Replaces any existing order.
299    /// Takes a comma-separated string of field names. Prefix with '-' for descending order.
300    /// e.g., "score,-playerName"
301    pub fn order(&mut self, field_names: &str) -> &mut Self {
302        self.order = Some(field_names.to_string());
303        self
304    }
305
306    // Helper to append to the order string
307    fn append_order_key(&mut self, key: &str, descending: bool) {
308        let prefix = if descending { "-" } else { "" };
309        let new_order_key = format!("{}{}", prefix, key);
310        if let Some(existing_order) = &mut self.order {
311            if !existing_order.is_empty() {
312                existing_order.push(',');
313            }
314            existing_order.push_str(&new_order_key);
315        } else {
316            self.order = Some(new_order_key);
317        }
318    }
319
320    /// Sorts the results by a given key in ascending order. Replaces existing sort order.
321    pub fn order_by_ascending(&mut self, key: &str) -> &mut Self {
322        self.order = Some(key.to_string());
323        self
324    }
325
326    /// Sorts the results by a given key in descending order. Replaces existing sort order.
327    pub fn order_by_descending(&mut self, key: &str) -> &mut Self {
328        self.order = Some(format!("-{}", key));
329        self
330    }
331
332    /// Adds a key to sort the results by in ascending order. Appends to existing sort order.
333    pub fn add_ascending_order(&mut self, key: &str) -> &mut Self {
334        self.append_order_key(key, false);
335        self
336    }
337
338    /// Adds a key to sort the results by in descending order. Appends to existing sort order.
339    pub fn add_descending_order(&mut self, key: &str) -> &mut Self {
340        self.append_order_key(key, true);
341        self
342    }
343
344    /// Includes nested ParseObjects for the given pointer key(s).
345    /// The included field's data will be fetched and returned with the main object.
346    pub fn include(&mut self, keys_to_include: &[&str]) -> &mut Self {
347        let current_include = self.include.take().unwrap_or_default();
348        let mut include_parts: Vec<&str> = current_include
349            .split(',')
350            .filter(|s| !s.is_empty())
351            .collect();
352        include_parts.extend(keys_to_include.iter().cloned());
353        include_parts.sort_unstable(); // Optional: keep it sorted for consistency
354        include_parts.dedup();
355        self.include = Some(include_parts.join(","));
356        self
357    }
358
359    /// Restricts the fields returned for all matching objects.
360    pub fn select(&mut self, keys_to_select: &[&str]) -> &mut Self {
361        let current_keys = self.keys.take().unwrap_or_default();
362        let mut select_parts: Vec<&str> =
363            current_keys.split(',').filter(|s| !s.is_empty()).collect();
364        select_parts.extend(keys_to_select.iter().cloned());
365        select_parts.sort_unstable(); // Optional: keep it sorted
366        select_parts.dedup();
367        self.keys = Some(select_parts.join(","));
368        self
369    }
370
371    // --- Execution Methods ---
372
373    // Internal helper to build query parameters for reqwest
374    pub fn build_query_params(&self) -> Vec<(String, String)> {
375        let mut params = Vec::new();
376        if !self.conditions.is_empty() {
377            if let Ok(where_json) = serde_json::to_string(&self.conditions) {
378                params.push(("where".to_string(), where_json));
379            }
380        }
381        if let Some(limit_val) = self.limit {
382            params.push(("limit".to_string(), limit_val.to_string()));
383        }
384        if let Some(skip_val) = self.skip {
385            params.push(("skip".to_string(), skip_val.to_string()));
386        }
387        if let Some(order_val) = &self.order {
388            params.push(("order".to_string(), order_val.clone()));
389        }
390        if let Some(include_val) = &self.include {
391            params.push(("include".to_string(), include_val.clone()));
392        }
393        if let Some(keys_val) = &self.keys {
394            params.push(("keys".to_string(), keys_val.clone()));
395        }
396        params
397    }
398
399    async fn find_raw<T: DeserializeOwned + Send + Sync + 'static>(
400        &self,
401        client: &Parse,
402    ) -> Result<FindResponse<T>, ParseError> {
403        let endpoint = format!("classes/{}", self.class_name);
404        let params = self.build_query_params();
405        let response_wrapper: FindResponse<T> = client
406            ._get_with_url_params(&endpoint, &params, self.use_master_key, None)
407            .await?;
408        Ok(response_wrapper)
409    }
410
411    async fn first_raw<T: DeserializeOwned + Send + Sync + 'static>(
412        &self,
413        client: &Parse,
414    ) -> Result<Option<T>, ParseError> {
415        let mut query_clone = self.clone();
416        query_clone.limit(1);
417        let endpoint = format!("classes/{}", query_clone.class_name);
418        let params = query_clone.build_query_params();
419        let response_wrapper: FindResponse<T> = client
420            ._get_with_url_params(&endpoint, &params, self.use_master_key, None)
421            .await?;
422        Ok(response_wrapper.results.into_iter().next())
423    }
424
425    /// Retrieves a list of `ParseObject`s that match this query.
426    pub async fn find<T: DeserializeOwned + Send + Sync + 'static>(
427        &self,
428        client: &Parse,
429    ) -> Result<Vec<T>, ParseError> {
430        let response_wrapper = self.find_raw(client).await?;
431        Ok(response_wrapper.results)
432    }
433
434    /// Retrieves the first `ParseObject` that matches this query.
435    pub async fn first<T: DeserializeOwned + Send + Sync + 'static>(
436        &self,
437        client: &Parse,
438    ) -> Result<Option<T>, ParseError> {
439        self.first_raw(client).await
440    }
441
442    /// Retrieves a specific `ParseObject` by its ID from the class associated with this query.
443    /// Note: This method ignores other query constraints like `equalTo`, `limit`, etc., and directly fetches by ID.
444    pub async fn get<T: DeserializeOwned + Send + Sync + 'static>(
445        &self,
446        object_id: &str,
447        client: &Parse,
448    ) -> Result<T, ParseError> {
449        let endpoint = format!("classes/{}/{}", self.class_name, object_id);
450        let params = self.build_query_params();
451        client
452            ._get_with_url_params(&endpoint, &params, self.use_master_key, None)
453            .await
454    }
455
456    /// Counts the number of objects that match this query.
457    pub async fn count(&self, client: &Parse) -> Result<u64, ParseError> {
458        let mut query_clone = self.clone();
459        query_clone.limit(0); // Limit 0 is for count
460
461        let endpoint = format!("classes/{}", query_clone.class_name);
462        let mut params = query_clone.build_query_params();
463        params.push(("count".to_string(), "1".to_string()));
464
465        let response_wrapper: CountResponse = client
466            ._get_with_url_params(&endpoint, &params, self.use_master_key, None)
467            .await?;
468        Ok(response_wrapper.count)
469    }
470
471    /// Executes a distinct query for a specific field.
472    /// Returns a vector of unique values for the given field that match the query conditions.
473    pub async fn distinct<T: DeserializeOwned + Send + Sync + 'static>(
474        &self,
475        client: &Parse,
476        field: &str,
477    ) -> Result<Vec<T>, ParseError> {
478        let endpoint = format!("aggregate/{}", self.class_name);
479
480        let mut pipeline: Vec<Value> = Vec::new();
481
482        // Add $match stage if there are 'where' conditions
483        if !self.conditions.is_empty() {
484            pipeline.push(json!({
485                "$match": self.conditions
486            }));
487        }
488
489        // Add $group stage for distinct operation
490        pipeline.push(json!({
491            "$group": { "_id": format!("${}", field) } // Use "_id" as the output field name
492        }));
493
494        // Serialize the pipeline
495        let pipeline_json = serde_json::to_string(&pipeline).map_err(|e| {
496            ParseError::SerializationError(format!(
497                "Failed to serialize pipeline for distinct query: {}",
498                e
499            ))
500        })?;
501
502        let params = vec![("pipeline".to_string(), pipeline_json)];
503
504        // The server returns { "results": [ { "objectId": value1 }, { "objectId": value2 }, ... ] }
505        // despite the $group stage specifying { "_id": ... }. This is a Parse Server behavior.
506        #[derive(serde::Deserialize, Debug)]
507        struct DistinctItem<V> {
508            #[serde(rename = "objectId")] // Parse Server returns the grouped key as "objectId"
509            value: V,
510        }
511
512        let response_wrapper: FindResponse<DistinctItem<T>> = client
513            ._get_with_url_params(&endpoint, &params, true, None) // Always use master key for aggregate
514            .await?;
515
516        // Extract the actual values from the DistinctItem wrappers
517        let distinct_values = response_wrapper
518            .results
519            .into_iter()
520            .map(|item| item.value)
521            .collect();
522
523        Ok(distinct_values)
524    }
525
526    /// Executes an aggregation query.
527    ///
528    /// The pipeline is a series of data aggregation steps. Refer to MongoDB aggregation pipeline documentation.
529    /// Each stage in the pipeline should be a `serde_json::Value` object.
530    /// This operation typically requires the master key.
531    ///
532    /// # Arguments
533    /// * `pipeline` - A vector of `serde_json::Value` representing the aggregation stages.
534    /// * `client` - The `Parse` to use for the request.
535    ///
536    /// # Returns
537    /// A `Result` containing a `Vec<T>` of the deserialized results, or a `ParseError`.
538    pub async fn aggregate<T: DeserializeOwned + Send + Sync + 'static>(
539        &self,
540        pipeline: Vec<Value>,
541        client: &crate::client::Parse,
542    ) -> Result<Vec<T>, crate::error::ParseError> {
543        client
544            .execute_aggregate(&self.class_name, serde_json::Value::Array(pipeline))
545            .await
546    }
547}
548
549#[derive(Debug, Deserialize)]
550struct FindResponse<T> {
551    results: Vec<T>,
552}
553
554#[derive(Debug, Deserialize)]
555struct CountResponse {
556    count: u64,
557}
558
559#[cfg(test)]
560mod tests {
561    // ... existing tests ...
562}