My agentic slop goes here. Not intended for anyone else!
at main 25 kB view raw
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