···88 Reference: RFC 8620 Sections 5.1-5.6
99*)
10101111+(** Local helper functions to avoid circular dependency with Jmap_parser *)
1212+module Helpers = struct
1313+ let expect_object = function
1414+ | `O fields -> fields
1515+ | _ -> raise (Jmap_error.Parse_error "Expected JSON object")
1616+1717+ let expect_string = function
1818+ | `String s -> s
1919+ | _ -> raise (Jmap_error.Parse_error "Expected JSON string")
2020+2121+ let find_field name fields = List.assoc_opt name fields
2222+2323+ let require_field name fields =
2424+ match find_field name fields with
2525+ | Some v -> v
2626+ | None -> raise (Jmap_error.Parse_error (Printf.sprintf "Missing required field: %s" name))
2727+2828+ let get_string name fields =
2929+ match require_field name fields with
3030+ | `String s -> s
3131+ | _ -> raise (Jmap_error.Parse_error (Printf.sprintf "Field %s must be a string" name))
3232+3333+ let get_string_opt name fields =
3434+ match find_field name fields with
3535+ | Some (`String s) -> Some s
3636+ | Some _ -> raise (Jmap_error.Parse_error (Printf.sprintf "Field %s must be a string" name))
3737+ | None -> None
3838+3939+ let get_bool name fields =
4040+ match require_field name fields with
4141+ | `Bool b -> b
4242+ | _ -> raise (Jmap_error.Parse_error (Printf.sprintf "Field %s must be a boolean" name))
4343+4444+ let parse_array parse_elem = function
4545+ | `A items -> List.map parse_elem items
4646+ | `Null -> []
4747+ | _ -> raise (Jmap_error.Parse_error "Expected JSON array")
4848+end
4949+1150(** Standard /get method (RFC 8620 Section 5.1) *)
1251module Get = struct
1352 type 'a request = {
···44834584 (** Parse request from JSON.
4685 Test files: test/data/core/request_get.json *)
4747- let request_of_json _parse_obj json =
4848- (* TODO: Implement JSON parsing *)
4949- ignore json;
5050- raise (Jmap_error.Parse_error "Get.request_of_json not yet implemented")
8686+ let request_of_json parse_obj json =
8787+ ignore parse_obj;
8888+ let open Helpers in
8989+ let fields = expect_object json in
9090+ let account_id = Jmap_id.of_json (require_field "accountId" fields) in
9191+ let ids = match find_field "ids" fields with
9292+ | Some `Null | None -> None
9393+ | Some v -> Some (parse_array Jmap_id.of_json v)
9494+ in
9595+ let properties = match find_field "properties" fields with
9696+ | Some `Null | None -> None
9797+ | Some v -> Some (parse_array expect_string v)
9898+ in
9999+ { account_id; ids; properties }
5110052101 (** Parse response from JSON.
53102 Test files: test/data/core/response_get.json *)
5454- let response_of_json _parse_obj json =
5555- (* TODO: Implement JSON parsing *)
5656- ignore json;
5757- raise (Jmap_error.Parse_error "Get.response_of_json not yet implemented")
103103+ let response_of_json parse_obj json =
104104+ let open Helpers in
105105+ let fields = expect_object json in
106106+ let account_id = Jmap_id.of_json (require_field "accountId" fields) in
107107+ let state = get_string "state" fields in
108108+ let list = parse_array parse_obj (require_field "list" fields) in
109109+ let not_found = match find_field "notFound" fields with
110110+ | Some v -> parse_array Jmap_id.of_json v
111111+ | None -> []
112112+ in
113113+ { account_id; state; list; not_found }
58114end
5911560116(** Standard /changes method (RFC 8620 Section 5.2) *)
···99155100156 (** Parse request from JSON.
101157 Test files: test/data/core/request_changes.json *)
102102- let request_of_json _json =
103103- (* TODO: Implement JSON parsing *)
104104- raise (Jmap_error.Parse_error "Changes.request_of_json not yet implemented")
158158+ let request_of_json json =
159159+ let open Helpers in
160160+ let fields = expect_object json in
161161+ let account_id = Jmap_id.of_json (require_field "accountId" fields) in
162162+ let since_state = get_string "sinceState" fields in
163163+ let max_changes = match find_field "maxChanges" fields with
164164+ | Some v -> Some (Jmap_primitives.UnsignedInt.of_json v)
165165+ | None -> None
166166+ in
167167+ { account_id; since_state; max_changes }
105168106169 (** Parse response from JSON.
107170 Test files: test/data/core/response_changes.json *)
108108- let response_of_json _json =
109109- (* TODO: Implement JSON parsing *)
110110- raise (Jmap_error.Parse_error "Changes.response_of_json not yet implemented")
171171+ let response_of_json json =
172172+ let open Helpers in
173173+ let fields = expect_object json in
174174+ let account_id = Jmap_id.of_json (require_field "accountId" fields) in
175175+ let old_state = get_string "oldState" fields in
176176+ let new_state = get_string "newState" fields in
177177+ let has_more_changes = get_bool "hasMoreChanges" fields in
178178+ let created = parse_array Jmap_id.of_json (require_field "created" fields) in
179179+ let updated = parse_array Jmap_id.of_json (require_field "updated" fields) in
180180+ let destroyed = parse_array Jmap_id.of_json (require_field "destroyed" fields) in
181181+ { account_id; old_state; new_state; has_more_changes; created; updated; destroyed }
111182end
112183113184(** Standard /set method (RFC 8620 Section 5.3) *)
···167238 - test/data/core/request_set_update.json
168239 - test/data/core/request_set_destroy.json
169240 *)
170170- let request_of_json _parse_obj _json =
171171- (* TODO: Implement JSON parsing *)
172172- raise (Jmap_error.Parse_error "Set.request_of_json not yet implemented")
241241+ let request_of_json parse_obj json =
242242+ let open Helpers in
243243+ let fields = expect_object json in
244244+ let account_id = Jmap_id.of_json (require_field "accountId" fields) in
245245+ let if_in_state = get_string_opt "ifInState" fields in
246246+ let create = match find_field "create" fields with
247247+ | Some `Null | None -> None
248248+ | Some (`O pairs) ->
249249+ Some (List.map (fun (k, v) -> (Jmap_id.of_string k, parse_obj v)) pairs)
250250+ | Some _ -> raise (Jmap_error.Parse_error "create must be an object")
251251+ in
252252+ let update = match find_field "update" fields with
253253+ | Some `Null | None -> None
254254+ | Some (`O pairs) ->
255255+ Some (List.map (fun (k, v) ->
256256+ let id = Jmap_id.of_string k in
257257+ let patch = match v with
258258+ | `O patch_fields ->
259259+ List.map (fun (pk, pv) ->
260260+ match pv with
261261+ | `Null -> (pk, None)
262262+ | _ -> (pk, Some pv)
263263+ ) patch_fields
264264+ | _ -> raise (Jmap_error.Parse_error "update value must be an object")
265265+ in
266266+ (id, patch)
267267+ ) pairs)
268268+ | Some _ -> raise (Jmap_error.Parse_error "update must be an object")
269269+ in
270270+ let destroy = match find_field "destroy" fields with
271271+ | Some `Null | None -> None
272272+ | Some v -> Some (parse_array Jmap_id.of_json v)
273273+ in
274274+ { account_id; if_in_state; create; update; destroy }
173275174276 (** Parse response from JSON.
175277 Test files:
···177279 - test/data/core/response_set_update.json
178280 - test/data/core/response_set_destroy.json
179281 *)
180180- let response_of_json _parse_obj _json =
181181- (* TODO: Implement JSON parsing *)
182182- raise (Jmap_error.Parse_error "Set.response_of_json not yet implemented")
282282+ let response_of_json parse_obj json =
283283+ let open Helpers in
284284+ let fields = expect_object json in
285285+ let account_id = Jmap_id.of_json (require_field "accountId" fields) in
286286+ let old_state = get_string_opt "oldState" fields in
287287+ let new_state = get_string "newState" fields in
288288+ let created = match find_field "created" fields with
289289+ | Some `Null | None -> None
290290+ | Some (`O pairs) ->
291291+ Some (List.map (fun (k, v) -> (Jmap_id.of_string k, parse_obj v)) pairs)
292292+ | Some _ -> raise (Jmap_error.Parse_error "created must be an object")
293293+ in
294294+ let updated = match find_field "updated" fields with
295295+ | Some `Null | None -> None
296296+ | Some (`O pairs) ->
297297+ Some (List.map (fun (k, v) ->
298298+ let id = Jmap_id.of_string k in
299299+ match v with
300300+ | `Null -> (id, None)
301301+ | _ -> (id, Some (parse_obj v))
302302+ ) pairs)
303303+ | Some _ -> raise (Jmap_error.Parse_error "updated must be an object")
304304+ in
305305+ let destroyed = match find_field "destroyed" fields with
306306+ | Some `Null | None -> None
307307+ | Some v -> Some (parse_array Jmap_id.of_json v)
308308+ in
309309+ let not_created = match find_field "notCreated" fields with
310310+ | Some `Null | None -> None
311311+ | Some (`O pairs) ->
312312+ Some (List.map (fun (k, v) ->
313313+ (Jmap_id.of_string k, Jmap_error.parse_set_error_detail v)
314314+ ) pairs)
315315+ | Some _ -> raise (Jmap_error.Parse_error "notCreated must be an object")
316316+ in
317317+ let not_updated = match find_field "notUpdated" fields with
318318+ | Some `Null | None -> None
319319+ | Some (`O pairs) ->
320320+ Some (List.map (fun (k, v) ->
321321+ (Jmap_id.of_string k, Jmap_error.parse_set_error_detail v)
322322+ ) pairs)
323323+ | Some _ -> raise (Jmap_error.Parse_error "notUpdated must be an object")
324324+ in
325325+ let not_destroyed = match find_field "notDestroyed" fields with
326326+ | Some `Null | None -> None
327327+ | Some (`O pairs) ->
328328+ Some (List.map (fun (k, v) ->
329329+ (Jmap_id.of_string k, Jmap_error.parse_set_error_detail v)
330330+ ) pairs)
331331+ | Some _ -> raise (Jmap_error.Parse_error "notDestroyed must be an object")
332332+ in
333333+ { account_id; old_state; new_state; created; updated; destroyed;
334334+ not_created; not_updated; not_destroyed }
183335end
184336185337(** Standard /copy method (RFC 8620 Section 5.4) *)
···293445294446 (** Parse request from JSON.
295447 Test files: test/data/core/request_query.json *)
296296- let request_of_json _parse_filter _json =
297297- (* TODO: Implement JSON parsing *)
298298- raise (Jmap_error.Parse_error "Query.request_of_json not yet implemented")
448448+ let request_of_json parse_filter json =
449449+ let open Helpers in
450450+ let fields = expect_object json in
451451+ let account_id = Jmap_id.of_json (require_field "accountId" fields) in
452452+ let filter = match find_field "filter" fields with
453453+ | Some v -> Some (Jmap_filter.of_json parse_filter v)
454454+ | None -> None
455455+ in
456456+ let sort = match find_field "sort" fields with
457457+ | Some v -> Some (parse_array Jmap_comparator.of_json v)
458458+ | None -> None
459459+ in
460460+ let position = match find_field "position" fields with
461461+ | Some v -> Some (Jmap_primitives.Int53.of_json v)
462462+ | None -> None
463463+ in
464464+ let anchor = match find_field "anchor" fields with
465465+ | Some v -> Some (Jmap_id.of_json v)
466466+ | None -> None
467467+ in
468468+ let anchor_offset = match find_field "anchorOffset" fields with
469469+ | Some v -> Some (Jmap_primitives.Int53.of_json v)
470470+ | None -> None
471471+ in
472472+ let limit = match find_field "limit" fields with
473473+ | Some v -> Some (Jmap_primitives.UnsignedInt.of_json v)
474474+ | None -> None
475475+ in
476476+ let calculate_total = match find_field "calculateTotal" fields with
477477+ | Some (`Bool b) -> Some b
478478+ | Some _ -> raise (Jmap_error.Parse_error "calculateTotal must be a boolean")
479479+ | None -> None
480480+ in
481481+ { account_id; filter; sort; position; anchor; anchor_offset; limit; calculate_total }
299482300483 (** Parse response from JSON.
301484 Test files: test/data/core/response_query.json *)
302302- let response_of_json _json =
303303- (* TODO: Implement JSON parsing *)
304304- raise (Jmap_error.Parse_error "Query.response_of_json not yet implemented")
485485+ let response_of_json json =
486486+ let open Helpers in
487487+ let fields = expect_object json in
488488+ let account_id = Jmap_id.of_json (require_field "accountId" fields) in
489489+ let query_state = get_string "queryState" fields in
490490+ let can_calculate_changes = get_bool "canCalculateChanges" fields in
491491+ let position = Jmap_primitives.UnsignedInt.of_json (require_field "position" fields) in
492492+ let ids = parse_array Jmap_id.of_json (require_field "ids" fields) in
493493+ let total = match find_field "total" fields with
494494+ | Some v -> Some (Jmap_primitives.UnsignedInt.of_json v)
495495+ | None -> None
496496+ in
497497+ let limit = match find_field "limit" fields with
498498+ | Some v -> Some (Jmap_primitives.UnsignedInt.of_json v)
499499+ | None -> None
500500+ in
501501+ { account_id; query_state; can_calculate_changes; position; ids; total; limit }
305502end
306503307504(** Standard /queryChanges method (RFC 8620 Section 5.6) *)
···365562366563 (** Parse request from JSON.
367564 Test files: test/data/core/request_query_changes.json *)
368368- let request_of_json _parse_filter _json =
369369- (* TODO: Implement JSON parsing *)
370370- raise (Jmap_error.Parse_error "QueryChanges.request_of_json not yet implemented")
565565+ let request_of_json parse_filter json =
566566+ let open Helpers in
567567+ let fields = expect_object json in
568568+ let account_id = Jmap_id.of_json (require_field "accountId" fields) in
569569+ let filter = match find_field "filter" fields with
570570+ | Some v -> Some (Jmap_filter.of_json parse_filter v)
571571+ | None -> None
572572+ in
573573+ let sort = match find_field "sort" fields with
574574+ | Some v -> Some (parse_array Jmap_comparator.of_json v)
575575+ | None -> None
576576+ in
577577+ let since_query_state = get_string "sinceQueryState" fields in
578578+ let max_changes = match find_field "maxChanges" fields with
579579+ | Some v -> Some (Jmap_primitives.UnsignedInt.of_json v)
580580+ | None -> None
581581+ in
582582+ let up_to_id = match find_field "upToId" fields with
583583+ | Some v -> Some (Jmap_id.of_json v)
584584+ | None -> None
585585+ in
586586+ let calculate_total = match find_field "calculateTotal" fields with
587587+ | Some (`Bool b) -> Some b
588588+ | Some _ -> raise (Jmap_error.Parse_error "calculateTotal must be a boolean")
589589+ | None -> None
590590+ in
591591+ { account_id; filter; sort; since_query_state; max_changes; up_to_id; calculate_total }
371592372593 (** Parse response from JSON.
373594 Test files: test/data/core/response_query_changes.json *)
374374- let response_of_json _json =
375375- (* TODO: Implement JSON parsing *)
376376- raise (Jmap_error.Parse_error "QueryChanges.response_of_json not yet implemented")
595595+ let response_of_json json =
596596+ let open Helpers in
597597+ let fields = expect_object json in
598598+ let account_id = Jmap_id.of_json (require_field "accountId" fields) in
599599+ let old_query_state = get_string "oldQueryState" fields in
600600+ let new_query_state = get_string "newQueryState" fields in
601601+ let total = match find_field "total" fields with
602602+ | Some v -> Some (Jmap_primitives.UnsignedInt.of_json v)
603603+ | None -> None
604604+ in
605605+ let removed = parse_array Jmap_id.of_json (require_field "removed" fields) in
606606+ let added = match require_field "added" fields with
607607+ | `A items ->
608608+ List.map (fun item ->
609609+ match item with
610610+ | `O item_fields ->
611611+ let id = Jmap_id.of_json (require_field "id" item_fields) in
612612+ let index = Jmap_primitives.UnsignedInt.of_json (require_field "index" item_fields) in
613613+ { id; index }
614614+ | _ -> raise (Jmap_error.Parse_error "Added item must be an object")
615615+ ) items
616616+ | _ -> raise (Jmap_error.Parse_error "added must be an array")
617617+ in
618618+ { account_id; old_query_state; new_query_state; total; removed; added }
377619end
378620379621(** Core/echo method (RFC 8620 Section 7.3) *)
+657-75
stack/jmap/jmap-mail/jmap_email.ml
···3838 "email": "bob@example.com"
3939 }
4040 *)
4141- let of_json _json =
4242- raise (Jmap_core.Jmap_error.Parse_error "EmailAddress.of_json not yet implemented")
4141+ let of_json json =
4242+ let open Jmap_core.Jmap_parser.Helpers in
4343+ let fields = expect_object json in
4444+ let name = get_string_opt "name" fields in
4545+ let email = get_string "email" fields in
4646+ { name; email }
43474444- let to_json _t =
4545- raise (Jmap_core.Jmap_error.Parse_error "EmailAddress.to_json not yet implemented")
4848+ let to_json t =
4949+ let fields = [("email", `String t.email)] in
5050+ let fields = match t.name with
5151+ | Some n -> ("name", `String n) :: fields
5252+ | None -> fields
5353+ in
5454+ `O fields
46554756 (* Accessors *)
4857 let name t = t.name
···6069 value : string; (** Header field value (decoded) *)
6170 }
62716363- let of_json _json =
6464- raise (Jmap_core.Jmap_error.Parse_error "EmailHeader.of_json not yet implemented")
7272+ let of_json json =
7373+ let open Jmap_core.Jmap_parser.Helpers in
7474+ let fields = expect_object json in
7575+ let name = get_string "name" fields in
7676+ let value = get_string "value" fields in
7777+ { name; value }
65786666- let to_json _t =
6767- raise (Jmap_core.Jmap_error.Parse_error "EmailHeader.to_json not yet implemented")
7979+ let to_json t =
8080+ `O [
8181+ ("name", `String t.name);
8282+ ("value", `String t.value);
8383+ ]
68846985 (* Accessors *)
7086 let name t = t.name
···115131 "subParts": [...]
116132 }
117133 *)
118118- let of_json _json =
119119- raise (Jmap_core.Jmap_error.Parse_error "BodyPart.of_json not yet implemented")
134134+ let rec of_json json =
135135+ let open Jmap_core.Jmap_parser.Helpers in
136136+ let fields = expect_object json in
137137+ let part_id = get_string_opt "partId" fields in
138138+ let blob_id = match find_field "blobId" fields with
139139+ | Some (`String s) -> Some (Jmap_core.Jmap_id.of_string s)
140140+ | Some `Null | None -> None
141141+ | Some _ -> raise (Jmap_core.Jmap_error.Parse_error "blobId must be a string")
142142+ in
143143+ let size = match find_field "size" fields with
144144+ | Some s -> Jmap_core.Jmap_primitives.UnsignedInt.of_json s
145145+ | None -> Jmap_core.Jmap_primitives.UnsignedInt.of_int 0
146146+ in
147147+ let headers = match find_field "headers" fields with
148148+ | Some (`A items) -> List.map EmailHeader.of_json items
149149+ | Some `Null | None -> []
150150+ | Some _ -> raise (Jmap_core.Jmap_error.Parse_error "headers must be an array")
151151+ in
152152+ let name = get_string_opt "name" fields in
153153+ let type_ = get_string "type" fields in
154154+ let charset = get_string_opt "charset" fields in
155155+ let disposition = get_string_opt "disposition" fields in
156156+ let cid = get_string_opt "cid" fields in
157157+ let language = match find_field "language" fields with
158158+ | Some (`A items) -> Some (List.map expect_string items)
159159+ | Some `Null | None -> None
160160+ | Some _ -> raise (Jmap_core.Jmap_error.Parse_error "language must be an array")
161161+ in
162162+ let location = get_string_opt "location" fields in
163163+ let sub_parts = match find_field "subParts" fields with
164164+ | Some (`A items) -> Some (List.map of_json items)
165165+ | Some `Null | None -> None
166166+ | Some _ -> raise (Jmap_core.Jmap_error.Parse_error "subParts must be an array")
167167+ in
168168+ { part_id; blob_id; size; headers; name; type_; charset;
169169+ disposition; cid; language; location; sub_parts }
120170121121- let to_json _t =
122122- raise (Jmap_core.Jmap_error.Parse_error "BodyPart.to_json not yet implemented")
171171+ let rec to_json t =
172172+ let fields = [("type", `String t.type_)] in
173173+ let fields = match t.part_id with
174174+ | Some id -> ("partId", `String id) :: fields
175175+ | None -> fields
176176+ in
177177+ let fields = match t.blob_id with
178178+ | Some id -> ("blobId", Jmap_core.Jmap_id.to_json id) :: fields
179179+ | None -> fields
180180+ in
181181+ let fields = ("size", Jmap_core.Jmap_primitives.UnsignedInt.to_json t.size) :: fields in
182182+ let fields = if t.headers <> [] then
183183+ ("headers", `A (List.map EmailHeader.to_json t.headers)) :: fields
184184+ else
185185+ fields
186186+ in
187187+ let fields = match t.name with
188188+ | Some n -> ("name", `String n) :: fields
189189+ | None -> fields
190190+ in
191191+ let fields = match t.charset with
192192+ | Some c -> ("charset", `String c) :: fields
193193+ | None -> fields
194194+ in
195195+ let fields = match t.disposition with
196196+ | Some d -> ("disposition", `String d) :: fields
197197+ | None -> fields
198198+ in
199199+ let fields = match t.cid with
200200+ | Some c -> ("cid", `String c) :: fields
201201+ | None -> fields
202202+ in
203203+ let fields = match t.language with
204204+ | Some l -> ("language", `A (List.map (fun s -> `String s) l)) :: fields
205205+ | None -> fields
206206+ in
207207+ let fields = match t.location with
208208+ | Some l -> ("location", `String l) :: fields
209209+ | None -> fields
210210+ in
211211+ let fields = match t.sub_parts with
212212+ | Some parts -> ("subParts", `A (List.map to_json parts)) :: fields
213213+ | None -> fields
214214+ in
215215+ `O fields
123216124217 (* Accessors *)
125218 let part_id t = t.part_id
···160253 "isTruncated": false
161254 }
162255 *)
163163- let of_json _json =
164164- raise (Jmap_core.Jmap_error.Parse_error "BodyValue.of_json not yet implemented")
256256+ let of_json json =
257257+ let open Jmap_core.Jmap_parser.Helpers in
258258+ let fields = expect_object json in
259259+ let value = get_string "value" fields in
260260+ let is_encoding_problem = get_bool_opt "isEncodingProblem" fields false in
261261+ let is_truncated = get_bool_opt "isTruncated" fields false in
262262+ { value; is_encoding_problem; is_truncated }
165263166166- let to_json _t =
167167- raise (Jmap_core.Jmap_error.Parse_error "BodyValue.to_json not yet implemented")
264264+ let to_json t =
265265+ `O [
266266+ ("value", `String t.value);
267267+ ("isEncodingProblem", `Bool t.is_encoding_problem);
268268+ ("isTruncated", `Bool t.is_truncated);
269269+ ]
168270169271 (* Accessors *)
170272 let value t = t.value
···247349 reply_to; subject; sent_at; body_structure; body_values; text_body;
248350 html_body; attachments; has_attachment; preview }
249351352352+(** Parse Email from JSON.
353353+ Test files: test/data/mail/email_get_response.json (list field)
354354+355355+ Expected structure:
356356+ {
357357+ "id": "e001",
358358+ "blobId": "Ge5f13e2d7b8a9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8",
359359+ "threadId": "t001",
360360+ "mailboxIds": { "mb001": true },
361361+ "keywords": { "$seen": true },
362362+ "size": 15234,
363363+ "receivedAt": "2025-10-05T09:15:30Z",
364364+ ...
365365+ }
366366+*)
367367+let of_json json =
368368+ let open Jmap_core.Jmap_parser.Helpers in
369369+ let fields = expect_object json in
370370+371371+ (* Required fields *)
372372+ let id = Jmap_core.Jmap_id.of_json (require_field "id" fields) in
373373+ let blob_id = Jmap_core.Jmap_id.of_json (require_field "blobId" fields) in
374374+ let thread_id = Jmap_core.Jmap_id.of_json (require_field "threadId" fields) in
375375+376376+ (* mailboxIds - map of id -> bool *)
377377+ let mailbox_ids = match require_field "mailboxIds" fields with
378378+ | `O map_fields ->
379379+ List.map (fun (k, v) ->
380380+ (Jmap_core.Jmap_id.of_string k, expect_bool v)
381381+ ) map_fields
382382+ | _ -> raise (Jmap_core.Jmap_error.Parse_error "mailboxIds must be an object")
383383+ in
384384+385385+ (* keywords - map of string -> bool *)
386386+ let keywords = match require_field "keywords" fields with
387387+ | `O map_fields ->
388388+ List.map (fun (k, v) -> (k, expect_bool v)) map_fields
389389+ | _ -> raise (Jmap_core.Jmap_error.Parse_error "keywords must be an object")
390390+ in
391391+392392+ let size = Jmap_core.Jmap_primitives.UnsignedInt.of_json (require_field "size" fields) in
393393+ let received_at = Jmap_core.Jmap_primitives.UTCDate.of_json (require_field "receivedAt" fields) in
394394+395395+ (* Optional header fields *)
396396+ let message_id = match find_field "messageId" fields with
397397+ | Some (`A items) -> Some (List.map expect_string items)
398398+ | Some `Null | None -> None
399399+ | Some _ -> raise (Jmap_core.Jmap_error.Parse_error "messageId must be an array")
400400+ in
401401+ let in_reply_to = match find_field "inReplyTo" fields with
402402+ | Some (`A items) -> Some (List.map expect_string items)
403403+ | Some `Null | None -> None
404404+ | Some _ -> raise (Jmap_core.Jmap_error.Parse_error "inReplyTo must be an array")
405405+ in
406406+ let references = match find_field "references" fields with
407407+ | Some (`A items) -> Some (List.map expect_string items)
408408+ | Some `Null | None -> None
409409+ | Some _ -> raise (Jmap_core.Jmap_error.Parse_error "references must be an array")
410410+ in
411411+ let sender = match find_field "sender" fields with
412412+ | Some (`A items) -> Some (List.map EmailAddress.of_json items)
413413+ | Some `Null | None -> None
414414+ | Some _ -> raise (Jmap_core.Jmap_error.Parse_error "sender must be an array")
415415+ in
416416+ let from = match find_field "from" fields with
417417+ | Some (`A items) -> Some (List.map EmailAddress.of_json items)
418418+ | Some `Null | None -> None
419419+ | Some _ -> raise (Jmap_core.Jmap_error.Parse_error "from must be an array")
420420+ in
421421+ let to_ = match find_field "to" fields with
422422+ | Some (`A items) -> Some (List.map EmailAddress.of_json items)
423423+ | Some `Null | None -> None
424424+ | Some _ -> raise (Jmap_core.Jmap_error.Parse_error "to must be an array")
425425+ in
426426+ let cc = match find_field "cc" fields with
427427+ | Some (`A items) -> Some (List.map EmailAddress.of_json items)
428428+ | Some `Null | None -> None
429429+ | Some _ -> raise (Jmap_core.Jmap_error.Parse_error "cc must be an array")
430430+ in
431431+ let bcc = match find_field "bcc" fields with
432432+ | Some (`A items) -> Some (List.map EmailAddress.of_json items)
433433+ | Some `Null | None -> None
434434+ | Some _ -> raise (Jmap_core.Jmap_error.Parse_error "bcc must be an array")
435435+ in
436436+ let reply_to = match find_field "replyTo" fields with
437437+ | Some (`A items) -> Some (List.map EmailAddress.of_json items)
438438+ | Some `Null | None -> None
439439+ | Some _ -> raise (Jmap_core.Jmap_error.Parse_error "replyTo must be an array")
440440+ in
441441+ let subject = get_string_opt "subject" fields in
442442+ let sent_at = match find_field "sentAt" fields with
443443+ | Some (`String s) -> Some (Jmap_core.Jmap_primitives.Date.of_string s)
444444+ | Some `Null | None -> None
445445+ | Some _ -> raise (Jmap_core.Jmap_error.Parse_error "sentAt must be a string")
446446+ in
447447+448448+ (* Body properties *)
449449+ let body_structure = match find_field "bodyStructure" fields with
450450+ | Some ((`O _) as json) -> Some (BodyPart.of_json json)
451451+ | Some `Null | None -> None
452452+ | Some _ -> raise (Jmap_core.Jmap_error.Parse_error "bodyStructure must be an object")
453453+ in
454454+455455+ (* bodyValues - map of partId -> BodyValue *)
456456+ let body_values = match find_field "bodyValues" fields with
457457+ | Some (`O map_fields) ->
458458+ Some (List.map (fun (k, v) -> (k, BodyValue.of_json v)) map_fields)
459459+ | Some `Null | None -> None
460460+ | Some _ -> raise (Jmap_core.Jmap_error.Parse_error "bodyValues must be an object")
461461+ in
462462+463463+ let text_body = match find_field "textBody" fields with
464464+ | Some (`A items) -> Some (List.map BodyPart.of_json items)
465465+ | Some `Null | None -> None
466466+ | Some _ -> raise (Jmap_core.Jmap_error.Parse_error "textBody must be an array")
467467+ in
468468+ let html_body = match find_field "htmlBody" fields with
469469+ | Some (`A items) -> Some (List.map BodyPart.of_json items)
470470+ | Some `Null | None -> None
471471+ | Some _ -> raise (Jmap_core.Jmap_error.Parse_error "htmlBody must be an array")
472472+ in
473473+ let attachments = match find_field "attachments" fields with
474474+ | Some (`A items) -> Some (List.map BodyPart.of_json items)
475475+ | Some `Null | None -> None
476476+ | Some _ -> raise (Jmap_core.Jmap_error.Parse_error "attachments must be an array")
477477+ in
478478+479479+ let has_attachment = get_bool_opt "hasAttachment" fields false in
480480+ let preview = get_string "preview" fields in
481481+482482+ { id; blob_id; thread_id; mailbox_ids; keywords; size; received_at;
483483+ message_id; in_reply_to; references; sender; from; to_; cc; bcc;
484484+ reply_to; subject; sent_at; body_structure; body_values; text_body;
485485+ html_body; attachments; has_attachment; preview }
486486+487487+let to_json t =
488488+ let fields = [
489489+ ("id", Jmap_core.Jmap_id.to_json t.id);
490490+ ("blobId", Jmap_core.Jmap_id.to_json t.blob_id);
491491+ ("threadId", Jmap_core.Jmap_id.to_json t.thread_id);
492492+ ("mailboxIds", `O (List.map (fun (id, b) ->
493493+ (Jmap_core.Jmap_id.to_string id, `Bool b)) t.mailbox_ids));
494494+ ("keywords", `O (List.map (fun (k, b) -> (k, `Bool b)) t.keywords));
495495+ ("size", Jmap_core.Jmap_primitives.UnsignedInt.to_json t.size);
496496+ ("receivedAt", Jmap_core.Jmap_primitives.UTCDate.to_json t.received_at);
497497+ ("hasAttachment", `Bool t.has_attachment);
498498+ ("preview", `String t.preview);
499499+ ] in
500500+501501+ (* Add optional fields *)
502502+ let fields = match t.message_id with
503503+ | Some ids -> ("messageId", `A (List.map (fun s -> `String s) ids)) :: fields
504504+ | None -> fields
505505+ in
506506+ let fields = match t.in_reply_to with
507507+ | Some ids -> ("inReplyTo", `A (List.map (fun s -> `String s) ids)) :: fields
508508+ | None -> fields
509509+ in
510510+ let fields = match t.references with
511511+ | Some ids -> ("references", `A (List.map (fun s -> `String s) ids)) :: fields
512512+ | None -> fields
513513+ in
514514+ let fields = match t.sender with
515515+ | Some addrs -> ("sender", `A (List.map EmailAddress.to_json addrs)) :: fields
516516+ | None -> fields
517517+ in
518518+ let fields = match t.from with
519519+ | Some addrs -> ("from", `A (List.map EmailAddress.to_json addrs)) :: fields
520520+ | None -> fields
521521+ in
522522+ let fields = match t.to_ with
523523+ | Some addrs -> ("to", `A (List.map EmailAddress.to_json addrs)) :: fields
524524+ | None -> fields
525525+ in
526526+ let fields = match t.cc with
527527+ | Some addrs -> ("cc", `A (List.map EmailAddress.to_json addrs)) :: fields
528528+ | None -> fields
529529+ in
530530+ let fields = match t.bcc with
531531+ | Some addrs -> ("bcc", `A (List.map EmailAddress.to_json addrs)) :: fields
532532+ | None -> fields
533533+ in
534534+ let fields = match t.reply_to with
535535+ | Some addrs -> ("replyTo", `A (List.map EmailAddress.to_json addrs)) :: fields
536536+ | None -> fields
537537+ in
538538+ let fields = match t.subject with
539539+ | Some s -> ("subject", `String s) :: fields
540540+ | None -> fields
541541+ in
542542+ let fields = match t.sent_at with
543543+ | Some d -> ("sentAt", Jmap_core.Jmap_primitives.Date.to_json d) :: fields
544544+ | None -> fields
545545+ in
546546+ let fields = match t.body_structure with
547547+ | Some bs -> ("bodyStructure", BodyPart.to_json bs) :: fields
548548+ | None -> fields
549549+ in
550550+ let fields = match t.body_values with
551551+ | Some bv -> ("bodyValues", `O (List.map (fun (k, v) ->
552552+ (k, BodyValue.to_json v)) bv)) :: fields
553553+ | None -> fields
554554+ in
555555+ let fields = match t.text_body with
556556+ | Some tb -> ("textBody", `A (List.map BodyPart.to_json tb)) :: fields
557557+ | None -> fields
558558+ in
559559+ let fields = match t.html_body with
560560+ | Some hb -> ("htmlBody", `A (List.map BodyPart.to_json hb)) :: fields
561561+ | None -> fields
562562+ in
563563+ let fields = match t.attachments with
564564+ | Some att -> ("attachments", `A (List.map BodyPart.to_json att)) :: fields
565565+ | None -> fields
566566+ in
567567+ `O fields
568568+250569(** Email-specific filter for /query (RFC 8621 Section 4.4) *)
251570module Filter = struct
252571 type t = {
···272591 header : (string * string) list option; (** Header name contains value *)
273592 }
274593275275- let of_json _json =
276276- raise (Jmap_core.Jmap_error.Parse_error "Email.Filter.of_json not yet implemented")
594594+ let of_json json =
595595+ let open Jmap_core.Jmap_parser.Helpers in
596596+ let fields = expect_object json in
597597+ let in_mailbox = match find_field "inMailbox" fields with
598598+ | Some (`String s) -> Some (Jmap_core.Jmap_id.of_string s)
599599+ | Some `Null | None -> None
600600+ | Some _ -> raise (Jmap_core.Jmap_error.Parse_error "inMailbox must be a string")
601601+ in
602602+ let in_mailbox_other_than = match find_field "inMailboxOtherThan" fields with
603603+ | Some (`A items) -> Some (List.map (fun s -> Jmap_core.Jmap_id.of_json s) items)
604604+ | Some `Null | None -> None
605605+ | Some _ -> raise (Jmap_core.Jmap_error.Parse_error "inMailboxOtherThan must be an array")
606606+ in
607607+ let before = match find_field "before" fields with
608608+ | Some (`String s) -> Some (Jmap_core.Jmap_primitives.UTCDate.of_string s)
609609+ | Some `Null | None -> None
610610+ | Some _ -> raise (Jmap_core.Jmap_error.Parse_error "before must be a string")
611611+ in
612612+ let after = match find_field "after" fields with
613613+ | Some (`String s) -> Some (Jmap_core.Jmap_primitives.UTCDate.of_string s)
614614+ | Some `Null | None -> None
615615+ | Some _ -> raise (Jmap_core.Jmap_error.Parse_error "after must be a string")
616616+ in
617617+ let min_size = match find_field "minSize" fields with
618618+ | Some s -> Some (Jmap_core.Jmap_primitives.UnsignedInt.of_json s)
619619+ | None -> None
620620+ in
621621+ let max_size = match find_field "maxSize" fields with
622622+ | Some s -> Some (Jmap_core.Jmap_primitives.UnsignedInt.of_json s)
623623+ | None -> None
624624+ in
625625+ let all_in_thread_have_keyword = get_string_opt "allInThreadHaveKeyword" fields in
626626+ let some_in_thread_have_keyword = get_string_opt "someInThreadHaveKeyword" fields in
627627+ let none_in_thread_have_keyword = get_string_opt "noneInThreadHaveKeyword" fields in
628628+ let has_keyword = get_string_opt "hasKeyword" fields in
629629+ let not_keyword = get_string_opt "notKeyword" fields in
630630+ let has_attachment = match find_field "hasAttachment" fields with
631631+ | Some (`Bool b) -> Some b
632632+ | Some `Null | None -> None
633633+ | Some _ -> raise (Jmap_core.Jmap_error.Parse_error "hasAttachment must be a boolean")
634634+ in
635635+ let text = get_string_opt "text" fields in
636636+ let from = get_string_opt "from" fields in
637637+ let to_ = get_string_opt "to" fields in
638638+ let cc = get_string_opt "cc" fields in
639639+ let bcc = get_string_opt "bcc" fields in
640640+ let subject = get_string_opt "subject" fields in
641641+ let body = get_string_opt "body" fields in
642642+ let header = match find_field "header" fields with
643643+ | Some (`A items) ->
644644+ Some (List.map (fun item ->
645645+ let hdr_fields = expect_object item in
646646+ let name = get_string "name" hdr_fields in
647647+ let value = get_string "value" hdr_fields in
648648+ (name, value)
649649+ ) items)
650650+ | Some `Null | None -> None
651651+ | Some _ -> raise (Jmap_core.Jmap_error.Parse_error "header must be an array")
652652+ in
653653+ { in_mailbox; in_mailbox_other_than; before; after; min_size; max_size;
654654+ all_in_thread_have_keyword; some_in_thread_have_keyword;
655655+ none_in_thread_have_keyword; has_keyword; not_keyword; has_attachment;
656656+ text; from; to_; cc; bcc; subject; body; header }
277657278658 (* Accessors *)
279659 let in_mailbox t = t.in_mailbox
···346726 - test/data/mail/email_get_request.json
347727 - test/data/mail/email_get_full_request.json
348728 *)
349349- let request_of_json _json =
350350- raise (Jmap_core.Jmap_error.Parse_error "Email.Get.request_of_json not yet implemented")
729729+ let request_of_json json =
730730+ let open Jmap_core.Jmap_parser.Helpers in
731731+ let fields = expect_object json in
732732+ let account_id = Jmap_core.Jmap_id.of_json (require_field "accountId" fields) in
733733+ let ids = match find_field "ids" fields with
734734+ | Some (`A items) -> Some (List.map Jmap_core.Jmap_id.of_json items)
735735+ | Some `Null | None -> None
736736+ | Some _ -> raise (Jmap_core.Jmap_error.Parse_error "ids must be an array")
737737+ in
738738+ let properties = match find_field "properties" fields with
739739+ | Some (`A items) -> Some (List.map expect_string items)
740740+ | Some `Null | None -> None
741741+ | Some _ -> raise (Jmap_core.Jmap_error.Parse_error "properties must be an array")
742742+ in
743743+ let body_properties = match find_field "bodyProperties" fields with
744744+ | Some (`A items) -> Some (List.map expect_string items)
745745+ | Some `Null | None -> None
746746+ | Some _ -> raise (Jmap_core.Jmap_error.Parse_error "bodyProperties must be an array")
747747+ in
748748+ let fetch_text_body_values = match find_field "fetchTextBodyValues" fields with
749749+ | Some (`Bool b) -> Some b
750750+ | Some `Null | None -> None
751751+ | Some _ -> raise (Jmap_core.Jmap_error.Parse_error "fetchTextBodyValues must be a boolean")
752752+ in
753753+ let fetch_html_body_values = match find_field "fetchHTMLBodyValues" fields with
754754+ | Some (`Bool b) -> Some b
755755+ | Some `Null | None -> None
756756+ | Some _ -> raise (Jmap_core.Jmap_error.Parse_error "fetchHTMLBodyValues must be a boolean")
757757+ in
758758+ let fetch_all_body_values = match find_field "fetchAllBodyValues" fields with
759759+ | Some (`Bool b) -> Some b
760760+ | Some `Null | None -> None
761761+ | Some _ -> raise (Jmap_core.Jmap_error.Parse_error "fetchAllBodyValues must be a boolean")
762762+ in
763763+ let max_body_value_bytes = match find_field "maxBodyValueBytes" fields with
764764+ | Some v -> Some (Jmap_core.Jmap_primitives.UnsignedInt.of_json v)
765765+ | None -> None
766766+ in
767767+ { account_id; ids; properties; body_properties; fetch_text_body_values;
768768+ fetch_html_body_values; fetch_all_body_values; max_body_value_bytes }
351769352770 (** Parse get response from JSON.
353771 Test files:
354772 - test/data/mail/email_get_response.json
355773 - test/data/mail/email_get_full_response.json
356774 *)
357357- let response_of_json _json =
358358- raise (Jmap_core.Jmap_error.Parse_error "Email.Get.response_of_json not yet implemented")
775775+ let response_of_json json =
776776+ Jmap_core.Jmap_standard_methods.Get.response_of_json of_json json
359777end
360778361779(** Standard /changes method (RFC 8621 Section 4.3) *)
···363781 type request = Jmap_core.Jmap_standard_methods.Changes.request
364782 type response = Jmap_core.Jmap_standard_methods.Changes.response
365783366366- let request_of_json _json =
367367- raise (Jmap_core.Jmap_error.Parse_error "Email.Changes.request_of_json not yet implemented")
784784+ let request_of_json json =
785785+ Jmap_core.Jmap_standard_methods.Changes.request_of_json json
368786369369- let response_of_json _json =
370370- raise (Jmap_core.Jmap_error.Parse_error "Email.Changes.response_of_json not yet implemented")
787787+ let response_of_json json =
788788+ Jmap_core.Jmap_standard_methods.Changes.response_of_json json
371789end
372790373791(** Standard /query method (RFC 8621 Section 4.4) *)
···406824407825 (** Parse query request from JSON.
408826 Test files: test/data/mail/email_query_request.json *)
409409- let request_of_json _json =
410410- raise (Jmap_core.Jmap_error.Parse_error "Email.Query.request_of_json not yet implemented")
827827+ let request_of_json json =
828828+ let open Jmap_core.Jmap_parser.Helpers in
829829+ let fields = expect_object json in
830830+ let account_id = Jmap_core.Jmap_id.of_json (require_field "accountId" fields) in
831831+ let filter = match find_field "filter" fields with
832832+ | Some v -> Some (Jmap_core.Jmap_filter.of_json Filter.of_json v)
833833+ | None -> None
834834+ in
835835+ let sort = match find_field "sort" fields with
836836+ | Some (`A items) -> Some (List.map Jmap_core.Jmap_comparator.of_json items)
837837+ | Some `Null | None -> None
838838+ | Some _ -> raise (Jmap_core.Jmap_error.Parse_error "sort must be an array")
839839+ in
840840+ let position = match find_field "position" fields with
841841+ | Some v -> Some (Jmap_core.Jmap_primitives.Int53.of_json v)
842842+ | None -> None
843843+ in
844844+ let anchor = match find_field "anchor" fields with
845845+ | Some (`String s) -> Some (Jmap_core.Jmap_id.of_string s)
846846+ | Some `Null | None -> None
847847+ | Some _ -> raise (Jmap_core.Jmap_error.Parse_error "anchor must be a string")
848848+ in
849849+ let anchor_offset = match find_field "anchorOffset" fields with
850850+ | Some v -> Some (Jmap_core.Jmap_primitives.Int53.of_json v)
851851+ | None -> None
852852+ in
853853+ let limit = match find_field "limit" fields with
854854+ | Some v -> Some (Jmap_core.Jmap_primitives.UnsignedInt.of_json v)
855855+ | None -> None
856856+ in
857857+ let calculate_total = match find_field "calculateTotal" fields with
858858+ | Some (`Bool b) -> Some b
859859+ | Some `Null | None -> None
860860+ | Some _ -> raise (Jmap_core.Jmap_error.Parse_error "calculateTotal must be a boolean")
861861+ in
862862+ let collapse_threads = match find_field "collapseThreads" fields with
863863+ | Some (`Bool b) -> Some b
864864+ | Some `Null | None -> None
865865+ | Some _ -> raise (Jmap_core.Jmap_error.Parse_error "collapseThreads must be a boolean")
866866+ in
867867+ { account_id; filter; sort; position; anchor; anchor_offset;
868868+ limit; calculate_total; collapse_threads }
411869412870 (** Parse query response from JSON.
413871 Test files: test/data/mail/email_query_response.json *)
414414- let response_of_json _json =
415415- raise (Jmap_core.Jmap_error.Parse_error "Email.Query.response_of_json not yet implemented")
872872+ let response_of_json json =
873873+ Jmap_core.Jmap_standard_methods.Query.response_of_json json
416874end
417875418876(** Standard /queryChanges method (RFC 8621 Section 4.5) *)
···447905 { account_id; filter; sort; since_query_state; max_changes;
448906 up_to_id; calculate_total; collapse_threads }
449907450450- let request_of_json _json =
451451- raise (Jmap_core.Jmap_error.Parse_error "Email.QueryChanges.request_of_json not yet implemented")
908908+ let request_of_json json =
909909+ let open Jmap_core.Jmap_parser.Helpers in
910910+ let fields = expect_object json in
911911+ let account_id = Jmap_core.Jmap_id.of_json (require_field "accountId" fields) in
912912+ let filter = match find_field "filter" fields with
913913+ | Some v -> Some (Jmap_core.Jmap_filter.of_json Filter.of_json v)
914914+ | None -> None
915915+ in
916916+ let sort = match find_field "sort" fields with
917917+ | Some (`A items) -> Some (List.map Jmap_core.Jmap_comparator.of_json items)
918918+ | Some `Null | None -> None
919919+ | Some _ -> raise (Jmap_core.Jmap_error.Parse_error "sort must be an array")
920920+ in
921921+ let since_query_state = get_string "sinceQueryState" fields in
922922+ let max_changes = match find_field "maxChanges" fields with
923923+ | Some v -> Some (Jmap_core.Jmap_primitives.UnsignedInt.of_json v)
924924+ | None -> None
925925+ in
926926+ let up_to_id = match find_field "upToId" fields with
927927+ | Some (`String s) -> Some (Jmap_core.Jmap_id.of_string s)
928928+ | Some `Null | None -> None
929929+ | Some _ -> raise (Jmap_core.Jmap_error.Parse_error "upToId must be a string")
930930+ in
931931+ let calculate_total = match find_field "calculateTotal" fields with
932932+ | Some (`Bool b) -> Some b
933933+ | Some `Null | None -> None
934934+ | Some _ -> raise (Jmap_core.Jmap_error.Parse_error "calculateTotal must be a boolean")
935935+ in
936936+ let collapse_threads = match find_field "collapseThreads" fields with
937937+ | Some (`Bool b) -> Some b
938938+ | Some `Null | None -> None
939939+ | Some _ -> raise (Jmap_core.Jmap_error.Parse_error "collapseThreads must be a boolean")
940940+ in
941941+ { account_id; filter; sort; since_query_state; max_changes;
942942+ up_to_id; calculate_total; collapse_threads }
452943453453- let response_of_json _json =
454454- raise (Jmap_core.Jmap_error.Parse_error "Email.QueryChanges.response_of_json not yet implemented")
944944+ let response_of_json json =
945945+ Jmap_core.Jmap_standard_methods.QueryChanges.response_of_json json
455946end
456947457948(** Standard /set method (RFC 8621 Section 4.6) *)
···461952462953 (** Parse set request from JSON.
463954 Test files: test/data/mail/email_set_request.json *)
464464- let request_of_json _json =
465465- raise (Jmap_core.Jmap_error.Parse_error "Email.Set.request_of_json not yet implemented")
955955+ let request_of_json json =
956956+ Jmap_core.Jmap_standard_methods.Set.request_of_json of_json json
466957467958 (** Parse set response from JSON.
468959 Test files: test/data/mail/email_set_response.json *)
469469- let response_of_json _json =
470470- raise (Jmap_core.Jmap_error.Parse_error "Email.Set.response_of_json not yet implemented")
960960+ let response_of_json json =
961961+ Jmap_core.Jmap_standard_methods.Set.response_of_json of_json json
471962end
472963473964(** Standard /copy method (RFC 8621 Section 4.7) *)
···475966 type request = t Jmap_core.Jmap_standard_methods.Copy.request
476967 type response = t Jmap_core.Jmap_standard_methods.Copy.response
477968478478- let request_of_json _json =
479479- raise (Jmap_core.Jmap_error.Parse_error "Email.Copy.request_of_json not yet implemented")
969969+ let request_of_json json =
970970+ Jmap_core.Jmap_standard_methods.Copy.request_of_json of_json json
480971481481- let response_of_json _json =
482482- raise (Jmap_core.Jmap_error.Parse_error "Email.Copy.response_of_json not yet implemented")
972972+ let response_of_json json =
973973+ Jmap_core.Jmap_standard_methods.Copy.response_of_json of_json json
483974end
484975485976(** Email/import method (RFC 8621 Section 4.8) *)
···53810295391030 (** Parse import request from JSON.
5401031 Test files: test/data/mail/email_import_request.json *)
541541- let request_of_json _json =
542542- raise (Jmap_core.Jmap_error.Parse_error "Email.Import.request_of_json not yet implemented")
10321032+ let request_of_json json =
10331033+ let open Jmap_core.Jmap_parser.Helpers in
10341034+ let fields = expect_object json in
10351035+ let account_id = Jmap_core.Jmap_id.of_json (require_field "accountId" fields) in
10361036+ let if_in_state = get_string_opt "ifInState" fields in
10371037+ let emails = match require_field "emails" fields with
10381038+ | `O pairs ->
10391039+ List.map (fun (k, v) ->
10401040+ let ie_fields = expect_object v in
10411041+ let blob_id = Jmap_core.Jmap_id.of_json (require_field "blobId" ie_fields) in
10421042+ let mailbox_ids = match require_field "mailboxIds" ie_fields with
10431043+ | `O map_fields ->
10441044+ List.map (fun (mid, b) ->
10451045+ (Jmap_core.Jmap_id.of_string mid, expect_bool b)
10461046+ ) map_fields
10471047+ | _ -> raise (Jmap_core.Jmap_error.Parse_error "mailboxIds must be an object")
10481048+ in
10491049+ let keywords = match require_field "keywords" ie_fields with
10501050+ | `O map_fields ->
10511051+ List.map (fun (kw, b) -> (kw, expect_bool b)) map_fields
10521052+ | _ -> raise (Jmap_core.Jmap_error.Parse_error "keywords must be an object")
10531053+ in
10541054+ let received_at = match find_field "receivedAt" ie_fields with
10551055+ | Some (`String s) -> Some (Jmap_core.Jmap_primitives.UTCDate.of_string s)
10561056+ | Some `Null | None -> None
10571057+ | Some _ -> raise (Jmap_core.Jmap_error.Parse_error "receivedAt must be a string")
10581058+ in
10591059+ let import_email = { blob_id; mailbox_ids; keywords; received_at } in
10601060+ (Jmap_core.Jmap_id.of_string k, import_email)
10611061+ ) pairs
10621062+ | _ -> raise (Jmap_core.Jmap_error.Parse_error "emails must be an object")
10631063+ in
10641064+ { account_id; if_in_state; emails }
54310655441066 (** Parse import response from JSON.
5451067 Test files: test/data/mail/email_import_response.json *)
546546- let response_of_json _json =
547547- raise (Jmap_core.Jmap_error.Parse_error "Email.Import.response_of_json not yet implemented")
10681068+ let response_of_json json =
10691069+ let open Jmap_core.Jmap_parser.Helpers in
10701070+ let fields = expect_object json in
10711071+ let account_id = Jmap_core.Jmap_id.of_json (require_field "accountId" fields) in
10721072+ let old_state = get_string_opt "oldState" fields in
10731073+ let new_state = get_string "newState" fields in
10741074+ let created = match find_field "created" fields with
10751075+ | Some `Null | None -> None
10761076+ | Some (`O pairs) ->
10771077+ Some (List.map (fun (k, v) ->
10781078+ (Jmap_core.Jmap_id.of_string k, of_json v)
10791079+ ) pairs)
10801080+ | Some _ -> raise (Jmap_core.Jmap_error.Parse_error "created must be an object")
10811081+ in
10821082+ let not_created = match find_field "notCreated" fields with
10831083+ | Some `Null | None -> None
10841084+ | Some (`O pairs) ->
10851085+ Some (List.map (fun (k, v) ->
10861086+ (Jmap_core.Jmap_id.of_string k, Jmap_core.Jmap_error.parse_set_error_detail v)
10871087+ ) pairs)
10881088+ | Some _ -> raise (Jmap_core.Jmap_error.Parse_error "notCreated must be an object")
10891089+ in
10901090+ { account_id; old_state; new_state; created; not_created }
5481091end
54910925501093(** Email/parse method (RFC 8621 Section 4.9) *)
···59611395971140 (** Parse parse request from JSON.
5981141 Test files: test/data/mail/email_parse_request.json *)
599599- let request_of_json _json =
600600- raise (Jmap_core.Jmap_error.Parse_error "Email.Parse.request_of_json not yet implemented")
11421142+ let request_of_json json =
11431143+ let open Jmap_core.Jmap_parser.Helpers in
11441144+ let fields = expect_object json in
11451145+ let account_id = Jmap_core.Jmap_id.of_json (require_field "accountId" fields) in
11461146+ let blob_ids = match require_field "blobIds" fields with
11471147+ | `A items -> List.map Jmap_core.Jmap_id.of_json items
11481148+ | _ -> raise (Jmap_core.Jmap_error.Parse_error "blobIds must be an array")
11491149+ in
11501150+ let properties = match find_field "properties" fields with
11511151+ | Some (`A items) -> Some (List.map expect_string items)
11521152+ | Some `Null | None -> None
11531153+ | Some _ -> raise (Jmap_core.Jmap_error.Parse_error "properties must be an array")
11541154+ in
11551155+ let body_properties = match find_field "bodyProperties" fields with
11561156+ | Some (`A items) -> Some (List.map expect_string items)
11571157+ | Some `Null | None -> None
11581158+ | Some _ -> raise (Jmap_core.Jmap_error.Parse_error "bodyProperties must be an array")
11591159+ in
11601160+ let fetch_text_body_values = match find_field "fetchTextBodyValues" fields with
11611161+ | Some (`Bool b) -> Some b
11621162+ | Some `Null | None -> None
11631163+ | Some _ -> raise (Jmap_core.Jmap_error.Parse_error "fetchTextBodyValues must be a boolean")
11641164+ in
11651165+ let fetch_html_body_values = match find_field "fetchHTMLBodyValues" fields with
11661166+ | Some (`Bool b) -> Some b
11671167+ | Some `Null | None -> None
11681168+ | Some _ -> raise (Jmap_core.Jmap_error.Parse_error "fetchHTMLBodyValues must be a boolean")
11691169+ in
11701170+ let fetch_all_body_values = match find_field "fetchAllBodyValues" fields with
11711171+ | Some (`Bool b) -> Some b
11721172+ | Some `Null | None -> None
11731173+ | Some _ -> raise (Jmap_core.Jmap_error.Parse_error "fetchAllBodyValues must be a boolean")
11741174+ in
11751175+ let max_body_value_bytes = match find_field "maxBodyValueBytes" fields with
11761176+ | Some v -> Some (Jmap_core.Jmap_primitives.UnsignedInt.of_json v)
11771177+ | None -> None
11781178+ in
11791179+ { account_id; blob_ids; properties; body_properties; fetch_text_body_values;
11801180+ fetch_html_body_values; fetch_all_body_values; max_body_value_bytes }
60111816021182 (** Parse parse response from JSON.
6031183 Test files: test/data/mail/email_parse_response.json *)
604604- let response_of_json _json =
605605- raise (Jmap_core.Jmap_error.Parse_error "Email.Parse.response_of_json not yet implemented")
11841184+ let response_of_json json =
11851185+ let open Jmap_core.Jmap_parser.Helpers in
11861186+ let fields = expect_object json in
11871187+ let account_id = Jmap_core.Jmap_id.of_json (require_field "accountId" fields) in
11881188+ let parsed = match find_field "parsed" fields with
11891189+ | Some `Null | None -> None
11901190+ | Some (`O pairs) ->
11911191+ Some (List.map (fun (k, v) ->
11921192+ (Jmap_core.Jmap_id.of_string k, of_json v)
11931193+ ) pairs)
11941194+ | Some _ -> raise (Jmap_core.Jmap_error.Parse_error "parsed must be an object")
11951195+ in
11961196+ let not_parsable = match find_field "notParsable" fields with
11971197+ | Some (`A items) -> Some (List.map Jmap_core.Jmap_id.of_json items)
11981198+ | Some `Null | None -> None
11991199+ | Some _ -> raise (Jmap_core.Jmap_error.Parse_error "notParsable must be an array")
12001200+ in
12011201+ let not_found = match find_field "notFound" fields with
12021202+ | Some (`A items) -> Some (List.map Jmap_core.Jmap_id.of_json items)
12031203+ | Some `Null | None -> None
12041204+ | Some _ -> raise (Jmap_core.Jmap_error.Parse_error "notFound must be an array")
12051205+ in
12061206+ { account_id; parsed; not_parsable; not_found }
6061207end
6071208608608-(** Parser submodule *)
609609-module Parser = struct
610610- (** Parse Email from JSON.
611611- Test files: test/data/mail/email_get_response.json (list field)
612612-613613- Expected structure:
614614- {
615615- "id": "e001",
616616- "blobId": "Ge5f13e2d7b8a9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8",
617617- "threadId": "t001",
618618- "mailboxIds": { "mb001": true },
619619- "keywords": { "$seen": true },
620620- "size": 15234,
621621- "receivedAt": "2025-10-05T09:15:30Z",
622622- ...
623623- }
624624- *)
625625- let of_json _json =
626626- (* TODO: Implement JSON parsing *)
627627- raise (Jmap_core.Jmap_error.Parse_error "Email.Parser.of_json not yet implemented")
628628-629629- let to_json _t =
630630- (* TODO: Implement JSON serialization *)
631631- raise (Jmap_core.Jmap_error.Parse_error "Email.Parser.to_json not yet implemented")
632632-end
63312096341210(** Standard email keywords (RFC 8621 Section 4.1.1) *)
6351211module Keyword = struct
···6421218 let junk = "$junk" (* Message is junk/spam *)
6431219 let notjunk = "$notjunk" (* Message is definitely not junk *)
6441220end
12211221+12221222+(** Parser submodule *)
12231223+module Parser = struct
12241224+ let of_json = of_json
12251225+ let to_json = to_json
12261226+end