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, ¶ms, 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 ¶ms,
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}