parse_rs/
installation.rs

1// src/installation.rs
2use crate::client::Parse;
3use crate::error::ParseError;
4use crate::object::{CreateObjectResponse, UpdateObjectResponse};
5use crate::types::common::EmptyResponse;
6use crate::ParseACL;
7use reqwest::Method;
8use serde::{Deserialize, Serialize};
9use serde_json::Value;
10use std::collections::HashMap;
11
12/// Represents the type of device for an installation.
13#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)]
14#[serde(rename_all = "lowercase")]
15pub enum DeviceType {
16    #[default]
17    Js, // Web or JavaScript (Default)
18    Ios,
19    Android,
20    Winphone, // Windows Phone
21    Macos,
22    Windows,
23    Linux,
24    Embedded,      // For other embedded systems
25    Other(String), // For custom device types
26}
27
28/// Represents a new Parse Installation to be created.
29#[derive(Serialize, Debug, Clone, Default)]
30#[serde(rename_all = "camelCase")]
31pub struct NewParseInstallation {
32    pub device_type: DeviceType,
33    #[serde(skip_serializing_if = "Option::is_none")]
34    pub device_token: Option<String>,
35    #[serde(skip_serializing_if = "Option::is_none")]
36    pub installation_id: Option<String>, // Client-generated unique ID
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub app_name: Option<String>,
39    #[serde(skip_serializing_if = "Option::is_none")]
40    pub app_version: Option<String>,
41    #[serde(skip_serializing_if = "Option::is_none")]
42    pub app_identifier: Option<String>, // e.g., bundle ID
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub parse_version: Option<String>,
45    #[serde(skip_serializing_if = "Option::is_none")]
46    pub badge: Option<i64>,
47    #[serde(skip_serializing_if = "Option::is_none")]
48    pub time_zone: Option<String>,
49    #[serde(skip_serializing_if = "Option::is_none")]
50    pub channels: Option<Vec<String>>,
51    #[serde(skip_serializing_if = "Option::is_none")]
52    pub locale_identifier: Option<String>,
53    #[serde(skip_serializing_if = "Option::is_none")]
54    pub push_type: Option<String>, // e.g., "gcm" for Android
55    #[serde(skip_serializing_if = "Option::is_none")]
56    pub gcm_sender_id: Option<String>,
57    #[serde(skip_serializing_if = "Option::is_none")]
58    pub acl: Option<ParseACL>,
59    // You can add other custom fields as needed using a HashMap or by extending this struct.
60    #[serde(flatten, skip_serializing_if = "Option::is_none")]
61    pub custom_fields: Option<HashMap<String, Value>>,
62}
63
64impl NewParseInstallation {
65    pub fn new(device_type: DeviceType) -> Self {
66        Self {
67            device_type,
68            ..Default::default()
69        }
70    }
71}
72
73/// Represents a Parse Installation object retrieved from the server.
74#[derive(Deserialize, Debug, Clone)]
75#[serde(rename_all = "camelCase")]
76pub struct RetrievedParseInstallation {
77    #[serde(rename = "objectId")]
78    pub object_id: String,
79    #[serde(rename = "createdAt")]
80    pub created_at: String, // Parse Server returns these as ISO strings for Installation class
81    #[serde(rename = "updatedAt")]
82    pub updated_at: String, // Parse Server returns these as ISO strings for Installation class
83    pub device_type: DeviceType,
84    #[serde(skip_serializing_if = "Option::is_none")]
85    pub device_token: Option<String>,
86    #[serde(skip_serializing_if = "Option::is_none")]
87    pub installation_id: Option<String>,
88    #[serde(skip_serializing_if = "Option::is_none")]
89    pub app_name: Option<String>,
90    #[serde(skip_serializing_if = "Option::is_none")]
91    pub app_version: Option<String>,
92    #[serde(skip_serializing_if = "Option::is_none")]
93    pub app_identifier: Option<String>,
94    #[serde(skip_serializing_if = "Option::is_none")]
95    pub parse_version: Option<String>,
96    #[serde(skip_serializing_if = "Option::is_none")]
97    pub badge: Option<i64>,
98    #[serde(skip_serializing_if = "Option::is_none")]
99    pub time_zone: Option<String>,
100    #[serde(skip_serializing_if = "Option::is_none")]
101    pub channels: Option<Vec<String>>,
102    #[serde(skip_serializing_if = "Option::is_none")]
103    pub locale_identifier: Option<String>,
104    #[serde(skip_serializing_if = "Option::is_none")]
105    pub push_type: Option<String>,
106    #[serde(skip_serializing_if = "Option::is_none")]
107    pub gcm_sender_id: Option<String>,
108    #[serde(skip_serializing_if = "Option::is_none")]
109    pub acl: Option<ParseACL>,
110    // Captures any other fields returned by the server not explicitly defined.
111    #[serde(flatten)]
112    pub custom_fields: HashMap<String, Value>,
113}
114
115/// Represents the fields that can be updated on an existing Parse Installation.
116/// All fields are optional, allowing for partial updates.
117#[derive(Serialize, Debug, Clone, Default, PartialEq)]
118#[serde(rename_all = "camelCase")]
119pub struct UpdateParseInstallation {
120    #[serde(skip_serializing_if = "Option::is_none")]
121    pub device_type: Option<DeviceType>,
122    #[serde(skip_serializing_if = "Option::is_none")]
123    pub device_token: Option<String>,
124    #[serde(skip_serializing_if = "Option::is_none")]
125    pub installation_id: Option<String>,
126    #[serde(skip_serializing_if = "Option::is_none")]
127    pub app_name: Option<String>,
128    #[serde(skip_serializing_if = "Option::is_none")]
129    pub app_version: Option<String>,
130    #[serde(skip_serializing_if = "Option::is_none")]
131    pub app_identifier: Option<String>, // e.g., com.example.app
132    #[serde(skip_serializing_if = "Option::is_none")]
133    pub time_zone: Option<String>, // e.g., America/New_York
134    #[serde(skip_serializing_if = "Option::is_none")]
135    pub locale_identifier: Option<String>, // e.g., en-US
136    #[serde(skip_serializing_if = "Option::is_none")]
137    pub badge: Option<i32>,
138    #[serde(skip_serializing_if = "Option::is_none")]
139    pub channels: Option<Vec<String>>,
140    #[serde(skip_serializing_if = "Option::is_none")]
141    pub push_type: Option<String>, // Only for specific push services like gcm
142    #[serde(skip_serializing_if = "Option::is_none")]
143    pub gcm_sender_id: Option<String>, // For Android GCM
144    #[serde(skip_serializing_if = "Option::is_none")]
145    pub acl: Option<ParseACL>,
146    // For any other custom fields to update.
147    // Use `serde_json::json!({ "customField": "value" })` or build a HashMap.
148    #[serde(flatten, skip_serializing_if = "HashMap::is_empty")]
149    pub custom_fields: HashMap<String, Value>,
150}
151
152impl UpdateParseInstallation {
153    pub fn new() -> Self {
154        Self::default()
155    }
156}
157
158impl Parse {
159    /// Creates a new Installation object on the Parse Server.
160    ///
161    /// # Arguments
162    /// * `installation_data`: A `NewParseInstallation` struct containing the data for the new installation.
163    ///
164    /// # Returns
165    /// A `Result` containing a `CreateObjectResponse` (which includes `objectId` and `createdAt`) or a `ParseError`.
166    pub async fn create_installation(
167        &self,
168        installation_data: &NewParseInstallation,
169    ) -> Result<CreateObjectResponse, ParseError> {
170        // Installations are typically created without a session token, but can be associated with a user later.
171        // The _Installation class usually requires the Master Key, JS Key, or REST API Key for creation.
172        let use_master_key = self.master_key.is_some();
173        let session_token_to_use = None;
174
175        self._request(
176            Method::POST,
177            "installations",
178            Some(installation_data),
179            use_master_key,
180            session_token_to_use,
181        )
182        .await
183    }
184
185    /// Retrieves a specific Installation object by its objectId.
186    ///
187    /// # Arguments
188    /// * `object_id`: The objectId of the installation to retrieve.
189    ///
190    /// # Returns
191    /// A `Result` containing the `RetrievedParseInstallation` or a `ParseError`.
192    pub async fn get_installation(
193        &self,
194        object_id: &str,
195    ) -> Result<RetrievedParseInstallation, ParseError> {
196        if object_id.is_empty() {
197            return Err(ParseError::InvalidInput(
198                "Object ID cannot be empty.".to_string(),
199            ));
200        }
201        let endpoint = format!("installations/{}", object_id);
202        // Retrieving an installation usually requires Master Key, JS Key, or REST API Key.
203        // It's generally not tied to a user session for direct GET by ID.
204        let use_master_key = self.master_key.is_some();
205        let session_token_to_use = None;
206
207        self._request(
208            Method::GET,
209            &endpoint,
210            None::<Value>.as_ref(), // No body for GET
211            use_master_key,
212            session_token_to_use,
213        )
214        .await
215    }
216
217    /// Updates an existing Installation object on the Parse Server.
218    ///
219    /// # Arguments
220    /// * `object_id`: The objectId of the installation to update.
221    /// * `update_data`: An `UpdateParseInstallation` struct containing the fields to update.
222    ///
223    /// # Returns
224    /// A `Result` containing an `UpdateObjectResponse` (which includes `updatedAt`) or a `ParseError`.
225    pub async fn update_installation(
226        &self,
227        object_id: &str,
228        update_data: &UpdateParseInstallation,
229    ) -> Result<UpdateObjectResponse, ParseError> {
230        if object_id.is_empty() {
231            return Err(ParseError::InvalidInput(
232                "Object ID cannot be empty.".to_string(),
233            ));
234        }
235        let endpoint = format!("installations/{}", object_id);
236        // Updating an installation usually requires Master Key, JS Key, or REST API Key.
237        let use_master_key = self.master_key.is_some();
238        let session_token_to_use = None;
239
240        self._request(
241            Method::PUT,
242            &endpoint,
243            Some(update_data),
244            use_master_key,
245            session_token_to_use,
246        )
247        .await
248    }
249
250    /// Deletes an Installation object from the Parse Server.
251    ///
252    /// # Arguments
253    /// * `object_id`: The objectId of the installation to delete.
254    ///
255    /// # Returns
256    /// A `Result` containing an `EmptyResponse` or a `ParseError`.
257    pub async fn delete_installation(&self, object_id: &str) -> Result<EmptyResponse, ParseError> {
258        if object_id.is_empty() {
259            return Err(ParseError::InvalidInput(
260                "Object ID cannot be empty.".to_string(),
261            ));
262        }
263        let endpoint = format!("installations/{}", object_id);
264        // Deleting an installation usually requires Master Key.
265        let use_master_key = self.master_key.is_some();
266        if !use_master_key {
267            // Log a warning or return an error if master key is preferred/required by server rules
268            log::warn!("Attempting to delete an installation without the master key. This might be restricted by server ACLs/CLPs.");
269        }
270        let session_token_to_use = None;
271
272        self._request(
273            Method::DELETE,
274            &endpoint,
275            None::<Value>.as_ref(), // No body for DELETE
276            use_master_key,
277            session_token_to_use,
278        )
279        .await
280    }
281}
282
283// We'll add other impl Parse methods (update, delete) here in subsequent steps.