My agentic slop goes here. Not intended for anyone else!
1(** JMAP Standard Method Types
2
3 This module defines the request and response types for all standard
4 JMAP methods that work across different object types.
5
6 These types are polymorphic over the object type 'a.
7
8 Reference: RFC 8620 Sections 5.1-5.6
9*)
10
11(** Local helper functions to avoid circular dependency with Jmap_parser *)
12module Helpers = struct
13 let expect_object = function
14 | `O fields -> fields
15 | _ -> raise (Jmap_error.Parse_error "Expected JSON object")
16
17 let expect_string = function
18 | `String s -> s
19 | _ -> raise (Jmap_error.Parse_error "Expected JSON string")
20
21 let find_field name fields = List.assoc_opt name fields
22
23 let require_field name fields =
24 match find_field name fields with
25 | Some v -> v
26 | None -> raise (Jmap_error.Parse_error (Printf.sprintf "Missing required field: %s" name))
27
28 let get_string name fields =
29 match require_field name fields with
30 | `String s -> s
31 | _ -> raise (Jmap_error.Parse_error (Printf.sprintf "Field %s must be a string" name))
32
33 let get_string_opt name fields =
34 match find_field name fields with
35 | Some (`String s) -> Some s
36 | Some _ -> raise (Jmap_error.Parse_error (Printf.sprintf "Field %s must be a string" name))
37 | None -> None
38
39 let get_bool name fields =
40 match require_field name fields with
41 | `Bool b -> b
42 | _ -> raise (Jmap_error.Parse_error (Printf.sprintf "Field %s must be a boolean" name))
43
44 let parse_array parse_elem = function
45 | `A items -> List.map parse_elem items
46 | `Null -> []
47 | _ -> raise (Jmap_error.Parse_error "Expected JSON array")
48end
49
50(** Standard /get method (RFC 8620 Section 5.1) *)
51module Get = struct
52 type 'a request = {
53 account_id : Jmap_id.t;
54 ids : Jmap_id.t list option; (** null = fetch all *)
55 properties : string list option; (** null = fetch all properties *)
56 }
57
58 type 'a response = {
59 account_id : Jmap_id.t;
60 state : string;
61 list : 'a list;
62 not_found : Jmap_id.t list;
63 }
64
65 (** Accessors for request *)
66 let account_id (r : 'a request) = r.account_id
67 let ids (r : 'a request) = r.ids
68 let properties (r : 'a request) = r.properties
69
70 (** Constructor for request *)
71 let v ~account_id ?ids ?properties () =
72 { account_id; ids; properties }
73
74 (** Accessors for response *)
75 let response_account_id (r : 'a response) = r.account_id
76 let state (r : 'a response) = r.state
77 let list (r : 'a response) = r.list
78 let not_found (r : 'a response) = r.not_found
79
80 (** Constructor for response *)
81 let response_v ~account_id ~state ~list ~not_found =
82 { account_id; state; list; not_found }
83
84 (** Parse request from JSON.
85 Test files: test/data/core/request_get.json *)
86 let request_of_json parse_obj json =
87 ignore parse_obj;
88 let open Helpers in
89 let fields = expect_object json in
90 let account_id = Jmap_id.of_json (require_field "accountId" fields) in
91 let ids = match find_field "ids" fields with
92 | Some `Null | None -> None
93 | Some v -> Some (parse_array Jmap_id.of_json v)
94 in
95 let properties = match find_field "properties" fields with
96 | Some `Null | None -> None
97 | Some v -> Some (parse_array expect_string v)
98 in
99 { account_id; ids; properties }
100
101 (** Convert request to JSON *)
102 let request_to_json (req : 'a request) =
103 let fields = [
104 ("accountId", Jmap_id.to_json req.account_id);
105 ] in
106 let fields = match req.ids with
107 | Some ids -> ("ids", `A (List.map Jmap_id.to_json ids)) :: fields
108 | None -> fields
109 in
110 let fields = match req.properties with
111 | Some props -> ("properties", `A (List.map (fun s -> `String s) props)) :: fields
112 | None -> fields
113 in
114 `O fields
115
116 (** Parse response from JSON.
117 Test files: test/data/core/response_get.json *)
118 let response_of_json parse_obj json =
119 let open Helpers in
120 let fields = expect_object json in
121 let account_id = Jmap_id.of_json (require_field "accountId" fields) in
122 let state = get_string "state" fields in
123 let list = parse_array parse_obj (require_field "list" fields) in
124 let not_found = match find_field "notFound" fields with
125 | Some v -> parse_array Jmap_id.of_json v
126 | None -> []
127 in
128 { account_id; state; list; not_found }
129end
130
131(** Standard /changes method (RFC 8620 Section 5.2) *)
132module Changes = struct
133 type request = {
134 account_id : Jmap_id.t;
135 since_state : string;
136 max_changes : Jmap_primitives.UnsignedInt.t option;
137 }
138
139 type response = {
140 account_id : Jmap_id.t;
141 old_state : string;
142 new_state : string;
143 has_more_changes : bool;
144 created : Jmap_id.t list;
145 updated : Jmap_id.t list;
146 destroyed : Jmap_id.t list;
147 }
148
149 (** Accessors for request *)
150 let account_id (r : request) = r.account_id
151 let since_state (r : request) = r.since_state
152 let max_changes (r : request) = r.max_changes
153
154 (** Constructor for request *)
155 let v ~account_id ~since_state ?max_changes () =
156 { account_id; since_state; max_changes }
157
158 (** Accessors for response *)
159 let response_account_id (r : response) = r.account_id
160 let old_state (r : response) = r.old_state
161 let new_state (r : response) = r.new_state
162 let has_more_changes (r : response) = r.has_more_changes
163 let created (r : response) = r.created
164 let updated (r : response) = r.updated
165 let destroyed (r : response) = r.destroyed
166
167 (** Constructor for response *)
168 let response_v ~account_id ~old_state ~new_state ~has_more_changes ~created ~updated ~destroyed =
169 { account_id; old_state; new_state; has_more_changes; created; updated; destroyed }
170
171 (** Parse request from JSON.
172 Test files: test/data/core/request_changes.json *)
173 let request_of_json json =
174 let open Helpers in
175 let fields = expect_object json in
176 let account_id = Jmap_id.of_json (require_field "accountId" fields) in
177 let since_state = get_string "sinceState" fields in
178 let max_changes = match find_field "maxChanges" fields with
179 | Some v -> Some (Jmap_primitives.UnsignedInt.of_json v)
180 | None -> None
181 in
182 { account_id; since_state; max_changes }
183
184 (** Parse response from JSON.
185 Test files: test/data/core/response_changes.json *)
186 let response_of_json json =
187 let open Helpers in
188 let fields = expect_object json in
189 let account_id = Jmap_id.of_json (require_field "accountId" fields) in
190 let old_state = get_string "oldState" fields in
191 let new_state = get_string "newState" fields in
192 let has_more_changes = get_bool "hasMoreChanges" fields in
193 let created = parse_array Jmap_id.of_json (require_field "created" fields) in
194 let updated = parse_array Jmap_id.of_json (require_field "updated" fields) in
195 let destroyed = parse_array Jmap_id.of_json (require_field "destroyed" fields) in
196 { account_id; old_state; new_state; has_more_changes; created; updated; destroyed }
197end
198
199(** Standard /set method (RFC 8620 Section 5.3) *)
200module Set = struct
201 (** PatchObject - JSON Pointer paths to values *)
202 type patch_object = (string * Ezjsonm.value option) list
203
204 type 'a request = {
205 account_id : Jmap_id.t;
206 if_in_state : string option;
207 create : (Jmap_id.t * 'a) list option;
208 update : (Jmap_id.t * patch_object) list option;
209 destroy : Jmap_id.t list option;
210 }
211
212 type 'a response = {
213 account_id : Jmap_id.t;
214 old_state : string option;
215 new_state : string;
216 created : (Jmap_id.t * 'a) list option;
217 updated : (Jmap_id.t * 'a option) list option;
218 destroyed : Jmap_id.t list option;
219 not_created : (Jmap_id.t * Jmap_error.set_error_detail) list option;
220 not_updated : (Jmap_id.t * Jmap_error.set_error_detail) list option;
221 not_destroyed : (Jmap_id.t * Jmap_error.set_error_detail) list option;
222 }
223
224 (** Accessors for request *)
225 let account_id (r : 'a request) = r.account_id
226 let if_in_state (r : 'a request) = r.if_in_state
227 let create (r : 'a request) = r.create
228 let update (r : 'a request) = r.update
229 let destroy (r : 'a request) = r.destroy
230
231 (** Constructor for request *)
232 let v ~account_id ?if_in_state ?create ?update ?destroy () =
233 { account_id; if_in_state; create; update; destroy }
234
235 (** Accessors for response *)
236 let response_account_id (r : 'a response) = r.account_id
237 let old_state (r : 'a response) = r.old_state
238 let new_state (r : 'a response) = r.new_state
239 let created (r : 'a response) = r.created
240 let updated (r : 'a response) = r.updated
241 let destroyed (r : 'a response) = r.destroyed
242 let not_created (r : 'a response) = r.not_created
243 let not_updated (r : 'a response) = r.not_updated
244 let not_destroyed (r : 'a response) = r.not_destroyed
245
246 (** Constructor for response *)
247 let response_v ~account_id ?old_state ~new_state ?created ?updated ?destroyed ?not_created ?not_updated ?not_destroyed () =
248 { account_id; old_state; new_state; created; updated; destroyed; not_created; not_updated; not_destroyed }
249
250 (** Parse request from JSON.
251 Test files:
252 - test/data/core/request_set_create.json
253 - test/data/core/request_set_update.json
254 - test/data/core/request_set_destroy.json
255 *)
256 let request_of_json parse_obj json =
257 let open Helpers in
258 let fields = expect_object json in
259 let account_id = Jmap_id.of_json (require_field "accountId" fields) in
260 let if_in_state = get_string_opt "ifInState" fields in
261 let create = match find_field "create" fields with
262 | Some `Null | None -> None
263 | Some (`O pairs) ->
264 Some (List.map (fun (k, v) -> (Jmap_id.of_string k, parse_obj v)) pairs)
265 | Some _ -> raise (Jmap_error.Parse_error "create must be an object")
266 in
267 let update = match find_field "update" fields with
268 | Some `Null | None -> None
269 | Some (`O pairs) ->
270 Some (List.map (fun (k, v) ->
271 let id = Jmap_id.of_string k in
272 let patch = match v with
273 | `O patch_fields ->
274 List.map (fun (pk, pv) ->
275 match pv with
276 | `Null -> (pk, None)
277 | _ -> (pk, Some pv)
278 ) patch_fields
279 | _ -> raise (Jmap_error.Parse_error "update value must be an object")
280 in
281 (id, patch)
282 ) pairs)
283 | Some _ -> raise (Jmap_error.Parse_error "update must be an object")
284 in
285 let destroy = match find_field "destroy" fields with
286 | Some `Null | None -> None
287 | Some v -> Some (parse_array Jmap_id.of_json v)
288 in
289 { account_id; if_in_state; create; update; destroy }
290
291 (** Parse response from JSON.
292 Test files:
293 - test/data/core/response_set_create.json
294 - test/data/core/response_set_update.json
295 - test/data/core/response_set_destroy.json
296 *)
297 let response_of_json parse_obj json =
298 let open Helpers in
299 let fields = expect_object json in
300 let account_id = Jmap_id.of_json (require_field "accountId" fields) in
301 let old_state = get_string_opt "oldState" fields in
302 let new_state = get_string "newState" fields in
303 let created = match find_field "created" fields with
304 | Some `Null | None -> None
305 | Some (`O pairs) ->
306 Some (List.map (fun (k, v) -> (Jmap_id.of_string k, parse_obj v)) pairs)
307 | Some _ -> raise (Jmap_error.Parse_error "created must be an object")
308 in
309 let updated = match find_field "updated" fields with
310 | Some `Null | None -> None
311 | Some (`O pairs) ->
312 Some (List.map (fun (k, v) ->
313 let id = Jmap_id.of_string k in
314 match v with
315 | `Null -> (id, None)
316 | _ -> (id, Some (parse_obj v))
317 ) pairs)
318 | Some _ -> raise (Jmap_error.Parse_error "updated must be an object")
319 in
320 let destroyed = match find_field "destroyed" fields with
321 | Some `Null | None -> None
322 | Some v -> Some (parse_array Jmap_id.of_json v)
323 in
324 let not_created = match find_field "notCreated" fields with
325 | Some `Null | None -> None
326 | Some (`O pairs) ->
327 Some (List.map (fun (k, v) ->
328 (Jmap_id.of_string k, Jmap_error.parse_set_error_detail v)
329 ) pairs)
330 | Some _ -> raise (Jmap_error.Parse_error "notCreated must be an object")
331 in
332 let not_updated = match find_field "notUpdated" fields with
333 | Some `Null | None -> None
334 | Some (`O pairs) ->
335 Some (List.map (fun (k, v) ->
336 (Jmap_id.of_string k, Jmap_error.parse_set_error_detail v)
337 ) pairs)
338 | Some _ -> raise (Jmap_error.Parse_error "notUpdated must be an object")
339 in
340 let not_destroyed = match find_field "notDestroyed" fields with
341 | Some `Null | None -> None
342 | Some (`O pairs) ->
343 Some (List.map (fun (k, v) ->
344 (Jmap_id.of_string k, Jmap_error.parse_set_error_detail v)
345 ) pairs)
346 | Some _ -> raise (Jmap_error.Parse_error "notDestroyed must be an object")
347 in
348 { account_id; old_state; new_state; created; updated; destroyed;
349 not_created; not_updated; not_destroyed }
350end
351
352(** Standard /copy method (RFC 8620 Section 5.4) *)
353module Copy = struct
354 type 'a request = {
355 from_account_id : Jmap_id.t;
356 if_from_in_state : string option;
357 account_id : Jmap_id.t;
358 if_in_state : string option;
359 create : (Jmap_id.t * 'a) list; (** Each object must include source id *)
360 on_success_destroy_original : bool option;
361 destroy_from_if_in_state : string option;
362 }
363
364 type 'a response = {
365 from_account_id : Jmap_id.t;
366 account_id : Jmap_id.t;
367 old_state : string option;
368 new_state : string;
369 created : (Jmap_id.t * 'a) list option;
370 not_created : (Jmap_id.t * Jmap_error.set_error_detail) list option;
371 }
372
373 (** Accessors for request *)
374 let from_account_id (r : 'a request) = r.from_account_id
375 let if_from_in_state (r : 'a request) = r.if_from_in_state
376 let account_id (r : 'a request) = r.account_id
377 let if_in_state (r : 'a request) = r.if_in_state
378 let create (r : 'a request) = r.create
379 let on_success_destroy_original (r : 'a request) = r.on_success_destroy_original
380 let destroy_from_if_in_state (r : 'a request) = r.destroy_from_if_in_state
381
382 (** Constructor for request *)
383 let v ~from_account_id ?if_from_in_state ~account_id ?if_in_state ~create ?on_success_destroy_original ?destroy_from_if_in_state () =
384 { from_account_id; if_from_in_state; account_id; if_in_state; create; on_success_destroy_original; destroy_from_if_in_state }
385
386 (** Accessors for response *)
387 let response_from_account_id (r : 'a response) = r.from_account_id
388 let response_account_id (r : 'a response) = r.account_id
389 let old_state (r : 'a response) = r.old_state
390 let new_state (r : 'a response) = r.new_state
391 let created (r : 'a response) = r.created
392 let not_created (r : 'a response) = r.not_created
393
394 (** Constructor for response *)
395 let response_v ~from_account_id ~account_id ?old_state ~new_state ?created ?not_created () =
396 { from_account_id; account_id; old_state; new_state; created; not_created }
397
398 (** Parse request from JSON.
399 Test files: test/data/core/request_copy.json *)
400 let request_of_json _parse_obj _json =
401 (* TODO: Implement JSON parsing *)
402 raise (Jmap_error.Parse_error "Copy.request_of_json not yet implemented")
403
404 (** Parse response from JSON.
405 Test files: test/data/core/response_copy.json *)
406 let response_of_json _parse_obj _json =
407 (* TODO: Implement JSON parsing *)
408 raise (Jmap_error.Parse_error "Copy.response_of_json not yet implemented")
409end
410
411(** Standard /query method (RFC 8620 Section 5.5) *)
412module Query = struct
413 type 'filter request = {
414 account_id : Jmap_id.t;
415 filter : 'filter Jmap_filter.t option;
416 sort : Jmap_comparator.t list option;
417 position : Jmap_primitives.Int53.t option;
418 anchor : Jmap_id.t option;
419 anchor_offset : Jmap_primitives.Int53.t option;
420 limit : Jmap_primitives.UnsignedInt.t option;
421 calculate_total : bool option;
422 }
423
424 type response = {
425 account_id : Jmap_id.t;
426 query_state : string;
427 can_calculate_changes : bool;
428 position : Jmap_primitives.UnsignedInt.t;
429 ids : Jmap_id.t list;
430 total : Jmap_primitives.UnsignedInt.t option; (** Only if calculateTotal=true *)
431 limit : Jmap_primitives.UnsignedInt.t option; (** If server limited results *)
432 }
433
434 (** Accessors for request *)
435 let account_id (r : 'f request) = r.account_id
436 let filter (r : 'f request) = r.filter
437 let sort (r : 'f request) = r.sort
438 let position (r : 'f request) = r.position
439 let anchor (r : 'f request) = r.anchor
440 let anchor_offset (r : 'f request) = r.anchor_offset
441 let limit (r : 'f request) = r.limit
442 let calculate_total (r : 'f request) = r.calculate_total
443
444 (** Constructor for request *)
445 let v ~account_id ?filter ?sort ?position ?anchor ?anchor_offset ?limit ?calculate_total () =
446 { account_id; filter; sort; position; anchor; anchor_offset; limit; calculate_total }
447
448 (** Accessors for response *)
449 let response_account_id (r : response) = r.account_id
450 let query_state (r : response) = r.query_state
451 let can_calculate_changes (r : response) = r.can_calculate_changes
452 let response_position (r : response) = r.position
453 let ids (r : response) = r.ids
454 let total (r : response) = r.total
455 let response_limit (r : response) = r.limit
456
457 (** Constructor for response *)
458 let response_v ~account_id ~query_state ~can_calculate_changes ~position ~ids ?total ?limit () =
459 { account_id; query_state; can_calculate_changes; position; ids; total; limit }
460
461 (** Parse request from JSON.
462 Test files: test/data/core/request_query.json *)
463 let request_of_json parse_filter json =
464 let open Helpers in
465 let fields = expect_object json in
466 let account_id = Jmap_id.of_json (require_field "accountId" fields) in
467 let filter = match find_field "filter" fields with
468 | Some v -> Some (Jmap_filter.of_json parse_filter v)
469 | None -> None
470 in
471 let sort = match find_field "sort" fields with
472 | Some v -> Some (parse_array Jmap_comparator.of_json v)
473 | None -> None
474 in
475 let position = match find_field "position" fields with
476 | Some v -> Some (Jmap_primitives.Int53.of_json v)
477 | None -> None
478 in
479 let anchor = match find_field "anchor" fields with
480 | Some v -> Some (Jmap_id.of_json v)
481 | None -> None
482 in
483 let anchor_offset = match find_field "anchorOffset" fields with
484 | Some v -> Some (Jmap_primitives.Int53.of_json v)
485 | None -> None
486 in
487 let limit = match find_field "limit" fields with
488 | Some v -> Some (Jmap_primitives.UnsignedInt.of_json v)
489 | None -> None
490 in
491 let calculate_total = match find_field "calculateTotal" fields with
492 | Some (`Bool b) -> Some b
493 | Some _ -> raise (Jmap_error.Parse_error "calculateTotal must be a boolean")
494 | None -> None
495 in
496 { account_id; filter; sort; position; anchor; anchor_offset; limit; calculate_total }
497
498 (** Parse response from JSON.
499 Test files: test/data/core/response_query.json *)
500 let response_of_json json =
501 let open Helpers in
502 let fields = expect_object json in
503 let account_id = Jmap_id.of_json (require_field "accountId" fields) in
504 let query_state = get_string "queryState" fields in
505 let can_calculate_changes = get_bool "canCalculateChanges" fields in
506 let position = Jmap_primitives.UnsignedInt.of_json (require_field "position" fields) in
507 let ids = parse_array Jmap_id.of_json (require_field "ids" fields) in
508 let total = match find_field "total" fields with
509 | Some v -> Some (Jmap_primitives.UnsignedInt.of_json v)
510 | None -> None
511 in
512 let limit = match find_field "limit" fields with
513 | Some v -> Some (Jmap_primitives.UnsignedInt.of_json v)
514 | None -> None
515 in
516 { account_id; query_state; can_calculate_changes; position; ids; total; limit }
517end
518
519(** Standard /queryChanges method (RFC 8620 Section 5.6) *)
520module QueryChanges = struct
521 (** Item added to query results *)
522 type added_item = {
523 id : Jmap_id.t;
524 index : Jmap_primitives.UnsignedInt.t;
525 }
526
527 type 'filter request = {
528 account_id : Jmap_id.t;
529 filter : 'filter Jmap_filter.t option;
530 sort : Jmap_comparator.t list option;
531 since_query_state : string;
532 max_changes : Jmap_primitives.UnsignedInt.t option;
533 up_to_id : Jmap_id.t option;
534 calculate_total : bool option;
535 }
536
537 type response = {
538 account_id : Jmap_id.t;
539 old_query_state : string;
540 new_query_state : string;
541 total : Jmap_primitives.UnsignedInt.t option;
542 removed : Jmap_id.t list;
543 added : added_item list;
544 }
545
546 (** Accessors for added_item *)
547 let added_item_id a = a.id
548 let added_item_index a = a.index
549
550 (** Constructor for added_item *)
551 let added_item_v ~id ~index = { id; index }
552
553 (** Accessors for request *)
554 let account_id (r : 'f request) = r.account_id
555 let filter (r : 'f request) = r.filter
556 let sort (r : 'f request) = r.sort
557 let since_query_state (r : 'f request) = r.since_query_state
558 let max_changes (r : 'f request) = r.max_changes
559 let up_to_id (r : 'f request) = r.up_to_id
560 let calculate_total (r : 'f request) = r.calculate_total
561
562 (** Constructor for request *)
563 let v ~account_id ?filter ?sort ~since_query_state ?max_changes ?up_to_id ?calculate_total () =
564 { account_id; filter; sort; since_query_state; max_changes; up_to_id; calculate_total }
565
566 (** Accessors for response *)
567 let response_account_id (r : response) = r.account_id
568 let old_query_state (r : response) = r.old_query_state
569 let new_query_state (r : response) = r.new_query_state
570 let total (r : response) = r.total
571 let removed (r : response) = r.removed
572 let added (r : response) = r.added
573
574 (** Constructor for response *)
575 let response_v ~account_id ~old_query_state ~new_query_state ?total ~removed ~added () =
576 { account_id; old_query_state; new_query_state; total; removed; added }
577
578 (** Parse request from JSON.
579 Test files: test/data/core/request_query_changes.json *)
580 let request_of_json parse_filter json =
581 let open Helpers in
582 let fields = expect_object json in
583 let account_id = Jmap_id.of_json (require_field "accountId" fields) in
584 let filter = match find_field "filter" fields with
585 | Some v -> Some (Jmap_filter.of_json parse_filter v)
586 | None -> None
587 in
588 let sort = match find_field "sort" fields with
589 | Some v -> Some (parse_array Jmap_comparator.of_json v)
590 | None -> None
591 in
592 let since_query_state = get_string "sinceQueryState" fields in
593 let max_changes = match find_field "maxChanges" fields with
594 | Some v -> Some (Jmap_primitives.UnsignedInt.of_json v)
595 | None -> None
596 in
597 let up_to_id = match find_field "upToId" fields with
598 | Some v -> Some (Jmap_id.of_json v)
599 | None -> None
600 in
601 let calculate_total = match find_field "calculateTotal" fields with
602 | Some (`Bool b) -> Some b
603 | Some _ -> raise (Jmap_error.Parse_error "calculateTotal must be a boolean")
604 | None -> None
605 in
606 { account_id; filter; sort; since_query_state; max_changes; up_to_id; calculate_total }
607
608 (** Parse response from JSON.
609 Test files: test/data/core/response_query_changes.json *)
610 let response_of_json json =
611 let open Helpers in
612 let fields = expect_object json in
613 let account_id = Jmap_id.of_json (require_field "accountId" fields) in
614 let old_query_state = get_string "oldQueryState" fields in
615 let new_query_state = get_string "newQueryState" fields in
616 let total = match find_field "total" fields with
617 | Some v -> Some (Jmap_primitives.UnsignedInt.of_json v)
618 | None -> None
619 in
620 let removed = parse_array Jmap_id.of_json (require_field "removed" fields) in
621 let added = match require_field "added" fields with
622 | `A items ->
623 List.map (fun item ->
624 match item with
625 | `O item_fields ->
626 let id = Jmap_id.of_json (require_field "id" item_fields) in
627 let index = Jmap_primitives.UnsignedInt.of_json (require_field "index" item_fields) in
628 { id; index }
629 | _ -> raise (Jmap_error.Parse_error "Added item must be an object")
630 ) items
631 | _ -> raise (Jmap_error.Parse_error "added must be an array")
632 in
633 { account_id; old_query_state; new_query_state; total; removed; added }
634end
635
636(** Core/echo method (RFC 8620 Section 7.3) *)
637module Echo = struct
638 (** Echo simply returns the arguments unchanged *)
639 type t = Ezjsonm.value
640
641 (** Test files:
642 - test/data/core/request_echo.json
643 - test/data/core/response_echo.json *)
644 let of_json json = json
645 let to_json t = t
646end