1use jmap_types::{Id, Invocation, JmapError, State};
8use serde_json::{json, Value};
9
10use crate::backend::{GetObject, JmapBackend, JmapObject, QueryObject};
11use crate::helpers::{extract_account_id, not_found_json, ser};
12
13pub async fn handle_get<O: GetObject, B: JmapBackend>(
22 backend: &B,
23 caller: &B::CallerCtx,
24 args: Value,
25) -> Result<(Value, Vec<Invocation>), JmapError> {
26 let (account_id, mut args) = extract_account_id(args)?;
27 if !backend
28 .account_exists(caller, &account_id)
29 .await
30 .map_err(|e| JmapError::server_fail(e.to_string()))?
31 {
32 return Err(JmapError::account_not_found());
33 }
34
35 let ids: Option<Vec<Id>> = match args.remove("ids").unwrap_or(Value::Null) {
36 Value::Null => None,
37 v => Some(
38 serde_json::from_value(v)
39 .map_err(|_| JmapError::invalid_arguments("ids must be an Id array"))?,
40 ),
41 };
42
43 let properties: Option<Vec<String>> = match args.remove("properties").unwrap_or(Value::Null) {
44 Value::Null => None,
45 v => Some(
46 serde_json::from_value(v)
47 .map_err(|_| JmapError::invalid_arguments("properties must be a string array"))?,
48 ),
49 };
50
51 let ids_slice = ids.as_deref();
52 let (list, not_found) = backend
53 .get_objects::<O>(caller, &account_id, ids_slice, properties.as_deref())
54 .await
55 .map_err(|e| JmapError::server_fail(e.to_string()))?;
56
57 let state = backend
58 .get_state::<O>(caller, &account_id)
59 .await
60 .map_err(|e| JmapError::server_fail(e.to_string()))?;
61
62 let list_json: Vec<Value> = list.iter().map(ser).collect::<Result<Vec<_>, _>>()?;
63
64 Ok((
65 json!({
66 "accountId": account_id.as_ref(),
67 "state": state.as_ref(),
68 "list": list_json,
69 "notFound": not_found_json(¬_found),
70 }),
71 vec![],
72 ))
73}
74
75pub async fn handle_changes<O: JmapObject, B: JmapBackend>(
91 backend: &B,
92 caller: &B::CallerCtx,
93 args: Value,
94) -> Result<(Value, Vec<Invocation>), JmapError> {
95 let (account_id, args) = extract_account_id(args)?;
96 if !backend
97 .account_exists(caller, &account_id)
98 .await
99 .map_err(|e| JmapError::server_fail(e.to_string()))?
100 {
101 return Err(JmapError::account_not_found());
102 }
103
104 let since_state: State = match args.get("sinceState").and_then(|v| v.as_str()) {
105 Some(s) => State::from(s),
106 None => return Err(JmapError::invalid_arguments("sinceState is required")),
107 };
108
109 let max_changes: Option<u64> = match args.get("maxChanges") {
110 None | Some(Value::Null) => None,
111 Some(v) => Some(v.as_u64().filter(|&n| n > 0).ok_or_else(|| {
112 JmapError::invalid_arguments("maxChanges must be a positive integer")
113 })?),
114 };
115
116 let result = backend
117 .get_changes::<O>(caller, &account_id, &since_state, max_changes)
118 .await
119 .map_err(JmapError::from)?;
120
121 Ok((
122 json!({
123 "accountId": account_id.as_ref(),
124 "oldState": since_state.as_ref(),
125 "newState": result.new_state.as_ref(),
126 "hasMoreChanges": result.has_more_changes,
127 "updatedProperties": Value::Null,
128 "created": result.created.iter().map(|id| id.as_ref()).collect::<Vec<_>>(),
129 "updated": result.updated.iter().map(|id| id.as_ref()).collect::<Vec<_>>(),
130 "destroyed": result.destroyed.iter().map(|id| id.as_ref()).collect::<Vec<_>>(),
131 }),
132 vec![],
133 ))
134}
135
136pub async fn handle_query<O: QueryObject, B: JmapBackend>(
145 backend: &B,
146 caller: &B::CallerCtx,
147 args: Value,
148) -> Result<(Value, Vec<Invocation>), JmapError> {
149 let (account_id, mut args) = extract_account_id(args)?;
150 if !backend
151 .account_exists(caller, &account_id)
152 .await
153 .map_err(|e| JmapError::server_fail(e.to_string()))?
154 {
155 return Err(JmapError::account_not_found());
156 }
157
158 let calculate_total: bool = args
159 .get("calculateTotal")
160 .and_then(|v| v.as_bool())
161 .unwrap_or(false);
162
163 let limit: Option<u64> = match args.get("limit") {
164 None | Some(Value::Null) => None,
165 Some(v) => match v.as_u64() {
166 Some(n) => Some(n),
167 None => {
168 return Err(JmapError::invalid_arguments(format!(
169 "limit: expected a non-negative integer, got {v}"
170 )))
171 }
172 },
173 };
174
175 let position: i64 = match args.get("position") {
176 None | Some(Value::Null) => 0,
177 Some(v) => v.as_i64().ok_or_else(|| {
178 JmapError::invalid_arguments(format!("position: expected an integer, got {v}"))
179 })?,
180 };
181
182 let filter: Option<O::Filter> = match args.remove("filter").unwrap_or(Value::Null) {
183 Value::Null => None,
184 v => Some(serde_json::from_value(v).map_err(|_| JmapError::unsupported_filter())?),
185 };
186
187 let sort: Option<Vec<O::Comparator>> = match args.remove("sort").unwrap_or(Value::Null) {
188 Value::Null => None,
189 v => Some(
190 serde_json::from_value(v)
191 .map_err(|_| JmapError::invalid_arguments("sort must be an array"))?,
192 ),
193 };
194
195 let result = backend
196 .query_objects::<O>(
197 caller,
198 &account_id,
199 filter.as_ref(),
200 sort.as_deref(),
201 limit,
202 position,
203 )
204 .await
205 .map_err(|e| JmapError::server_fail(e.to_string()))?;
206
207 let mut resp = json!({
208 "accountId": account_id.as_ref(),
209 "queryState": result.query_state.as_ref(),
210 "canCalculateChanges": result.can_calculate_changes,
211 "position": result.position,
212 "ids": result.ids.iter().map(|id| id.as_ref()).collect::<Vec<_>>(),
213 });
214 if calculate_total {
215 if let Some(t) = result.total {
216 resp["total"] = json!(t);
217 }
218 }
219
220 Ok((resp, vec![]))
221}
222
223pub async fn handle_query_changes<O: QueryObject, B: JmapBackend>(
234 backend: &B,
235 caller: &B::CallerCtx,
236 args: Value,
237) -> Result<(Value, Vec<Invocation>), JmapError> {
238 let (account_id, mut args) = extract_account_id(args)?;
239 if !backend
240 .account_exists(caller, &account_id)
241 .await
242 .map_err(|e| JmapError::server_fail(e.to_string()))?
243 {
244 return Err(JmapError::account_not_found());
245 }
246
247 let since_query_state: State = match args.get("sinceQueryState").and_then(|v| v.as_str()) {
248 Some(s) => State::from(s),
249 None => return Err(JmapError::invalid_arguments("sinceQueryState is required")),
250 };
251
252 let max_changes: Option<u64> = match args.get("maxChanges") {
253 None | Some(Value::Null) => None,
254 Some(v) => Some(v.as_u64().filter(|&n| n > 0).ok_or_else(|| {
255 JmapError::invalid_arguments("maxChanges must be a positive integer")
256 })?),
257 };
258
259 let up_to_id: Option<Id> = match args.get("upToId") {
260 None | Some(Value::Null) => None,
261 Some(Value::String(s)) => Some(Id::from(s.as_str())),
262 Some(_) => {
263 return Err(JmapError::invalid_arguments(
264 "upToId must be a string Id or null",
265 ))
266 }
267 };
268
269 let calculate_total: bool = args
270 .get("calculateTotal")
271 .and_then(|v| v.as_bool())
272 .unwrap_or(false);
273
274 let filter: Option<O::Filter> = match args.remove("filter").unwrap_or(Value::Null) {
275 Value::Null => None,
276 v => Some(serde_json::from_value(v).map_err(|_| JmapError::unsupported_filter())?),
277 };
278
279 let sort: Option<Vec<O::Comparator>> = match args.remove("sort").unwrap_or(Value::Null) {
280 Value::Null => None,
281 v => Some(
282 serde_json::from_value(v)
283 .map_err(|_| JmapError::invalid_arguments("sort must be an array"))?,
284 ),
285 };
286
287 let result = backend
288 .query_changes::<O>(
289 caller,
290 &account_id,
291 &since_query_state,
292 filter.as_ref(),
293 sort.as_deref(),
294 max_changes,
295 up_to_id.as_ref(),
296 false, )
298 .await
299 .map_err(JmapError::from)?;
300
301 let removed: Vec<&str> = result.removed.iter().map(|id| id.as_ref()).collect();
302 let added: Vec<Value> = result
303 .added
304 .iter()
305 .map(|item| {
306 json!({
307 "id": item.id.as_ref(),
308 "index": item.index,
309 })
310 })
311 .collect();
312
313 let mut resp = json!({
314 "accountId": account_id.as_ref(),
315 "oldQueryState": result.old_query_state.as_ref(),
316 "newQueryState": result.new_query_state.as_ref(),
317 "removed": removed,
318 "added": added,
319 });
320 if calculate_total {
321 if let Some(t) = result.total {
322 resp["total"] = json!(t);
323 }
324 }
325
326 Ok((resp, vec![]))
327}