parse_rs/
cloud.rs

1// src/cloud.rs
2
3use crate::{client::Parse, error::ParseError};
4use serde::{de::DeserializeOwned, Deserialize, Serialize};
5
6/// Internal helper struct to deserialize the `{"result": ...}` wrapper from Parse Cloud Function responses.
7///
8/// Parse Server wraps the actual return value of a cloud function within a JSON object under the key `"result"`.
9/// This struct facilitates deserializing that wrapper to extract the actual expected type `T`.
10#[derive(Deserialize, Debug)]
11struct CloudFunctionResponse<T> {
12    result: T,
13}
14
15/// Provides methods for interacting with Parse Cloud Code functions.
16///
17/// An instance of `ParseCloud` is obtained by calling the [`cloud()`](crate::Parse::cloud)
18/// method on a `Parse` instance. It allows for executing server-side Cloud Code functions,
19/// passing parameters, and receiving their results.
20///
21/// Cloud Code functions are custom JavaScript (or other supported language) functions deployed to your
22/// Parse Server, enabling server-side logic, data validation, triggers, and more, without exposing
23/// sensitive operations or master key usage directly to the client.
24///
25/// This handle operates in the context of the `Parse` it was created from, using its configuration
26/// (server URL, app ID, keys, and current session token if any) for API requests to the `/functions` endpoint.
27#[derive(Debug)]
28pub struct ParseCloud<'a> {
29    client: &'a Parse,
30}
31
32impl<'a> ParseCloud<'a> {
33    /// Creates a new `ParseCloud` handler.
34    pub(crate) fn new(client: &'a Parse) -> Self {
35        ParseCloud { client }
36    }
37
38    /// Runs a Parse Cloud Function and returns its result.
39    ///
40    /// This method sends a POST request to the `/functions/:functionName` endpoint, where
41    /// `:functionName` is the name of the Cloud Code function to execute. The `params` argument
42    /// is serialized to JSON and sent as the request body.
43    ///
44    /// The Parse Server executes the specified function and is expected to return a JSON object
45    /// of the form `{"result": ...}`, where `...` is the actual value returned by the function.
46    /// This method automatically unwraps the `result` field and deserializes its content into
47    /// the type `R` specified by the caller.
48    ///
49    /// # Type Parameters
50    ///
51    /// * `P`: The type of the `params` argument. This type must implement `Serialize`, `Send`, and `Sync`.
52    ///   It can be any serializable type, such as a custom struct, `serde_json::Value`, or a `HashMap`.
53    /// * `R`: The expected type of the `result` field from the cloud function's response. This type
54    ///   must implement `DeserializeOwned`, `Send`, `Sync`, and be `'static`.
55    ///
56    /// # Arguments
57    ///
58    /// * `function_name`: A string slice representing the name of the cloud function to execute.
59    /// * `params`: A reference to the parameters to pass to the cloud function.
60    ///
61    /// # Returns
62    ///
63    /// A `Result` containing the deserialized value from the `result` field of the cloud function's
64    /// response if successful. Returns a `ParseError` if the function name is invalid, parameters
65    /// cannot be serialized, the server returns an error (e.g., function not found, internal error
66    /// in cloud code), the response cannot be deserialized into `R`, or any other network/request error occurs.
67    ///
68    /// # Examples
69    ///
70    /// ```rust,no_run
71    /// use parse_rs::{Parse, ParseError};
72    /// use serde::{Serialize, Deserialize};
73    /// use serde_json::json; // For ad-hoc JSON parameters
74    ///
75    /// // Define a struct for expected parameters if your function takes structured input
76    /// #[derive(Serialize)]
77    /// struct HelloParams<'a> {
78    ///     name: &'a str,
79    /// }
80    ///
81    /// // Define a struct for the expected result if your function returns structured data
82    /// #[derive(Deserialize, Debug)]
83    /// struct HelloResponse {
84    ///     message: String,
85    ///     timestamp: u64,
86    /// }
87    ///
88    /// # #[tokio::main]
89    /// # async fn main() -> Result<(), ParseError> {
90    /// # let server_url = std::env::var("PARSE_SERVER_URL").unwrap_or_else(|_| "http://localhost:1338/parse".to_string());
91    /// # let app_id = std::env::var("PARSE_APP_ID").unwrap_or_else(|_| "myAppId".to_string());
92    /// # let client = Parse::new(&server_url, &app_id, None, None, None)?;
93    ///
94    /// // Example 1: Calling a cloud function named "hello" with simple JSON parameters
95    /// // and expecting a simple string response.
96    /// // Assume cloud function "hello" is defined as: Parse.Cloud.define("hello", req => `Hello, ${req.params.name}!`);
97    /// let params1 = json!({ "name": "World" });
98    /// match client.cloud().run::<_, String>("hello", &params1).await {
99    ///     Ok(response_message) => {
100    ///         println!("Cloud function 'hello' responded: {}", response_message);
101    ///         assert_eq!(response_message, "Hello, World!");
102    ///     }
103    ///     Err(e) => eprintln!("Failed to run cloud function 'hello': {}", e),
104    /// }
105    ///
106    /// // Example 2: Calling a cloud function "complexHello" with structured parameters
107    /// // and expecting a structured response.
108    /// // Assume cloud function "complexHello" is defined as:
109    /// // Parse.Cloud.define("complexHello", req => {
110    /// //   return { message: `Complex hello to ${req.params.name}!`, timestamp: Date.now() };
111    /// // });
112    /// let params2 = HelloParams { name: "SDK User" };
113    /// match client.cloud().run::<HelloParams, HelloResponse>("complexHello", &params2).await {
114    ///     Ok(response_data) => {
115    ///         println!(
116    ///             "Cloud function 'complexHello' responded with message: '{}' at {}",
117    ///             response_data.message,
118    ///             response_data.timestamp
119    ///         );
120    ///         assert!(response_data.message.contains("SDK User"));
121    ///     }
122    ///     Err(e) => eprintln!("Failed to run cloud function 'complexHello': {}", e),
123    /// }
124    ///
125    /// // Example 3: Calling a cloud function that takes no parameters and returns a number
126    /// // Assume cloud function "randomNumber" is defined as: Parse.Cloud.define("randomNumber", () => Math.random() * 100);
127    /// let empty_params = json!({}); // Or use a unit type `()` if your function truly takes no params and server handles it.
128    /// match client.cloud().run::<_, f64>("randomNumber", &empty_params).await {
129    ///     Ok(number) => {
130    ///         println!("Cloud function 'randomNumber' responded: {}", number);
131    ///         assert!(number >= 0.0 && number <= 100.0);
132    ///     }
133    ///     Err(e) => eprintln!("Failed to run cloud function 'randomNumber': {}", e),
134    /// }
135    /// # Ok(())
136    /// # }
137    /// ```
138    pub async fn run<P, R>(&self, function_name: &str, params: &P) -> Result<R, ParseError>
139    where
140        P: Serialize + Send + Sync,
141        R: DeserializeOwned + Send + Sync + 'static,
142    {
143        let endpoint = format!("functions/{}", function_name);
144        let response_wrapper: CloudFunctionResponse<R> =
145            self.client.post(&endpoint, params).await?;
146        Ok(response_wrapper.result)
147    }
148
149    // Note: Background jobs are triggered via /parse/jobs endpoint and typically require MasterKey.
150    // This could be a separate method `trigger_job` if needed in the future.
151}