1use crate::acl::ParseACL;
4use crate::client::Parse;
5use crate::types::date::ParseDate;
6use crate::ParseError;
7use serde::de::{DeserializeOwned, Deserializer};
8use serde::{Deserialize, Serialize};
9use serde_json::{json, Value};
10use std::collections::HashMap;
11
12pub fn deserialize_string_to_option_parse_date<'de, D>(
14 deserializer: D,
15) -> Result<Option<ParseDate>, D::Error>
16where
17 D: Deserializer<'de>,
18{
19 let s: Option<String> = Option::deserialize(deserializer)?;
20 Ok(s.map(ParseDate::new))
21}
22
23pub fn deserialize_string_to_parse_date<'de, D>(deserializer: D) -> Result<ParseDate, D::Error>
25where
26 D: Deserializer<'de>,
27{
28 let s: String = String::deserialize(deserializer)?;
29 Ok(ParseDate::new(s))
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct ParseObject {
34 #[serde(skip_serializing_if = "Option::is_none", rename = "objectId")]
35 pub object_id: Option<String>,
36 #[serde(
37 deserialize_with = "deserialize_string_to_option_parse_date",
38 skip_serializing_if = "Option::is_none",
39 rename = "createdAt"
40 )]
41 pub created_at: Option<ParseDate>,
42 #[serde(
43 deserialize_with = "deserialize_string_to_option_parse_date",
44 skip_serializing_if = "Option::is_none",
45 rename = "updatedAt"
46 )]
47 pub updated_at: Option<ParseDate>,
48 #[serde(flatten)]
49 pub fields: HashMap<String, Value>,
50 #[serde(rename = "ACL", skip_serializing_if = "Option::is_none")]
51 pub acl: Option<ParseACL>,
52 #[serde(skip_serializing, default)]
53 pub class_name: String,
55}
56
57impl ParseObject {
58 pub fn new(class_name: &str) -> Self {
59 ParseObject {
60 class_name: class_name.to_string(),
61 fields: HashMap::new(),
62 acl: None,
63 object_id: None,
64 created_at: None,
65 updated_at: None,
66 }
67 }
68
69 pub fn set<T: Serialize>(&mut self, field_name: &str, value: T) {
70 self.fields
71 .insert(field_name.to_string(), serde_json::to_value(value).unwrap());
72 }
73
74 pub fn get<T: DeserializeOwned>(&self, field_name: &str) -> Option<T> {
75 self.fields
76 .get(field_name)
77 .and_then(|v| serde_json::from_value(v.clone()).ok())
78 }
79
80 pub fn set_acl(&mut self, acl: ParseACL) {
81 self.acl = Some(acl);
82 }
83
84 pub fn increment(&mut self, field_name: &str, amount: i64) {
85 let op = json!({
86 "__op": "Increment",
87 "amount": amount
88 });
89 self.fields.insert(field_name.to_string(), op);
90 }
91
92 pub fn decrement(&mut self, field_name: &str, amount: i64) {
93 self.increment(field_name, -amount);
94 }
95
96 pub fn add_to_array<T: Serialize>(&mut self, field_name: &str, items: &[T]) {
97 let op = json!({
98 "__op": "Add",
99 "objects": items
100 });
101 self.fields.insert(field_name.to_string(), op);
102 }
103
104 pub fn add_unique_to_array<T: Serialize>(&mut self, field_name: &str, items: &[T]) {
105 let op = json!({
106 "__op": "AddUnique",
107 "objects": items
108 });
109 self.fields.insert(field_name.to_string(), op);
110 }
111
112 pub fn remove_from_array<T: Serialize>(&mut self, field_name: &str, items: &[T]) {
113 let op = json!({
114 "__op": "Remove",
115 "objects": items
116 });
117 self.fields.insert(field_name.to_string(), op);
118 }
119}
120
121#[derive(Deserialize, Debug, Clone)]
122#[serde(rename_all = "camelCase")]
123pub struct RetrievedParseObject {
124 pub object_id: String,
125 #[serde(deserialize_with = "deserialize_string_to_parse_date")]
126 pub created_at: ParseDate,
127 #[serde(deserialize_with = "deserialize_string_to_parse_date")]
128 pub updated_at: ParseDate,
129 #[serde(flatten)]
130 pub fields: HashMap<String, Value>,
131 #[serde(rename = "ACL")]
132 pub acl: Option<ParseACL>,
133}
134
135#[derive(Deserialize, Debug, Clone)]
136#[serde(rename_all = "camelCase")]
137pub struct CreateObjectResponse {
138 pub object_id: String,
139 #[serde(deserialize_with = "deserialize_string_to_parse_date")]
140 pub created_at: ParseDate,
141}
142
143#[derive(Deserialize, Debug, Clone)]
144#[serde(rename_all = "camelCase")]
145pub struct UpdateObjectResponse {
146 #[serde(deserialize_with = "deserialize_string_to_parse_date")]
147 pub updated_at: ParseDate,
148}
149
150impl Parse {
151 pub async fn create_object<T: Serialize + Send + Sync>(
152 &self,
153 class_name: &str,
154 data: &T,
155 ) -> Result<CreateObjectResponse, ParseError> {
156 if class_name.is_empty() {
157 return Err(ParseError::InvalidInput(
158 "Class name cannot be empty".to_string(),
159 ));
160 }
161 if !class_name
162 .chars()
163 .next()
164 .is_some_and(|c| c.is_alphabetic() || c == '_')
165 {
166 return Err(ParseError::InvalidInput(
167 "Invalid class name: must start with a letter or underscore.".to_string(),
168 ));
169 }
170 if !class_name.chars().all(|c| c.is_alphanumeric() || c == '_') {
171 return Err(ParseError::InvalidInput(
172 "Invalid class name: can only contain letters, numbers, or underscores."
173 .to_string(),
174 ));
175 }
176
177 let endpoint = format!("classes/{}", class_name);
178 match self.post(&endpoint, data).await {
179 Ok(res) => Ok(res),
180 Err(e) => Err(e),
181 }
182 }
183
184 pub async fn retrieve_object(
185 &self,
186 class_name: &str,
187 object_id: &str,
188 ) -> Result<RetrievedParseObject, ParseError> {
189 if class_name.is_empty() {
190 return Err(ParseError::InvalidInput(
191 "Class name cannot be empty".to_string(),
192 ));
193 }
194 if object_id.is_empty() {
195 return Err(ParseError::InvalidInput(
196 "Object ID cannot be empty".to_string(),
197 ));
198 }
199 if !class_name
200 .chars()
201 .next()
202 .is_some_and(|c| c.is_alphabetic() || c == '_')
203 {
204 return Err(ParseError::InvalidInput(
205 "Invalid class name: must start with a letter or underscore.".to_string(),
206 ));
207 }
208 if !class_name.chars().all(|c| c.is_alphanumeric() || c == '_') {
209 return Err(ParseError::InvalidInput(
210 "Invalid class name: can only contain letters, numbers, or underscores."
211 .to_string(),
212 ));
213 }
214
215 let endpoint = format!("classes/{}/{}", class_name, object_id);
216 self.get(&endpoint).await
217 }
218
219 pub async fn update_object<T: Serialize + Send + Sync>(
220 &self,
221 class_name: &str,
222 object_id: &str,
223 data: &T,
224 ) -> Result<UpdateObjectResponse, ParseError> {
225 if class_name.is_empty() {
226 return Err(ParseError::InvalidInput(
227 "Class name cannot be empty".to_string(),
228 ));
229 }
230 if object_id.is_empty() {
231 return Err(ParseError::InvalidInput(
232 "Object ID cannot be empty".to_string(),
233 ));
234 }
235 if !class_name
236 .chars()
237 .next()
238 .is_some_and(|c| c.is_alphabetic() || c == '_')
239 {
240 return Err(ParseError::InvalidInput(
241 "Invalid class name: must start with a letter or underscore.".to_string(),
242 ));
243 }
244 if !class_name.chars().all(|c| c.is_alphanumeric() || c == '_') {
245 return Err(ParseError::InvalidInput(
246 "Invalid class name: can only contain letters, numbers, or underscores."
247 .to_string(),
248 ));
249 }
250
251 let endpoint = format!("classes/{}/{}", class_name, object_id);
252 self.put(&endpoint, data).await
253 }
254
255 pub async fn delete_object(&self, class_name: &str, object_id: &str) -> Result<(), ParseError> {
256 if class_name.is_empty() {
257 return Err(ParseError::InvalidInput(
258 "Class name cannot be empty".to_string(),
259 ));
260 }
261 if object_id.is_empty() {
262 return Err(ParseError::InvalidInput(
263 "Object ID cannot be empty".to_string(),
264 ));
265 }
266 if !class_name
267 .chars()
268 .next()
269 .is_some_and(|c| c.is_alphabetic() || c == '_')
270 {
271 return Err(ParseError::InvalidInput(
272 "Invalid class name: must start with a letter or underscore.".to_string(),
273 ));
274 }
275 if !class_name.chars().all(|c| c.is_alphanumeric() || c == '_') {
276 return Err(ParseError::InvalidInput(
277 "Invalid class name: can only contain letters, numbers, or underscores."
278 .to_string(),
279 ));
280 }
281
282 let endpoint = format!("classes/{}/{}", class_name, object_id);
283 let response_value: Value = self.delete::<Value>(&endpoint).await?;
284
285 if response_value.is_object()
286 && response_value.as_object().is_some_and(|obj| obj.is_empty())
287 {
288 Ok(())
289 } else {
290 Err(ParseError::UnexpectedResponse(format!(
291 "Expected empty JSON object {{}} for delete, got: {:?}",
292 response_value
293 )))
294 }
295 }
296}