parse_rs/
client.rs

1// src/client.rs
2
3use crate::error::ParseError;
4use crate::object::ParseObject;
5use crate::schema::{GetAllSchemasResponse, ParseSchema};
6use crate::user::ParseUserHandle;
7use crate::FileField;
8use crate::ParseCloud;
9use crate::ParseQuery;
10
11use reqwest::header::{HeaderMap, HeaderValue, CONTENT_TYPE};
12use reqwest::{Client, Method, Url};
13use serde::de::DeserializeOwned;
14use serde::Serialize;
15use serde_json::Value;
16
17/// Specifies the type of authentication credentials to be used for an API request.
18///
19/// This enum helps determine which keys or tokens are prioritized when constructing
20/// the headers for a request to the Parse Server.
21pub enum AuthType {
22    /// Use the current session token. This is typically obtained after a user logs in.
23    /// If no session token is available, the request might fail or use other credentials
24    /// based on client configuration.
25    SessionToken,
26    /// Use the Master Key. This key bypasses all ACLs and Class-Level Permissions.
27    /// It should be used sparingly and kept secure.
28    MasterKey,
29    /// Use the REST API Key. If the REST API Key is not configured on the client,
30    /// it may fall back to using the JavaScript Key if that is configured.
31    /// This key is typically used for general API access from trusted server environments.
32    RestApiKey,
33    /// No specific authentication to be actively chosen for this request.
34    /// The request will rely on the default headers configured in the `Parse`
35    /// (e.g., Application ID, and potentially a pre-configured JavaScript Key or REST API Key if no Master Key was set globally).
36    /// This is suitable for operations that don't require user context or elevated privileges,
37    /// such as public data queries or user signup/login endpoints themselves.
38    NoAuth,
39}
40
41/// The main client for interacting with a Parse Server instance.
42///
43/// `Parse` handles the configuration of server connection details (URL, Application ID, API keys)
44/// and provides methods for making authenticated or unauthenticated requests to various Parse Server endpoints.
45/// It manages session tokens for authenticated users and uses an underlying `reqwest::Client` for HTTP communication.
46///
47/// Most operations are performed by calling methods directly on `Parse` or by obtaining specialized
48/// handles (like `ParseUserHandle`, `ParseSessionHandle`, `ParseCloud`) through methods on this client.
49///
50/// # Initialization
51///
52/// A `Parse` is typically created using the [`Parse::new()`] method, providing the server URL,
53/// Application ID, and any relevant API keys (JavaScript, REST, Master).
54///
55/// ```rust,no_run
56/// use parse_rs::Parse;
57/// # use parse_rs::ParseError;
58///
59/// # #[tokio::main]
60/// # async fn main() -> Result<(), ParseError> {
61/// let server_url = "http://localhost:1338/parse";
62/// let app_id = "myAppId";
63/// let master_key = "myMasterKey";
64///
65/// // Create a client instance with Master Key
66/// let mut client = Parse::new(
67///     server_url,
68///     app_id,
69///     None, // javascript_key
70///     None, // rest_api_key
71///     Some(master_key), // master_key
72/// )?;
73///
74/// // Client is now ready to be used
75/// # Ok(())
76/// # }
77/// ```
78#[derive(Debug, Clone)]
79pub struct Parse {
80    pub server_url: String, // Changed from Url to String
81    pub(crate) app_id: String,
82    #[allow(dead_code)] // Not used by current auth features
83    pub(crate) javascript_key: Option<String>,
84    pub(crate) rest_api_key: Option<String>,
85    pub(crate) master_key: Option<String>,
86    pub(crate) http_client: Client, // Updated to use alias
87    pub(crate) session_token: Option<String>,
88}
89
90impl Parse {
91    /// Creates a new `Parse` instance.
92    ///
93    /// This constructor initializes the client with the necessary credentials and configuration
94    /// to communicate with your Parse Server.
95    ///
96    /// # Arguments
97    ///
98    /// * `server_url`: The base URL of your Parse Server (e.g., `"http://localhost:1338/parse"`).
99    ///   The client will attempt to normalize this URL (e.g., ensure scheme, remove trailing `/parse` if present
100    ///   to derive the true server base for constructing endpoint paths).
101    /// * `app_id`: Your Parse Application ID. This is a required header for all requests.
102    /// * `javascript_key`: Optional. Your Parse JavaScript Key. If provided and `master_key` is not,
103    ///   this key will be included in requests by default, unless overridden by a session token or explicit master key usage.
104    /// * `rest_api_key`: Optional. Your Parse REST API Key. If provided and both `master_key` and `javascript_key` are not,
105    ///   this key will be included in requests by default. It's generally preferred over the JavaScript Key for server-to-server communication.
106    /// * `master_key`: Optional. Your Parse Master Key. If provided, this key will be included in requests by default,
107    ///   granting unrestricted access. Use with caution. It supersedes other keys for default authentication if present.
108    ///
109    /// # Returns
110    ///
111    /// A `Result` containing the new `Parse` instance if successful, or a `ParseError` if
112    /// configuration is invalid (e.g., invalid URL, invalid header values).
113    ///
114    /// # Key Precedence for Default Headers
115    /// When the client makes requests, the authentication key used in the default headers (when no session token is active
116    /// and `use_master_key` is not explicitly set for an operation) follows this precedence:
117    /// 1. Master Key (if provided at initialization)
118    /// 2. JavaScript Key (if provided and Master Key is not)
119    /// 3. REST API Key (if provided and neither Master Key nor JavaScript Key are)
120    ///
121    /// A session token, once set (e.g., after login), will typically take precedence over these default keys for most operations.
122    ///
123    /// # Example
124    ///
125    /// ```rust,no_run
126    /// use parse_rs::Parse;
127    /// # use parse_rs::ParseError;
128    ///
129    /// # #[tokio::main]
130    /// # async fn main() -> Result<(), ParseError> {
131    /// let server_url = "http://localhost:1338/parse";
132    /// let app_id = "myAppId";
133    /// let js_key = "myJavascriptKey";
134    ///
135    /// // Initialize with JavaScript Key
136    /// let mut client_with_js_key = Parse::new(
137    ///     server_url,
138    ///     app_id,
139    ///     Some(js_key),
140    ///     None, // rest_api_key
141    ///     None, // master_key
142    /// )?;
143    ///
144    /// // Initialize with Master Key (will take precedence for default auth)
145    /// let master_key = "myMasterKey";
146    /// let mut client_with_master_key = Parse::new(
147    ///     server_url,
148    ///     app_id,
149    ///     Some(js_key), // JS key is also provided
150    ///     None,         // REST API key
151    ///     Some(master_key), // Master key will be used by default
152    /// )?;
153    /// # Ok(())
154    /// # }
155    /// ```
156    pub fn new(
157        server_url: &str,
158        app_id: &str,
159        javascript_key: Option<&str>,
160        rest_api_key: Option<&str>,
161        master_key: Option<&str>,
162    ) -> Result<Self, ParseError> {
163        let mut temp_url_string = server_url.to_string();
164
165        // Ensure scheme is present
166        if !temp_url_string.starts_with("http://") && !temp_url_string.starts_with("https://") {
167            temp_url_string = format!("http://{}", temp_url_string);
168        }
169
170        let parsed_server_url = Url::parse(&temp_url_string)?;
171
172        if parsed_server_url.cannot_be_a_base() {
173            return Err(ParseError::SdkError(format!(
174                "The server_url '{}' (after ensuring scheme) resolved to '{}', which cannot be a base URL. Please provide a full base URL (e.g., http://localhost:1337/parse).",
175                server_url, parsed_server_url
176            )));
177        }
178
179        let mut default_headers = HeaderMap::new();
180        default_headers.insert(
181            "X-Parse-Application-Id",
182            HeaderValue::from_str(app_id).map_err(ParseError::InvalidHeaderValue)?,
183        );
184
185        if let Some(mk_str) = master_key {
186            default_headers.insert(
187                "X-Parse-Master-Key",
188                HeaderValue::from_str(mk_str).map_err(ParseError::InvalidHeaderValue)?,
189            );
190        } else if let Some(js_key_str) = javascript_key {
191            default_headers.insert(
192                "X-Parse-Javascript-Key",
193                HeaderValue::from_str(js_key_str).map_err(ParseError::InvalidHeaderValue)?,
194            );
195        } else if let Some(rk_str) = rest_api_key {
196            default_headers.insert(
197                "X-Parse-REST-API-Key",
198                HeaderValue::from_str(rk_str).map_err(ParseError::InvalidHeaderValue)?,
199            );
200        }
201
202        let http_client = Client::builder() // Updated to use alias
203            .default_headers(default_headers)
204            .build()
205            .map_err(ParseError::ReqwestError)?;
206
207        let mut final_server_url = parsed_server_url.as_str().trim_end_matches('/').to_string();
208
209        // If the URL ends with /parse, strip it to get the true base server URL.
210        // This makes the client resilient to PARSE_SERVER_URL being http://host/parse or http://host.
211        if final_server_url.ends_with("/parse") {
212            final_server_url.truncate(final_server_url.len() - "/parse".len());
213        }
214        // Ensure it's not empty after stripping (e.g. if PARSE_SERVER_URL was just "/parse")
215        if final_server_url.is_empty() && parsed_server_url.scheme() == "http"
216            || parsed_server_url.scheme() == "https"
217        {
218            // This case is unlikely if original URL was valid, but as a safeguard.
219            // Reconstruct from scheme and host if available, or error.
220            if let Some(host_str) = parsed_server_url.host_str() {
221                final_server_url = format!("{}://{}", parsed_server_url.scheme(), host_str);
222                if let Some(port) = parsed_server_url.port() {
223                    final_server_url.push_str(&format!(":{}", port));
224                }
225            } else {
226                return Err(ParseError::SdkError("Server URL became empty after stripping /parse and could not be reconstructed.".to_string()));
227            }
228        }
229
230        log::debug!(
231            "Parse initialized with base server_url: {}",
232            final_server_url
233        );
234
235        Ok(Self {
236            server_url: final_server_url,
237            app_id: app_id.to_string(),
238            javascript_key: javascript_key.map(|s| s.to_string()),
239            rest_api_key: rest_api_key.map(|s| s.to_string()),
240            master_key: master_key.map(|s| s.to_string()),
241            http_client,
242            session_token: None,
243        })
244    }
245
246    // Internal method to set or clear the session token.
247    pub(crate) fn _set_session_token(&mut self, token: Option<String>) {
248        self.session_token = token;
249    }
250
251    /// Returns the current session token, if one is set on the client.
252    ///
253    /// A session token is typically obtained after a user successfully logs in
254    /// and is used to authenticate subsequent requests for that user.
255    ///
256    /// # Examples
257    ///
258    /// ```rust,no_run
259    /// # use parse_rs::{Parse, ParseError};
260    /// # #[tokio::main]
261    /// # async fn main() -> Result<(), ParseError> {
262    /// # let mut client = Parse::new("http://localhost:1338/parse", "myAppId", None, None, Some("myMasterKey"))?;
263    /// // After a user logs in, the client might have a session token.
264    /// if let Some(token) = client.session_token() {
265    ///     println!("Current session token: {}", token);
266    /// } else {
267    ///     println!("No active session token.");
268    /// }
269    /// # Ok(())
270    /// # }
271    /// ```
272    pub fn session_token(&self) -> Option<&str> {
273        self.session_token.as_deref()
274    }
275
276    // Installation related methods
277
278    /// Creates a query for Installation objects.
279    ///
280    /// # Returns
281    /// A `ParseQuery` instance, configured for the "_Installation" class.
282    pub fn query_installations(&self) -> ParseQuery {
283        ParseQuery::new("_Installation")
284    }
285
286    /// Checks if the client currently has an active session token.
287    ///
288    /// This is a convenience method equivalent to `client.session_token().is_some()`.
289    ///
290    /// # Examples
291    ///
292    /// ```rust,no_run
293    /// # use parse_rs::{Parse, ParseError};
294    /// # #[tokio::main]
295    /// # async fn main() -> Result<(), ParseError> {
296    /// # let mut client = Parse::new("http://localhost:1338/parse", "myAppId", None, None, Some("myMasterKey"))?;
297    /// if client.is_authenticated() {
298    ///     println!("Client has an active session.");
299    /// } else {
300    ///     println!("Client does not have an active session.");
301    /// }
302    /// # Ok(())
303    /// # }
304    /// ```
305    pub fn is_authenticated(&self) -> bool {
306        self.session_token.is_some()
307    }
308
309    /// Uploads a file to the Parse Server.
310    ///
311    /// This method sends the raw byte data of a file to the Parse Server, which then stores it
312    /// and returns a `FileField` containing the URL and name of the stored file. This `FileField`
313    /// can then be associated with a `ParseObject`.
314    ///
315    /// Note: File uploads require the Master Key to be configured on the `Parse` or for the
316    /// `use_master_key` parameter in the underlying `_request_file_upload` to be true (which is the default for this public method).
317    ///
318    /// # Arguments
319    ///
320    /// * `file_name`: A string slice representing the desired name for the file on the server (e.g., `"photo.jpg"`).
321    /// * `data`: A `Vec<u8>` containing the raw byte data of the file.
322    /// * `mime_type`: A string slice representing the MIME type of the file (e.g., `"image/jpeg"`, `"application/pdf"`).
323    ///
324    /// # Returns
325    ///
326    /// A `Result` containing a `FileField` on success, which includes the `name` and `url` of the uploaded file.
327    /// Returns a `ParseError` if the upload fails due to network issues, server errors, incorrect permissions,
328    /// or misconfiguration.
329    ///
330    /// # Examples
331    ///
332    /// ```rust,no_run
333    /// use parse_rs::{Parse, ParseError, FileField, ParseObject, object::CreateObjectResponse};
334    /// use serde_json::Value;
335    /// use std::collections::HashMap;
336    ///
337    /// # #[tokio::main]
338    /// # async fn main() -> Result<(), ParseError> {
339    /// # let server_url = std::env::var("PARSE_SERVER_URL").unwrap_or_else(|_| "http://localhost:1338/parse".to_string());
340    /// # let app_id = std::env::var("PARSE_APP_ID").unwrap_or_else(|_| "myAppId".to_string());
341    /// # let master_key = std::env::var("PARSE_MASTER_KEY").unwrap_or_else(|_| "myMasterKey".to_string());
342    /// # let mut client = Parse::new(&server_url, &app_id, None, None, Some(&master_key))?;
343    ///
344    /// let file_name = "profile.png";
345    /// let file_data: Vec<u8> = vec![0, 1, 2, 3, 4, 5]; // Example byte data
346    /// let mime_type = "image/png";
347    ///
348    /// // Upload the file
349    /// let file_field: FileField = client.upload_file(file_name, file_data, mime_type).await?;
350    ///
351    /// println!("File uploaded successfully: Name - {}, URL - {}", file_field.name, file_field.url);
352    ///
353    /// // Now, you can associate this FileField with a ParseObject
354    /// let mut player_profile_data = HashMap::new();
355    /// player_profile_data.insert("playerName".to_string(), Value::String("John Doe".to_string()));
356    /// player_profile_data.insert("profilePicture".to_string(), serde_json::to_value(file_field)?);
357    ///
358    /// let mut player_profile = ParseObject::new("PlayerProfile");
359    /// let created_profile: CreateObjectResponse = client.create_object("PlayerProfile", &player_profile).await?;
360    ///
361    /// println!("Created PlayerProfile with ID: {}", created_profile.object_id);
362    /// # Ok(())
363    /// # }
364    /// ```
365    pub async fn upload_file(
366        &self,
367        file_name: &str,
368        data: Vec<u8>,
369        mime_type: &str,
370    ) -> Result<FileField, ParseError> {
371        let file_path_segment = format!("files/{}", file_name); // Path relative to /parse endpoint
372        let server_url_str = self.server_url.as_str();
373
374        let mut full_url_str: String;
375        if server_url_str.ends_with("/parse") || server_url_str.ends_with("/parse/") {
376            // server_url already contains /parse, e.g., http://domain/parse
377            full_url_str = server_url_str.trim_end_matches('/').to_string();
378            full_url_str = format!(
379                "{}/{}",
380                full_url_str,
381                file_path_segment.trim_start_matches('/')
382            );
383        } else {
384            // server_url is base, e.g., http://domain, needs /parse segment added
385            full_url_str = format!(
386                "{}/parse/{}",
387                server_url_str.trim_end_matches('/'),
388                file_path_segment.trim_start_matches('/')
389            );
390        }
391
392        let final_url = Url::parse(&full_url_str)?;
393
394        let mut request_builder = self.http_client.post(final_url.clone());
395
396        // Set headers for file upload
397        let mut headers = HeaderMap::new();
398        headers.insert(
399            "X-Parse-Application-Id",
400            HeaderValue::from_str(&self.app_id).map_err(ParseError::InvalidHeaderValue)?,
401        );
402        // Master key is typically required for creating files directly, unless CLPs are very open.
403        // If session token is present, it might be used depending on server config / CLPs.
404        // For simplicity here, let's assume master key if no session token, or make it explicit if needed.
405        if let Some(token) = &self.session_token {
406            headers.insert(
407                "X-Parse-Session-Token",
408                HeaderValue::from_str(token).map_err(ParseError::InvalidHeaderValue)?,
409            );
410        } else if let Some(mk) = &self.master_key {
411            headers.insert(
412                "X-Parse-Master-Key",
413                HeaderValue::from_str(mk).map_err(ParseError::InvalidHeaderValue)?,
414            );
415        } else {
416            // Or return an error if neither is present and master key is strictly required
417            // For now, proceed, relying on server's default behavior / public CLPs if any.
418            log::warn!(
419                "Uploading file without explicit master key or session token. Relies on CLPs."
420            );
421        }
422        headers.insert(
423            CONTENT_TYPE,
424            HeaderValue::from_str(mime_type).map_err(ParseError::InvalidHeaderValue)?,
425        );
426
427        request_builder = request_builder.headers(headers);
428
429        let data_len = data.len(); // Capture length before move
430        request_builder = request_builder.body(data); // data is moved here
431
432        // Log details before sending (similar to _request)
433        log::debug!("--- Parse: Uploading File ---");
434        log::debug!("URL: {}", final_url.as_str());
435        log::debug!("Method: POST");
436        // Headers are already part of request_builder, logging them directly from it is complex.
437        // For now, we'll skip detailed header logging here, assuming _request's logging is the primary source.
438        log::debug!("Content-Type: {}", mime_type);
439        log::debug!("Body: <binary data of size {}>", data_len); // Use captured length
440        log::debug!("-----------------------------------");
441
442        let response = request_builder
443            .send()
444            .await
445            .map_err(ParseError::ReqwestError)?;
446
447        let upload_response: FileUploadResponse = self
448            ._send_and_process_response(response, &file_path_segment)
449            .await?; // Pass response and endpoint context
450
451        Ok(FileField {
452            _type: "File".to_string(),
453            name: upload_response.name,
454            url: upload_response.url,
455        })
456    }
457
458    // Aggregate queries
459    /// Executes an aggregation pipeline against a specified class and returns the results.
460    ///
461    /// Aggregation queries allow for complex data processing and computation directly on the server.
462    /// The pipeline is defined as a `serde_json::Value`, typically an array of stages (e.g., `$match`, `$group`, `$sort`).
463    /// This method requires the Master Key to be configured on the `Parse` and is used for the request.
464    ///
465    /// Refer to the [Parse Server aggregation documentation](https://docs.parseplatform.org/rest/guide/#aggregate) and
466    /// [MongoDB aggregation pipeline documentation](https://www.mongodb.com/docs/manual/core/aggregation-pipeline/) for details on constructing pipelines.
467    ///
468    /// # Type Parameters
469    ///
470    /// * `T`: The type that each element of the result set is expected to deserialize into. This type must implement `DeserializeOwned`.
471    ///
472    /// # Arguments
473    ///
474    /// * `class_name`: The name of the class to perform the aggregation on (e.g., `"GameScore"`).
475    /// * `pipeline`: A `serde_json::Value` representing the aggregation pipeline. This is usually a JSON array.
476    ///
477    /// # Returns
478    ///
479    /// A `Result` containing a `Vec<T>` where `T` is the deserialized type of the aggregation results.
480    /// Returns a `ParseError` if the aggregation fails, the pipeline is invalid, or the Master Key is not available.
481    ///
482    /// # Examples
483    ///
484    /// ```rust,no_run
485    /// use parse_rs::{Parse, ParseError};
486    /// use serde::Deserialize;
487    /// use serde_json::json; // for constructing the pipeline value
488    ///
489    /// #[derive(Deserialize, Debug)]
490    /// struct PlayerStats {
491    ///     // Note: Parse Server might return grouped _id as "objectId"
492    ///     #[serde(rename = "objectId")]
493    ///     player_name: String,
494    ///     total_score: i64,
495    ///     average_score: f64,
496    /// }
497    ///
498    /// # #[tokio::main]
499    /// # async fn main() -> Result<(), ParseError> {
500    /// # let server_url = std::env::var("PARSE_SERVER_URL").unwrap_or_else(|_| "http://localhost:1338/parse".to_string());
501    /// # let app_id = std::env::var("PARSE_APP_ID").unwrap_or_else(|_| "myAppId".to_string());
502    /// # let master_key = std::env::var("PARSE_MASTER_KEY").unwrap_or_else(|_| "myMasterKey".to_string());
503    /// let client = Parse::new(&server_url, &app_id, None, None, Some(&master_key))?;
504    /// let class_name = "GameScore";
505    /// let pipeline = json!([
506    ///     { "$match": { "playerName": { "$exists": true } } },
507    ///     { "$group": {
508    ///         "_id": "$playerName",
509    ///         "totalScore": { "$sum": "$score" },
510    ///         "averageScore": { "$avg": "$score" }
511    ///     }},
512    ///     { "$sort": { "totalScore": -1 } },
513    ///     { "$project": {
514    ///         "_id": 0, // Exclude the default _id field from MongoDB if not needed
515    ///         "playerName": "$_id", // Rename _id to playerName
516    ///         "total_score": "$totalScore",
517    ///         "average_score": "$averageScore"
518    ///     }}
519    /// ]);
520    ///
521    /// let results: Vec<PlayerStats> = client.execute_aggregate(class_name, pipeline).await?;
522    ///
523    /// for stats in results {
524    ///     println!("Player: {}, Total Score: {}, Avg Score: {:.2}",
525    ///              stats.player_name, stats.total_score, stats.average_score);
526    /// }
527    /// # Ok(())
528    /// # }
529    /// ```
530    pub async fn execute_aggregate<T: DeserializeOwned + Send + 'static>(
531        &self,
532        class_name: &str,
533        pipeline: Value, // Assuming pipeline is a serde_json::Value (e.g., array of stages)
534    ) -> Result<Vec<T>, ParseError> {
535        let endpoint = format!("aggregate/{}", class_name);
536        // Serialize the pipeline to a JSON string
537        let pipeline_str = serde_json::to_string(&pipeline).map_err(|e| {
538            ParseError::SerializationError(format!("Failed to serialize pipeline: {}", e))
539        })?;
540
541        // Construct query parameters
542        let params = vec![("pipeline".to_string(), pipeline_str)];
543
544        // Deserialize into AggregateResponse<T> first
545        let response_wrapper: AggregateResponse<T> = self
546            ._get_with_url_params(&endpoint, &params, true, None)
547            .await?;
548
549        Ok(response_wrapper.results) // Then extract the results vector
550    }
551
552    /// Deletes an object from a class using the Master Key.
553    ///
554    /// This method provides a direct way to delete any object by its class name and object ID,
555    /// bypassing ACLs and Class-Level Permissions due to the use of the Master Key.
556    /// The Master Key must be configured on the `Parse` for this operation to succeed.
557    ///
558    /// For more general object deletion that respects ACLs and uses the current session's
559    /// authentication, use the `delete` method on a `ParseObject` instance retrieved via the client,
560    /// or the `delete` method available on the `ParseUserHandle` for users.
561    ///
562    /// # Arguments
563    ///
564    /// * `endpoint`: A string slice representing the relative path to the object, typically in the
565    ///   format `"classes/ClassName/objectId"` (e.g., `"classes/GameScore/xWMyZ4YEGZ"`).
566    ///
567    /// # Returns
568    ///
569    /// A `Result` containing a `serde_json::Value` (which is usually an empty JSON object `{}`
570    /// upon successful deletion by the Parse Server) or a `ParseError` if the deletion fails
571    /// (e.g., object not found, Master Key not configured, network issue).
572    ///
573    /// # Examples
574    ///
575    /// ```rust,no_run
576    /// use parse_rs::{Parse, ParseError};
577    /// use serde_json::Value;
578    ///
579    /// # #[tokio::main]
580    /// # async fn main() -> Result<(), ParseError> {
581    /// # let server_url = std::env::var("PARSE_SERVER_URL").unwrap_or_else(|_| "http://localhost:1338/parse".to_string());
582    /// # let app_id = std::env::var("PARSE_APP_ID").unwrap_or_else(|_| "myAppId".to_string());
583    /// # let master_key = std::env::var("PARSE_MASTER_KEY").unwrap_or_else(|_| "myMasterKey".to_string());
584    /// # let client = Parse::new(&server_url, &app_id, None, None, Some(&master_key))?;
585    ///
586    /// let class_name = "OldGameData";
587    /// let object_id_to_delete = "someObjectId123";
588    /// let endpoint_to_delete = format!("classes/{}/{}", class_name, object_id_to_delete);
589    ///
590    /// // Ensure client is configured with Master Key for this to work
591    /// match client.delete_object_with_master_key(&endpoint_to_delete).await {
592    ///     Ok(_) => println!("Successfully deleted object {} from class {}.", object_id_to_delete, class_name),
593    ///     Err(e) => eprintln!("Failed to delete object: {}", e),
594    /// }
595    /// # Ok(())
596    /// # }
597    /// ```
598    pub async fn delete_object_with_master_key(
599        &self,
600        endpoint: &str, // Expects relative endpoint like "classes/MyClass/objectId"
601    ) -> Result<Value, ParseError> {
602        if self.master_key.is_none() {
603            return Err(ParseError::MasterKeyRequired(
604                "Master key is required for delete_with_master_key but not configured.".to_string(),
605            ));
606        }
607        self._request(Method::DELETE, endpoint, None::<&Value>, true, None) // Pass relative endpoint
608            .await
609    }
610
611    /// Deletes a specific User by their objectId.
612    ///
613    /// This operation requires the Master Key to be configured on the client and will use it.
614    ///
615    /// # Arguments
616    /// * `object_id`: The objectId of the user to delete.
617    ///
618    /// # Returns
619    /// A `Result` indicating success (`Ok(())`) or a `ParseError`.
620    /// Note: The Parse Server typically returns an empty JSON object `{}` on successful deletion.
621    /// This method maps a successful response (any `Ok(Value)`) to `Ok(())`.
622    pub async fn delete_user(&self, object_id: &str) -> Result<(), ParseError> {
623        if self.master_key.is_none() {
624            return Err(ParseError::MasterKeyRequired(
625                "Deleting a user by objectId requires the Master Key to be configured on the client.".to_string(),
626            ));
627        }
628        let endpoint = format!("users/{}", object_id);
629        // Always use master key for deleting another user by ID.
630        let _response: Value = self
631            ._request(
632                Method::DELETE,
633                &endpoint,
634                None::<&()>, // No body for DELETE
635                true,        // use_master_key
636                None,        // No session token needed when using master key
637            )
638            .await?;
639        Ok(())
640    }
641
642    /// Executes a `ParseQuery` and returns a list of matching objects.
643    ///
644    /// # Arguments
645    /// * `query`: A reference to the `ParseQuery` to execute.
646    ///
647    /// # Returns
648    /// A `Result` containing a `Vec<T>` of the deserialized objects or a `ParseError`.
649    pub async fn execute_query<T: DeserializeOwned + Send + Sync + 'static>(
650        &self,
651        query: &crate::query::ParseQuery, // Corrected: ParseQuery is not generic itself
652    ) -> Result<Vec<T>, ParseError> {
653        let class_name = query.class_name();
654        let base_endpoint = format!("classes/{}", class_name);
655        let params = query.build_query_params(); // Assuming this method exists on ParseQuery
656
657        // Queries generally do not require the master key by default.
658        // Auth is typically handled by session token or API keys based on ACLs.
659        let use_master_key = query.uses_master_key(); // Check if query explicitly needs master key
660        let session_token_to_use = self.session_token.as_deref();
661
662        let response: QueryResponse<T> = self
663            ._get_with_url_params(
664                &base_endpoint, // Pass relative endpoint
665                &params,
666                use_master_key,
667                session_token_to_use,
668            )
669            .await?;
670        Ok(response.results)
671    }
672
673    /// Executes a `ParseQuery` and returns a list of `ParseObject` instances,
674    /// ensuring the `class_name` field is populated for each object from the query.
675    ///
676    /// # Arguments
677    /// * `query`: A reference to the `ParseQuery` to execute.
678    ///
679    /// # Returns
680    /// A `Result` containing a `Vec<ParseObject>` or a `ParseError`.
681    pub async fn find_objects(
682        &self,
683        query: &crate::query::ParseQuery,
684    ) -> Result<Vec<ParseObject>, ParseError> {
685        let mut objects: Vec<ParseObject> = self.execute_query(query).await?;
686        let class_name_from_query = query.class_name().to_string();
687
688        for object in objects.iter_mut() {
689            object.class_name = class_name_from_query.clone();
690        }
691
692        Ok(objects)
693    }
694
695    /// Creates a new class schema in your Parse application.
696    ///
697    /// This operation requires the Master Key to be configured on the `Parse`
698    /// and will use it for authentication.
699    ///
700    /// # Arguments
701    ///
702    /// * `class_name`: The name of the class to create. This must match the `className` field in the `schema_payload`.
703    /// * `schema_payload`: A `serde_json::Value` representing the schema to create. It must include
704    ///   `className` and `fields`. Optionally, `classLevelPermissions` and `indexes` can be included.
705    ///   Example:
706    ///   ```json
707    ///   {
708    ///     "className": "MyNewClass",
709    ///     "fields": {
710    ///       "name": { "type": "String", "required": true },
711    ///       "score": { "type": "Number", "defaultValue": 0 }
712    ///     },
713    ///     "classLevelPermissions": {
714    ///       "find": { "*": true },
715    ///       "get": { "*": true }
716    ///     }
717    ///   }
718    ///   ```
719    ///
720    /// # Returns
721    ///
722    /// A `Result` containing the `ParseSchema` of the newly created class,
723    /// or a `ParseError` if the request fails (e.g., Master Key not provided, schema definition error, class already exists, network error).
724    ///
725    /// # Examples
726    ///
727    /// ```rust,no_run
728    /// use parse_rs::Parse;
729    /// use serde_json::json;
730    /// # use parse_rs::ParseError;
731    ///
732    /// # #[tokio::main]
733    /// # async fn main() -> Result<(), ParseError> {
734    /// # let server_url = std::env::var("PARSE_SERVER_URL").unwrap_or_else(|_| "http://localhost:1338/parse".to_string());
735    /// # let app_id = std::env::var("PARSE_APP_ID").unwrap_or_else(|_| "myAppId".to_string());
736    /// # let master_key = std::env::var("PARSE_MASTER_KEY").unwrap_or_else(|_| "myMasterKey".to_string());
737    /// let client = Parse::new(&server_url, &app_id, None, None, Some(&master_key))?;
738    /// let new_class_name = "MyTemporaryClass";
739    ///
740    /// let schema_payload = json!({
741    ///     "className": new_class_name,
742    ///     "fields": {
743    ///         "playerName": { "type": "String" },
744    ///         "score": { "type": "Number", "required": true, "defaultValue": 0 }
745    ///     },
746    ///     "classLevelPermissions": {
747    ///         "find": { "*": true },
748    ///         "get": { "*": true },
749    ///         "create": { "*": true }, // Allow creation for testing
750    ///         "update": { "*": true }, // Allow update for testing
751    ///         "delete": { "*": true }  // Allow deletion for testing
752    ///     }
753    /// });
754    ///
755    /// match client.create_class_schema(new_class_name, &schema_payload).await {
756    ///     Ok(schema) => {
757    ///         println!("Successfully created schema for class '{}':", schema.class_name);
758    ///         println!("Fields: {:?}", schema.fields.keys());
759    ///         // You can now create objects of this class, e.g., using client.create_object(...)
760    ///     }
761    ///     Err(e) => eprintln!("Failed to create schema for class '{}': {}", new_class_name, e),
762    /// }
763    ///
764    /// // Clean up: Delete the class schema (optional, for testing)
765    /// // Ensure the class is empty before deleting, or set drop_class_if_objects_exist to true.
766    /// client.delete_class_schema(new_class_name, true).await.ok();
767    /// # Ok(())
768    /// # }
769    /// ```
770    pub async fn create_class_schema<T: Serialize + Send + Sync>(
771        &self,
772        class_name: &str, // class_name in path must match className in body
773        schema_payload: &T,
774    ) -> Result<ParseSchema, ParseError> {
775        if self.master_key.is_none() {
776            return Err(ParseError::MasterKeyRequired(format!(
777                "Master key is required to create schema for class '{}'.",
778                class_name
779            )));
780        }
781
782        let endpoint = format!("schemas/{}", class_name);
783        self._request(
784            Method::POST,
785            &endpoint,
786            Some(schema_payload),
787            true, // Use master key
788            None, // No session token override
789        )
790        .await
791    }
792
793    /// Updates the schema for an existing class in your Parse application.
794    ///
795    /// This can be used to add or remove fields, change field types (with caution),
796    /// update Class-Level Permissions (CLP), or add/remove indexes.
797    /// To delete a field or index, use the `{"__op": "Delete"}` operator in the payload.
798    ///
799    /// This operation requires the Master Key to be configured on the `Parse`
800    /// and will use it for authentication.
801    ///
802    /// # Arguments
803    ///
804    /// * `class_name`: The name of the class whose schema is to be updated.
805    /// * `schema_update_payload`: A `serde_json::Value` representing the changes to apply.
806    ///   Example to add a field and delete another:
807    ///   ```json
808    ///   {
809    ///     "className": "MyExistingClass", // Should match class_name argument
810    ///     "fields": {
811    ///       "newField": { "type": "Boolean" },
812    ///       "oldField": { "__op": "Delete" }
813    ///     },
814    ///     "classLevelPermissions": {
815    ///       "update": { "role:Admin": true } // Example CLP update
816    ///     }
817    ///   }
818    ///   ```
819    ///
820    /// # Returns
821    ///
822    /// A `Result` containing the updated `ParseSchema` of the class,
823    /// or a `ParseError` if the request fails (e.g., Master Key not provided, class not found, invalid update operation, network error).
824    ///
825    /// # Examples
826    ///
827    /// ```rust,no_run
828    /// use parse_rs::Parse;
829    /// use serde_json::json;
830    /// # use parse_rs::ParseError;
831    ///
832    /// # #[tokio::main]
833    /// # async fn main() -> Result<(), ParseError> {
834    /// # let server_url = std::env::var("PARSE_SERVER_URL").unwrap_or_else(|_| "http://localhost:1338/parse".to_string());
835    /// # let app_id = std::env::var("PARSE_APP_ID").unwrap_or_else(|_| "myAppId".to_string());
836    /// # let master_key = std::env::var("PARSE_MASTER_KEY").unwrap_or_else(|_| "myMasterKey".to_string());
837    /// let client = Parse::new(&server_url, &app_id, None, None, Some(&master_key))?;
838    /// let class_to_update = "MyUpdatableClass";
839    ///
840    /// // 1. Ensure the class exists (create it for the example)
841    /// let initial_payload = json!({
842    ///    "className": class_to_update,
843    ///    "fields": { "initialField": { "type": "String" } },
844    ///    "classLevelPermissions": { "find": {"*": true}, "get": {"*": true}, "create": {"*": true}, "update": {"*": true}, "delete": {"*": true} }
845    /// });
846    /// client.create_class_schema(class_to_update, &initial_payload).await.ok(); // Ignore error if already exists
847    ///
848    /// // 2. Prepare the update payload
849    /// let update_payload = json!({
850    ///     "className": class_to_update, // Must match
851    ///     "fields": {
852    ///         "addedField": { "type": "Number" },
853    ///         "initialField": { "__op": "Delete" } // Delete the initial field
854    ///     },
855    ///     "classLevelPermissions": {
856    ///         "get": { "role:Moderator": true, "*": false } // Change CLP
857    ///     }
858    /// });
859    ///
860    /// match client.update_class_schema(class_to_update, &update_payload).await {
861    ///     Ok(schema) => {
862    ///         println!("Successfully updated schema for class '{}':", schema.class_name);
863    ///         println!("Current Fields: {:?}", schema.fields.keys());
864    ///         if let Some(clp) = schema.class_level_permissions {
865    ///             println!("Current CLP (get): {:?}", clp.get);
866    ///         }
867    ///     }
868    ///     Err(e) => eprintln!("Failed to update schema for class '{}': {}", class_to_update, e),
869    /// }
870    ///
871    /// // Clean up: Delete the class schema (optional, for testing)
872    /// client.delete_class_schema(class_to_update, true).await.ok();
873    /// # Ok(())
874    /// # }
875    /// ```
876    pub async fn update_class_schema<T: Serialize + Send + Sync>(
877        &self,
878        class_name: &str,
879        schema_update_payload: &T,
880    ) -> Result<ParseSchema, ParseError> {
881        if self.master_key.is_none() {
882            return Err(ParseError::MasterKeyRequired(format!(
883                "Master key is required to update schema for class '{}'.",
884                class_name
885            )));
886        }
887
888        let endpoint = format!("schemas/{}", class_name);
889        self._request(
890            Method::PUT,
891            &endpoint,
892            Some(schema_update_payload),
893            true, // Use master key
894            None, // No session token override
895        )
896        .await
897    }
898
899    /// Fetches the schema for a specific class in your Parse application.
900    ///
901    /// This operation requires the Master Key to be configured on the `Parse`
902    /// and will use it for authentication.
903    ///
904    /// # Arguments
905    ///
906    /// * `class_name`: The name of the class for which to fetch the schema.
907    ///
908    /// # Returns
909    ///
910    /// A `Result` containing the `ParseSchema` for the specified class,
911    /// or a `ParseError` if the request fails (e.g., Master Key not provided, class not found, network error).
912    ///
913    /// # Examples
914    ///
915    /// ```rust,no_run
916    /// use parse_rs::Parse;
917    /// # use parse_rs::ParseError;
918    ///
919    /// # #[tokio::main]
920    /// # async fn main() -> Result<(), ParseError> {
921    /// # let server_url = std::env::var("PARSE_SERVER_URL").unwrap_or_else(|_| "http://localhost:1338/parse".to_string());
922    /// # let app_id = std::env::var("PARSE_APP_ID").unwrap_or_else(|_| "myAppId".to_string());
923    /// # let master_key = std::env::var("PARSE_MASTER_KEY").unwrap_or_else(|_| "myMasterKey".to_string());
924    /// let client = Parse::new(&server_url, &app_id, None, None, Some(&master_key))?;
925    /// let class_to_fetch = "MyTestClass"; // Assume this class exists
926    ///
927    /// // First, ensure the class exists by creating it if it doesn't (for testability)
928    /// // For a real scenario, you'd likely expect the class to exist.
929    /// let initial_schema_payload = serde_json::json!({
930    ///    "className": class_to_fetch,
931    ///    "fields": {
932    ///        "someField": { "type": "String" }
933    ///    }
934    /// });
935    /// client.create_class_schema(class_to_fetch, &initial_schema_payload).await.ok(); // Ignore error if already exists
936    ///
937    /// match client.get_class_schema(class_to_fetch).await {
938    ///     Ok(schema) => {
939    ///         println!("Successfully fetched schema for class '{}':", schema.class_name);
940    ///         println!("Fields: {:?}", schema.fields.keys());
941    ///         if let Some(clp) = &schema.class_level_permissions {
942    ///             println!("CLP: {:?}", clp.get);
943    ///         }
944    ///     }
945    ///     Err(e) => eprintln!("Failed to fetch schema for class '{}': {}", class_to_fetch, e),
946    /// }
947    ///
948    /// // Clean up the test class (optional)
949    /// client.delete_class_schema(class_to_fetch, true).await.ok();
950    /// # Ok(())
951    /// # }
952    /// ```
953    pub async fn get_class_schema(&self, class_name: &str) -> Result<ParseSchema, ParseError> {
954        if self.master_key.is_none() {
955            return Err(ParseError::MasterKeyRequired(format!(
956                "Master key is required to fetch schema for class '{}'.",
957                class_name
958            )));
959        }
960
961        let endpoint = format!("schemas/{}", class_name);
962        self._request(
963            Method::GET,
964            &endpoint,
965            None::<&Value>, // No body for GET request
966            true,           // Use master key
967            None,           // No session token override
968        )
969        .await
970    }
971
972    /// Deletes an existing class schema from your Parse application.
973    ///
974    /// **Important:** The class must be empty (contain no objects) for the deletion to succeed.
975    /// If the class contains objects, the Parse Server will return an error.
976    ///
977    /// This operation requires the Master Key to be configured on the `Parse`
978    /// and will use it for authentication.
979    ///
980    /// # Arguments
981    ///
982    /// * `class_name`: The name of the class whose schema is to be deleted.
983    /// * `fail_if_objects_exist`: If `true` (the default), the operation will fail if the class contains objects.
984    ///   Currently, the Parse API does not support automatically deleting objects along with the schema in a single call.
985    ///   You must delete all objects from the class manually before calling this method if you want to delete a non-empty class.
986    ///   Setting this to `false` is not currently supported by the underlying API and will behave like `true`.
987    ///
988    /// # Returns
989    ///
990    /// A `Result<(), ParseError>` which is `Ok(())` on successful deletion,
991    /// or a `ParseError` if the request fails (e.g., Master Key not provided, class not found, class not empty, network error).
992    ///
993    /// # Examples
994    ///
995    /// ```rust,no_run
996    /// use parse_rs::Parse;
997    /// use serde_json::json;
998    /// # use parse_rs::ParseError;
999    ///
1000    /// # #[tokio::main]
1001    /// # async fn main() -> Result<(), ParseError> {
1002    /// # let server_url = std::env::var("PARSE_SERVER_URL").unwrap_or_else(|_| "http://localhost:1338/parse".to_string());
1003    /// # let app_id = std::env::var("PARSE_APP_ID").unwrap_or_else(|_| "myAppId".to_string());
1004    /// # let master_key = std::env::var("PARSE_MASTER_KEY").unwrap_or_else(|_| "myMasterKey".to_string());
1005    /// let client = Parse::new(&server_url, &app_id, None, None, Some(&master_key))?;
1006    /// let class_to_delete = "MyClassToDelete";
1007    ///
1008    /// // 1. Create a class for the example (ensure it's empty for successful deletion)
1009    /// let schema_payload = json!({
1010    ///     "className": class_to_delete,
1011    ///     "fields": { "tempField": { "type": "String" } },
1012    ///     "classLevelPermissions": { "find": {"*": true}, "get": {"*": true}, "create": {"*": true}, "update": {"*": true}, "delete": {"*": true} }
1013    /// });
1014    /// client.create_class_schema(class_to_delete, &schema_payload).await.ok(); // Create it, ignore if already exists for test idempotency
1015    ///
1016    /// // 2. Attempt to delete the (empty) class schema
1017    /// match client.delete_class_schema(class_to_delete, true).await {
1018    ///     Ok(()) => println!("Successfully deleted schema for class '{}'", class_to_delete),
1019    ///     Err(e) => eprintln!("Failed to delete schema for class '{}': {}. Ensure it's empty.", class_to_delete, e),
1020    /// }
1021    ///
1022    /// // Example of trying to delete a class that might not be empty (will likely fail if objects exist)
1023    /// // let another_class = "PotentiallyNonEmptyClass";
1024    /// // if let Err(e) = client.delete_class_schema(another_class, true).await {
1025    /// //     eprintln!("Could not delete schema for '{}': {}. It might not be empty or might not exist.", another_class, e);
1026    /// // }
1027    /// # Ok(())
1028    /// # }
1029    /// ```
1030    pub async fn delete_class_schema(
1031        &self,
1032        class_name: &str,
1033        _fail_if_objects_exist: bool, // Parameter kept for future API changes, currently server enforces emptiness
1034    ) -> Result<(), ParseError> {
1035        if self.master_key.is_none() {
1036            return Err(ParseError::MasterKeyRequired(format!(
1037                "Master key is required to delete schema for class '{}'.",
1038                class_name
1039            )));
1040        }
1041
1042        let endpoint = format!("schemas/{}", class_name);
1043        // The response for a successful DELETE is often an empty JSON object {} or no content.
1044        // We map it to Ok(()) if successful.
1045        let _response: Value = self
1046            ._request(
1047                Method::DELETE,
1048                &endpoint,
1049                None::<&Value>, // No body for DELETE request
1050                true,           // Use master key
1051                None,           // No session token override
1052            )
1053            .await?;
1054        Ok(())
1055    }
1056
1057    /// Methods to get handles for specific Parse features
1058    /// Returns a `ParseUserHandle` for managing user authentication and user-specific operations.
1059    ///
1060    /// The `ParseUserHandle` provides methods like `signup`, `login`, `logout`, `request_password_reset`,
1061    /// `get_current_user`, `update_current_user`, and `delete_current_user`.
1062    /// It operates in the context of the current `Parse` instance, using its configuration
1063    /// and session state.
1064    ///
1065    /// # Examples
1066    ///
1067    /// ```rust,no_run
1068    /// use parse_rs::{Parse, ParseError};
1069    /// use serde_json::Value;
1070    /// use std::collections::HashMap;
1071    ///
1072    /// # #[tokio::main]
1073    /// # async fn main() -> Result<(), ParseError> {
1074    /// # let server_url = std::env::var("PARSE_SERVER_URL").unwrap_or_else(|_| "http://localhost:1338/parse".to_string());
1075    /// # let app_id = std::env::var("PARSE_APP_ID").unwrap_or_else(|_| "myAppId".to_string());
1076    /// # let mut client = Parse::new(&server_url, &app_id, None, None, None)?;
1077    ///
1078    /// let mut user_data = HashMap::new();
1079    /// user_data.insert("email".to_string(), Value::String("test@example.com".to_string()));
1080    /// // Add other fields as needed for signup
1081    ///
1082    /// // Get the user handle and sign up a new user
1083    /// // let new_user = client.user().signup("testuser", "password123", Some(user_data)).await?;
1084    /// // println!("New user signed up with ID: {}", new_user.get_object_id().unwrap_or_default());
1085    ///
1086    /// // Later, to log in:
1087    /// // let logged_in_user = client.user().login("testuser", "password123").await?;
1088    /// // println!("User logged in. Session token: {}", client.session_token().unwrap_or_default());
1089    /// # Ok(())
1090    /// # }
1091    /// ```
1092    pub fn user(&mut self) -> ParseUserHandle<'_> {
1093        ParseUserHandle::new(self)
1094    }
1095
1096    /// Returns a `ParseSessionHandle` for managing session-specific operations.
1097    ///
1098    /// The `ParseSessionHandle` provides methods like `get_current_session` (to validate the current client's session token)
1099    /// and `delete_session` (to delete a specific session, requires Master Key).
1100    ///
1101    /// # Examples
1102    ///
1103    /// ```rust,no_run
1104    /// use parse_rs::{Parse, ParseError, ParseSession};
1105    ///
1106    /// # #[tokio::main]
1107    /// # async fn main() -> Result<(), ParseError> {
1108    /// # let server_url = std::env::var("PARSE_SERVER_URL").unwrap_or_else(|_| "http://localhost:1338/parse".to_string());
1109    /// # let app_id = std::env::var("PARSE_APP_ID").unwrap_or_else(|_| "myAppId".to_string());
1110    /// # let mut client = Parse::new(&server_url, &app_id, None, None, None)?;
1111    ///
1112    /// // After a user logs in, their session token is stored in the client.
1113    /// // You can then get details about the current session:
1114    /// // if client.is_authenticated() {
1115    /// //     match client.session().get_current_session().await {
1116    /// //         Ok(current_session_details) => {
1117    /// //             println!("Current session is valid for user: {}",
1118    /// //                      current_session_details.get_user().map_or("N/A", |u| u.get_object_id().unwrap_or_default()));
1119    /// //         }
1120    /// //         Err(e) => eprintln!("Could not get current session details: {}", e),
1121    /// //     }
1122    /// // }
1123    /// # Ok(())
1124    /// # }
1125    /// ```
1126    pub fn session(&self) -> crate::session::ParseSessionHandle<'_> {
1127        crate::session::ParseSessionHandle::new(self)
1128    }
1129
1130    /// Returns a `ParseCloud` handle for calling Parse Cloud Code functions.
1131    ///
1132    /// The `ParseCloud` handle provides the `call_function` method to execute server-side Cloud Code.
1133    ///
1134    /// # Examples
1135    ///
1136    /// ```rust,no_run
1137    /// use parse_rs::{Parse, ParseError};
1138    /// use serde_json::json; // For creating parameters
1139    ///
1140    /// # #[tokio::main]
1141    /// # async fn main() -> Result<(), ParseError> {
1142    /// # let server_url = std::env::var("PARSE_SERVER_URL").unwrap_or_else(|_| "http://localhost:1338/parse".to_string());
1143    /// # let app_id = std::env::var("PARSE_APP_ID").unwrap_or_else(|_| "myAppId".to_string());
1144    /// # let client = Parse::new(&server_url, &app_id, None, None, None)?;
1145    ///
1146    /// let function_name = "helloWorld";
1147    /// let params = json!({ "name": "Rustaceans" });
1148    ///
1149    /// // match client.cloud().call_function(function_name, Some(params)).await {
1150    /// //     Ok(result) => println!("Cloud function '{}' returned: {}", function_name, result),
1151    /// //     Err(e) => eprintln!("Cloud function '{}' failed: {}", function_name, e),
1152    /// // }
1153    /// # Ok(())
1154    /// # }
1155    /// ```
1156    pub fn cloud(&self) -> ParseCloud<'_> {
1157        ParseCloud::new(self)
1158    }
1159
1160    /// Fetches the schemas for all classes in your Parse application.
1161    ///
1162    /// This operation requires the Master Key to be configured on the `Parse`
1163    /// and will use it for authentication.
1164    ///
1165    /// # Returns
1166    ///
1167    /// A `Result` containing a `GetAllSchemasResponse` which includes a list of `ParseSchema` objects,
1168    /// or a `ParseError` if the request fails (e.g., Master Key not provided, network error).
1169    ///
1170    /// # Examples
1171    ///
1172    /// ```rust,no_run
1173    /// use parse_rs::Parse;
1174    /// # use parse_rs::ParseError;
1175    ///
1176    /// # #[tokio::main]
1177    /// # async fn main() -> Result<(), ParseError> {
1178    /// # let server_url = std::env::var("PARSE_SERVER_URL").unwrap_or_else(|_| "http://localhost:1338/parse".to_string());
1179    /// # let app_id = std::env::var("PARSE_APP_ID").unwrap_or_else(|_| "myAppId".to_string());
1180    /// # let master_key = std::env::var("PARSE_MASTER_KEY").unwrap_or_else(|_| "myMasterKey".to_string());
1181    /// let client = Parse::new(&server_url, &app_id, None, None, Some(&master_key))?;
1182    ///
1183    /// match client.get_all_schemas().await {
1184    ///     Ok(response) => {
1185    ///         println!("Successfully fetched {} schemas:", response.results.len());
1186    ///         for schema in response.results {
1187    ///             println!("- Class: {}, Fields: {:?}", schema.class_name, schema.fields.keys());
1188    ///         }
1189    ///     }
1190    ///     Err(e) => eprintln!("Failed to fetch schemas: {}", e),
1191    /// }
1192    /// # Ok(())
1193    /// # }
1194    /// ```
1195    pub async fn get_all_schemas(&self) -> Result<GetAllSchemasResponse, ParseError> {
1196        if self.master_key.is_none() {
1197            return Err(ParseError::MasterKeyRequired(
1198                "Master key is required to fetch all schemas.".to_string(),
1199            ));
1200        }
1201
1202        self._request(
1203            Method::GET,
1204            "schemas",
1205            None::<&Value>, // No body for GET request
1206            true,           // Use master key
1207            None,           // No session token override
1208        )
1209        .await
1210    }
1211}
1212
1213// Temporary struct for deserializing file upload response
1214#[derive(serde::Deserialize, Debug)]
1215struct FileUploadResponse {
1216    name: String,
1217    url: String,
1218}
1219
1220/// Response for a successful config update.
1221#[derive(serde::Deserialize, Debug)]
1222pub struct UpdateConfigResponse {
1223    pub result: bool,
1224}
1225
1226// Response for aggregate queries
1227#[derive(serde::Deserialize, Debug)]
1228struct AggregateResponse<T> {
1229    results: Vec<T>,
1230}
1231
1232// Response for standard queries
1233#[derive(serde::Deserialize, Debug)]
1234pub struct QueryResponse<T> {
1235    // Made public for potential use in ParseQuery if it ever handles responses directly
1236    pub results: Vec<T>,
1237}
1238
1239// Helper method for GET requests with URL parameters (e.g., queries, aggregations)
1240impl Parse {
1241    pub(crate) async fn _get_with_url_params<R: DeserializeOwned + Send + 'static>(
1242        &self,
1243        endpoint: &str,
1244        params: &[(String, String)],
1245        use_master_key: bool,
1246        session_token_override: Option<&str>,
1247    ) -> Result<R, ParseError> {
1248        let base_url = Url::parse(&self.server_url).map_err(|e| {
1249            ParseError::InvalidUrl(format!(
1250                "Base server URL '{}' is invalid: {}",
1251                self.server_url, e
1252            ))
1253        })?;
1254
1255        let api_path = format!("/parse/{}", endpoint.trim_start_matches('/'));
1256
1257        let mut full_url = base_url.join(&api_path).map_err(|e| {
1258            ParseError::InvalidUrl(format!(
1259                "Failed to join base URL '{}' with API path '{}': {}",
1260                base_url, api_path, e
1261            ))
1262        })?;
1263
1264        // Add query parameters
1265        if !params.is_empty() {
1266            for (key, value) in params {
1267                full_url.query_pairs_mut().append_pair(key, value);
1268            }
1269        }
1270
1271        log::debug!(
1272            "Preparing GET request with params: URL={}, UseMasterKey={}, SessionTokenOverride={:?}",
1273            full_url.as_str(),
1274            use_master_key,
1275            session_token_override
1276        );
1277
1278        let mut request_builder = self.http_client.get(full_url.clone());
1279
1280        // Apply authentication headers based on context
1281        let mut headers = HeaderMap::new();
1282        headers.insert(
1283            "X-Parse-Application-Id",
1284            HeaderValue::from_str(&self.app_id).map_err(ParseError::InvalidHeaderValue)?,
1285        );
1286        headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
1287
1288        // Authentication headers - applied in order of precedence
1289        if let Some(token_override) = session_token_override {
1290            headers.insert(
1291                "X-Parse-Session-Token",
1292                HeaderValue::from_str(token_override).map_err(ParseError::InvalidHeaderValue)?,
1293            );
1294        } else if use_master_key {
1295            if let Some(master_key) = &self.master_key {
1296                headers.insert(
1297                    "X-Parse-Master-Key",
1298                    HeaderValue::from_str(master_key).map_err(ParseError::InvalidHeaderValue)?,
1299                );
1300            } else {
1301                log::warn!("Master key requested for operation but not configured.");
1302                return Err(ParseError::MasterKeyRequired(
1303                    "Master key is required for this operation but not configured.".to_string(),
1304                ));
1305            }
1306        } else if let Some(session_token) = &self.session_token {
1307            // Client's default session token
1308            headers.insert(
1309                "X-Parse-Session-Token",
1310                HeaderValue::from_str(session_token).map_err(ParseError::InvalidHeaderValue)?,
1311            );
1312        } else if let Some(js_key) = &self.javascript_key {
1313            headers.insert(
1314                "X-Parse-Javascript-Key",
1315                HeaderValue::from_str(js_key).map_err(ParseError::InvalidHeaderValue)?,
1316            );
1317        } else if let Some(rest_key) = &self.rest_api_key {
1318            // Fallback to REST API Key
1319            headers.insert(
1320                "X-Parse-REST-API-Key",
1321                HeaderValue::from_str(rest_key).map_err(ParseError::InvalidHeaderValue)?,
1322            );
1323        }
1324
1325        request_builder = request_builder.headers(headers.clone()); // Clone headers for logging if needed
1326
1327        // Log request details before sending
1328        if log::log_enabled!(log::Level::Debug) {
1329            log::debug!("--- Parse GET Request --- ");
1330            log::debug!("URL: {}", full_url.as_str());
1331            log::debug!("Method: GET");
1332            for (name, value) in headers.iter() {
1333                log::debug!(
1334                    "Header: {}: {:?}",
1335                    name.as_str(),
1336                    value.to_str().unwrap_or("[non-ASCII value]")
1337                );
1338            }
1339            log::debug!("------------------------------");
1340        }
1341
1342        // Perform the actual HTTP request
1343        let response = request_builder
1344            .send()
1345            .await
1346            .map_err(ParseError::ReqwestError)?;
1347
1348        // Log response status and headers (conditionally)
1349        if log::log_enabled!(log::Level::Debug) {
1350            log::debug!("--- Parse Response ---");
1351            log::debug!("Status: {}", response.status());
1352            for (name, value) in response.headers() {
1353                log::debug!("Header: {}: {:?}", name, value);
1354            }
1355        }
1356
1357        let status = response.status();
1358        if status.is_success() {
1359            let body_bytes = response.bytes().await.map_err(ParseError::ReqwestError)?;
1360            log::debug!(
1361                "Request successful. Response body: {}",
1362                String::from_utf8_lossy(&body_bytes)
1363            );
1364            serde_json::from_slice(&body_bytes).map_err(|e| {
1365                ParseError::JsonDeserializationFailed(format!(
1366                    "Error: {}, Body: {}",
1367                    e,
1368                    String::from_utf8_lossy(&body_bytes).into_owned()
1369                ))
1370            })
1371        } else {
1372            let error_body_bytes = response.bytes().await.map_err(ParseError::ReqwestError)?;
1373            let error_body_string = String::from_utf8_lossy(&error_body_bytes).to_string();
1374            log::warn!(
1375                "Request failed with status {} and body: {}",
1376                status,
1377                error_body_string
1378            );
1379            match serde_json::from_slice::<Value>(&error_body_bytes) {
1380                Ok(json_value) => Err(ParseError::from_response(status.as_u16(), json_value)),
1381                Err(_) => {
1382                    let fallback_json = serde_json::json!({
1383                        "code": status.as_u16(),
1384                        "error": error_body_string
1385                    });
1386                    Err(ParseError::from_response(status.as_u16(), fallback_json))
1387                }
1388            }
1389        }
1390    }
1391
1392    // Central request method
1393    pub(crate) async fn _request<
1394        T: Serialize + Send + Sync,
1395        R: DeserializeOwned + Send + 'static,
1396    >(
1397        &self,
1398        method: Method,
1399        endpoint: &str, // Takes relative endpoint string
1400        body: Option<&T>,
1401        use_master_key: bool,
1402        session_token_override: Option<&str>,
1403    ) -> Result<R, ParseError> {
1404        let base_url = Url::parse(&self.server_url).map_err(|e| {
1405            ParseError::InvalidUrl(format!(
1406                "Base server URL '{}' is invalid: {}",
1407                self.server_url, e
1408            ))
1409        })?;
1410
1411        // Ensure the endpoint starts with "/parse/" and then the specific API path.
1412        // Trim any leading slashes from the original endpoint to avoid issues like "/parse//classes".
1413        let api_path = format!("/parse/{}", endpoint.trim_start_matches('/'));
1414
1415        let full_url = base_url.join(&api_path).map_err(|e| {
1416            ParseError::InvalidUrl(format!(
1417                "Failed to join base URL '{}' with API path '{}': {}",
1418                base_url, api_path, e
1419            ))
1420        })?;
1421
1422        log::debug!(
1423            "Preparing request: Method={}, URL={}, UseMasterKey={}, SessionTokenOverride={:?}",
1424            method,
1425            full_url.as_str(), // Log the full_url
1426            use_master_key,
1427            session_token_override
1428        );
1429
1430        let mut request_builder = self.http_client.request(method.clone(), full_url.clone());
1431
1432        let mut headers = HeaderMap::new(); // Start with an empty map for request-specific headers
1433
1434        // Determine effective session token
1435        let effective_session_token = session_token_override.or(self.session_token.as_deref());
1436
1437        if let Some(token) = effective_session_token {
1438            headers.insert(
1439                "X-Parse-Session-Token",
1440                HeaderValue::from_str(token).map_err(ParseError::InvalidHeaderValue)?,
1441            );
1442        } else if use_master_key {
1443            // Only add Master Key if no session token is being used for this request
1444            if let Some(master_key) = &self.master_key {
1445                headers.insert(
1446                    "X-Parse-Master-Key",
1447                    HeaderValue::from_str(master_key).map_err(ParseError::InvalidHeaderValue)?,
1448                );
1449            } else {
1450                log::warn!("Master key requested for operation but not configured for the client.");
1451                return Err(ParseError::MasterKeyRequired(
1452                    "Master key is required for this operation but not configured on the client."
1453                        .to_string(),
1454                ));
1455            }
1456        }
1457        // Note: App ID and User-Agent are part of http_client.default_headers().
1458        // If no session token or master key is specified here, and if the client was initialized
1459        // with a JS key or REST key in its default_headers, those will be used by reqwest.
1460
1461        if method == Method::POST || method == Method::PUT || method == Method::PATCH {
1462            headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
1463        }
1464
1465        let mut body_str_for_log: Option<String> = None;
1466        if let Some(body_data) = body {
1467            let body_str =
1468                serde_json::to_string_pretty(body_data).map_err(ParseError::JsonError)?;
1469            body_str_for_log = Some(body_str.clone());
1470            request_builder = request_builder.body(body_str);
1471        }
1472
1473        // Apply the request-specific headers. The http_client will merge these with its defaults.
1474        request_builder = request_builder.headers(headers.clone()); // Clone headers for logging if needed
1475
1476        // For logging, we want to see the effective headers. Reqwest doesn't easily show
1477        // the final merged headers before sending. So, we'll log what we're adding,
1478        // acknowledging that http_client adds its defaults (AppID, UserAgent, potentially initial JS/REST/Master key).
1479        log::debug!(
1480            "Preparing request: Method={}, URL={}, UseMasterKey={}, SessionTokenOverride={:?}",
1481            method,
1482            full_url,
1483            use_master_key,
1484            session_token_override
1485        );
1486
1487        if let Some(log_body) = &body_str_for_log {
1488            log::debug!("Request body: {}", log_body);
1489        } else {
1490            log::debug!("Request body: None");
1491        }
1492
1493        // Send the request
1494        let response = request_builder
1495            .send()
1496            .await
1497            .map_err(ParseError::ReqwestError)?;
1498
1499        // Process the response
1500        if response.status().is_success() {
1501            // For 204 No Content, deserialize to a default value if R is Option or unit type
1502            if response.status() == reqwest::StatusCode::NO_CONTENT {
1503                return serde_json::from_str("{}").map_err(ParseError::JsonError);
1504            }
1505            let body_bytes = response.bytes().await.map_err(ParseError::ReqwestError)?;
1506            log::debug!(
1507                "Request successful. Response body: {}",
1508                String::from_utf8_lossy(&body_bytes)
1509            );
1510            serde_json::from_slice(&body_bytes).map_err(ParseError::JsonError)
1511        } else {
1512            let status = response.status();
1513            let error_body_bytes = response.bytes().await.map_err(ParseError::ReqwestError)?;
1514            let error_body_str = String::from_utf8_lossy(&error_body_bytes).to_string();
1515            log::warn!(
1516                "Request failed with status {}. Response body: {}",
1517                status,
1518                error_body_str
1519            );
1520            match serde_json::from_slice::<Value>(&error_body_bytes) {
1521                Ok(json_value) => Err(ParseError::from_response(status.as_u16(), json_value)),
1522                Err(_) => {
1523                    let fallback_json = serde_json::json!({
1524                        "code": status.as_u16(),
1525                        "error": error_body_str
1526                    });
1527                    Err(ParseError::from_response(status.as_u16(), fallback_json))
1528                }
1529            }
1530        }
1531    }
1532}