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", ¶ms1).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", ¶ms2).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}