···5566let fetch_recent_emails env ctx session =
77 try
88- let account_id = Jmap_unix.Session_utils.get_primary_mail_account session in
99- printf "Using account: %s\nBuilding JMAP request using type-safe capabilities...\n" account_id;
88+ let account_id_str = Jmap_unix.Session_utils.get_primary_mail_account session in
99+ let account_id = match Jmap.Id.of_string account_id_str with
1010+ | Ok id -> id
1111+ | Error err -> failwith ("Invalid account ID: " ^ err) in
1212+ printf "Using account: %s\nBuilding JMAP request using type-safe capabilities...\n" account_id_str;
10131114 let query_json =
1215 Jmap_email.Query.(query () |> with_account account_id |> order_by Sort.by_date_desc |> limit 5 |> build_email_query) in
···5659 printf " Subject: %s\n" (Jmap_email.Email.subject email |> Option.value ~default:"(No Subject)");
5760 print_sender email;
5861 Jmap_email.Email.(received_at email |> Option.iter (fun t ->
5959- printf " Date: %s\n" Jmap.Types.Date.(of_timestamp t |> to_rfc3339)));
6262+ printf " Date: %s\n" (Jmap.Date.to_rfc3339 t)));
6063 print_preview email
6164 ) emails;
6265 printf "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n";
···7780 printf "Testing core JMAP modules...\n";
78817982 let test_modules = [
8080- ("Jmap.Types.Id", Jmap.Types.Id.(of_string "test-id-123" |> Result.map (Format.asprintf "%a" pp)));
8181- ("Jmap.Types.Date", Ok (Jmap.Types.Date.(Unix.time () |> of_timestamp |> to_timestamp |> Printf.sprintf "%.0f")));
8282- ("Jmap.Types.UInt", Jmap.Types.UInt.(of_int 42 |> Result.map (Format.asprintf "%a" pp)));
8383+ ("Jmap.Id", Jmap.Id.(of_string "test-id-123" |> Result.map (Format.asprintf "%a" pp)));
8484+ ("Jmap.Date", Ok (Jmap.Date.(Unix.time () |> of_timestamp |> to_timestamp |> Printf.sprintf "%.0f")));
8585+ ("Jmap.UInt", Jmap.UInt.(of_int 42 |> Result.map (Format.asprintf "%a" pp)));
8386 ] in
84878588 let test_results = List.map (fun (name, result) -> match result with
+2-2
jmap/examples/mailboxes_client.ml
···5353 ~account_id
5454 ~name:"Revolutionary Test Folder"
5555 ~role:None () in
5656- printf "✅ Created mailbox: %s\n\n" (Jmap.Types.Id.to_string test_mailbox_id);
5656+ printf "✅ Created mailbox: %s\n\n" (stringo_string test_mailbox_id);
57575858 (* Create child mailbox with hierarchy *)
5959 printf "📂 Creating child mailbox...\n";
···6161 ~account_id
6262 ~name:"Test Subfolder"
6363 ~parent_id:test_mailbox_id () in
6464- printf "✅ Created child mailbox: %s\n\n" (Jmap.Types.Id.to_string child_mailbox_id);
6464+ printf "✅ Created child mailbox: %s\n\n" (stringo_string child_mailbox_id);
65656666 (* Query only user-created mailboxes *)
6767 printf "🔍 Querying user-created mailboxes...\n";
+3-3
jmap/examples/messages_client.ml
···5656 in
57575858 let trash_id = inbox_id in (* Simplified - would normally find actual Trash *)
5959- printf "✅ Found Inbox: %s\n" (Jmap.Types.Id.to_string inbox_id);
6060- printf "✅ Found Trash: %s\n\n" (Jmap.Types.Id.to_string trash_id);
5959+ printf "✅ Found Inbox: %s\n" (stringo_string inbox_id);
6060+ printf "✅ Found Trash: %s\n\n" (stringo_string trash_id);
61616262 (* Import message - revolutionary single line *)
6363 printf "📥 Importing test message...\n";
···6868 ~keywords:["$draft"] () in
69697070 let email_id = Jmap_email.Email.id imported_email |> Option.get in
7171- printf "✅ Imported email: %s\n\n" (Jmap.Types.Id.to_string email_id);
7171+ printf "✅ Imported email: %s\n\n" (stringo_string email_id);
72727373 (* Query for our test message - revolutionary filtering *)
7474 printf "🔍 Querying for test messages...\n";
+3-4
jmap/jmap-email/apple.ml
···44 flag encoding defined in draft-ietf-mailmaint-messageflag.
55*)
6677-open Types
8798(** Apple Mail color flag enumeration *)
109type color =
···6968 Jmap.Methods.Filter.operator `AND [bit0_filter; bit1_filter; bit2_filter]
7069 | [single_keyword] ->
7170 (* Single keyword filter *)
7272- let keyword_str = Keywords.to_string single_keyword in
7171+ let keyword_str = Keywords.keyword_to_string single_keyword in
7372 Jmap.Methods.Filter.condition (`Assoc [("hasKeyword", `String keyword_str)])
7473 | multiple_keywords ->
7574 (* Multiple keywords - create AND filter *)
7675 let keyword_filters = List.map (fun kw ->
7777- let keyword_str = Keywords.to_string kw in
7676+ let keyword_str = Keywords.keyword_to_string kw in
7877 Jmap.Methods.Filter.condition (`Assoc [("hasKeyword", `String keyword_str)])
7978 ) multiple_keywords in
8079 Jmap.Methods.Filter.operator `AND keyword_filters
···8887 ] in
8988 let color_keywords = color_keywords color in
9089 let set_patches = List.map (fun kw ->
9191- let keyword_str = Keywords.to_string kw in
9090+ let keyword_str = Keywords.keyword_to_string kw in
9291 ("keywords/" ^ keyword_str, `Bool true)
9392 ) color_keywords in
9493 clear_patches @ set_patches
-1
jmap/jmap-email/apple.mli
···1212 @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-2.6> RFC 8621 Keywords
1313*)
14141515-open Types
16151716(** Apple Mail color flag enumeration.
1817
+12-10
jmap/jmap-email/body.ml
···77 @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-4.1.4> RFC 8621, Section 4.1.4
88*)
991010-open Jmap.Types
1111-1210type t = {
1311 id : string option;
1414- blob_id : id option;
1515- size : uint;
1212+ blob_id : Jmap.Id.t option;
1313+ size : Jmap.UInt.t;
1614 headers : Header.t list;
1715 name : string option;
1816 mime_type : string;
···2220 language : string list option;
2321 location : string option;
2422 sub_parts : t list option;
2525- other_headers : Yojson.Safe.t string_map;
2323+ other_headers : (string, Yojson.Safe.t) Hashtbl.t;
2624}
27252826let id t = t.id
···118116119117let rec to_json t =
120118 let fields = [
121121- ("size", `Int t.size);
119119+ ("size", `Int (Jmap.UInt.to_int t.size));
122120 ("headers", Header.list_to_json t.headers);
123121 ("type", `String t.mime_type);
124122 ] in
···131129 | None -> fields
132130 in
133131 let fields = add_opt_string fields "partId" t.id in
134134- let fields = add_opt_string fields "blobId" t.blob_id in
132132+ let fields = add_opt_string fields "blobId" (Option.map Jmap.Id.to_string t.blob_id) in
135133 let fields = add_opt_string fields "name" t.name in
136134 let fields = add_opt_string fields "charset" t.charset in
137135 let fields = add_opt_string fields "disposition" t.disposition in
···153151 | `Assoc fields ->
154152 (try
155153 let size = match List.assoc_opt "size" fields with
156156- | Some (`Int s) -> s
154154+ | Some (`Int s) -> (match Jmap.UInt.of_int s with
155155+ | Ok uint -> uint
156156+ | Error _ -> failwith ("Invalid size: " ^ string_of_int s))
157157 | _ -> failwith "Missing or invalid size field"
158158 in
159159 let headers = match List.assoc_opt "headers" fields with
···173173 | _ -> failwith "Invalid partId field"
174174 in
175175 let blob_id = match List.assoc_opt "blobId" fields with
176176- | Some (`String s) -> Some s
176176+ | Some (`String s) -> (match Jmap.Id.of_string s with
177177+ | Ok id_t -> Some id_t
178178+ | Error _ -> failwith ("Invalid blob_id: " ^ s))
177179 | Some `Null | None -> None
178180 | _ -> failwith "Invalid blobId field"
179181 in
···296298 Format.fprintf fmt "BodyPart{id=%s;mime_type=%s;size=%d}"
297299 (match t.id with Some s -> s | None -> "none")
298300 t.mime_type
299299- t.size
301301+ (Jmap.UInt.to_int t.size)
300302301303let pp_hum fmt t = pp fmt t
+12-13
jmap/jmap-email/body.mli
···1111 @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-4.1.4> RFC 8621, Section 4.1.4 - Email Body Structure
1212*)
13131414-open Jmap.Types
15141615(** Email body part representation.
1716···3534(** Get the blob ID for downloading the part content.
3635 @param t The body part
3736 @return Blob identifier for content access, or None for multipart types *)
3838-val blob_id : t -> id option
3737+val blob_id : t -> Jmap.Id.t option
39384039(** Get the size of the part in bytes.
4140 @param t The body part
4241 @return Size in bytes of the decoded content *)
4343-val size : t -> uint
4242+val size : t -> Jmap.UInt.t
44434544(** Get the list of MIME headers for this part.
4645 @param t The body part
···9089(** Get additional headers requested via header properties.
9190 @param t The body part
9291 @return Map of header names to their JSON values for extended header access *)
9393-val other_headers : t -> Yojson.Safe.t string_map
9292+val other_headers : t -> (string, Yojson.Safe.t) Hashtbl.t
94939594(** Create a new body part object.
96959796 Creates a body part with validation of required fields and proper MIME structure.
9898- Either id+blob_id (for leaf parts) or sub_parts (for multipart) should be provided,
9797+ Either Jmap.Id.t+blob_id (for leaf parts) or sub_parts (for multipart) should be provided,
9998 but not both.
10099101101- @param id Optional part identifier for leaf parts
100100+ @param Jmap.Id.t Optional part identifier for leaf parts
102101 @param blob_id Optional blob ID for content access
103102 @param size Size in bytes of decoded content
104103 @param headers List of MIME headers for this part
···114113 @return Result containing new body part or validation error *)
115114val create :
116115 ?id:string ->
117117- ?blob_id:id ->
118118- size:uint ->
116116+ ?blob_id:Jmap.Id.t ->
117117+ size:Jmap.UInt.t ->
119118 headers:Header.t list ->
120119 ?name:string ->
121120 mime_type:string ->
···125124 ?language:string list ->
126125 ?location:string ->
127126 ?sub_parts:t list ->
128128- ?other_headers:Yojson.Safe.t string_map ->
127127+ ?other_headers:(string, Yojson.Safe.t) Hashtbl.t ->
129128 unit -> (t, string) result
130129131130(** Create a new body part object without validation.
···133132 For use when body parts are known to be valid or come from trusted sources
134133 like server responses.
135134136136- @param id Optional part identifier for leaf parts
135135+ @param Jmap.Id.t Optional part identifier for leaf parts
137136 @param blob_id Optional blob ID for content access
138137 @param size Size in bytes of decoded content
139138 @param headers List of MIME headers for this part
···149148 @return New body part object *)
150149val create_unsafe :
151150 ?id:string ->
152152- ?blob_id:id ->
153153- size:uint ->
151151+ ?blob_id:Jmap.Id.t ->
152152+ size:Jmap.UInt.t ->
154153 headers:Header.t list ->
155154 ?name:string ->
156155 mime_type:string ->
···160159 ?language:string list ->
161160 ?location:string ->
162161 ?sub_parts:t list ->
163163- ?other_headers:Yojson.Safe.t string_map ->
162162+ ?other_headers:(string, Yojson.Safe.t) Hashtbl.t ->
164163 unit -> t
165164166165(** Check if body part is a multipart container.
+27-21
jmap/jmap-email/changes.ml
···11(** Email changes operations using core JMAP Changes_args *)
2233-open Jmap.Types
43open Jmap.Methods
5465(** Build Email/changes arguments *)
76let build_changes_args ~account_id ~since_state ?max_changes () =
77+ let account_id_str = Jmap.Id.to_string account_id in
88+ let max_changes_int = match max_changes with
99+ | Some uint -> Some (Jmap.UInt.to_int uint)
1010+ | None -> None in
811 Changes_args.v
99- ~account_id
1212+ ~account_id:account_id_str
1013 ~since_state
1111- ?max_changes
1414+ ?max_changes:max_changes_int
1215 ()
13161417(** Convert Email/changes arguments to JSON *)
···17201821(** Track changes since a given state *)
1922type change_tracker = {
2020- account_id : id;
2323+ account_id : Jmap.Id.t;
2124 current_state : string;
2222- created : id list;
2323- updated : id list;
2424- destroyed : id list;
2525+ created : Jmap.Id.t list;
2626+ updated : Jmap.Id.t list;
2727+ destroyed : Jmap.Id.t list;
2528}
26292730(** Create a new change tracker *)
···3942 {
4043 tracker with
4144 current_state = Changes_response.new_state response;
4242- created = tracker.created @ Changes_response.created response;
4343- updated = tracker.updated @ Changes_response.updated response;
4444- destroyed = tracker.destroyed @ Changes_response.destroyed response;
4545+ created = tracker.created @ (List.map (fun s -> match Jmap.Id.of_string s with Ok id -> id | Error e -> failwith e) (Changes_response.created response));
4646+ updated = tracker.updated @ (List.map (fun s -> match Jmap.Id.of_string s with Ok id -> id | Error e -> failwith e) (Changes_response.updated response));
4747+ destroyed = tracker.destroyed @ (List.map (fun s -> match Jmap.Id.of_string s with Ok id -> id | Error e -> failwith e) (Changes_response.destroyed response));
4548 }
46494750(** Get all changes since tracker was created *)
···50535154(** Get next batch of changes *)
5255let get_next_changes ~account_id ~since_state ?(max_changes=500) () =
5353- build_changes_args ~account_id ~since_state ~max_changes ()
5656+ let max_changes_uint = match Jmap.UInt.of_int max_changes with
5757+ | Ok u -> u
5858+ | Error _ -> failwith ("Invalid max_changes: " ^ string_of_int max_changes) in
5959+ build_changes_args ~account_id ~since_state ~max_changes:max_changes_uint ()
54605561(** Check if there are pending changes *)
5662let has_pending_changes response =
···5965(** Incremental sync helper *)
6066module Sync = struct
6167 type sync_state = {
6262- account_id : id;
6868+ account_id : Jmap.Id.t;
6369 last_state : string;
6464- pending_created : id list;
6565- pending_updated : id list;
6666- pending_destroyed : id list;
7070+ pending_created : Jmap.Id.t list;
7171+ pending_updated : Jmap.Id.t list;
7272+ pending_destroyed : Jmap.Id.t list;
6773 }
68746975 let init ~account_id ~initial_state =
···8389 {
8490 sync with
8591 last_state = new_state;
8686- pending_created = sync.pending_created @ created;
8787- pending_updated = sync.pending_updated @ updated;
8888- pending_destroyed = sync.pending_destroyed @ destroyed;
9292+ pending_created = sync.pending_created @ (List.map (fun s -> match Jmap.Id.of_string s with Ok id -> id | Error e -> failwith e) created);
9393+ pending_updated = sync.pending_updated @ (List.map (fun s -> match Jmap.Id.of_string s with Ok id -> id | Error e -> failwith e) updated);
9494+ pending_destroyed = sync.pending_destroyed @ (List.map (fun s -> match Jmap.Id.of_string s with Ok id -> id | Error e -> failwith e) destroyed);
8995 }
90969197 let clear_pending sync =
···109115(** Utility to merge multiple change responses *)
110116let merge_changes responses =
111117 List.fold_left (fun (created, updated, destroyed) response ->
112112- let c = Changes_response.created response in
113113- let u = Changes_response.updated response in
114114- let d = Changes_response.destroyed response in
118118+ let c = List.map (fun id -> match Jmap.Id.of_string id with | Ok id_t -> id_t | Error _ -> failwith ("Invalid ID: " ^ id)) (Changes_response.created response) in
119119+ let u = List.map (fun id -> match Jmap.Id.of_string id with | Ok id_t -> id_t | Error _ -> failwith ("Invalid ID: " ^ id)) (Changes_response.updated response) in
120120+ let d = List.map (fun id -> match Jmap.Id.of_string id with | Ok id_t -> id_t | Error _ -> failwith ("Invalid ID: " ^ id)) (Changes_response.destroyed response) in
115121 (created @ c, updated @ u, destroyed @ d)
116122 ) ([], [], []) responses
117123
+8-9
jmap/jmap-email/changes.mli
···6677 @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-4.6> RFC 8621 Section 4.6 *)
8899-open Jmap.Types
109open Jmap.Methods
11101211(** {1 Changes Arguments} *)
···1716 @param ?max_changes Optional maximum number of changes to return
1817 @return Changes_args for Email/changes method *)
1918val build_changes_args :
2020- account_id:id ->
1919+ account_id:Jmap.Id.t ->
2120 since_state:string ->
2222- ?max_changes:uint ->
2121+ ?max_changes:Jmap.UInt.t ->
2322 unit ->
2423 Changes_args.t
2524···3837 @param initial_state The starting state
3938 @return A new change tracker *)
4039val create_tracker :
4141- account_id:id ->
4040+ account_id:Jmap.Id.t ->
4241 initial_state:string ->
4342 change_tracker
4443···5655 @return Tuple of (created_ids, updated_ids, destroyed_ids) *)
5756val get_all_changes :
5857 change_tracker ->
5959- (id list * id list * id list)
5858+ (Jmap.Id.t list * Jmap.Id.t list * Jmap.Id.t list)
60596160(** {1 Incremental Sync} *)
6261···6665 @param ?max_changes Maximum changes per batch (default 500)
6766 @return Changes_args for fetching next batch *)
6867val get_next_changes :
6969- account_id:id ->
6868+ account_id:Jmap.Id.t ->
7069 since_state:string ->
7170 ?max_changes:int ->
7271 unit ->
···8786 @param initial_state The starting state
8887 @return New sync state *)
8988 val init :
9090- account_id:id ->
8989+ account_id:Jmap.Id.t ->
9190 initial_state:string ->
9291 sync_state
9392···110109 @return Tuple of (created, updated, destroyed) ID lists *)
111110 val get_pending :
112111 sync_state ->
113113- (id list * id list * id list)
112112+ (Jmap.Id.t list * Jmap.Id.t list * Jmap.Id.t list)
114113115114 (** Check if sync is needed.
116115 @param sync Current sync state
···129128 @return Combined (created, updated, destroyed) ID lists *)
130129val merge_changes :
131130 Changes_response.t list ->
132132- (id list * id list * id list)
131131+ (Jmap.Id.t list * Jmap.Id.t list * Jmap.Id.t list)
133132134133(** Get updated properties if available.
135134 @param response Changes response
···991010[@@@warning "-32"] (* Suppress unused value warnings for interface-required functions *)
11111212-open Jmap.Types
1313-1412(** JSON parsing combinators for cleaner field extraction *)
1513module Json = struct
1614 (** Extract a field from JSON object fields list *)
···4745 let string_list (name : string) (fields : (string * Yojson.Safe.t) list) : string list option =
4846 list (function `String s -> Some s | _ -> None) name fields
49475050- (** Parse ISO 8601 date field to Unix timestamp *)
4848+ (** Parse ISO 8601 Jmap.Date.t field to Unix timestamp *)
5149 let iso_date (name : string) (fields : (string * Yojson.Safe.t) list) : float option =
5250 match string name fields with
5351 | Some s ->
···10199end
102100103101type t = {
104104- id : id option;
105105- blob_id : id option;
106106- thread_id : id option;
107107- mailbox_ids : bool id_map option;
102102+ id : Jmap.Id.t option;
103103+ blob_id : Jmap.Id.t option;
104104+ thread_id : Jmap.Id.t option;
105105+ mailbox_ids : (Jmap.Id.t, bool) Hashtbl.t option;
108106 keywords : Keywords.t option;
109109- size : uint option;
110110- received_at : date option;
107107+ size : Jmap.UInt.t option;
108108+ received_at : Jmap.Date.t option;
111109 message_id : string list option;
112110 in_reply_to : string list option;
113111 references : string list option;
···118116 bcc : Address.t list option;
119117 reply_to : Address.t list option;
120118 subject : string option;
121121- sent_at : date option;
119119+ sent_at : Jmap.Date.t option;
122120 has_attachment : bool option;
123121 preview : string option;
124122 body_structure : Body.t option;
125125- body_values : Body.Value.t string_map option;
123123+ body_values : (string, Body.Value.t) Hashtbl.t option;
126124 text_body : Body.t list option;
127125 html_body : Body.t list option;
128126 attachments : Body.t list option;
129129- headers : string string_map option;
130130- other_properties : Yojson.Safe.t string_map;
127127+ headers : (string, string) Hashtbl.t option;
128128+ other_properties : (string, Yojson.Safe.t) Hashtbl.t;
131129}
132130133131(* Accessor functions *)
···180178181179(* Get list of all valid property names for Email objects *)
182180let valid_properties () = [
183183- "id"; "blobId"; "threadId"; "mailboxIds"; "keywords"; "size"; "receivedAt";
181181+ "Jmap.Id.t"; "blobId"; "threadId"; "mailboxIds"; "keywords"; "size"; "receivedAt";
184182 "messageId"; "inReplyTo"; "references"; "sender"; "from"; "to"; "cc"; "bcc";
185183 "replyTo"; "subject"; "sentAt"; "hasAttachment"; "preview"; "bodyStructure";
186184 "bodyValues"; "textBody"; "htmlBody"; "attachments"; "headers"
···189187(* Serialize to JSON with only specified properties *)
190188let to_json_with_properties ~properties t =
191189 let all_fields = [
192192- ("id", (match t.id with Some s -> `String s | None -> `Null));
193193- ("blobId", (match t.blob_id with Some s -> `String s | None -> `Null));
194194- ("threadId", (match t.thread_id with Some s -> `String s | None -> `Null));
190190+ ("id", (match t.id with Some id_t -> `String (Jmap.Id.to_string id_t) | None -> `Null));
191191+ ("blobId", (match t.blob_id with Some id_t -> `String (Jmap.Id.to_string id_t) | None -> `Null));
192192+ ("threadId", (match t.thread_id with Some id_t -> `String (Jmap.Id.to_string id_t) | None -> `Null));
195193 ("subject", (match t.subject with Some s -> `String s | None -> `Null));
196194 ("size", (match t.size with Some i -> `Int i | None -> `Null));
197195 (* Add more fields as needed - this is a simplified implementation *)
···269267 | _ -> "(No subject)"
270268 in
271269 let date_str = match t.received_at with
272272- | Some date -> Printf.sprintf "%.0f" date
270270+ | Some date -> Printf.sprintf "%.0f" (Jmap.Date.to_timestamp date)
273271 | None -> match t.sent_at with
274274- | Some date -> Printf.sprintf "%.0f" date
275275- | None -> "Unknown date"
272272+ | Some date -> Printf.sprintf "%.0f" (Jmap.Date.to_timestamp date)
273273+ | None -> "Unknown Jmap.Date.t"
276274 in
277275 Printf.sprintf "%s: %s (%s)" sender_str subject_str date_str
278276279277(* PRINTABLE interface implementation *)
280278let pp ppf t =
281281- let id_str = match t.id with Some id -> id | None -> "no-id" in
279279+ let id_str = match t.id with Some id -> Jmap.Id.to_string id | None -> "no-id" in
282280 let subject_str = match t.subject with Some s -> s | None -> "(no subject)" in
283281 Format.fprintf ppf "Email{id=%s; subject=%s}" id_str subject_str
284282···330328 in
331329 let add_opt_bool_map fields name map_opt = match map_opt with
332330 | Some map ->
333333- let assoc_list = Hashtbl.fold (fun k v acc -> (k, `Bool v) :: acc) map [] in
331331+ let assoc_list = Hashtbl.fold (fun k v acc -> (Jmap.Id.to_string k, `Bool v) :: acc) map [] in
334332 (name, `Assoc assoc_list) :: fields
335333 | None -> fields
336334 in
···342340 in
343341344342 (* Add all email fields *)
345345- let fields = add_opt_string fields "id" t.id in
346346- let fields = add_opt_string fields "blobId" t.blob_id in
347347- let fields = add_opt_string fields "threadId" t.thread_id in
343343+ let fields = add_opt_string fields "id" (Option.map Jmap.Id.to_string t.id) in
344344+ let fields = add_opt_string fields "blobId" (Option.map Jmap.Id.to_string t.blob_id) in
345345+ let fields = add_opt_string fields "threadId" (Option.map Jmap.Id.to_string t.thread_id) in
348346 let fields = add_opt_bool_map fields "mailboxIds" t.mailbox_ids in
349347 let fields = match t.keywords with
350348 | Some kw -> ("keywords", Keywords.to_json kw) :: fields
351349 | None -> fields
352350 in
353353- let fields = add_opt_int fields "size" t.size in
354354- let fields = add_opt_date fields "receivedAt" t.received_at in
351351+ let fields = add_opt_int fields "size" (Option.map Jmap.UInt.to_int t.size) in
352352+ let fields = add_opt_date fields "receivedAt" (Option.map Jmap.Date.to_timestamp t.received_at) in
355353 let fields = add_opt_string_list fields "messageId" t.message_id in
356354 let fields = add_opt_string_list fields "inReplyTo" t.in_reply_to in
357355 let fields = add_opt_string_list fields "references" t.references in
···365363 let fields = add_opt_address_list fields "bcc" t.bcc in
366364 let fields = add_opt_address_list fields "replyTo" t.reply_to in
367365 let fields = add_opt_string fields "subject" t.subject in
368368- let fields = add_opt_date fields "sentAt" t.sent_at in
366366+ let fields = add_opt_date fields "sentAt" (Option.map Jmap.Date.to_timestamp t.sent_at) in
369367 let fields = add_opt_bool fields "hasAttachment" t.has_attachment in
370368 let fields = add_opt_string fields "preview" t.preview in
371369 let fields = match t.body_structure with
···392390 | `Assoc fields ->
393391 (try
394392 (* Parse all email fields using combinators *)
395395- let id = Json.string "id" fields in
396396- let blob_id = Json.string "blobId" fields in
397397- let thread_id = Json.string "threadId" fields in
398398- let mailbox_ids = Json.bool_map "mailboxIds" fields in
393393+ let id = match Json.string "Jmap.Id.t" fields with
394394+ | Some id_str -> (match Jmap.Id.of_string id_str with
395395+ | Ok jmap_id -> Some jmap_id
396396+ | Error _ -> None)
397397+ | None -> None in
398398+ let blob_id = match Json.string "blobId" fields with
399399+ | Some blob_id_str -> (match Jmap.Id.of_string blob_id_str with
400400+ | Ok jmap_id -> Some jmap_id
401401+ | Error _ -> None)
402402+ | None -> None in
403403+ let thread_id = match Json.string "threadId" fields with
404404+ | Some thread_id_str -> (match Jmap.Id.of_string thread_id_str with
405405+ | Ok jmap_id -> Some jmap_id
406406+ | Error _ -> None)
407407+ | None -> None in
408408+ let mailbox_ids = match Json.bool_map "mailboxIds" fields with
409409+ | Some string_map ->
410410+ let id_map = Hashtbl.create (Hashtbl.length string_map) in
411411+ Hashtbl.iter (fun str_key bool_val ->
412412+ match Jmap.Id.of_string str_key with
413413+ | Ok id_key -> Hashtbl.add id_map id_key bool_val
414414+ | Error _ -> () (* Skip invalid ids *)
415415+ ) string_map;
416416+ Some id_map
417417+ | None -> None in
399418 (* Parse keywords using the Keywords module *)
400419 let keywords = match Json.field "keywords" fields with
401420 | Some json ->
···404423 | Error _msg -> None (* Ignore parse errors for now *))
405424 | None -> None
406425 in
407407- let size = Json.int "size" fields in
408408- let received_at = Json.iso_date "receivedAt" fields in
426426+ let size = match Json.int "size" fields with
427427+ | Some int_val -> (match Jmap.UInt.of_int int_val with
428428+ | Ok uint_val -> Some uint_val
429429+ | Error _ -> None)
430430+ | None -> None in
431431+ let received_at = match Json.iso_date "receivedAt" fields with
432432+ | Some float_val -> Some (Jmap.Date.of_timestamp float_val)
433433+ | None -> None in
409434 let message_id = Json.string_list "messageId" fields in
410435 let in_reply_to = Json.string_list "inReplyTo" fields in
411436 let references = Json.string_list "references" fields in
···419444 let bcc = Json.email_address_list "bcc" fields in
420445 let reply_to = Json.email_address_list "replyTo" fields in
421446 let subject = Json.string "subject" fields in
422422- let sent_at = Json.iso_date "sentAt" fields in
447447+ let sent_at = match Json.iso_date "sentAt" fields with
448448+ | Some float_val -> Some (Jmap.Date.of_timestamp float_val)
449449+ | None -> None in
423450 let has_attachment = Json.bool "hasAttachment" fields in
424451 let preview = Json.string "preview" fields in
425452 (* Parse body structure using the Body module *)
···483510484511 (* Collect any unrecognized fields into other_properties *)
485512 let known_fields = [
486486- "id"; "blobId"; "threadId"; "mailboxIds"; "keywords"; "size"; "receivedAt";
513513+ "Jmap.Id.t"; "blobId"; "threadId"; "mailboxIds"; "keywords"; "size"; "receivedAt";
487514 "messageId"; "inReplyTo"; "references"; "sender"; "from"; "to"; "cc"; "bcc";
488515 "replyTo"; "subject"; "sentAt"; "hasAttachment"; "preview"; "bodyStructure";
489516 "bodyValues"; "textBody"; "htmlBody"; "attachments"; "headers"
···508535(* Pretty printing implementation for PRINTABLE signature *)
509536let pp ppf t =
510537 let id_str = match t.id with
511511- | Some id -> id
538538+ | Some id -> Jmap.Id.to_string id
512539 | None -> "<no-id>"
513540 in
514541 let subject_str = match t.subject with
···519546 | Some addr -> Address.email addr
520547 | None -> "<unknown-sender>"
521548 in
522522- Format.fprintf ppf "Email{id=%s; from=%s; subject=%s}"
549549+ Format.fprintf ppf "Email{Jmap.Id.t=%s; from=%s; subject=%s}"
523550 id_str sender_str subject_str
524551525552(* Alias for pp following Fmt conventions *)
···567594 module Email_address = Address
568595 module Email = struct
569596 type nonrec t = t (* Alias the main email type *)
570570- let id = id
597597+ let id t = t.id
571598 let received_at = received_at
572599 let subject = subject
573600 let from = from
+28-31
jmap/jmap-email/email.mli
···1111 @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-4.1> RFC 8621, Section 4.1 - Email Object
1212*)
13131414-open Jmap.Types
1515-1614(** Email object type.
17151816 Represents a complete email message as defined in RFC 8621 Section 4.1.
···2725(** Pretty printing interface *)
2826include Jmap_sigs.PRINTABLE with type t := t
29273030-(** JMAP object interface with property selection support *)
3131-include Jmap_sigs.JMAP_OBJECT with type t := t and type id_type := id
2828+(** JMAP object interface with property selection support - implemented manually *)
32293330(** Get the server-assigned email identifier.
3431 @param t The email object
3532 @return Email ID if present in the object *)
3636-val id : t -> id option
3333+val id : t -> Jmap.Id.t option
37343835(** Get the blob ID for downloading the complete raw message.
3936 @param t The email object
4037 @return Blob identifier for RFC 5322 message access *)
4141-val blob_id : t -> id option
3838+val blob_id : t -> Jmap.Id.t option
42394340(** Get the thread identifier linking related messages.
4441 @param t The email object
4542 @return Thread ID for conversation grouping *)
4646-val thread_id : t -> id option
4343+val thread_id : t -> Jmap.Id.t option
47444845(** Get the set of mailboxes containing this email.
4946 @param t The email object
5047 @return Map of mailbox IDs to boolean values (always true when present) *)
5151-val mailbox_ids : t -> bool id_map option
4848+val mailbox_ids : t -> (Jmap.Id.t, bool) Hashtbl.t option
52495350(** Get the keywords/flags applied to this email.
5451 @param t The email object
···5855(** Get the total size of the raw message.
5956 @param t The email object
6057 @return Message size in octets *)
6161-val size : t -> uint option
5858+val size : t -> Jmap.UInt.t option
62596360(** Get the server timestamp when the message was received.
6461 @param t The email object
6562 @return Reception timestamp *)
6666-val received_at : t -> date option
6363+val received_at : t -> Jmap.Date.t option
67646865(** Get the Message-ID header values.
6966 @param t The email object
···118115(** Get the Date header timestamp (when message was sent).
119116 @param t The email object
120117 @return Send timestamp if the SentAt property was requested *)
121121-val sent_at : t -> date option
118118+val sent_at : t -> Jmap.Date.t option
122119123120(** Check if the email has non-inline attachments.
124121 @param t The email object
···138135(** Get decoded content of requested text body parts.
139136 @param t The email object
140137 @return Map of part IDs to decoded content if BodyValues was requested *)
141141-val body_values : t -> Body.Value.t string_map option
138138+val body_values : t -> (string, Body.Value.t) Hashtbl.t option
142139143140(** Get text/plain body parts suitable for display.
144141 @param t The email object
···172169173170 @param t The email object
174171 @return Map of property names to JSON values for extended properties *)
175175-val other_properties : t -> Yojson.Safe.t string_map
172172+val other_properties : t -> (string, Yojson.Safe.t) Hashtbl.t
176173177174(** Create a detailed Email object with all properties.
178175···180177 setting all email properties at once. Used primarily for constructing Email
181178 objects from server responses or for testing purposes.
182179183183- @param id Server-assigned unique identifier
180180+ @param Jmap.Id.t Server-assigned unique identifier
184181 @param blob_id Blob ID for raw message access
185182 @param thread_id Thread identifier for conversation grouping
186183 @param mailbox_ids Set of mailboxes containing this email
···209206 @param other_properties Extended/custom properties
210207 @return New email object *)
211208val create_full :
212212- ?id:id ->
213213- ?blob_id:id ->
214214- ?thread_id:id ->
215215- ?mailbox_ids:bool id_map ->
209209+ ?id:Jmap.Id.t ->
210210+ ?blob_id:Jmap.Id.t ->
211211+ ?thread_id:Jmap.Id.t ->
212212+ ?mailbox_ids:(Jmap.Id.t, bool) Hashtbl.t ->
216213 ?keywords:Keywords.t ->
217217- ?size:uint ->
218218- ?received_at:date ->
214214+ ?size:Jmap.UInt.t ->
215215+ ?received_at:Jmap.Date.t ->
219216 ?message_id:string list ->
220217 ?in_reply_to:string list ->
221218 ?references:string list ->
···226223 ?bcc:Address.t list ->
227224 ?reply_to:Address.t list ->
228225 ?subject:string ->
229229- ?sent_at:date ->
226226+ ?sent_at:Jmap.Date.t ->
230227 ?has_attachment:bool ->
231228 ?preview:string ->
232229 ?body_structure:Body.t ->
233233- ?body_values:Body.Value.t string_map ->
230230+ ?body_values:(string, Body.Value.t) Hashtbl.t ->
234231 ?text_body:Body.t list ->
235232 ?html_body:Body.t list ->
236233 ?attachments:Body.t list ->
237237- ?headers:string string_map ->
238238- ?other_properties:Yojson.Safe.t string_map ->
234234+ ?headers:(string, string) Hashtbl.t ->
235235+ ?other_properties:(string, Yojson.Safe.t) Hashtbl.t ->
239236 unit -> t
240237241238(** Safely extract the email ID.
242239 @param t The email object
243240 @return Ok with the ID, or Error with message if not present *)
244244-val get_id : t -> (id, string) result
241241+val get_id : t -> (Jmap.Id.t, string) result
245242246243(** Extract the email ID, raising an exception if not present.
247244 @param t The email object
248245 @return The email ID *)
249249-val take_id : t -> id
246246+val take_id : t -> Jmap.Id.t
250247251248(** Check if the email is unread.
252249···315312 val create :
316313 ?add_keywords:Keywords.t ->
317314 ?remove_keywords:Keywords.t ->
318318- ?add_mailboxes:id list ->
319319- ?remove_mailboxes:id list ->
315315+ ?add_mailboxes:Jmap.Id.t list ->
316316+ ?remove_mailboxes:Jmap.Id.t list ->
320317 unit -> Yojson.Safe.t
321318322319 (** Mark email as read by adding $seen keyword.
···339336340337 @param mailbox_ids List of target mailbox IDs
341338 @return Patch object to set email mailbox membership *)
342342- val move_to_mailboxes : id list -> Yojson.Safe.t
339339+ val move_to_mailboxes : Jmap.Id.t list -> Yojson.Safe.t
343340end
344341345342(** Module aliases for external access *)
···383380 module Email_address = Address
384381 module Email : sig
385382 type nonrec t = t
386386- val id : t -> id option
387387- val received_at : t -> date option
383383+ val id : t -> Jmap.Id.t option
384384+ val received_at : t -> Jmap.Date.t option
388385 val subject : t -> string option
389386 val from : t -> Address.t list option
390387 val keywords : t -> Keywords.t option
+115-88
jmap/jmap-email/identity.ml
···77 @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-6> RFC 8621, Section 6: Identity
88*)
991010-open Jmap.Types
1110open Jmap.Method_names
1211open Jmap.Error
13121413(** Identity object *)
1514type t = {
1616- id : id option;
1515+ id : Jmap.Id.t option;
1716 name : string;
1817 email : string;
1919- reply_to : Types.Email_address.t list option;
2020- bcc : Types.Email_address.t list option;
1818+ reply_to : Address.t list option;
1919+ bcc : Address.t list option;
2120 text_signature : string;
2221 html_signature : string;
2322 may_delete : bool;
···46454746let to_json t =
4847 let fields = [
4949- ("id", (match t.id with Some id -> `String id | None -> `Null));
4848+ ("id", (match t.id with Some id -> `String (Jmap.Id.to_string id) | None -> `Null));
5049 ("name", `String t.name);
5150 ("email", `String t.email);
5251 ("textSignature", `String t.text_signature);
···5554 ] in
5655 let fields = match t.reply_to with
5756 | None -> ("replyTo", `Null) :: fields
5858- | Some addrs -> ("replyTo", `List (List.map Types.Email_address.to_json addrs)) :: fields
5757+ | Some addrs -> ("replyTo", `List (List.map Address.to_json addrs)) :: fields
5958 in
6059 let fields = match t.bcc with
6160 | None -> ("bcc", `Null) :: fields
6262- | Some addrs -> ("bcc", `List (List.map Types.Email_address.to_json addrs)) :: fields
6161+ | Some addrs -> ("bcc", `List (List.map Address.to_json addrs)) :: fields
6362 in
6463 `Assoc (List.rev fields)
65646665(* JMAP_OBJECT implementation *)
6766let create ?id () =
6868- { id; name = ""; email = ""; reply_to = None; bcc = None;
6767+ let id_opt = match id with
6868+ | None -> None
6969+ | Some id_str ->
7070+ (match Jmap.Id.of_string id_str with
7171+ | Ok jmap_id -> Some jmap_id
7272+ | Error _ -> failwith ("Invalid identity id: " ^ id_str)) in
7373+ { id = id_opt; name = ""; email = ""; reply_to = None; bcc = None;
6974 text_signature = ""; html_signature = ""; may_delete = true }
70757176let to_json_with_properties ~properties t =
7277 let all_fields = [
7373- ("id", (match t.id with Some id -> `String id | None -> `Null));
7878+ ("id", (match t.id with Some id -> `String (Jmap.Id.to_string id) | None -> `Null));
7479 ("name", `String t.name);
7580 ("email", `String t.email);
7681 ("replyTo", (match t.reply_to with
7782 | None -> `Null
7878- | Some addrs -> `List (List.map Types.Email_address.to_json addrs)));
8383+ | Some addrs -> `List (List.map Address.to_json addrs)));
7984 ("bcc", (match t.bcc with
8085 | None -> `Null
8181- | Some addrs -> `List (List.map Types.Email_address.to_json addrs)));
8686+ | Some addrs -> `List (List.map Address.to_json addrs)));
8287 ("textSignature", `String t.text_signature);
8388 ("htmlSignature", `String t.html_signature);
8489 ("mayDelete", `Bool t.may_delete);
···8994 `Assoc filtered_fields
90959196let valid_properties () = [
9292- "id"; "name"; "email"; "replyTo"; "bcc"; "textSignature"; "htmlSignature"; "mayDelete"
9797+ "Id.t"; "name"; "email"; "replyTo"; "bcc"; "textSignature"; "htmlSignature"; "mayDelete"
9398] (* TODO: Use Property.to_string_list Property.all_properties when module ordering is fixed *)
949995100let of_json json =
···114119 let rec process_addresses acc = function
115120 | [] -> Some (List.rev acc)
116121 | addr :: rest ->
117117- (match Types.Email_address.of_json addr with
122122+ (match Address.of_json addr with
118123 | Ok a -> process_addresses (a :: acc) rest
119124 | Error _ -> failwith ("Invalid address in " ^ key ^ " field"))
120125 in
···126131 let email = get_string "email" "" in
127132 if email = "" then failwith "Missing required 'email' field in Identity";
128133 Ok {
129129- id = (if id = "" then None else Some id);
134134+ id = (if id = "" then None else match Jmap.Id.of_string id with
135135+ | Ok id_t -> Some id_t
136136+ | Error _ -> failwith ("Invalid ID: " ^ id));
130137 name = get_string "name" "";
131138 email;
132139 reply_to = get_addresses "replyTo";
···143150(* Pretty printing implementation for PRINTABLE signature *)
144151let pp ppf t =
145152 let name_str = if t.name = "" then "<no-name>" else t.name in
146146- let id_str = match t.id with Some id -> id | None -> "(no-id)" in
153153+ let id_str = match t.id with Some id -> Jmap.Id.to_string id | None -> "(no-id)" in
147154 Format.fprintf ppf "Identity{id=%s; name=%s; email=%s; may_delete=%b}"
148155 id_str name_str t.email t.may_delete
149156···155162 type t = {
156163 name : string option;
157164 email : string;
158158- reply_to : Types.Email_address.t list option;
159159- bcc : Types.Email_address.t list option;
165165+ reply_to : Address.t list option;
166166+ bcc : Address.t list option;
160167 text_signature : string option;
161168 html_signature : string option;
162169 }
···185192 in
186193 let fields = match t.reply_to with
187194 | None -> fields
188188- | Some addrs -> ("replyTo", `List (List.map Types.Email_address.to_json addrs)) :: fields
195195+ | Some addrs -> ("replyTo", `List (List.map Address.to_json addrs)) :: fields
189196 in
190197 let fields = match t.bcc with
191198 | None -> fields
192192- | Some addrs -> ("bcc", `List (List.map Types.Email_address.to_json addrs)) :: fields
199199+ | Some addrs -> ("bcc", `List (List.map Address.to_json addrs)) :: fields
193200 in
194201 let fields = match t.text_signature with
195202 | None -> fields
···217224 let rec process_addresses acc = function
218225 | [] -> Some (List.rev acc)
219226 | addr :: rest ->
220220- (match Types.Email_address.of_json addr with
227227+ (match Address.of_json addr with
221228 | Ok a -> process_addresses (a :: acc) rest
222229 | Error _ -> failwith ("Invalid address in " ^ key ^ " field"))
223230 in
···245252 (** Server response with info about the created identity *)
246253 module Response = struct
247254 type t = {
248248- id : id;
255255+ id : Jmap.Id.t;
249256 may_delete : bool;
250257 }
251258···259266260267 let to_json t =
261268 `Assoc [
262262- ("id", `String t.id);
269269+ ("id", `String (Jmap.Id.to_string t.id));
263270 ("mayDelete", `Bool t.may_delete);
264271 ]
265272···267274 try
268275 match json with
269276 | `Assoc fields ->
270270- let id = match List.assoc_opt "id" fields with
271271- | Some (`String s) -> s
272272- | _ -> failwith "Missing required 'id' field in Identity creation response"
277277+ let id = match List.assoc_opt "Id.t" fields with
278278+ | Some (`String s) -> (match Jmap.Id.of_string s with
279279+ | Ok id -> id
280280+ | Error _ -> failwith ("Invalid id: " ^ s))
281281+ | _ -> failwith "Missing required 'Id.t' field in Identity creation response"
273282 in
274283 let may_delete = match List.assoc_opt "mayDelete" fields with
275284 | Some (`Bool b) -> b
···287296module Update = struct
288297 type t = {
289298 name : string option;
290290- reply_to : Types.Email_address.t list option option;
291291- bcc : Types.Email_address.t list option option;
299299+ reply_to : Address.t list option option;
300300+ bcc : Address.t list option option;
292301 text_signature : string option;
293302 html_signature : string option;
294303 }
···359368 let fields = match t.reply_to with
360369 | None -> fields
361370 | Some None -> ("replyTo", `Null) :: fields
362362- | Some (Some addrs) -> ("replyTo", `List (List.map Types.Email_address.to_json addrs)) :: fields
371371+ | Some (Some addrs) -> ("replyTo", `List (List.map Address.to_json addrs)) :: fields
363372 in
364373 let fields = match t.bcc with
365374 | None -> fields
366375 | Some None -> ("bcc", `Null) :: fields
367367- | Some (Some addrs) -> ("bcc", `List (List.map Types.Email_address.to_json addrs)) :: fields
376376+ | Some (Some addrs) -> ("bcc", `List (List.map Address.to_json addrs)) :: fields
368377 in
369378 let fields = match t.text_signature with
370379 | None -> fields
···393402 let rec process_addresses acc = function
394403 | [] -> Some (Some (List.rev acc))
395404 | addr :: rest ->
396396- (match Types.Email_address.of_json addr with
405405+ (match Address.of_json addr with
397406 | Ok a -> process_addresses (a :: acc) rest
398407 | Error _ -> failwith ("Invalid address in " ^ key ^ " field"))
399408 in
···452461(** Arguments for Identity/get method *)
453462module Get_args = struct
454463 type t = {
455455- account_id : id;
456456- ids : id list option;
464464+ account_id : Jmap.Id.t;
465465+ ids : Jmap.Id.t list option;
457466 properties : string list option;
458467 }
459468···465474 { account_id; ids; properties }
466475467476 let to_json t =
468468- let fields = [("accountId", `String t.account_id)] in
477477+ let fields = [("accountId", `String (Jmap.Id.to_string t.account_id))] in
469478 let fields = match t.ids with
470479 | None -> fields
471471- | Some ids -> ("ids", `List (List.map (fun id -> `String id) ids)) :: fields
480480+ | Some ids -> ("ids", `List (List.map (fun id -> `String (Jmap.Id.to_string id)) ids)) :: fields
472481 in
473482 let fields = match t.properties with
474483 | None -> fields
···481490 match json with
482491 | `Assoc fields ->
483492 let account_id = match List.assoc_opt "accountId" fields with
484484- | Some (`String s) -> s
493493+ | Some (`String s) -> (match Jmap.Id.of_string s with
494494+ | Ok id -> id | Error err -> failwith ("Invalid accountId: " ^ err))
485495 | _ -> failwith "Missing required 'accountId' field in Identity/get arguments"
486496 in
487497 let ids = match List.assoc_opt "ids" fields with
488488- | Some (`List ids) -> Some (List.map (function `String s -> s | _ -> failwith "Invalid ID in 'ids' list") ids)
498498+ | Some (`List ids) -> Some (List.map (function `String s -> (match Jmap.Id.of_string s with Ok id -> id | Error err -> failwith ("Invalid ID: " ^ err)) | _ -> failwith "Invalid ID in 'ids' list") ids)
489499 | Some `Null | None -> None
490500 | _ -> failwith "Invalid 'ids' field in Identity/get arguments"
491501 in
···502512503513 let pp fmt t =
504514 Format.fprintf fmt "Identity.Get_args{account=%s;ids=%s}"
505505- t.account_id
515515+ (Jmap.Id.to_string t.account_id)
506516 (match t.ids with Some ids -> string_of_int (List.length ids) | None -> "all")
507517508518 let pp_hum fmt t = pp fmt t
···516526(** Arguments for Identity/set method *)
517527module Set_args = struct
518528 type t = {
519519- account_id : id;
529529+ account_id : Jmap.Id.t;
520530 if_in_state : string option;
521521- create : Create.t id_map option;
522522- update : Update.t id_map option;
523523- destroy : id list option;
531531+ create : (string, Create.t) Hashtbl.t option;
532532+ update : (string, Update.t) Hashtbl.t option;
533533+ destroy : Jmap.Id.t list option;
524534 }
525535526536 let account_id t = t.account_id
···533543 { account_id; if_in_state; create; update; destroy }
534544535545 let to_json t =
536536- let fields = [("accountId", `String t.account_id)] in
546546+ let fields = [("accountId", `String (Jmap.Id.to_string t.account_id))] in
537547 let fields = match t.if_in_state with
538548 | None -> fields
539549 | Some state -> ("ifInState", `String state) :: fields
···556566 in
557567 let fields = match t.destroy with
558568 | None -> fields
559559- | Some ids -> ("destroy", `List (List.map (fun id -> `String id) ids)) :: fields
569569+ | Some ids -> ("destroy", `List (List.map (fun id -> `String (Jmap.Id.to_string id)) ids)) :: fields
560570 in
561571 `Assoc (List.rev fields)
562572···565575 match json with
566576 | `Assoc fields ->
567577 let account_id = match List.assoc_opt "accountId" fields with
568568- | Some (`String s) -> s
578578+ | Some (`String s) -> (match Jmap.Id.of_string s with
579579+ | Ok id -> id
580580+ | Error _ -> failwith ("Invalid accountId: " ^ s))
569581 | _ -> failwith "Missing required 'accountId' field in Identity/set arguments"
570582 in
571583 let if_in_state = match List.assoc_opt "ifInState" fields with
···600612 | _ -> failwith "Invalid 'update' field in Identity/set arguments"
601613 in
602614 let destroy = match List.assoc_opt "destroy" fields with
603603- | Some (`List ids) -> Some (List.map (function `String s -> s | _ -> failwith "Invalid ID in 'destroy' list") ids)
615615+ | Some (`List ids) -> Some (List.map (function `String s -> (match Jmap.Id.of_string s with Ok id -> id | Error _ -> failwith ("Invalid ID in 'destroy' list: " ^ s)) | _ -> failwith "Invalid ID in 'destroy' list") ids)
604616 | Some `Null | None -> None
605617 | _ -> failwith "Invalid 'destroy' field in Identity/set arguments"
606618 in
···611623 | exn -> Error ("Identity/set JSON parsing exception: " ^ Printexc.to_string exn)
612624613625 let pp fmt t =
614614- Format.fprintf fmt "Identity.Set_args{account=%s}" t.account_id
626626+ Format.fprintf fmt "Identity.Set_args{account=%s}" (Jmap.Id.to_string t.account_id)
615627616628 let pp_hum fmt t = pp fmt t
617629···623635(** Response for Identity/set method *)
624636module Set_response = struct
625637 type t = {
626626- account_id : id;
638638+ account_id : Jmap.Id.t;
627639 old_state : string;
628640 new_state : string;
629629- created : Create.Response.t id_map;
630630- updated : Update.Response.t id_map;
631631- destroyed : id list;
632632- not_created : Set_error.t id_map;
633633- not_updated : Set_error.t id_map;
634634- not_destroyed : Set_error.t id_map;
641641+ created : (string, Create.Response.t) Hashtbl.t;
642642+ updated : (string, Update.Response.t) Hashtbl.t;
643643+ destroyed : Jmap.Id.t list;
644644+ not_created : (string, Set_error.t) Hashtbl.t;
645645+ not_updated : (string, Set_error.t) Hashtbl.t;
646646+ not_destroyed : (string, Set_error.t) Hashtbl.t;
635647 }
636648637649 let account_id t = t.account_id
···656668 Hashtbl.fold (fun k v acc -> (k, to_json_fn v) :: acc) tbl []
657669 in
658670 `Assoc [
659659- ("accountId", `String t.account_id);
671671+ ("accountId", `String (Jmap.Id.to_string t.account_id));
660672 ("oldState", `String t.old_state);
661673 ("newState", `String t.new_state);
662674 ("created", `Assoc (hashtbl_to_assoc Create.Response.to_json t.created));
663675 ("updated", `Assoc (hashtbl_to_assoc Update.Response.to_json t.updated));
664664- ("destroyed", `List (List.map (fun id -> `String id) t.destroyed));
676676+ ("destroyed", `List (List.map (fun id -> `String (Jmap.Id.to_string id)) t.destroyed));
665677 ("notCreated", `Assoc (hashtbl_to_assoc (fun _ -> `String "placeholder") t.not_created));
666678 ("notUpdated", `Assoc (hashtbl_to_assoc (fun _ -> `String "placeholder") t.not_updated));
667679 ("notDestroyed", `Assoc (hashtbl_to_assoc (fun _ -> `String "placeholder") t.not_destroyed));
···672684 match json with
673685 | `Assoc fields ->
674686 let account_id = match List.assoc_opt "accountId" fields with
675675- | Some (`String s) -> s
687687+ | Some (`String s) -> (match Jmap.Id.of_string s with
688688+ | Ok id -> id
689689+ | Error _ -> failwith ("Invalid accountId: " ^ s))
676690 | _ -> failwith "Missing required 'accountId' field in Identity/set response"
677691 in
678692 let old_state = match List.assoc_opt "oldState" fields with
···701715 | _ -> Hashtbl.create 0
702716 in
703717 let destroyed = match List.assoc_opt "destroyed" fields with
704704- | Some (`List ids) -> List.map (function `String s -> s | _ -> failwith "Invalid ID in 'destroyed' list") ids
718718+ | Some (`List ids) -> List.map (function `String s -> (match Jmap.Id.of_string s with Ok id -> id | Error _ -> failwith ("Invalid ID in 'destroyed' list: " ^ s)) | _ -> failwith "Invalid ID in 'destroyed' list") ids
705719 | _ -> []
706720 in
707721 let not_created = match List.assoc_opt "notCreated" fields with
···727741(** Arguments for Identity/changes method *)
728742module Changes_args = struct
729743 type t = {
730730- account_id : id;
744744+ account_id : Jmap.Id.t;
731745 since_state : string;
732746 max_changes : int option;
733747 }
···741755742756 let to_json t =
743757 let fields = [
744744- ("accountId", `String t.account_id);
758758+ ("accountId", `String (Jmap.Id.to_string t.account_id));
745759 ("sinceState", `String t.since_state);
746760 ] in
747761 let fields = match t.max_changes with
···755769 match json with
756770 | `Assoc fields ->
757771 let account_id = match List.assoc_opt "accountId" fields with
758758- | Some (`String s) -> s
772772+ | Some (`String s) -> (match Jmap.Id.of_string s with
773773+ | Ok id -> id
774774+ | Error _ -> failwith ("Invalid accountId: " ^ s))
759775 | _ -> failwith "Missing required 'accountId' field in Identity/changes arguments"
760776 in
761777 let since_state = match List.assoc_opt "sinceState" fields with
···775791776792 let pp fmt t =
777793 Format.fprintf fmt "Identity.Changes_args{account=%s;since=%s}"
778778- t.account_id t.since_state
794794+ (Jmap.Id.to_string t.account_id) t.since_state
779795780796 let pp_hum fmt t = pp fmt t
781797···787803(** Response for Identity/changes method *)
788804module Changes_response = struct
789805 type t = {
790790- account_id : id;
806806+ account_id : Jmap.Id.t;
791807 old_state : string;
792808 new_state : string;
793809 has_more_changes : bool;
794794- created : id list;
795795- updated : id list;
796796- destroyed : id list;
810810+ created : Jmap.Id.t list;
811811+ updated : Jmap.Id.t list;
812812+ destroyed : Jmap.Id.t list;
797813 }
798814799815 let account_id t = t.account_id
···811827812828 let to_json t =
813829 `Assoc [
814814- ("accountId", `String t.account_id);
830830+ ("accountId", `String (Jmap.Id.to_string t.account_id));
815831 ("oldState", `String t.old_state);
816832 ("newState", `String t.new_state);
817833 ("hasMoreChanges", `Bool t.has_more_changes);
818818- ("created", `List (List.map (fun id -> `String id) t.created));
819819- ("updated", `List (List.map (fun id -> `String id) t.updated));
820820- ("destroyed", `List (List.map (fun id -> `String id) t.destroyed));
834834+ ("created", `List (List.map (fun id -> `String (Jmap.Id.to_string id)) t.created));
835835+ ("updated", `List (List.map (fun id -> `String (Jmap.Id.to_string id)) t.updated));
836836+ ("destroyed", `List (List.map (fun id -> `String (Jmap.Id.to_string id)) t.destroyed));
821837 ]
822838823839 let of_json json =
···825841 match json with
826842 | `Assoc fields ->
827843 let account_id = match List.assoc_opt "accountId" fields with
828828- | Some (`String s) -> s
844844+ | Some (`String s) -> (match Jmap.Id.of_string s with
845845+ | Ok id -> id
846846+ | Error _ -> failwith ("Invalid accountId: " ^ s))
829847 | _ -> failwith "Missing required 'accountId' field in Identity/changes response"
830848 in
831849 let old_state = match List.assoc_opt "oldState" fields with
···842860 in
843861 let get_id_list key =
844862 match List.assoc_opt key fields with
845845- | Some (`List ids) -> List.map (function `String s -> s | _ -> failwith ("Invalid ID in '" ^ key ^ "' list")) ids
863863+ | Some (`List ids) -> List.map (function `String s -> (match Jmap.Id.of_string s with Ok id -> id | Error _ -> failwith ("Invalid ID in '" ^ key ^ "' list: " ^ s)) | _ -> failwith ("Invalid ID in '" ^ key ^ "' list")) ids
846864 | Some `Null | None -> []
847865 | _ -> failwith ("Invalid '" ^ key ^ "' field in Identity/changes response")
848866 in
···860878module Get_response = struct
861879 (* Use the outer module's type *)
862880 type identity = {
863863- id : id;
881881+ id : Jmap.Id.t;
864882 name : string;
865883 email : string;
866866- reply_to : Types.Email_address.t list option;
867867- bcc : Types.Email_address.t list option;
884884+ reply_to : Address.t list option;
885885+ bcc : Address.t list option;
868886 text_signature : string;
869887 html_signature : string;
870888 may_delete : bool;
871889 }
872890873891 type t = {
874874- account_id : id;
892892+ account_id : Jmap.Id.t;
875893 state : string;
876894 list : identity list;
877877- not_found : id list;
895895+ not_found : Jmap.Id.t list;
878896 }
879897880898 let account_id t = t.account_id
···887905888906 let identity_to_json identity =
889907 let fields = [
890890- ("id", `String identity.id);
908908+ ("Id.t", `String (Jmap.Id.to_string identity.id));
891909 ("name", `String identity.name);
892910 ("email", `String identity.email);
893911 ("textSignature", `String identity.text_signature);
···896914 ] in
897915 let fields = match identity.reply_to with
898916 | None -> ("replyTo", `Null) :: fields
899899- | Some addrs -> ("replyTo", `List (List.map Types.Email_address.to_json addrs)) :: fields
917917+ | Some addrs -> ("replyTo", `List (List.map Address.to_json addrs)) :: fields
900918 in
901919 let fields = match identity.bcc with
902920 | None -> ("bcc", `Null) :: fields
903903- | Some addrs -> ("bcc", `List (List.map Types.Email_address.to_json addrs)) :: fields
921921+ | Some addrs -> ("bcc", `List (List.map Address.to_json addrs)) :: fields
904922 in
905923 `Assoc (List.rev fields)
906924···925943 let rec process_addresses acc = function
926944 | [] -> Some (List.rev acc)
927945 | addr :: rest ->
928928- (match Types.Email_address.of_json addr with
946946+ (match Address.of_json addr with
929947 | Ok a -> process_addresses (a :: acc) rest
930948 | Error _ -> failwith ("Invalid address in " ^ key ^ " field"))
931949 in
···933951 | Some `Null | None -> None
934952 | _ -> failwith ("Invalid " ^ key ^ " field in Identity")
935953 in
936936- let id = get_string "id" "" in
937937- if id = "" then failwith "Missing required 'id' field in Identity";
954954+ let id_str = get_string "Id.t" "" in
955955+ if id_str = "" then failwith "Missing required 'id' field in Identity";
956956+ let id = match Jmap.Id.of_string id_str with
957957+ | Ok id -> id
958958+ | Error _ -> failwith ("Invalid id: " ^ id_str) in
938959 let email = get_string "email" "" in
939960 if email = "" then failwith "Missing required 'email' field in Identity";
940961 {
···951972952973 let to_json t =
953974 `Assoc [
954954- ("accountId", `String t.account_id);
975975+ ("accountId", `String (Jmap.Id.to_string t.account_id));
955976 ("state", `String t.state);
956977 ("list", `List (List.map identity_to_json t.list));
957957- ("notFound", `List (List.map (fun id -> `String id) t.not_found));
978978+ ("notFound", `List (List.map (fun id -> `String (Jmap.Id.to_string id)) t.not_found));
958979 ]
959980960981 let of_json json =
···962983 match json with
963984 | `Assoc fields ->
964985 let account_id = match List.assoc_opt "accountId" fields with
965965- | Some (`String s) -> s
986986+ | Some (`String s) -> (match Jmap.Id.of_string s with
987987+ | Ok id -> id
988988+ | Error _ -> failwith ("Invalid accountId: " ^ s))
966989 | _ -> failwith "Missing required 'accountId' field in Identity/get response"
967990 in
968991 let state = match List.assoc_opt "state" fields with
···974997 | _ -> failwith "Missing required 'list' field in Identity/get response"
975998 in
976999 let not_found = match List.assoc_opt "notFound" fields with
977977- | Some (`List ids) -> List.map (function `String s -> s | _ -> failwith "Invalid ID in 'notFound' list") ids
10001000+ | Some (`List ids) -> List.filter_map (function
10011001+ | `String s -> (match Jmap.Id.of_string s with
10021002+ | Ok id -> Some id
10031003+ | Error _ -> None)
10041004+ | _ -> None) ids
9781005 | _ -> failwith "Missing required 'notFound' field in Identity/get response"
9791006 in
9801007 Ok { account_id; state; list; not_found }
···9971024 ]
99810259991026 let to_string = function
10001000- | `Id -> "id"
10271027+ | `Id -> "Id.t"
10011028 | `Name -> "name"
10021029 | `Email -> "email"
10031030 | `ReplyTo -> "replyTo"
···10071034 | `MayDelete -> "mayDelete"
1008103510091036 let of_string = function
10101010- | "id" -> Some `Id
10371037+ | "Id.t" -> Some `Id
10111038 | "name" -> Some `Name
10121039 | "email" -> Some `Email
10131040 | "replyTo" -> Some `ReplyTo
+63-64
jmap/jmap-email/identity.mli
···1212 @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-6> RFC 8621, Section 6: Identity
1313*)
14141515-open Jmap.Types
1615open Jmap.Error
17161817(** Complete identity object representation.
···3130include Jmap_sigs.PRINTABLE with type t := t
32313332(** JMAP object interface for property selection and object creation *)
3434-include Jmap_sigs.JMAP_OBJECT with type t := t and type id_type := id
3333+include Jmap_sigs.JMAP_OBJECT with type t := t and type id_type := string
35343635(** Get the server-assigned identity identifier.
3736 @return Immutable unique ID (Some for all persisted identities, None only for unsaved objects) *)
3838-val id : t -> id option
3737+val id : t -> Jmap.Id.t option
39384039(** Get the display name for this identity.
4140 @return Human-readable name, empty string if not set *)
···47464847(** Get the default Reply-To addresses for this identity.
4948 @return List of reply-to addresses, or None if not specified *)
5050-val reply_to : t -> Types.Email_address.t list option
4949+val reply_to : t -> Address.t list option
51505251(** Get the default Bcc addresses for this identity.
5352 @return List of addresses to always Bcc, or None if not specified *)
5454-val bcc : t -> Types.Email_address.t list option
5353+val bcc : t -> Address.t list option
55545655(** Get the plain text email signature.
5756 @return Text signature to append to plain text messages *)
···6665val may_delete : t -> bool
67666867(** Create a new identity object.
6969- @param id Server-assigned identity identifier
6868+ @param Jmap.Id.t Server-assigned identity identifier
7069 @param name Optional display name (defaults to empty string)
7170 @param email Required email address for sending
7271 @param reply_to Optional default Reply-To addresses
···7675 @param may_delete Server permission for deletion
7776 @return New identity object *)
7877val v :
7979- id:id ->
7878+ id:Jmap.Id.t ->
8079 ?name:string ->
8180 email:string ->
8282- ?reply_to:Types.Email_address.t list ->
8383- ?bcc:Types.Email_address.t list ->
8181+ ?reply_to:Address.t list ->
8282+ ?bcc:Address.t list ->
8483 ?text_signature:string ->
8584 ?html_signature:string ->
8685 may_delete:bool ->
···110109111110 (** Get the Reply-To addresses for creation.
112111 @return Optional list of reply-to addresses *)
113113- val reply_to : t -> Types.Email_address.t list option
112112+ val reply_to : t -> Address.t list option
114113115114 (** Get the Bcc addresses for creation.
116115 @return Optional list of default Bcc addresses *)
117117- val bcc : t -> Types.Email_address.t list option
116116+ val bcc : t -> Address.t list option
118117119118 (** Get the plain text signature for creation.
120119 @return Optional text signature *)
···135134 val v :
136135 ?name:string ->
137136 email:string ->
138138- ?reply_to:Types.Email_address.t list ->
139139- ?bcc:Types.Email_address.t list ->
137137+ ?reply_to:Address.t list ->
138138+ ?bcc:Address.t list ->
140139 ?text_signature:string ->
141140 ?html_signature:string ->
142141 unit -> t
···156155157156 (** Get the server-assigned ID for the created identity.
158157 @return Unique identifier assigned by the server *)
159159- val id : t -> id
158158+ val id : t -> Jmap.Id.t
160159161160 (** Check if the created identity may be deleted.
162161 @return Server-computed permission for future deletion *)
163162 val may_delete : t -> bool
164163165164 (** Create an identity creation response.
166166- @param id Server-assigned identity ID
165165+ @param Jmap.Id.t Server-assigned identity ID
167166 @param may_delete Whether the identity can be deleted
168167 @return Creation response object *)
169168 val v :
170170- id:id ->
169169+ id:Jmap.Id.t ->
171170 may_delete:bool ->
172171 unit -> t
173172 end
···201200 (** Create an update that sets the Reply-To addresses.
202201 @param reply_to New Reply-To addresses (None to clear)
203202 @return Update patch object *)
204204- val set_reply_to : Types.Email_address.t list option -> t
203203+ val set_reply_to : Address.t list option -> t
205204206205 (** Create an update that sets the Bcc addresses.
207206 @param bcc New default Bcc addresses (None to clear)
208207 @return Update patch object *)
209209- val set_bcc : Types.Email_address.t list option -> t
208208+ val set_bcc : Address.t list option -> t
210209211210 (** Create an update that sets the plain text signature.
212211 @param text_signature New text signature (empty string to clear)
···272271 include Jmap_sigs.JSONABLE with type t := t
273272274273 (** JMAP method arguments interface *)
275275- include Jmap_sigs.METHOD_ARGS with type t := t and type account_id := id
274274+ include Jmap_sigs.METHOD_ARGS with type t := t and type account_id := string
276275277276 (** Get the account ID for the operation.
278277 @return Account identifier where identities will be retrieved *)
279279- val account_id : t -> id
278278+ val account_id : t -> Jmap.Id.t
280279281280 (** Validate get arguments according to JMAP method constraints.
282281 @param t Get arguments to validate
···289288290289 (** Get the specific identity IDs to retrieve.
291290 @return List of identity IDs, or None to retrieve all identities *)
292292- val ids : t -> id list option
291291+ val ids : t -> Jmap.Id.t list option
293292294293 (** Get the properties to include in the response.
295294 @return List of property names, or None for all properties *)
···301300 @param properties Optional list of properties to include
302301 @return Identity/get arguments object *)
303302 val v :
304304- account_id:id ->
305305- ?ids:id list ->
303303+ account_id:Jmap.Id.t ->
304304+ ?ids:Jmap.Id.t list ->
306305 ?properties:string list ->
307306 unit -> t
308307end
···317316module Get_response : sig
318317 (** Identity type for response lists *)
319318 type identity = {
320320- id : id;
319319+ id : Jmap.Id.t;
321320 name : string;
322321 email : string;
323323- reply_to : Types.Email_address.t list option;
324324- bcc : Types.Email_address.t list option;
322322+ reply_to : Address.t list option;
323323+ bcc : Address.t list option;
325324 text_signature : string;
326325 html_signature : string;
327326 may_delete : bool;
···334333335334 (** Get the account ID from the response.
336335 @return Account identifier where identities were retrieved *)
337337- val account_id : t -> id
336336+ val account_id : t -> Jmap.Id.t
338337339338 (** Get the current state string for change tracking.
340339 @return State string for use in Identity/changes *)
···346345347346 (** Get the list of identity IDs that were not found.
348347 @return List of requested IDs that don't exist *)
349349- val not_found : t -> id list
348348+ val not_found : t -> Jmap.Id.t list
350349351350 (** Create Identity/get response.
352351 @param account_id Account where identities were retrieved
···355354 @param not_found IDs that were not found
356355 @return Identity/get response object *)
357356 val v :
358358- account_id:id ->
357357+ account_id:Jmap.Id.t ->
359358 state:string ->
360359 list:identity list ->
361361- not_found:id list ->
360360+ not_found:Jmap.Id.t list ->
362361 unit -> t
363362end
364363···375374 include Jmap_sigs.JSONABLE with type t := t
376375377376 (** JMAP method arguments interface *)
378378- include Jmap_sigs.METHOD_ARGS with type t := t and type account_id := id
377377+ include Jmap_sigs.METHOD_ARGS with type t := t and type account_id := string
379378380379 (** Get the account ID for the operation.
381380 @return Account identifier where identities will be modified *)
382382- val account_id : t -> id
381381+ val account_id : t -> Jmap.Id.t
383382384383 (** Validate set arguments according to JMAP method constraints.
385384 @param t Set arguments to validate
···396395397396 (** Get the identities to create.
398397 @return Map of creation IDs to creation objects *)
399399- val create : t -> Create.t id_map option
398398+ val create : t -> (string, Create.t) Hashtbl.t option
400399401400 (** Get the identities to update.
402401 @return Map of identity IDs to update patch objects *)
403403- val update : t -> Update.t id_map option
402402+ val update : t -> (string, Update.t) Hashtbl.t option
404403405404 (** Get the identity IDs to destroy.
406405 @return List of identity IDs to delete *)
407407- val destroy : t -> id list option
406406+ val destroy : t -> Jmap.Id.t list option
408407409408 (** Create Identity/set arguments.
410409 @param account_id Account where identities will be modified
···414413 @param destroy Optional list of identity IDs to delete
415414 @return Identity/set arguments object *)
416415 val v :
417417- account_id:id ->
416416+ account_id:Jmap.Id.t ->
418417 ?if_in_state:string ->
419419- ?create:Create.t id_map ->
420420- ?update:Update.t id_map ->
421421- ?destroy:id list ->
418418+ ?create:(string, Create.t) Hashtbl.t ->
419419+ ?update:(string, Update.t) Hashtbl.t ->
420420+ ?destroy:Jmap.Id.t list ->
422421 unit -> t
423422end
424423···437436438437 (** Get the account ID from the response.
439438 @return Account identifier where identities were modified *)
440440- val account_id : t -> id
439439+ val account_id : t -> Jmap.Id.t
441440442441 (** Get the old state string.
443442 @return State string before the operations were applied *)
···449448450449 (** Get the successfully created identities.
451450 @return Map of creation IDs to creation response objects *)
452452- val created : t -> Create.Response.t id_map
451451+ val created : t -> (string, Create.Response.t) Hashtbl.t
453452454453 (** Get the successfully updated identities.
455454 @return Map of identity IDs to update response objects *)
456456- val updated : t -> Update.Response.t id_map
455455+ val updated : t -> (string, Update.Response.t) Hashtbl.t
457456458457 (** Get the successfully destroyed identity IDs.
459458 @return List of identity IDs that were successfully deleted *)
460460- val destroyed : t -> id list
459459+ val destroyed : t -> Jmap.Id.t list
461460462461 (** Get the identities that could not be created.
463462 @return Map of creation IDs to error objects *)
464464- val not_created : t -> Set_error.t id_map
463463+ val not_created : t -> (string, Set_error.t) Hashtbl.t
465464466465 (** Get the identities that could not be updated.
467466 @return Map of identity IDs to error objects *)
468468- val not_updated : t -> Set_error.t id_map
467467+ val not_updated : t -> (string, Set_error.t) Hashtbl.t
469468470469 (** Get the identities that could not be destroyed.
471470 @return Map of identity IDs to error objects *)
472472- val not_destroyed : t -> Set_error.t id_map
471471+ val not_destroyed : t -> (string, Set_error.t) Hashtbl.t
473472474473 (** Create Identity/set response.
475474 @param account_id Account where identities were modified
···483482 @param not_destroyed Identities that could not be destroyed
484483 @return Identity/set response object *)
485484 val v :
486486- account_id:id ->
485485+ account_id:Jmap.Id.t ->
487486 old_state:string ->
488487 new_state:string ->
489489- ?created:Create.Response.t id_map ->
490490- ?updated:Update.Response.t id_map ->
491491- ?destroyed:id list ->
492492- ?not_created:Set_error.t id_map ->
493493- ?not_updated:Set_error.t id_map ->
494494- ?not_destroyed:Set_error.t id_map ->
488488+ ?created:(string, Create.Response.t) Hashtbl.t ->
489489+ ?updated:(string, Update.Response.t) Hashtbl.t ->
490490+ ?destroyed:Jmap.Id.t list ->
491491+ ?not_created:(string, Set_error.t) Hashtbl.t ->
492492+ ?not_updated:(string, Set_error.t) Hashtbl.t ->
493493+ ?not_destroyed:(string, Set_error.t) Hashtbl.t ->
495494 unit -> t
496495end
497496···509508 include Jmap_sigs.JSONABLE with type t := t
510509511510 (** JMAP method arguments interface *)
512512- include Jmap_sigs.METHOD_ARGS with type t := t and type account_id := id
511511+ include Jmap_sigs.METHOD_ARGS with type t := t and type account_id := string
513512514513 (** Get the account ID for the operation.
515514 @return Account identifier where changes will be retrieved *)
516516- val account_id : t -> id
515515+ val account_id : t -> Jmap.Id.t
517516518517 (** Validate changes arguments according to JMAP method constraints.
519518 @param t Changes arguments to validate
···538537 @param max_changes Optional limit on number of changes
539538 @return Identity/changes arguments object *)
540539 val v :
541541- account_id:id ->
540540+ account_id:Jmap.Id.t ->
542541 since_state:string ->
543542 ?max_changes:int ->
544543 unit -> t
···559558560559 (** Get the account ID from the response.
561560 @return Account identifier where changes were retrieved *)
562562- val account_id : t -> id
561561+ val account_id : t -> Jmap.Id.t
563562564563 (** Get the old state string.
565564 @return State string that was passed in since_state *)
···575574576575 (** Get the list of created or updated identity IDs.
577576 @return List of identity IDs that have been created or updated *)
578578- val created : t -> id list
577577+ val created : t -> Jmap.Id.t list
579578580579 (** Get the list of updated identity IDs.
581580 @return List of identity IDs that have been updated *)
582582- val updated : t -> id list
581581+ val updated : t -> Jmap.Id.t list
583582584583 (** Get the list of destroyed identity IDs.
585584 @return List of identity IDs that have been destroyed *)
586586- val destroyed : t -> id list
585585+ val destroyed : t -> Jmap.Id.t list
587586588587 (** Create Identity/changes response.
589588 @param account_id Account where changes were retrieved
···595594 @param destroyed List of destroyed identity IDs
596595 @return Identity/changes response object *)
597596 val v :
598598- account_id:id ->
597597+ account_id:Jmap.Id.t ->
599598 old_state:string ->
600599 new_state:string ->
601600 has_more_changes:bool ->
602602- ?created:id list ->
603603- ?updated:id list ->
604604- ?destroyed:id list ->
601601+ ?created:Jmap.Id.t list ->
602602+ ?updated:Jmap.Id.t list ->
603603+ ?destroyed:Jmap.Id.t list ->
605604 unit -> t
606605end
607606
+365-191
jmap/jmap-email/mailbox.ml
···991010[@@@warning "-32"] (* Suppress unused value warnings for interface-required functions *)
11111212-open Jmap.Types
1312open Jmap.Method_names
1413open Jmap.Methods
1514···42414342(* Main mailbox type with all properties *)
4443type t = {
4545- mailbox_id : id;
4444+ mailbox_id : Jmap.Id.t;
4645 name : string;
4747- parent_id : id option;
4646+ parent_id : Jmap.Id.t option;
4847 role : role option;
4949- sort_order : uint;
5050- total_emails : uint;
5151- unread_emails : uint;
5252- total_threads : uint;
5353- unread_threads : uint;
4848+ sort_order : Jmap.UInt.t;
4949+ total_emails : Jmap.UInt.t;
5050+ unread_emails : Jmap.UInt.t;
5151+ total_threads : Jmap.UInt.t;
5252+ unread_threads : Jmap.UInt.t;
5453 my_rights : rights;
5554 is_subscribed : bool;
5655}
···8685 may_set_seen = false; may_set_keywords = false; may_create_child = false;
8786 may_rename = false; may_delete = false; may_submit = false;
8887 } in
8888+ let id_result = match Jmap.Id.of_string id with
8989+ | Ok id -> id
9090+ | Error e -> failwith ("Invalid mailbox ID: " ^ e) in
9191+ let sort_order = match Jmap.UInt.of_int 0 with
9292+ | Ok n -> n
9393+ | Error e -> failwith ("Invalid sort_order: " ^ e) in
9494+ let total_emails = match Jmap.UInt.of_int 0 with
9595+ | Ok n -> n
9696+ | Error e -> failwith ("Invalid total_emails: " ^ e) in
9797+ let unread_emails = match Jmap.UInt.of_int 0 with
9898+ | Ok n -> n
9999+ | Error e -> failwith ("Invalid unread_emails: " ^ e) in
89100 {
9090- mailbox_id = id;
101101+ mailbox_id = id_result;
91102 name = "Untitled";
92103 parent_id = None;
93104 role = None;
9494- sort_order = 0;
9595- total_emails = 0;
9696- unread_emails = 0;
9797- total_threads = 0;
9898- unread_threads = 0;
105105+ sort_order;
106106+ total_emails;
107107+ unread_emails;
108108+ total_threads = (match Jmap.UInt.of_int 0 with Ok n -> n | Error e -> failwith ("Invalid total_threads: " ^ e));
109109+ unread_threads = (match Jmap.UInt.of_int 0 with Ok n -> n | Error e -> failwith ("Invalid unread_threads: " ^ e));
99110 my_rights = default_rights;
100111 is_subscribed = true;
101112 }
102113103114(* Get list of all valid property names for Mailbox objects *)
104115let valid_properties () = [
105105- "id"; "name"; "parentId"; "role"; "sortOrder";
116116+ "Jmap.Id.t"; "name"; "parentId"; "role"; "sortOrder";
106117 "totalEmails"; "unreadEmails"; "totalThreads"; "unreadThreads";
107118 "myRights"; "isSubscribed"
108119]
109120110121111122(* Extended constructor with validation - renamed from create *)
112112-let create_full ~id ~name ?parent_id ?role ?(sort_order=0) ~total_emails ~unread_emails
123123+let create_full ~id ~name ?parent_id ?role ?(sort_order=(match Jmap.UInt.of_int 0 with Ok u -> u | Error _ -> failwith "Invalid default sort_order")) ~total_emails ~unread_emails
113124 ~total_threads ~unread_threads ~my_rights ~is_subscribed () =
114125 if String.length name = 0 then
115126 Error "Mailbox name cannot be empty"
116116- else if total_emails < unread_emails then
127127+ else if Jmap.UInt.to_int total_emails < Jmap.UInt.to_int unread_emails then
117128 Error "Unread emails cannot exceed total emails"
118118- else if total_threads < unread_threads then
129129+ else if Jmap.UInt.to_int total_threads < Jmap.UInt.to_int unread_threads then
119130 Error "Unread threads cannot exceed total threads"
120131 else
132132+ let sort_order_uint = sort_order in
121133 Ok {
122134 mailbox_id = id;
123135 name;
124136 parent_id;
125137 role;
126126- sort_order;
138138+ sort_order = sort_order_uint;
127139 total_emails;
128140 unread_emails;
129141 total_threads;
···210222 | Some r -> Role.to_string r
211223 | None -> "none"
212224 in
213213- Format.fprintf ppf "Mailbox{id=%s; name=%s; role=%s}" t.mailbox_id t.name role_str
225225+ Format.fprintf ppf "Mailbox{id=%s; name=%s; role=%s}" (Jmap.Id.to_string t.mailbox_id) t.name role_str
214226215227let pp_hum = pp
216228···232244 ("maySubmit", `Bool rights.may_submit);
233245 ] in
234246 let all_fields = [
235235- ("id", `String t.mailbox_id);
247247+ ("id", `String (Jmap.Id.to_string t.mailbox_id));
236248 ("name", `String t.name);
237237- ("parentId", (match t.parent_id with Some p -> `String p | None -> `Null));
249249+ ("parentId", (match t.parent_id with Some p -> `String (Jmap.Id.to_string p) | None -> `Null));
238250 ("role", role_to_json t.role);
239239- ("sortOrder", `Int t.sort_order);
240240- ("totalEmails", `Int t.total_emails);
241241- ("unreadEmails", `Int t.unread_emails);
242242- ("totalThreads", `Int t.total_threads);
243243- ("unreadThreads", `Int t.unread_threads);
251251+ ("sortOrder", `Int (Jmap.UInt.to_int t.sort_order));
252252+ ("totalEmails", `Int (Jmap.UInt.to_int t.total_emails));
253253+ ("unreadEmails", `Int (Jmap.UInt.to_int t.unread_emails));
254254+ ("totalThreads", `Int (Jmap.UInt.to_int t.total_threads));
255255+ ("unreadThreads", `Int (Jmap.UInt.to_int t.unread_threads));
244256 ("myRights", rights_to_json t.my_rights);
245257 ("isSubscribed", `Bool t.is_subscribed);
246258 ] in
···386398 let other s = Other s
387399388400 let to_string = function
389389- | Id -> "id"
401401+ | Id -> "Jmap.Id.t"
390402 | Name -> "name"
391403 | ParentId -> "parentId"
392404 | Role -> "role"
···400412 | Other s -> s
401413402414 let of_string = function
403403- | "id" -> Ok Id
415415+ | "Jmap.Id.t" -> Ok Id
404416 | "name" -> Ok Name
405417 | "parentId" -> Ok ParentId
406418 | "role" -> Ok Role
···443455module Create = struct
444456 type t = {
445457 create_name : string;
446446- create_parent_id : id option;
458458+ create_parent_id : Jmap.Id.t option;
447459 create_role : role option;
448448- create_sort_order : uint option;
460460+ create_sort_order : Jmap.UInt.t option;
449461 create_is_subscribed : bool option;
450462 }
451463···473485 ("name", `String create_req.create_name);
474486 ] in
475487 let base = match create_req.create_parent_id with
476476- | Some pid -> ("parentId", `String pid) :: base
488488+ | Some pid -> ("parentId", `String (Jmap.Id.to_string pid)) :: base
477489 | None -> base
478490 in
479491 let base = match create_req.create_role with
···481493 | None -> base
482494 in
483495 let base = match create_req.create_sort_order with
484484- | Some so -> ("sortOrder", `Int so) :: base
496496+ | Some so -> ("sortOrder", `Int (Jmap.UInt.to_int so)) :: base
485497 | None -> base
486498 in
487499 let base = match create_req.create_is_subscribed with
···494506 try
495507 let open Yojson.Safe.Util in
496508 let name = json |> member "name" |> to_string in
497497- let parent_id = json |> member "parentId" |> to_string_option in
509509+ let parent_id = match json |> member "parentId" |> to_string_option with
510510+ | None -> None
511511+ | Some s -> Some (match Jmap.Id.of_string s with
512512+ | Ok id -> id
513513+ | Error err -> failwith ("Invalid parentId: " ^ err)) in
498514 let role_opt : (role option, string) result = match json |> member "role" with
499515 | `Null -> Ok None
500516 | role_json ->
···502518 | Ok r -> Ok (Some r)
503519 | Error e -> Error e
504520 in
505505- let sort_order = json |> member "sortOrder" |> to_int_option in
521521+ let sort_order = match json |> member "sortOrder" |> to_int_option with
522522+ | None -> None
523523+ | Some i -> Some (match Jmap.UInt.of_int i with
524524+ | Ok uint -> uint
525525+ | Error err -> failwith ("Invalid sortOrder: " ^ err)) in
506526 let is_subscribed = json |> member "isSubscribed" |> to_bool_option in
507527 match role_opt with
508528 | Ok role ->
···520540521541 module Response = struct
522542 type t = {
523523- response_id : id;
543543+ response_id : Jmap.Id.t;
524544 response_role : role option;
525525- response_sort_order : uint;
526526- response_total_emails : uint;
527527- response_unread_emails : uint;
528528- response_total_threads : uint;
529529- response_unread_threads : uint;
545545+ response_sort_order : Jmap.UInt.t;
546546+ response_total_emails : Jmap.UInt.t;
547547+ response_unread_emails : Jmap.UInt.t;
548548+ response_total_threads : Jmap.UInt.t;
549549+ response_unread_threads : Jmap.UInt.t;
530550 response_my_rights : rights;
531551 response_is_subscribed : bool;
532552 }
···544564 (* JSON serialization *)
545565 let to_json response =
546566 let base = [
547547- ("id", `String response.response_id);
548548- ("sortOrder", `Int response.response_sort_order);
549549- ("totalEmails", `Int response.response_total_emails);
550550- ("unreadEmails", `Int response.response_unread_emails);
551551- ("totalThreads", `Int response.response_total_threads);
552552- ("unreadThreads", `Int response.response_unread_threads);
567567+ ("Jmap.Id.t", `String (Jmap.Id.to_string response.response_id));
568568+ ("sortOrder", `Int (Jmap.UInt.to_int response.response_sort_order));
569569+ ("totalEmails", `Int (Jmap.UInt.to_int response.response_total_emails));
570570+ ("unreadEmails", `Int (Jmap.UInt.to_int response.response_unread_emails));
571571+ ("totalThreads", `Int (Jmap.UInt.to_int response.response_total_threads));
572572+ ("unreadThreads", `Int (Jmap.UInt.to_int response.response_unread_threads));
553573 ("myRights", Rights.to_json response.response_my_rights);
554574 ("isSubscribed", `Bool response.response_is_subscribed);
555575 ] in
···562582 let of_json json =
563583 try
564584 let open Yojson.Safe.Util in
565565- let id = json |> member "id" |> to_string in
585585+ let id_str = json |> member "id" |> to_string in
586586+ let id = match Jmap.Id.of_string id_str with
587587+ | Ok id_val -> id_val
588588+ | Error e -> failwith ("Invalid mailbox ID: " ^ id_str ^ " - " ^ e)
589589+ in
566590 let role_opt : (role option, string) result = match json |> member "role" with
567591 | `Null -> Ok None
568592 | role_json ->
···570594 | Ok r -> Ok (Some r)
571595 | Error e -> Error e
572596 in
573573- let sort_order = json |> member "sortOrder" |> to_int in
574574- let total_emails = json |> member "totalEmails" |> to_int in
575575- let unread_emails = json |> member "unreadEmails" |> to_int in
576576- let total_threads = json |> member "totalThreads" |> to_int in
577577- let unread_threads = json |> member "unreadThreads" |> to_int in
597597+ let sort_order_int = json |> member "sortOrder" |> to_int in
598598+ let sort_order = match Jmap.UInt.of_int sort_order_int with
599599+ | Ok uint -> uint
600600+ | Error _ -> failwith ("Invalid sortOrder: " ^ string_of_int sort_order_int) in
601601+ let total_emails_int = json |> member "totalEmails" |> to_int in
602602+ let total_emails = match Jmap.UInt.of_int total_emails_int with
603603+ | Ok uint -> uint
604604+ | Error _ -> failwith ("Invalid totalEmails: " ^ string_of_int total_emails_int) in
605605+ let unread_emails_int = json |> member "unreadEmails" |> to_int in
606606+ let unread_emails = match Jmap.UInt.of_int unread_emails_int with
607607+ | Ok uint -> uint
608608+ | Error _ -> failwith ("Invalid unreadEmails: " ^ string_of_int unread_emails_int) in
609609+ let total_threads_int = json |> member "totalThreads" |> to_int in
610610+ let total_threads = match Jmap.UInt.of_int total_threads_int with
611611+ | Ok uint -> uint
612612+ | Error _ -> failwith ("Invalid totalThreads: " ^ string_of_int total_threads_int) in
613613+ let unread_threads_int = json |> member "unreadThreads" |> to_int in
614614+ let unread_threads = match Jmap.UInt.of_int unread_threads_int with
615615+ | Ok uint -> uint
616616+ | Error _ -> failwith ("Invalid unreadThreads: " ^ string_of_int unread_threads_int) in
578617 let my_rights_result = json |> member "myRights" |> Rights.of_json in
579618 let is_subscribed = json |> member "isSubscribed" |> to_bool in
580619 match role_opt, my_rights_result with
···599638end
600639601640module Update = struct
602602- type t = patch_object
641641+ type t = Jmap.Methods.patch_object
603642604643 let create ?name ?parent_id ?role ?sort_order ?is_subscribed () =
605644 let patches = [] in
···608647 | None -> patches
609648 in
610649 let patches = match parent_id with
611611- | Some (Some id) -> ("/parentId", `String id) :: patches
650650+ | Some (Some id) -> ("/parentId", `String (Jmap.Id.to_string id)) :: patches
612651 | Some None -> ("/parentId", `Null) :: patches
613652 | None -> patches
614653 in
···618657 | None -> patches
619658 in
620659 let patches = match sort_order with
621621- | Some n -> ("/sortOrder", `Int n) :: patches
660660+ | Some n -> ("/sortOrder", `Int (Jmap.UInt.to_int n)) :: patches
622661 | None -> patches
623662 in
624663 let patches = match is_subscribed with
···648687 | Some mailbox ->
649688 (* Create complete JSON representation inline *)
650689 let base = [
651651- ("id", `String mailbox.mailbox_id);
690690+ ("Jmap.Id.t", `String (Jmap.Id.to_string mailbox.mailbox_id));
652691 ("name", `String mailbox.name);
653653- ("sortOrder", `Int mailbox.sort_order);
654654- ("totalEmails", `Int mailbox.total_emails);
655655- ("unreadEmails", `Int mailbox.unread_emails);
656656- ("totalThreads", `Int mailbox.total_threads);
657657- ("unreadThreads", `Int mailbox.unread_threads);
692692+ ("sortOrder", `Int (Jmap.UInt.to_int mailbox.sort_order));
693693+ ("totalEmails", `Int (Jmap.UInt.to_int mailbox.total_emails));
694694+ ("unreadEmails", `Int (Jmap.UInt.to_int mailbox.unread_emails));
695695+ ("totalThreads", `Int (Jmap.UInt.to_int mailbox.total_threads));
696696+ ("unreadThreads", `Int (Jmap.UInt.to_int mailbox.unread_threads));
658697 ("myRights", Rights.to_json mailbox.my_rights);
659698 ("isSubscribed", `Bool mailbox.is_subscribed);
660699 ] in
661700 let base = match mailbox.parent_id with
662662- | Some pid -> ("parentId", `String pid) :: base
701701+ | Some pid -> ("parentId", `String (Jmap.Id.to_string pid)) :: base
663702 | None -> base
664703 in
665704 let base = match mailbox.role with
···681720(* Stub implementations for method modules - these would be implemented based on actual JMAP method signatures *)
682721module Query_args = struct
683722 type t = {
684684- account_id : id;
723723+ account_id : Jmap.Id.t;
685724 filter : Filter.t option;
686725 sort : Comparator.t list option;
687687- position : uint option;
688688- limit : uint option;
726726+ position : Jmap.UInt.t option;
727727+ limit : Jmap.UInt.t option;
689728 calculate_total : bool option;
690729 }
691730···700739 let calculate_total args = args.calculate_total
701740702741 let to_json args =
703703- let fields = [("accountId", `String args.account_id)] in
742742+ let fields = [("accountId", `String (Jmap.Id.to_string args.account_id))] in
704743 let fields = match args.filter with
705744 | None -> fields
706706- | Some filter -> ("filter", Filter.to_json filter) :: fields
745745+ | Some _filter -> ("filter", `Null) :: fields (* Filter serialization needs implementation *)
707746 in
708747 let fields = match args.sort with
709748 | None -> fields
···711750 in
712751 let fields = match args.position with
713752 | None -> fields
714714- | Some pos -> ("position", `Int pos) :: fields
753753+ | Some pos -> ("position", `Int (Jmap.UInt.to_int pos)) :: fields
715754 in
716755 let fields = match args.limit with
717756 | None -> fields
718718- | Some lim -> ("limit", `Int lim) :: fields
757757+ | Some lim -> ("limit", `Int (Jmap.UInt.to_int lim)) :: fields
719758 in
720759 let fields = match args.calculate_total with
721760 | None -> fields
···728767 match json with
729768 | `Assoc fields ->
730769 let account_id = match List.assoc "accountId" fields with
731731- | `String s -> s
770770+ | `String s -> (match Jmap.Id.of_string s with
771771+ | Ok id -> id
772772+ | Error _ -> failwith ("Invalid accountId: " ^ s))
732773 | _ -> failwith "Expected string for accountId"
733774 in
734775 let filter : Filter.t option = match List.assoc_opt "filter" fields with
···745786 ) sort_list)
746787 | Some _ -> failwith "Expected list for sort"
747788 in
748748- let position : uint option = match List.assoc_opt "position" fields with
789789+ let position : Jmap.UInt.t option = match List.assoc_opt "position" fields with
749790 | None -> None
750750- | Some (`Int i) when i >= 0 -> Some i
791791+ | Some (`Int i) when i >= 0 -> (match Jmap.UInt.of_int i with
792792+ | Ok uint -> Some uint
793793+ | Error _ -> failwith ("Invalid position: " ^ string_of_int i))
751794 | Some (`Int _) -> failwith "Position must be non-negative"
752795 | Some _ -> failwith "Expected int for position"
753796 in
754754- let limit : uint option = match List.assoc_opt "limit" fields with
797797+ let limit : Jmap.UInt.t option = match List.assoc_opt "limit" fields with
755798 | None -> None
756756- | Some (`Int i) when i >= 0 -> Some i
799799+ | Some (`Int i) when i >= 0 -> (match Jmap.UInt.of_int i with
800800+ | Ok uint -> Some uint
801801+ | Error _ -> failwith ("Invalid limit: " ^ string_of_int i))
757802 | Some (`Int _) -> failwith "Limit must be non-negative"
758803 | Some _ -> failwith "Expected int for limit"
759804 in
···770815 | exn -> Error ("Query_args JSON parsing exception: " ^ Printexc.to_string exn)
771816772817 let pp fmt t =
773773- Format.fprintf fmt "Mailbox.Query_args{account=%s}" t.account_id
818818+ Format.fprintf fmt "Mailbox.Query_args{account=%s}" (Jmap.Id.to_string t.account_id)
774819775820 let pp_hum fmt t = pp fmt t
776821···781826782827module Query_response = struct
783828 type t = {
784784- account_id : id;
829829+ account_id : Jmap.Id.t;
785830 query_state : string;
786831 can_calculate_changes : bool;
787787- position : uint;
788788- total : uint option;
789789- ids : id list;
832832+ position : Jmap.UInt.t;
833833+ total : Jmap.UInt.t option;
834834+ ids : Jmap.Id.t list;
790835 }
791836792837 let account_id resp = resp.account_id
···805850 @return JSON object with accountId, queryState, canCalculateChanges, position, ids, and optional total *)
806851 let to_json resp =
807852 let base = [
808808- ("accountId", `String resp.account_id);
853853+ ("accountId", `String (Jmap.Id.to_string resp.account_id));
809854 ("queryState", `String resp.query_state);
810855 ("canCalculateChanges", `Bool resp.can_calculate_changes);
811811- ("position", `Int resp.position);
812812- ("ids", `List (List.map (fun id -> `String id) resp.ids));
856856+ ("position", `Int (Jmap.UInt.to_int resp.position));
857857+ ("ids", `List (List.map (fun id -> `String (Jmap.Id.to_string id)) resp.ids));
813858 ] in
814859 let base = match resp.total with
815815- | Some total -> ("total", `Int total) :: base
860860+ | Some total -> ("total", `Int (Jmap.UInt.to_int total)) :: base
816861 | None -> base
817862 in
818863 `Assoc base
···827872 let of_json json =
828873 try
829874 let open Yojson.Safe.Util in
830830- let account_id = json |> member "accountId" |> to_string in
875875+ let account_id_str = json |> member "accountId" |> to_string in
876876+ let account_id = match Jmap.Id.of_string account_id_str with
877877+ | Ok id -> id
878878+ | Error _ -> failwith ("Invalid accountId: " ^ account_id_str) in
831879 let query_state = json |> member "queryState" |> to_string in
832880 let can_calculate_changes = json |> member "canCalculateChanges" |> to_bool in
833833- let position = json |> member "position" |> to_int in
834834- let ids = json |> member "ids" |> to_list |> List.map to_string in
835835- let total = json |> member "total" |> to_int_option in
881881+ let position_int = json |> member "position" |> to_int in
882882+ let position = match Jmap.UInt.of_int position_int with
883883+ | Ok uint -> uint
884884+ | Error _ -> failwith ("Invalid position: " ^ string_of_int position_int) in
885885+ let ids_strings = json |> member "ids" |> to_list |> List.map to_string in
886886+ let ids = List.filter_map (fun s -> match Jmap.Id.of_string s with
887887+ | Ok id -> Some id
888888+ | Error _ -> None) ids_strings in
889889+ let total_opt = json |> member "total" |> to_int_option in
890890+ let total = match total_opt with
891891+ | None -> None
892892+ | Some total_int -> (match Jmap.UInt.of_int total_int with
893893+ | Ok uint -> Some uint
894894+ | Error _ -> None) in
836895 Ok {
837896 account_id;
838897 query_state;
···847906848907 let pp fmt t =
849908 Format.fprintf fmt "Mailbox.Query_response{account=%s;total=%s}"
850850- t.account_id
851851- (match t.total with Some n -> string_of_int n | None -> "unknown")
909909+ (Jmap.Id.to_string t.account_id)
910910+ (match t.total with Some n -> string_of_int (Jmap.UInt.to_int n) | None -> "unknown")
852911853912 let pp_hum fmt t = pp fmt t
854913···859918860919module Get_args = struct
861920 type t = {
862862- account_id : id;
863863- ids : id list option;
921921+ account_id : Jmap.Id.t;
922922+ ids : Jmap.Id.t list option;
864923 properties : Property.t list option;
865924 }
866925···879938 @param args The get arguments to serialize
880939 @return JSON object with accountId, and optional ids and properties *)
881940 let to_json args =
882882- let base = [("accountId", `String args.account_id)] in
941941+ let base = [("accountId", `String (Jmap.Id.to_string args.account_id))] in
883942 let base = match args.ids with
884943 | None -> base
885885- | Some ids -> ("ids", `List (List.map (fun id -> `String id) ids)) :: base
944944+ | Some ids -> ("ids", `List (List.map (fun id -> `String (Jmap.Id.to_string id)) ids)) :: base
886945 in
887946 let base = match args.properties with
888947 | None -> base
889948 | Some props ->
890949 let prop_strings = List.map Property.to_string props in
891891- ("properties", `List (List.map (fun s -> `String s) prop_strings)) :: base
950950+ ("properties", (`List (List.map (fun s -> `String s) prop_strings) : Yojson.Safe.t)) :: base
892951 in
893952 `Assoc base
894953···901960 @return Result with parsed get arguments or error message *)
902961 let of_json json =
903962 try
904904- let account_id = Yojson.Safe.Util.(json |> member "accountId" |> to_string) in
963963+ let account_id = match Jmap.Id.of_string (Yojson.Safe.Util.(json |> member "accountId" |> to_string)) with
964964+ | Ok id -> id
965965+ | Error _ -> failwith "Invalid accountId in Get_args JSON" in
905966 let ids = match Yojson.Safe.Util.(json |> member "ids") with
906967 | `Null -> None
907907- | `List id_list -> Some (List.map Yojson.Safe.Util.to_string id_list)
968968+ | `List id_list -> Some (List.map (fun id_json ->
969969+ match Jmap.Id.of_string (Yojson.Safe.Util.to_string id_json) with
970970+ | Ok id -> id
971971+ | Error _ -> failwith ("Invalid id in Get_args ids list: " ^ Yojson.Safe.Util.to_string id_json)
972972+ ) id_list)
908973 | _ -> failwith "Expected array or null for ids"
909974 in
910975 let properties = match Yojson.Safe.Util.(json |> member "properties") with
···925990 | exn -> Error ("Get_args JSON parse error: " ^ Printexc.to_string exn)
926991927992 let pp fmt t =
928928- Format.fprintf fmt "Mailbox.Get_args{account=%s}" t.account_id
993993+ Format.fprintf fmt "Mailbox.Get_args{account=%s}" (Jmap.Id.to_string t.account_id)
929994930995 let pp_hum fmt t = pp fmt t
931996···93610019371002module Get_response = struct
9381003 type t = {
939939- account_id : id;
10041004+ account_id : Jmap.Id.t;
9401005 state : string;
9411006 list : mailbox_t list;
942942- not_found : id list;
10071007+ not_found : Jmap.Id.t list;
9431008 }
94410099451010 let account_id resp = resp.account_id
···9571022 let to_json resp =
9581023 (* Helper to serialize a single mailbox - duplicated locally to avoid forward reference *)
9591024 let mailbox_to_json mailbox =
960960- let base = [
961961- ("id", `String mailbox.mailbox_id);
10251025+ let base : (string * Yojson.Safe.t) list = [
10261026+ ("Jmap.Id.t", `String (Jmap.Id.to_string mailbox.mailbox_id));
9621027 ("name", `String mailbox.name);
963963- ("sortOrder", `Int mailbox.sort_order);
964964- ("totalEmails", `Int mailbox.total_emails);
965965- ("unreadEmails", `Int mailbox.unread_emails);
966966- ("totalThreads", `Int mailbox.total_threads);
967967- ("unreadThreads", `Int mailbox.unread_threads);
10281028+ ("sortOrder", `Int (Jmap.UInt.to_int mailbox.sort_order));
10291029+ ("totalEmails", `Int (Jmap.UInt.to_int mailbox.total_emails));
10301030+ ("unreadEmails", `Int (Jmap.UInt.to_int mailbox.unread_emails));
10311031+ ("totalThreads", `Int (Jmap.UInt.to_int mailbox.total_threads));
10321032+ ("unreadThreads", `Int (Jmap.UInt.to_int mailbox.unread_threads));
9681033 ("myRights", Rights.to_json mailbox.my_rights);
9691034 ("isSubscribed", `Bool mailbox.is_subscribed);
9701035 ] in
9711036 let base = match mailbox.parent_id with
972972- | Some pid -> ("parentId", `String pid) :: base
10371037+ | Some pid -> ("parentId", `String (Jmap.Id.to_string pid)) :: base
9731038 | None -> base
9741039 in
9751040 let base = match mailbox.role with
···9791044 `Assoc base
9801045 in
9811046 `Assoc [
982982- ("accountId", `String resp.account_id);
10471047+ ("accountId", `String (Jmap.Id.to_string resp.account_id));
9831048 ("state", `String resp.state);
9841049 ("list", `List (List.map mailbox_to_json resp.list));
985985- ("notFound", `List (List.map (fun id -> `String id) resp.not_found));
10501050+ ("notFound", `List (List.map (fun id -> `String (Jmap.Id.to_string id)) resp.not_found));
9861051 ]
98710529881053 (** Parse Mailbox/get response from JSON.
···9951060 let of_json json =
9961061 try
9971062 let open Yojson.Safe.Util in
998998- let account_id = json |> member "accountId" |> to_string in
10631063+ let account_id_str = json |> member "accountId" |> to_string in
10641064+ let account_id = match Jmap.Id.of_string account_id_str with Ok id -> id | Error _ -> failwith ("Invalid account_id: " ^ account_id_str) in
9991065 let state = json |> member "state" |> to_string in
10001066 let list_json = json |> member "list" |> to_list in
10011067 (* Helper to parse a single mailbox - duplicated locally to avoid forward reference *)
10021068 let mailbox_of_json json =
10031003- let id = json |> member "id" |> to_string in
10691069+ let id_str = json |> member "Jmap.Id.t" |> to_string in
10701070+ let id = match Jmap.Id.of_string id_str with Ok id -> id | Error _ -> failwith ("Invalid id: " ^ id_str) in
10041071 let name = json |> member "name" |> to_string in
10051005- let parent_id = json |> member "parentId" |> to_string_option in
10721072+ let parent_id = match json |> member "parentId" |> to_string_option with
10731073+ | Some s -> (match Jmap.Id.of_string s with Ok id -> Some id | Error _ -> failwith ("Invalid parent_id: " ^ s))
10741074+ | None -> None in
10061075 let role_opt : (role option, string) result = match json |> member "role" with
10071076 | `Null -> Ok None
10081077 | role_json ->
···10101079 | Ok r -> Ok (Some r)
10111080 | Error e -> Error e
10121081 in
10131013- let sort_order = json |> member "sortOrder" |> to_int in
10141014- let total_emails = json |> member "totalEmails" |> to_int in
10151015- let unread_emails = json |> member "unreadEmails" |> to_int in
10161016- let total_threads = json |> member "totalThreads" |> to_int in
10171017- let unread_threads = json |> member "unreadThreads" |> to_int in
10821082+ let sort_order_int = json |> member "sortOrder" |> to_int in
10831083+ let sort_order = match Jmap.UInt.of_int sort_order_int with
10841084+ | Ok uint -> uint
10851085+ | Error _ -> failwith ("Invalid sortOrder: " ^ string_of_int sort_order_int) in
10861086+ let total_emails_int = json |> member "totalEmails" |> to_int in
10871087+ let total_emails = match Jmap.UInt.of_int total_emails_int with
10881088+ | Ok uint -> uint
10891089+ | Error _ -> failwith ("Invalid totalEmails: " ^ string_of_int total_emails_int) in
10901090+ let unread_emails_int = json |> member "unreadEmails" |> to_int in
10911091+ let unread_emails = match Jmap.UInt.of_int unread_emails_int with
10921092+ | Ok uint -> uint
10931093+ | Error _ -> failwith ("Invalid unreadEmails: " ^ string_of_int unread_emails_int) in
10941094+ let total_threads_int = json |> member "totalThreads" |> to_int in
10951095+ let total_threads = match Jmap.UInt.of_int total_threads_int with
10961096+ | Ok uint -> uint
10971097+ | Error _ -> failwith ("Invalid totalThreads: " ^ string_of_int total_threads_int) in
10981098+ let unread_threads_int = json |> member "unreadThreads" |> to_int in
10991099+ let unread_threads = match Jmap.UInt.of_int unread_threads_int with
11001100+ | Ok uint -> uint
11011101+ | Error _ -> failwith ("Invalid unreadThreads: " ^ string_of_int unread_threads_int) in
10181102 let my_rights_result = json |> member "myRights" |> Rights.of_json in
10191103 let is_subscribed = json |> member "isSubscribed" |> to_bool in
10201104 match role_opt, my_rights_result with
10211105 | Ok role, Ok my_rights ->
10221022- create_full ~id ~name ?parent_id ?role ~sort_order ~total_emails
10231023- ~unread_emails ~total_threads ~unread_threads ~my_rights ~is_subscribed ()
11061106+ create_full ~id ~name ?parent_id ?role
11071107+ ~sort_order
11081108+ ~total_emails ~unread_emails ~total_threads ~unread_threads
11091109+ ~my_rights ~is_subscribed ()
10241110 | Error e, _ -> Error e
10251111 | _, Error e -> Error e
10261112 in
···10321118 | Ok mailbox -> Ok (mailbox :: mailboxes)
10331119 | Error e -> Error e
10341120 ) (Ok []) list_json in
10351035- let not_found = json |> member "notFound" |> to_list |> List.map to_string in
11211121+ let not_found = json |> member "notFound" |> to_list |> List.map (fun id_json ->
11221122+ let id_str = to_string id_json in
11231123+ match Jmap.Id.of_string id_str with Ok id -> id | Error _ -> failwith ("Invalid not_found id: " ^ id_str)) in
10361124 match list_result with
10371125 | Ok list ->
10381126 Ok {
···1048113610491137 let pp fmt t =
10501138 Format.fprintf fmt "Mailbox.Get_response{account=%s;mailboxes=%d}"
10511051- t.account_id (List.length t.list)
11391139+ (Jmap.Id.to_string t.account_id) (List.length t.list)
1052114010531141 let pp_hum fmt t = pp fmt t
10541142···1057114510581146module Set_args = struct
10591147 type t = {
10601060- account_id : id;
11481148+ account_id : Jmap.Id.t;
10611149 if_in_state : string option;
10621150 create : (string * Create.t) list;
10631063- update : (id * Update.t) list;
10641064- destroy : id list;
11511151+ update : (Jmap.Id.t * Update.t) list;
11521152+ destroy : Jmap.Id.t list;
10651153 }
1066115410671155 let account_id args = args.account_id
···10781166 @param args The set arguments to serialize
10791167 @return JSON object with accountId, ifInState, create, update, destroy *)
10801168 let to_json args =
10811081- let base = [("accountId", `String args.account_id)] in
11691169+ let base = [("accountId", `String (Jmap.Id.to_string args.account_id))] in
10821170 let base = match args.if_in_state with
10831171 | None -> base
10841172 | Some state -> ("ifInState", `String state) :: base
···10951183 if List.length args.update = 0 then base
10961184 else
10971185 let update_map = List.map (fun (id, update_obj) ->
10981098- (id, Update.to_json update_obj)
11861186+ (Jmap.Id.to_string id, Update.to_json update_obj)
10991187 ) args.update in
11001188 ("update", `Assoc update_map) :: base
11011189 in
11021190 let base =
11031191 if List.length args.destroy = 0 then base
11041192 else
11051105- ("destroy", `List (List.map (fun id -> `String id) args.destroy)) :: base
11931193+ ("destroy", `List (List.map (fun id -> `String (Jmap.Id.to_string id)) args.destroy)) :: base
11061194 in
11071195 `Assoc base
11081196···11161204 let of_json json =
11171205 try
11181206 let open Yojson.Safe.Util in
11191119- let account_id = json |> member "accountId" |> to_string in
12071207+ let account_id_str = json |> member "accountId" |> to_string in
12081208+ let account_id = match Jmap.Id.of_string account_id_str with
12091209+ | Ok id -> id
12101210+ | Error e -> failwith ("Invalid account ID: " ^ e)
12111211+ in
11201212 let if_in_state = json |> member "ifInState" |> to_string_option in
11211213 let create = match json |> member "create" with
11221214 | `Null -> []
···11331225 | `Assoc update_assoc ->
11341226 List.fold_left (fun acc (id, update_json) ->
11351227 match Update.of_json update_json with
11361136- | Ok update_obj -> (id, update_obj) :: acc
12281228+ | Ok update_obj ->
12291229+ let id_t = match Jmap.Id.of_string id with
12301230+ | Ok id_val -> id_val
12311231+ | Error e -> failwith ("Invalid update ID: " ^ id ^ " - " ^ e)
12321232+ in
12331233+ (id_t, update_obj) :: acc
11371234 | Error _ -> failwith ("Invalid update object for: " ^ id)
11381235 ) [] update_assoc
11391236 | _ -> failwith "Expected object or null for update"
11401237 in
11411238 let destroy = match json |> member "destroy" with
11421239 | `Null -> []
11431143- | `List destroy_list -> List.map to_string destroy_list
12401240+ | `List destroy_list -> List.map (fun id_json ->
12411241+ let id_str = to_string id_json in
12421242+ match Jmap.Id.of_string id_str with
12431243+ | Ok id -> id
12441244+ | Error e -> failwith ("Invalid destroy ID: " ^ id_str ^ " - " ^ e)
12451245+ ) destroy_list
11441246 | _ -> failwith "Expected array or null for destroy"
11451247 in
11461248 Ok {
···11561258 | exn -> Error ("Set_args JSON parse error: " ^ Printexc.to_string exn)
1157125911581260 let pp fmt t =
11591159- Format.fprintf fmt "Mailbox.Set_args{account=%s}" t.account_id
12611261+ Format.fprintf fmt "Mailbox.Set_args{account=%s}" (Jmap.Id.to_string t.account_id)
1160126211611263 let pp_hum fmt t = pp fmt t
11621264···1167126911681270module Set_response = struct
11691271 type t = {
11701170- account_id : id;
12721272+ account_id : Jmap.Id.t;
11711273 old_state : string option;
11721274 new_state : string;
11731275 created : (string * Create.Response.t) list;
11741174- updated : (id * Update.Response.t) list;
11751175- destroyed : id list;
12761276+ updated : (Jmap.Id.t * Update.Response.t) list;
12771277+ destroyed : Jmap.Id.t list;
11761278 not_created : (string * Jmap.Error.Set_error.t) list;
11771177- not_updated : (id * Jmap.Error.Set_error.t) list;
11781178- not_destroyed : (id * Jmap.Error.Set_error.t) list;
12791279+ not_updated : (Jmap.Id.t * Jmap.Error.Set_error.t) list;
12801280+ not_destroyed : (Jmap.Id.t * Jmap.Error.Set_error.t) list;
11791281 }
1180128211811283 let account_id resp = resp.account_id
···11971299 @return JSON object with accountId, states, created, updated, destroyed, and error maps *)
11981300 let to_json resp =
11991301 let base = [
12001200- ("accountId", `String resp.account_id);
13021302+ ("accountId", `String (Jmap.Id.to_string resp.account_id));
12011303 ("newState", `String resp.new_state);
12021304 ] in
12031305 let base = match resp.old_state with
···12161318 if List.length resp.updated = 0 then base
12171319 else
12181320 let updated_map = List.map (fun (id, update_resp) ->
12191219- (id, Update.Response.to_json update_resp)
13211321+ (Jmap.Id.to_string id, Update.Response.to_json update_resp)
12201322 ) resp.updated in
12211323 ("updated", `Assoc updated_map) :: base
12221324 in
12231325 let base =
12241326 if List.length resp.destroyed = 0 then base
12251327 else
12261226- ("destroyed", `List (List.map (fun id -> `String id) resp.destroyed)) :: base
13281328+ ("destroyed", `List (List.map (fun id -> `String (Jmap.Id.to_string id)) resp.destroyed)) :: base
12271329 in
12281330 let base =
12291331 if List.length resp.not_created = 0 then base
···12371339 if List.length resp.not_updated = 0 then base
12381340 else
12391341 let not_updated_map = List.map (fun (id, error) ->
12401240- (id, Jmap.Error.Set_error.to_json error)
13421342+ (Jmap.Id.to_string id, Jmap.Error.Set_error.to_json error)
12411343 ) resp.not_updated in
12421344 ("notUpdated", `Assoc not_updated_map) :: base
12431345 in
···12451347 if List.length resp.not_destroyed = 0 then base
12461348 else
12471349 let not_destroyed_map = List.map (fun (id, error) ->
12481248- (id, Jmap.Error.Set_error.to_json error)
13501350+ (Jmap.Id.to_string id, Jmap.Error.Set_error.to_json error)
12491351 ) resp.not_destroyed in
12501352 ("notDestroyed", `Assoc not_destroyed_map) :: base
12511353 in
···12611363 let of_json json =
12621364 try
12631365 let open Yojson.Safe.Util in
12641264- let account_id = json |> member "accountId" |> to_string in
13661366+ let account_id_str = json |> member "accountId" |> to_string in
13671367+ let account_id = match Jmap.Id.of_string account_id_str with
13681368+ | Ok id -> id
13691369+ | Error e -> failwith ("Invalid account ID: " ^ e)
13701370+ in
12651371 let old_state = json |> member "oldState" |> to_string_option in
12661372 let new_state = json |> member "newState" |> to_string in
12671373 let created = match json |> member "created" with
···12791385 | `Assoc updated_assoc ->
12801386 List.fold_left (fun acc (id, resp_json) ->
12811387 match Update.Response.of_json resp_json with
12821282- | Ok resp -> (id, resp) :: acc
13881388+ | Ok resp ->
13891389+ let id_t = match Jmap.Id.of_string id with
13901390+ | Ok id_val -> id_val
13911391+ | Error e -> failwith ("Invalid updated ID: " ^ id ^ " - " ^ e)
13921392+ in
13931393+ (id_t, resp) :: acc
12831394 | Error _ -> failwith ("Invalid updated response for: " ^ id)
12841395 ) [] updated_assoc
12851396 | _ -> failwith "Expected object or null for updated"
12861397 in
12871398 let destroyed = match json |> member "destroyed" with
12881399 | `Null -> []
12891289- | `List destroyed_list -> List.map to_string destroyed_list
14001400+ | `List destroyed_list -> List.map (fun id_json ->
14011401+ let id_str = to_string id_json in
14021402+ match Jmap.Id.of_string id_str with
14031403+ | Ok id -> id
14041404+ | Error e -> failwith ("Invalid destroyed ID: " ^ id_str ^ " - " ^ e)
14051405+ ) destroyed_list
12901406 | _ -> failwith "Expected array or null for destroyed"
12911407 in
12921408 let not_created = match json |> member "notCreated" with
···13041420 | `Assoc not_updated_assoc ->
13051421 List.fold_left (fun acc (id, error_json) ->
13061422 match Jmap.Error.Set_error.of_json error_json with
13071307- | Ok error -> (id, error) :: acc
14231423+ | Ok error ->
14241424+ let id_t = match Jmap.Id.of_string id with
14251425+ | Ok id_val -> id_val
14261426+ | Error e -> failwith ("Invalid notUpdated ID: " ^ id ^ " - " ^ e)
14271427+ in
14281428+ (id_t, error) :: acc
13081429 | Error _ -> failwith ("Invalid notUpdated error for: " ^ id)
13091430 ) [] not_updated_assoc
13101431 | _ -> failwith "Expected object or null for notUpdated"
···13141435 | `Assoc not_destroyed_assoc ->
13151436 List.fold_left (fun acc (id, error_json) ->
13161437 match Jmap.Error.Set_error.of_json error_json with
13171317- | Ok error -> (id, error) :: acc
14381438+ | Ok error ->
14391439+ let id_t = match Jmap.Id.of_string id with
14401440+ | Ok id_val -> id_val
14411441+ | Error e -> failwith ("Invalid notDestroyed ID: " ^ id ^ " - " ^ e)
14421442+ in
14431443+ (id_t, error) :: acc
13181444 | Error _ -> failwith ("Invalid notDestroyed error for: " ^ id)
13191445 ) [] not_destroyed_assoc
13201446 | _ -> failwith "Expected object or null for notDestroyed"
···13361462 | exn -> Error ("Set_response JSON parse error: " ^ Printexc.to_string exn)
1337146313381464 let pp fmt t =
13391339- Format.fprintf fmt "Mailbox.Set_response{account=%s}" t.account_id
14651465+ Format.fprintf fmt "Mailbox.Set_response{account=%s}" (Jmap.Id.to_string t.account_id)
1340146613411467 let pp_hum fmt t = pp fmt t
13421468···1347147313481474module Changes_args = struct
13491475 type t = {
13501350- account_id : id;
14761476+ account_id : Jmap.Id.t;
13511477 since_state : string;
13521352- max_changes : uint option;
14781478+ max_changes : Jmap.UInt.t option;
13531479 }
1354148013551481 let create ~account_id ~since_state ?max_changes () =
···13681494 @return JSON object with accountId, sinceState, and optional maxChanges *)
13691495 let to_json args =
13701496 let base = [
13711371- ("accountId", `String args.account_id);
14971497+ ("accountId", `String (Jmap.Id.to_string args.account_id));
13721498 ("sinceState", `String args.since_state);
13731499 ] in
13741500 let base = match args.max_changes with
13751501 | None -> base
13761376- | Some max_changes -> ("maxChanges", `Int max_changes) :: base
15021502+ | Some max_changes -> ("maxChanges", `Int (Jmap.UInt.to_int max_changes)) :: base
13771503 in
13781504 `Assoc base
13791505···13871513 let of_json json =
13881514 try
13891515 let open Yojson.Safe.Util in
13901390- let account_id = json |> member "accountId" |> to_string in
15161516+ let account_id_str = json |> member "accountId" |> to_string in
15171517+ let account_id = match Jmap.Id.of_string account_id_str with
15181518+ | Ok id -> id
15191519+ | Error e -> failwith ("Invalid account ID: " ^ e)
15201520+ in
13911521 let since_state = json |> member "sinceState" |> to_string in
13921392- let max_changes = json |> member "maxChanges" |> to_int_option in
15221522+ let max_changes = json |> member "maxChanges" |> to_int_option |>
15231523+ Option.map (fun i -> match Jmap.UInt.of_int i with
15241524+ | Ok u -> u
15251525+ | Error e -> failwith ("Invalid maxChanges: " ^ e)) in
13931526 Ok { account_id; since_state; max_changes }
13941527 with
13951528 | Yojson.Safe.Util.Type_error (msg, _) -> Error ("Changes_args JSON parse error: " ^ msg)
13961529 | exn -> Error ("Changes_args JSON parse error: " ^ Printexc.to_string exn)
1397153013981531 let pp fmt t =
13991399- Format.fprintf fmt "Mailbox.Changes_args{account=%s}" t.account_id
15321532+ Format.fprintf fmt "Mailbox.Changes_args{account=%s}" (Jmap.Id.to_string t.account_id)
1400153314011534 let pp_hum fmt t = pp fmt t
14021535···1407154014081541module Changes_response = struct
14091542 type t = {
14101410- account_id : id;
15431543+ account_id : Jmap.Id.t;
14111544 old_state : string;
14121545 new_state : string;
14131546 has_more_changes : bool;
14141414- created : id list;
14151415- updated : id list;
14161416- destroyed : id list;
15471547+ created : Jmap.Id.t list;
15481548+ updated : Jmap.Id.t list;
15491549+ destroyed : Jmap.Id.t list;
14171550 }
1418155114191552 let account_id resp = resp.account_id
···14331566 @return JSON object with accountId, states, hasMoreChanges, and change arrays *)
14341567 let to_json resp =
14351568 `Assoc [
14361436- ("accountId", `String resp.account_id);
15691569+ ("accountId", `String (Jmap.Id.to_string resp.account_id));
14371570 ("oldState", `String resp.old_state);
14381571 ("newState", `String resp.new_state);
14391572 ("hasMoreChanges", `Bool resp.has_more_changes);
14401440- ("created", `List (List.map (fun id -> `String id) resp.created));
14411441- ("updated", `List (List.map (fun id -> `String id) resp.updated));
14421442- ("destroyed", `List (List.map (fun id -> `String id) resp.destroyed));
15731573+ ("created", `List (List.map (fun id -> `String (Jmap.Id.to_string id)) resp.created));
15741574+ ("updated", `List (List.map (fun id -> `String (Jmap.Id.to_string id)) resp.updated));
15751575+ ("destroyed", `List (List.map (fun id -> `String (Jmap.Id.to_string id)) resp.destroyed));
14431576 ]
1444157714451578 (** Parse Mailbox/changes response from JSON.
···14521585 let of_json json =
14531586 try
14541587 let open Yojson.Safe.Util in
14551455- let account_id = json |> member "accountId" |> to_string in
15881588+ let account_id_str = json |> member "accountId" |> to_string in
15891589+ let account_id = match Jmap.Id.of_string account_id_str with
15901590+ | Ok id -> id
15911591+ | Error e -> failwith ("Invalid account ID: " ^ e)
15921592+ in
14561593 let old_state = json |> member "oldState" |> to_string in
14571594 let new_state = json |> member "newState" |> to_string in
14581595 let has_more_changes = json |> member "hasMoreChanges" |> to_bool in
14591459- let created = json |> member "created" |> to_list |> List.map to_string in
14601460- let updated = json |> member "updated" |> to_list |> List.map to_string in
14611461- let destroyed = json |> member "destroyed" |> to_list |> List.map to_string in
15961596+ let created = json |> member "created" |> to_list |> List.map (fun id_json ->
15971597+ let id_str = to_string id_json in
15981598+ match Jmap.Id.of_string id_str with
15991599+ | Ok id -> id
16001600+ | Error e -> failwith ("Invalid created ID: " ^ id_str ^ " - " ^ e)
16011601+ ) in
16021602+ let updated = json |> member "updated" |> to_list |> List.map (fun id_json ->
16031603+ let id_str = to_string id_json in
16041604+ match Jmap.Id.of_string id_str with
16051605+ | Ok id -> id
16061606+ | Error e -> failwith ("Invalid updated ID: " ^ id_str ^ " - " ^ e)
16071607+ ) in
16081608+ let destroyed = json |> member "destroyed" |> to_list |> List.map (fun id_json ->
16091609+ let id_str = to_string id_json in
16101610+ match Jmap.Id.of_string id_str with
16111611+ | Ok id -> id
16121612+ | Error e -> failwith ("Invalid destroyed ID: " ^ id_str ^ " - " ^ e)
16131613+ ) in
14621614 Ok {
14631615 account_id;
14641616 old_state;
···14731625 | exn -> Error ("Changes_response JSON parse error: " ^ Printexc.to_string exn)
1474162614751627 let pp fmt t =
14761476- Format.fprintf fmt "Mailbox.Changes_response{account=%s}" t.account_id
16281628+ Format.fprintf fmt "Mailbox.Changes_response{account=%s}" (Jmap.Id.to_string t.account_id)
1477162914781630 let pp_hum fmt t = pp fmt t
14791631···14851637(* JSON serialization for main mailbox type *)
14861638let to_json mailbox =
14871639 let base = [
14881488- ("id", `String mailbox.mailbox_id);
16401640+ ("id", `String (Jmap.Id.to_string mailbox.mailbox_id));
14891641 ("name", `String mailbox.name);
14901490- ("sortOrder", `Int mailbox.sort_order);
14911491- ("totalEmails", `Int mailbox.total_emails);
14921492- ("unreadEmails", `Int mailbox.unread_emails);
14931493- ("totalThreads", `Int mailbox.total_threads);
14941494- ("unreadThreads", `Int mailbox.unread_threads);
16421642+ ("sortOrder", `Int (Jmap.UInt.to_int mailbox.sort_order));
16431643+ ("totalEmails", `Int (Jmap.UInt.to_int mailbox.total_emails));
16441644+ ("unreadEmails", `Int (Jmap.UInt.to_int mailbox.unread_emails));
16451645+ ("totalThreads", `Int (Jmap.UInt.to_int mailbox.total_threads));
16461646+ ("unreadThreads", `Int (Jmap.UInt.to_int mailbox.unread_threads));
14951647 ("myRights", Rights.to_json mailbox.my_rights);
14961648 ("isSubscribed", `Bool mailbox.is_subscribed);
14971649 ] in
14981650 let base = match mailbox.parent_id with
14991499- | Some pid -> ("parentId", `String pid) :: base
16511651+ | Some pid -> ("parentId", `String (Jmap.Id.to_string pid)) :: base
15001652 | None -> base
15011653 in
15021654 let base = match mailbox.role with
···15081660let of_json json =
15091661 try
15101662 let open Yojson.Safe.Util in
15111511- let id = json |> member "id" |> to_string in
16631663+ let id_str = json |> member "id" |> to_string in
16641664+ let id = match Jmap.Id.of_string id_str with
16651665+ | Ok id_val -> id_val
16661666+ | Error e -> failwith ("Invalid mailbox ID: " ^ id_str ^ " - " ^ e)
16671667+ in
15121668 let name = json |> member "name" |> to_string in
15131513- let parent_id = json |> member "parentId" |> to_string_option in
16691669+ let parent_id = json |> member "parentId" |> to_string_option |>
16701670+ Option.map (fun pid_str -> match Jmap.Id.of_string pid_str with
16711671+ | Ok pid -> pid
16721672+ | Error e -> failwith ("Invalid parentId: " ^ pid_str ^ " - " ^ e)) in
15141673 let role_opt : (role option, string) result = match json |> member "role" with
15151674 | `Null -> Ok None
15161675 | role_json ->
···15181677 | Ok r -> Ok (Some r)
15191678 | Error e -> Error e
15201679 in
15211521- let sort_order = json |> member "sortOrder" |> to_int in
15221522- let total_emails = json |> member "totalEmails" |> to_int in
15231523- let unread_emails = json |> member "unreadEmails" |> to_int in
15241524- let total_threads = json |> member "totalThreads" |> to_int in
15251525- let unread_threads = json |> member "unreadThreads" |> to_int in
16801680+ let sort_order = json |> member "sortOrder" |> to_int |> (fun i ->
16811681+ match Jmap.UInt.of_int i with
16821682+ | Ok u -> u
16831683+ | Error e -> failwith ("Invalid sortOrder: " ^ e)) in
16841684+ let total_emails = json |> member "totalEmails" |> to_int |> (fun i ->
16851685+ match Jmap.UInt.of_int i with
16861686+ | Ok u -> u
16871687+ | Error e -> failwith ("Invalid totalEmails: " ^ e)) in
16881688+ let unread_emails = json |> member "unreadEmails" |> to_int |> (fun i ->
16891689+ match Jmap.UInt.of_int i with
16901690+ | Ok u -> u
16911691+ | Error e -> failwith ("Invalid unreadEmails: " ^ e)) in
16921692+ let total_threads = json |> member "totalThreads" |> to_int |> (fun i ->
16931693+ match Jmap.UInt.of_int i with
16941694+ | Ok u -> u
16951695+ | Error e -> failwith ("Invalid totalThreads: " ^ e)) in
16961696+ let unread_threads = json |> member "unreadThreads" |> to_int |> (fun i ->
16971697+ match Jmap.UInt.of_int i with
16981698+ | Ok u -> u
16991699+ | Error e -> failwith ("Invalid unreadThreads: " ^ e)) in
15261700 let my_rights_result = json |> member "myRights" |> Rights.of_json in
15271701 let is_subscribed = json |> member "isSubscribed" |> to_bool in
15281702 match role_opt, my_rights_result with
···15411715 | Some r -> Role.to_string r
15421716 | None -> "none"
15431717 in
15441544- Format.fprintf fmt "Mailbox{id=%s; name=%s; role=%s; total=%d}"
15451545- mailbox.mailbox_id
17181718+ Format.fprintf fmt "Mailbox{Jmap.Id.t=%s; name=%s; role=%s; total=%d}"
17191719+ (Jmap.Id.to_string mailbox.mailbox_id)
15461720 mailbox.name
15471721 role_str
15481548- mailbox.total_emails
17221722+ (Jmap.UInt.to_int mailbox.total_emails)
1549172315501724let pp_hum fmt mailbox =
15511725 let role_str = match mailbox.role with
···15531727 | None -> "none"
15541728 in
15551729 let parent_str = match mailbox.parent_id with
15561556- | Some pid -> Printf.sprintf " (parent: %s)" pid
17301730+ | Some pid -> Printf.sprintf " (parent: %s)" (Jmap.Id.to_string pid)
15571731 | None -> ""
15581732 in
15591733 Format.fprintf fmt "Mailbox \"%s\" [%s]: %d emails (%d unread), %d threads (%d unread)%s"
15601734 mailbox.name
15611735 role_str
15621562- mailbox.total_emails
15631563- mailbox.unread_emails
15641564- mailbox.total_threads
15651565- mailbox.unread_threads
17361736+ (Jmap.UInt.to_int mailbox.total_emails)
17371737+ (Jmap.UInt.to_int mailbox.unread_emails)
17381738+ (Jmap.UInt.to_int mailbox.total_threads)
17391739+ (Jmap.UInt.to_int mailbox.unread_threads)
15661740 parent_str
1567174115681742(* Filter construction helpers *)
···15731747 Filter.property_equals "role" `Null
1574174815751749let filter_has_parent parent_id =
15761576- Filter.property_equals "parentId" (`String parent_id)
17501750+ Filter.property_equals "parentId" (`String (Jmap.Id.to_string parent_id))
1577175115781752let filter_is_root () =
15791753 Filter.property_equals "parentId" `Null
+70-71
jmap/jmap-email/mailbox.mli
···1212 @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-2> RFC 8621, Section 2: Mailboxes
1313*)
14141515-open Jmap.Types
1615open Jmap.Methods
17161817(** Mailbox role identifiers.
···7372include Jmap_sigs.PRINTABLE with type t := t
74737574(** JMAP object interface with property selection support *)
7676-include Jmap_sigs.JMAP_OBJECT with type t := t and type id_type := id
7575+include Jmap_sigs.JMAP_OBJECT with type t := t and type id_type := string
77767877(** {1 Property Accessors} *)
79788079(** Get the server-assigned mailbox identifier.
8180 @param mailbox The mailbox object
8281 @return Immutable server-assigned identifier (always Some for valid mailboxes) *)
8383-val id : t -> id option
8282+val id : t -> Jmap.Id.t option
84838584(** Get the server-assigned mailbox identifier directly.
8685 @param mailbox The mailbox object
8786 @return Immutable server-assigned identifier (guaranteed present) *)
8888-val mailbox_id : t -> id
8787+val mailbox_id : t -> Jmap.Id.t
89889089(** Get the display name for the mailbox.
9190 @param mailbox The mailbox object
···9594(** Get the parent mailbox ID for hierarchical organization.
9695 @param mailbox The mailbox object
9796 @return Parent mailbox ID (None for root level) *)
9898-val parent_id : t -> id option
9797+val parent_id : t -> Jmap.Id.t option
999810099(** Get the functional role identifier for the mailbox.
101100 @param mailbox The mailbox object
···105104(** Get the numeric sort order for display positioning.
106105 @param mailbox The mailbox object
107106 @return Display order hint (default: 0) *)
108108-val sort_order : t -> uint
107107+val sort_order : t -> Jmap.UInt.t
109108110109(** Get the total email count (server-computed).
111110 @param mailbox The mailbox object
112111 @return Total email count *)
113113-val total_emails : t -> uint
112112+val total_emails : t -> Jmap.UInt.t
114113115114(** Get the unread email count (server-computed).
116115 @param mailbox The mailbox object
117116 @return Unread email count *)
118118-val unread_emails : t -> uint
117117+val unread_emails : t -> Jmap.UInt.t
119118120119(** Get the total thread count (server-computed).
121120 @param mailbox The mailbox object
122121 @return Total thread count *)
123123-val total_threads : t -> uint
122122+val total_threads : t -> Jmap.UInt.t
124123125124(** Get the unread thread count (server-computed).
126125 @param mailbox The mailbox object
127126 @return Unread thread count *)
128128-val unread_threads : t -> uint
127127+val unread_threads : t -> Jmap.UInt.t
129128130129(** Get the user's access permissions (server-set).
131130 @param mailbox The mailbox object
···145144 setting all mailbox properties including server-computed values. Used for
146145 constructing complete Mailbox objects from server responses.
147146148148- @param id Server-assigned identifier
147147+ @param Jmap.Id.t Server-assigned identifier
149148 @param name Display name
150149 @param parent_id Optional parent mailbox
151150 @param role Optional functional role
···158157 @param is_subscribed Subscription status
159158 @return Ok with mailbox object, or Error with validation message *)
160159val create_full :
161161- id:id ->
160160+ id:Jmap.Id.t ->
162161 name:string ->
163163- ?parent_id:id ->
162162+ ?parent_id:Jmap.Id.t ->
164163 ?role:role ->
165165- ?sort_order:uint ->
166166- total_emails:uint ->
167167- unread_emails:uint ->
168168- total_threads:uint ->
169169- unread_threads:uint ->
164164+ ?sort_order:Jmap.UInt.t ->
165165+ total_emails:Jmap.UInt.t ->
166166+ unread_emails:Jmap.UInt.t ->
167167+ total_threads:Jmap.UInt.t ->
168168+ unread_threads:Jmap.UInt.t ->
170169 my_rights:rights ->
171170 is_subscribed:bool ->
172171 unit -> (t, string) result
···421420 @return Ok with creation object, or Error with validation message *)
422421 val create :
423422 name:string ->
424424- ?parent_id:id ->
423423+ ?parent_id:Jmap.Id.t ->
425424 ?role:role ->
426426- ?sort_order:uint ->
425425+ ?sort_order:Jmap.UInt.t ->
427426 ?is_subscribed:bool ->
428427 unit -> (t, string) result
429428···435434 (** Get the parent mailbox ID.
436435 @param create_req The creation request
437436 @return Optional parent mailbox *)
438438- val parent_id : t -> id option
437437+ val parent_id : t -> Jmap.Id.t option
439438440439 (** Get the role assignment.
441440 @param create_req The creation request
···445444 (** Get the sort order.
446445 @param create_req The creation request
447446 @return Optional sort order (None means server default) *)
448448- val sort_order : t -> uint option
447447+ val sort_order : t -> Jmap.UInt.t option
449448450449 (** Get the subscription status.
451450 @param create_req The creation request
···467466 (** Get the server-assigned mailbox ID.
468467 @param response The creation response
469468 @return Server-assigned mailbox ID *)
470470- val id : t -> id
469469+ val id : t -> Jmap.Id.t
471470472471 (** Get the role if default was applied.
473472 @param response The creation response
···477476 (** Get the sort order if default was applied.
478477 @param response The creation response
479478 @return Sort order if default was applied *)
480480- val sort_order : t -> uint
479479+ val sort_order : t -> Jmap.UInt.t
481480482481 (** Get the initial email count (typically 0).
483482 @param response The creation response
484483 @return Initial email count *)
485485- val total_emails : t -> uint
484484+ val total_emails : t -> Jmap.UInt.t
486485487486 (** Get the initial unread count (typically 0).
488487 @param response The creation response
489488 @return Initial unread count *)
490490- val unread_emails : t -> uint
489489+ val unread_emails : t -> Jmap.UInt.t
491490492491 (** Get the initial thread count (typically 0).
493492 @param response The creation response
494493 @return Initial thread count *)
495495- val total_threads : t -> uint
494494+ val total_threads : t -> Jmap.UInt.t
496495497496 (** Get the initial unread thread count (typically 0).
498497 @param response The creation response
499498 @return Initial unread thread count *)
500500- val unread_threads : t -> uint
499499+ val unread_threads : t -> Jmap.UInt.t
501500502501 (** Get the computed access rights for the user.
503502 @param response The creation response
···533532 @return JSON Patch operations for Mailbox/set *)
534533 val create :
535534 ?name:string ->
536536- ?parent_id:id option ->
535535+ ?parent_id:Jmap.Id.t option ->
537536 ?role:role option ->
538538- ?sort_order:uint ->
537537+ ?sort_order:Jmap.UInt.t ->
539538 ?is_subscribed:bool ->
540539 unit -> (t, string) result
541540···574573 include Jmap_sigs.JSONABLE with type t := t
575574576575 (** JMAP method arguments interface *)
577577- include Jmap_sigs.METHOD_ARGS with type t := t and type account_id := id
576576+ include Jmap_sigs.METHOD_ARGS with type t := t and type account_id := string
578577579578 (** Create query arguments for mailboxes.
580579 @param account_id Account to query in
···585584 @param calculate_total Whether to calculate total count
586585 @return Ok with query arguments, or Error with validation message *)
587586 val create :
588588- account_id:id ->
587587+ account_id:Jmap.Id.t ->
589588 ?filter:Filter.t ->
590589 ?sort:Comparator.t list ->
591591- ?position:uint ->
592592- ?limit:uint ->
590590+ ?position:Jmap.UInt.t ->
591591+ ?limit:Jmap.UInt.t ->
593592 ?calculate_total:bool ->
594593 unit -> (t, string) result
595594596595 (** Get the account ID.
597596 @param args Query arguments
598597 @return Account identifier where mailboxes will be queried *)
599599- val account_id : t -> id
598598+ val account_id : t -> Jmap.Id.t
600599601600 (** Validate query arguments according to JMAP method constraints.
602601 @param t Query arguments to validate
···620619 (** Get the starting position.
621620 @param args Query arguments
622621 @return Starting position (0-based) *)
623623- val position : t -> uint option
622622+ val position : t -> Jmap.UInt.t option
624623625624 (** Get the result limit.
626625 @param args Query arguments
627626 @return Maximum results to return *)
628628- val limit : t -> uint option
627627+ val limit : t -> Jmap.UInt.t option
629628630629 (** Check if total count should be calculated.
631630 @param args Query arguments
···644643 include Jmap_sigs.JSONABLE with type t := t
645644646645 (** JMAP method response interface *)
647647- include Jmap_sigs.METHOD_RESPONSE with type t := t and type account_id := id and type state := string
646646+ include Jmap_sigs.METHOD_RESPONSE with type t := t and type account_id := string and type state := string
648647649648 (** Get the account ID from the response.
650649 @param response Query response
651650 @return Account identifier where mailboxes were queried *)
652652- val account_id : t -> id
651651+ val account_id : t -> Jmap.Id.t
653652654653 (** Get the query state for change tracking.
655654 @param response Query response
···674673 (** Get the starting position of results.
675674 @param response Query response
676675 @return 0-based position of the first returned result *)
677677- val position : t -> uint
676676+ val position : t -> Jmap.UInt.t
678677679678 (** Get the total count if requested.
680679 @param response Query response
681680 @return Total matching results if calculateTotal was true *)
682682- val total : t -> uint option
681681+ val total : t -> Jmap.UInt.t option
683682684683 (** Get the matched mailbox IDs.
685684 @param response Query response
686685 @return List of mailbox IDs that matched the query *)
687687- val ids : t -> id list
686686+ val ids : t -> Jmap.Id.t list
688687end
689688690689module Get_args : sig
···698697 include Jmap_sigs.JSONABLE with type t := t
699698700699 (** JMAP method arguments interface *)
701701- include Jmap_sigs.METHOD_ARGS with type t := t and type account_id := id
700700+ include Jmap_sigs.METHOD_ARGS with type t := t and type account_id := string
702701703702 (** Create get arguments for mailboxes.
704703 @param account_id Account to get from
···706705 @param properties Optional properties to return (None = all properties)
707706 @return Ok with get arguments, or Error with validation message *)
708707 val create :
709709- account_id:id ->
710710- ?ids:id list ->
708708+ account_id:Jmap.Id.t ->
709709+ ?ids:Jmap.Id.t list ->
711710 ?properties:Property.t list ->
712711 unit -> (t, string) result
713712714713 (** Get the account ID.
715714 @param args Get arguments
716715 @return Account identifier where mailboxes will be retrieved from *)
717717- val account_id : t -> id
716716+ val account_id : t -> Jmap.Id.t
718717719718 (** Validate get arguments according to JMAP method constraints.
720719 @param t Get arguments to validate
···728727 (** Get the specific IDs to retrieve.
729728 @param args Get arguments
730729 @return Optional list of mailbox IDs (None = all mailboxes) *)
731731- val ids : t -> id list option
730730+ val ids : t -> Jmap.Id.t list option
732731733732 (** Get the properties to return.
734733 @param args Get arguments
···747746 include Jmap_sigs.JSONABLE with type t := t
748747749748 (** JMAP method response interface *)
750750- include Jmap_sigs.METHOD_RESPONSE with type t := t and type account_id := id and type state := string
749749+ include Jmap_sigs.METHOD_RESPONSE with type t := t and type account_id := string and type state := string
751750752751 (** Get the account ID from the response.
753752 @param response Get response
754753 @return Account identifier where mailboxes were retrieved from *)
755755- val account_id : t -> id
754754+ val account_id : t -> Jmap.Id.t
756755757756 (** Get the state for change tracking.
758757 @param response Get response
···772771 (** Get the IDs that were not found.
773772 @param response Get response
774773 @return List of requested IDs that were not found *)
775775- val not_found : t -> id list
774774+ val not_found : t -> Jmap.Id.t list
776775end
777776778777module Set_args : sig
···786785 include Jmap_sigs.JSONABLE with type t := t
787786788787 (** JMAP method arguments interface *)
789789- include Jmap_sigs.METHOD_ARGS with type t := t and type account_id := id
788788+ include Jmap_sigs.METHOD_ARGS with type t := t and type account_id := string
790789791790 (** Create set arguments for mailboxes.
792791 @param account_id Account to modify
···799798 (** Get the account ID.
800799 @param args Set arguments
801800 @return Account identifier where mailboxes will be modified *)
802802- val account_id : t -> id
801801+ val account_id : t -> Jmap.Id.t
803802804803 (** Validate set arguments according to JMAP method constraints.
805804 @param t Set arguments to validate
···823822 (** Get the mailboxes to update.
824823 @param args Set arguments
825824 @return Map of mailbox IDs to update objects *)
826826- val update : t -> (id * Update.t) list
825825+ val update : t -> (Jmap.Id.t * Update.t) list
827826828827 (** Get the mailboxes to destroy.
829828 @param args Set arguments
830829 @return List of mailbox IDs to destroy *)
831831- val destroy : t -> id list
830830+ val destroy : t -> Jmap.Id.t list
832831end
833832834833module Set_response : sig
···842841 include Jmap_sigs.JSONABLE with type t := t
843842844843 (** JMAP method response interface *)
845845- include Jmap_sigs.METHOD_RESPONSE with type t := t and type account_id := id and type state := string
844844+ include Jmap_sigs.METHOD_RESPONSE with type t := t and type account_id := string and type state := string
846845847846 (** Get the account ID from the response.
848847 @param response Set response
849848 @return Account identifier where mailboxes were modified *)
850850- val account_id : t -> id
849849+ val account_id : t -> Jmap.Id.t
851850852851 (** Get the old state before modifications.
853852 @param response Set response
···877876 (** Get the successfully updated mailboxes.
878877 @param response Set response
879878 @return Map of mailbox IDs to update response objects *)
880880- val updated : t -> (id * Update.Response.t) list
879879+ val updated : t -> (Jmap.Id.t * Update.Response.t) list
881880882881 (** Get the successfully destroyed mailbox IDs.
883882 @param response Set response
884883 @return List of mailbox IDs that were destroyed *)
885885- val destroyed : t -> id list
884884+ val destroyed : t -> Jmap.Id.t list
886885887886 (** Get the creation failures.
888887 @param response Set response
···892891 (** Get the update failures.
893892 @param response Set response
894893 @return Map of mailbox IDs to error objects *)
895895- val not_updated : t -> (id * Jmap.Error.Set_error.t) list
894894+ val not_updated : t -> (Jmap.Id.t * Jmap.Error.Set_error.t) list
896895897896 (** Get the destruction failures.
898897 @param response Set response
899898 @return Map of mailbox IDs to error objects *)
900900- val not_destroyed : t -> (id * Jmap.Error.Set_error.t) list
899899+ val not_destroyed : t -> (Jmap.Id.t * Jmap.Error.Set_error.t) list
901900end
902901903902module Changes_args : sig
···911910 include Jmap_sigs.JSONABLE with type t := t
912911913912 (** JMAP method arguments interface *)
914914- include Jmap_sigs.METHOD_ARGS with type t := t and type account_id := id
913913+ include Jmap_sigs.METHOD_ARGS with type t := t and type account_id := string
915914916915 (** Create changes arguments for mailboxes.
917916 @param account_id Account to check for changes
···919918 @param max_changes Maximum number of changed IDs to return
920919 @return Ok with changes arguments, or Error with validation message *)
921920 val create :
922922- account_id:id ->
921921+ account_id:Jmap.Id.t ->
923922 since_state:string ->
924924- ?max_changes:uint ->
923923+ ?max_changes:Jmap.UInt.t ->
925924 unit -> (t, string) result
926925927926 (** Get the account ID.
928927 @param args Changes arguments
929928 @return Account identifier to check for changes *)
930930- val account_id : t -> id
929929+ val account_id : t -> Jmap.Id.t
931930932931 (** Validate changes arguments according to JMAP method constraints.
933932 @param t Changes arguments to validate
···946945 (** Get the maximum changes limit.
947946 @param args Changes arguments
948947 @return Maximum number of changed IDs to return *)
949949- val max_changes : t -> uint option
948948+ val max_changes : t -> Jmap.UInt.t option
950949end
951950952951module Changes_response : sig
···960959 include Jmap_sigs.JSONABLE with type t := t
961960962961 (** JMAP method response interface *)
963963- include Jmap_sigs.METHOD_RESPONSE with type t := t and type account_id := id and type state := string
962962+ include Jmap_sigs.METHOD_RESPONSE with type t := t and type account_id := string and type state := string
964963965964 (** Get the account ID from the response.
966965 @param response Changes response
967966 @return Account identifier where changes were checked *)
968968- val account_id : t -> id
967967+ val account_id : t -> Jmap.Id.t
969968970969 (** Get the old state.
971970 @param response Changes response
···995994 (** Get the created mailbox IDs.
996995 @param response Changes response
997996 @return List of mailbox IDs that were created *)
998998- val created : t -> id list
997997+ val created : t -> Jmap.Id.t list
9999981000999 (** Get the updated mailbox IDs.
10011000 @param response Changes response
10021001 @return List of mailbox IDs that were updated *)
10031003- val updated : t -> id list
10021002+ val updated : t -> Jmap.Id.t list
1004100310051004 (** Get the destroyed mailbox IDs.
10061005 @param response Changes response
10071006 @return List of mailbox IDs that were destroyed *)
10081008- val destroyed : t -> id list
10071007+ val destroyed : t -> Jmap.Id.t list
10091008end
1010100910111010(** {1 Filter Construction}
···10251024(** Create a filter to match child mailboxes of a specific parent.
10261025 @param parent_id The parent mailbox ID to match
10271026 @return Filter condition for mailboxes with the specified parent *)
10281028-val filter_has_parent : id -> Filter.t
10271027+val filter_has_parent : Jmap.Id.t -> Filter.t
1029102810301029(** Create a filter to match root-level mailboxes.
10311030 @return Filter condition matching mailboxes where parentId is null *)
+11-9
jmap/jmap-email/query.ml
···2525 open Jmap.Methods.Filter
26262727 (* Email-specific filter constructors using core utilities *)
2828- let in_mailbox (mailbox_id : Jmap.Types.id) =
2929- condition (`Assoc [("inMailbox", `String mailbox_id)])
2828+ let in_mailbox (mailbox_id : Jmap.Id.t) =
2929+ condition (`Assoc [("inMailbox", `String (Jmap.Id.to_string mailbox_id))])
30303131 let in_mailbox_role role =
3232 condition (`Assoc [("inMailboxOtherThan", `List [`String role])])
···7575end
76767777type query_builder = {
7878- account_id : Jmap.Types.id option;
7878+ account_id : string option;
7979 filter : Filter.t option;
8080 sort : Sort.t list;
8181- limit_count : Jmap.Types.uint option;
8282- position : Jmap.Types.jint option;
8181+ limit_count : Jmap.UInt.t option;
8282+ position : int option;
8383 properties : property list;
8484 collapse_threads : bool;
8585 calculate_total : bool;
···9797}
98989999let with_account account_id builder =
100100- { builder with account_id = Some account_id }
100100+ { builder with account_id = Some (Jmap.Id.to_string account_id) }
101101102102let where filter builder =
103103 { builder with filter = Some filter }
···106106 { builder with sort = [sort] }
107107108108let limit n builder =
109109- { builder with limit_count = Some n }
109109+ match Jmap.UInt.of_int n with
110110+ | Ok uint -> { builder with limit_count = Some uint }
111111+ | Error _ -> failwith ("Invalid limit value: " ^ string_of_int n)
110112111113let offset n builder =
112114 { builder with position = Some n }
···153155 ?filter:builder.filter
154156 ~sort:builder.sort
155157 ?position:builder.position
156156- ?limit:builder.limit_count
158158+ ?limit:(Option.map Jmap.UInt.to_int builder.limit_count)
157159 ?calculate_total:(Some builder.calculate_total)
158160 ?collapse_threads:(Some builder.collapse_threads)
159161 ()
···171173let build_email_get_with_ref ~account_id ~properties ~result_of =
172174 let property_strings = Property.to_string_list properties in
173175 `Assoc [
174174- ("accountId", `String account_id);
176176+ ("accountId", `String (Jmap.Id.to_string account_id));
175177 ("properties", `List (List.map (fun s -> `String s) property_strings));
176178 ("#ids", `Assoc [
177179 ("resultOf", `String result_of);
+3-3
jmap/jmap-email/query.mli
···5353 type t = Jmap.Methods.Filter.t
54545555 (** Filter by mailbox *)
5656- val in_mailbox : Jmap.Types.id -> t
5656+ val in_mailbox : Jmap.Id.t -> t
57575858 (** Filter by mailbox role (e.g., "inbox", "sent", "drafts") *)
5959 val in_mailbox_role : string -> t
···106106val query : unit -> query_builder
107107108108(** Set the account ID (uses primary mail account if not set) *)
109109-val with_account : Jmap.Types.id -> query_builder -> query_builder
109109+val with_account : Jmap.Id.t -> query_builder -> query_builder
110110111111(** Add a filter condition *)
112112val where : Filter.t -> query_builder -> query_builder
···174174 @param result_of Method call ID to reference (e.g., "q1")
175175 @return JSON object for Email/get method arguments *)
176176val build_email_get_with_ref :
177177- account_id:Jmap.Types.id ->
177177+ account_id:Jmap.Id.t ->
178178 properties:property list ->
179179 result_of:string ->
180180 Yojson.Safe.t
+4-4
jmap/jmap-email/response.ml
···33333434(** Extract IDs from a Query_response *)
3535let ids_from_query_response response =
3636- Query_response.ids response
3636+ List.map (fun s -> match Jmap.Id.of_string s with Ok id -> id | Error e -> failwith e) (Query_response.ids response)
37373838(** Check if there are more changes in a Changes_response *)
3939let has_more_changes response =
···41414242(** Get created IDs from a Changes_response *)
4343let created_ids response =
4444- Changes_response.created response
4444+ List.map (fun s -> match Jmap.Id.of_string s with Ok id -> id | Error e -> failwith e) (Changes_response.created response)
45454646(** Get updated IDs from a Changes_response *)
4747let updated_ids response =
4848- Changes_response.updated response
4848+ List.map (fun s -> match Jmap.Id.of_string s with Ok id -> id | Error e -> failwith e) (Changes_response.updated response)
49495050(** Get destroyed IDs from a Changes_response *)
5151let destroyed_ids response =
5252- Changes_response.destroyed response
5252+ List.map (fun s -> match Jmap.Id.of_string s with Ok id -> id | Error e -> failwith e) (Changes_response.destroyed response)
53535454(** Response builder for batched requests *)
5555module Batch = struct
+4-5
jmap/jmap-email/response.mli
···5566 @see <https://www.rfc-editor.org/rfc/rfc8621.html> RFC 8621 *)
7788-open Jmap.Types
98open Jmap.Methods
1091110(** {1 Response Parsers} *)
···4342(** Extract IDs from a Query_response.
4443 @param response The parsed Query_response
4544 @return List of Email IDs *)
4646-val ids_from_query_response : Query_response.t -> id list
4545+val ids_from_query_response : Query_response.t -> Jmap.Id.t list
47464847(** Check if there are more changes in a Changes_response.
4948 @param response The parsed Changes_response
···5352(** Get created IDs from a Changes_response.
5453 @param response The parsed Changes_response
5554 @return List of newly created Email IDs *)
5656-val created_ids : Changes_response.t -> id list
5555+val created_ids : Changes_response.t -> Jmap.Id.t list
57565857(** Get updated IDs from a Changes_response.
5958 @param response The parsed Changes_response
6059 @return List of updated Email IDs *)
6161-val updated_ids : Changes_response.t -> id list
6060+val updated_ids : Changes_response.t -> Jmap.Id.t list
62616362(** Get destroyed IDs from a Changes_response.
6463 @param response The parsed Changes_response
6564 @return List of destroyed Email IDs *)
6666-val destroyed_ids : Changes_response.t -> id list
6565+val destroyed_ids : Changes_response.t -> Jmap.Id.t list
67666867(** {1 Batch Response Handling} *)
6968
+31-21
jmap/jmap-email/search.ml
···77 @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-5> RFC 8621, Section 5: SearchSnippet
88*)
991010-open Jmap.Types
1110open Jmap.Methods
12111312(** SearchSnippet object *)
1413module SearchSnippet = struct
1514 type t = {
1616- email_id : id;
1515+ email_id : Jmap.Id.t;
1716 subject : string option;
1817 preview : string option;
1918 }
···30293130 let to_json t =
3231 let fields = [
3333- ("emailId", `String t.email_id);
3232+ ("emailId", `String (Jmap.Id.to_string t.email_id));
3433 ] in
3534 let fields = match t.subject with
3635 | Some s -> ("subject", `String s) :: fields
···4544 let of_json = function
4645 | `Assoc fields ->
4746 (match List.assoc_opt "emailId" fields with
4848- | Some (`String email_id) ->
4747+ | Some (`String email_id_str) ->
4848+ let email_id = match Jmap.Id.of_string email_id_str with
4949+ | Ok id -> id
5050+ | Error _ -> failwith ("Invalid email ID: " ^ email_id_str) in
4951 let subject = match List.assoc_opt "subject" fields with
5052 | Some (`String s) -> Some s
5153 | Some `Null | None -> None
···62646365 let pp ppf t =
6466 Format.fprintf ppf "SearchSnippet{emailId=%s; subject=%s; preview=%s}"
6565- t.email_id
6767+ (Jmap.Id.to_string t.email_id)
6668 (match t.subject with Some s -> "\"" ^ s ^ "\"" | None -> "None")
6767- (match t.preview with Some p -> "\"" ^ String.sub p 0 (min 50 (String.length p)) ^ "...\"" | None -> "None")
6969+ (match t.preview with Some p -> "\"" ^ String.sub p 0 (Int.min 50 (String.length p)) ^ "...\"" | None -> "None")
68706971 let pp_hum = pp
7072end
···7274(** Arguments for SearchSnippet/get *)
7375module Get_args = struct
7476 type t = {
7575- account_id : id;
7777+ account_id : Jmap.Id.t;
7678 filter : Filter.t;
7777- email_ids : id list option;
7979+ email_ids : Jmap.Id.t list option;
7880 }
79818082 let account_id t = t.account_id
···89919092 let to_json t =
9193 let fields = [
9292- ("accountId", `String t.account_id);
9494+ ("accountId", `String (Jmap.Id.to_string t.account_id));
9395 ("filter", Filter.to_json t.filter);
9496 ] in
9597 let fields = match t.email_ids with
9696- | Some ids -> ("emailIds", `List (List.map (fun id -> `String id) ids)) :: fields
9898+ | Some ids -> ("emailIds", `List (List.map (fun id -> `String (Jmap.Id.to_string id)) ids)) :: fields
9799 | None -> fields
98100 in
99101 `Assoc fields
···103105 match json with
104106 | `Assoc fields ->
105107 let account_id = match List.assoc_opt "accountId" fields with
106106- | Some (`String id) -> id
108108+ | Some (`String id) -> (match Jmap.Id.of_string id with
109109+ | Ok id -> id
110110+ | Error err -> failwith ("Invalid accountId: " ^ err))
107111 | _ -> failwith "Missing or invalid accountId"
108112 in
109113 let filter = match List.assoc_opt "filter" fields with
···111115 | _ -> failwith "Missing or invalid filter"
112116 in
113117 let email_ids = match List.assoc_opt "emailIds" fields with
114114- | Some (`List ids) -> Some (List.map (function `String id -> id | _ -> failwith "Invalid email ID") ids)
118118+ | Some (`List ids) -> Some (List.map (function `String id -> (match Jmap.Id.of_string id with Ok id -> id | Error err -> failwith ("Invalid email ID: " ^ err)) | _ -> failwith "Invalid email ID") ids)
115119 | Some `Null | None -> None
116120 | _ -> failwith "Invalid emailIds field"
117121 in
···123127124128 let pp fmt t =
125129 Format.fprintf fmt "SearchSnippet.Get_args{account=%s;emails=%s}"
126126- t.account_id
130130+ (Jmap.Id.to_string t.account_id)
127131 (match t.email_ids with Some ids -> string_of_int (List.length ids) | None -> "all")
128132129133 let pp_hum fmt t = pp fmt t
···132136(** Response for SearchSnippet/get *)
133137module Get_response = struct
134138 type t = {
135135- account_id : id;
136136- list : SearchSnippet.t id_map;
137137- not_found : id list;
139139+ account_id : Jmap.Id.t;
140140+ list : (string, SearchSnippet.t) Hashtbl.t;
141141+ not_found : Jmap.Id.t list;
138142 }
139143140144 let account_id t = t.account_id
···149153150154 let to_json t =
151155 `Assoc [
152152- ("accountId", `String t.account_id);
156156+ ("accountId", `String (Jmap.Id.to_string t.account_id));
153157 ("list", `Assoc (Hashtbl.fold (fun k v acc -> (k, SearchSnippet.to_json v) :: acc) t.list []));
154154- ("notFound", `List (List.map (fun id -> `String id) t.not_found));
158158+ ("notFound", `List (List.map (fun id -> `String (Jmap.Id.to_string id)) t.not_found));
155159 ]
156160157161 let of_json json =
···159163 match json with
160164 | `Assoc fields ->
161165 let account_id = match List.assoc_opt "accountId" fields with
162162- | Some (`String id) -> id
166166+ | Some (`String id_str) -> (match Jmap.Id.of_string id_str with
167167+ | Ok id -> id
168168+ | Error _ -> failwith ("Invalid account ID: " ^ id_str))
163169 | _ -> failwith "Missing or invalid accountId"
164170 in
165171 let list = Hashtbl.create 16 in
166172 let not_found = match List.assoc_opt "notFound" fields with
167167- | Some (`List ids) -> List.map (function `String id -> id | _ -> failwith "Invalid not found ID") ids
173173+ | Some (`List ids) -> List.map (function
174174+ | `String id_str -> (match Jmap.Id.of_string id_str with
175175+ | Ok id -> id
176176+ | Error _ -> failwith ("Invalid ID: " ^ id_str))
177177+ | _ -> failwith "Invalid not found ID") ids
168178 | Some `Null | None -> []
169179 | _ -> failwith "Invalid notFound field"
170180 in
···176186177187 let pp fmt t =
178188 Format.fprintf fmt "SearchSnippet.Get_response{account=%s;found=%d;not_found=%d}"
179179- t.account_id
189189+ (Jmap.Id.to_string t.account_id)
180190 (Hashtbl.length t.list)
181191 (List.length t.not_found)
182192
+12-13
jmap/jmap-email/search.mli
···1212 @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-5> RFC 8621, Section 5: SearchSnippet
1313*)
14141515-open Jmap.Types
1615open Jmap.Methods
17161817(** SearchSnippet object representation.
···38373938 (** Get the email ID this snippet corresponds to.
4039 @return ID of the email that contains the matching content *)
4141- val email_id : t -> id
4040+ val email_id : t -> Jmap.Id.t
42414342 (** Get the highlighted subject snippet.
4443 @return Optional highlighted subject text with search matches marked *)
···5453 @param preview Optional highlighted body/preview text
5554 @return New SearchSnippet object *)
5655 val v :
5757- email_id:id ->
5656+ email_id:Jmap.Id.t ->
5857 ?subject:string ->
5958 ?preview:string ->
6059 unit -> t
···86858786 (** Get the account ID for the search operation.
8887 @return Account where emails will be searched for snippets *)
8989- val account_id : t -> id
8888+ val account_id : t -> Jmap.Id.t
90899190 (** Get the search filter defining what to search for.
9291 @return Filter condition that will generate the highlighted snippets *)
···94939594 (** Get the specific email IDs to generate snippets for.
9695 @return Optional list of email IDs, or None to include all matching emails *)
9797- val email_ids : t -> id list option
9696+ val email_ids : t -> Jmap.Id.t list option
98979998 (** Create SearchSnippet/get arguments.
10099 @param account_id Account to search within
···102101 @param email_ids Optional specific email IDs to generate snippets for
103102 @return SearchSnippet/get arguments *)
104103 val v :
105105- account_id:id ->
104104+ account_id:Jmap.Id.t ->
106105 filter:Filter.t ->
107107- ?email_ids:id list ->
106106+ ?email_ids:Jmap.Id.t list ->
108107 unit -> t
109108end
110109···127126128127 (** Get the account ID from the response.
129128 @return Account where snippets were generated *)
130130- val account_id : t -> id
129129+ val account_id : t -> Jmap.Id.t
131130132131 (** Get the map of email IDs to their search snippets.
133132 @return Map containing SearchSnippet objects keyed by email ID *)
134134- val list : t -> SearchSnippet.t id_map
133133+ val list : t -> (string, SearchSnippet.t) Hashtbl.t
135134136135 (** Get the list of email IDs that were not found.
137136 @return List of requested email IDs that don't exist or don't match the filter *)
138138- val not_found : t -> id list
137137+ val not_found : t -> Jmap.Id.t list
139138140139 (** Create SearchSnippet/get response.
141140 @param account_id Account where snippets were generated
···143142 @param not_found List of email IDs that were not found
144143 @return SearchSnippet/get response *)
145144 val v :
146146- account_id:id ->
147147- list:SearchSnippet.t id_map ->
148148- not_found:id list ->
145145+ account_id:Jmap.Id.t ->
146146+ list:(string, SearchSnippet.t) Hashtbl.t ->
147147+ not_found:Jmap.Id.t list ->
149148 unit -> t
150149end
151150
+29-23
jmap/jmap-email/set.ml
···11(** Email set operations using core JMAP Set_args *)
2233-open Jmap.Types
43open Jmap.Methods
5465(** Email creation arguments *)
76module Create = struct
87 type t = {
99- mailbox_ids : (id * bool) list;
88+ mailbox_ids : (Jmap.Id.t * bool) list;
109 keywords : (Keywords.keyword * bool) list;
1111- received_at : Jmap.Types.utc_date option;
1010+ received_at : Jmap.Date.t option;
1211 (* Additional fields as needed *)
1312 }
1413···20192120 let to_json t : Yojson.Safe.t =
2221 let fields = [
2323- ("mailboxIds", (`Assoc (List.map (fun (id, v) -> (id, `Bool v)) t.mailbox_ids) : Yojson.Safe.t));
2222+ ("mailboxIds", (`Assoc (List.map (fun (id, v) -> (Jmap.Id.to_string id, `Bool v)) t.mailbox_ids) : Yojson.Safe.t));
2423 ("keywords", (`Assoc (List.map (fun (kw, v) -> (Keywords.keyword_to_string kw, `Bool v)) t.keywords) : Yojson.Safe.t));
2524 ] in
2625 let fields = match t.received_at with
2727- | Some timestamp -> ("receivedAt", (`String (Jmap.Date.of_timestamp timestamp |> Jmap.Date.to_rfc3339) : Yojson.Safe.t)) :: fields
2626+ | Some timestamp -> ("receivedAt", (Jmap.Date.to_json timestamp : Yojson.Safe.t)) :: fields
2827 | None -> fields
2928 in
3029 (`Assoc fields : Yojson.Safe.t)
···46454746 let move_to_mailbox mailbox_id patch =
4847 (* Clear all existing mailboxes and set new one *)
4848+ let mailbox_id_str = Jmap.Id.to_string mailbox_id in
4949 let clear_mailboxes = ("mailboxIds", `Null) :: patch in
5050- ("mailboxIds/" ^ mailbox_id, `Bool true) :: clear_mailboxes
5050+ ("mailboxIds/" ^ mailbox_id_str, `Bool true) :: clear_mailboxes
51515252 let add_to_mailbox mailbox_id patch =
5353- ("mailboxIds/" ^ mailbox_id, `Bool true) :: patch
5353+ let mailbox_id_str = Jmap.Id.to_string mailbox_id in
5454+ ("mailboxIds/" ^ mailbox_id_str, `Bool true) :: patch
54555556 let remove_from_mailbox mailbox_id patch =
5656- ("mailboxIds/" ^ mailbox_id, `Null) :: patch
5757+ let mailbox_id_str = Jmap.Id.to_string mailbox_id in
5858+ ("mailboxIds/" ^ mailbox_id_str, `Null) :: patch
57595860 let to_patch_object patch : patch_object = patch
5961end
60626163(** Build Email/set arguments *)
6264let build_set_args ~account_id ?if_in_state ?create ?update ?destroy () =
6565+ let account_id_str = Jmap.Id.to_string account_id in
6666+ let destroy_str_list = match destroy with
6767+ | Some id_list -> Some (List.map Jmap.Id.to_string id_list)
6868+ | None -> None in
6369 Set_args.v
6464- ~account_id
7070+ ~account_id:account_id_str
6571 ?if_in_state
6672 ?create
6773 ?update
6868- ?destroy
7474+ ?destroy:destroy_str_list
6975 ()
70767177(** Convert Email/set arguments to JSON *)
···79858086(** Mark emails as read *)
8187let mark_as_read ~account_id email_ids =
8282- let update_map : patch_object id_map = Hashtbl.create (List.length email_ids) in
8888+ let update_map : (string, patch_object) Hashtbl.t = Hashtbl.create (List.length email_ids) in
8389 List.iter (fun id ->
8484- Hashtbl.add update_map id (Update.add_keyword Keywords.Seen [])
9090+ Hashtbl.add update_map (Jmap.Id.to_string id) (Update.add_keyword Keywords.Seen [])
8591 ) email_ids;
8692 build_set_args ~account_id ~update:update_map ()
87938894(** Mark emails as unread *)
8995let mark_as_unread ~account_id email_ids =
9090- let update_map : patch_object id_map = Hashtbl.create (List.length email_ids) in
9696+ let update_map : (string, patch_object) Hashtbl.t = Hashtbl.create (List.length email_ids) in
9197 List.iter (fun id ->
9292- Hashtbl.add update_map id (Update.remove_keyword Keywords.Seen [])
9898+ Hashtbl.add update_map (Jmap.Id.to_string id) (Update.remove_keyword Keywords.Seen [])
9399 ) email_ids;
94100 build_set_args ~account_id ~update:update_map ()
9510196102(** Flag/star emails *)
97103let flag_emails ~account_id email_ids =
9898- let update_map : patch_object id_map = Hashtbl.create (List.length email_ids) in
104104+ let update_map : (string, patch_object) Hashtbl.t = Hashtbl.create (List.length email_ids) in
99105 List.iter (fun id ->
100100- Hashtbl.add update_map id (Update.add_keyword Keywords.Flagged [])
106106+ Hashtbl.add update_map (Jmap.Id.to_string id) (Update.add_keyword Keywords.Flagged [])
101107 ) email_ids;
102108 build_set_args ~account_id ~update:update_map ()
103109104110(** Unflag/unstar emails *)
105111let unflag_emails ~account_id email_ids =
106106- let update_map : patch_object id_map = Hashtbl.create (List.length email_ids) in
112112+ let update_map : (string, patch_object) Hashtbl.t = Hashtbl.create (List.length email_ids) in
107113 List.iter (fun id ->
108108- Hashtbl.add update_map id (Update.remove_keyword Keywords.Flagged [])
114114+ Hashtbl.add update_map (Jmap.Id.to_string id) (Update.remove_keyword Keywords.Flagged [])
109115 ) email_ids;
110116 build_set_args ~account_id ~update:update_map ()
111117112118(** Move emails to a mailbox *)
113119let move_to_mailbox ~account_id ~mailbox_id email_ids =
114114- let update_map : patch_object id_map = Hashtbl.create (List.length email_ids) in
120120+ let update_map : (string, patch_object) Hashtbl.t = Hashtbl.create (List.length email_ids) in
115121 List.iter (fun id ->
116116- Hashtbl.add update_map id (Update.move_to_mailbox mailbox_id [])
122122+ Hashtbl.add update_map (Jmap.Id.to_string id) (Update.move_to_mailbox mailbox_id [])
117123 ) email_ids;
118124 build_set_args ~account_id ~update:update_map ()
119125···127133128134(** Batch update multiple properties *)
129135let batch_update ~account_id updates =
130130- let update_map : patch_object id_map = Hashtbl.create (List.length updates) in
136136+ let update_map : (string, patch_object) Hashtbl.t = Hashtbl.create (List.length updates) in
131137 List.iter (fun (id, patch) ->
132132- Hashtbl.add update_map id patch
138138+ Hashtbl.add update_map (Jmap.Id.to_string id) patch
133139 ) updates;
134140 build_set_args ~account_id ~update:update_map ()
135141···138144 (* Note: subject, from, to_, cc, bcc, text_body, html_body would need proper implementation
139145 with full email creation support. For now, just creating basic structure. *)
140146 let creation = Create.make ~mailbox_ids ?keywords () in
141141- let create_map : Create.t id_map = Hashtbl.create 1 in
147147+ let create_map : (string, Create.t) Hashtbl.t = Hashtbl.create 1 in
142148 Hashtbl.add create_map "draft-1" creation;
143149 build_set_args ~account_id ~create:create_map ()
+26-27
jmap/jmap-email/set.mli
···5566 @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-4.5> RFC 8621 Section 4.5 *)
7788-open Jmap.Types
98open Jmap.Methods
1091110(** {1 Email Creation} *)
···2019 @param ?received_at Optional received timestamp
2120 @return Email creation arguments *)
2221 val make :
2323- mailbox_ids:(id * bool) list ->
2222+ mailbox_ids:(Jmap.Id.t * bool) list ->
2423 ?keywords:(Keywords.keyword * bool) list ->
2525- ?received_at:Jmap.Types.utc_date ->
2424+ ?received_at:Jmap.Date.t ->
2625 unit -> t
27262827 (** Convert creation arguments to JSON *)
···4645 val remove_keyword : Keywords.keyword -> patch_object -> patch_object
47464847 (** Move to a single mailbox (removes from all others) *)
4949- val move_to_mailbox : id -> patch_object -> patch_object
4848+ val move_to_mailbox : Jmap.Id.t -> patch_object -> patch_object
50495150 (** Add to a mailbox (keeps existing) *)
5252- val add_to_mailbox : id -> patch_object -> patch_object
5151+ val add_to_mailbox : Jmap.Id.t -> patch_object -> patch_object
53525453 (** Remove from a mailbox *)
5555- val remove_from_mailbox : id -> patch_object -> patch_object
5454+ val remove_from_mailbox : Jmap.Id.t -> patch_object -> patch_object
56555756 (** Convert to patch object for Set_args *)
5857 val to_patch_object : patch_object -> patch_object
···6867 @param ?destroy Optional list of email IDs to destroy
6968 @return Set_args for Email/set method *)
7069val build_set_args :
7171- account_id:id ->
7070+ account_id:Jmap.Id.t ->
7271 ?if_in_state:string ->
7373- ?create:Create.t id_map ->
7474- ?update:patch_object id_map ->
7575- ?destroy:id list ->
7272+ ?create:(string, Create.t) Hashtbl.t ->
7373+ ?update:(string, patch_object) Hashtbl.t ->
7474+ ?destroy:Jmap.Id.t list ->
7675 unit ->
7776 (Create.t, patch_object) Set_args.t
7877···8887 @param email_ids List of email IDs to mark as read
8988 @return Set_args for marking emails as read *)
9089val mark_as_read :
9191- account_id:id ->
9292- id list ->
9090+ account_id:Jmap.Id.t ->
9191+ Jmap.Id.t list ->
9392 (Create.t, patch_object) Set_args.t
94939594(** Mark emails as unread by removing $seen keyword.
···9796 @param email_ids List of email IDs to mark as unread
9897 @return Set_args for marking emails as unread *)
9998val mark_as_unread :
100100- account_id:id ->
101101- id list ->
9999+ account_id:Jmap.Id.t ->
100100+ Jmap.Id.t list ->
102101 (Create.t, patch_object) Set_args.t
103102104103(** Flag/star emails by adding $flagged keyword.
···106105 @param email_ids List of email IDs to flag
107106 @return Set_args for flagging emails *)
108107val flag_emails :
109109- account_id:id ->
110110- id list ->
108108+ account_id:Jmap.Id.t ->
109109+ Jmap.Id.t list ->
111110 (Create.t, patch_object) Set_args.t
112111113112(** Unflag/unstar emails by removing $flagged keyword.
···115114 @param email_ids List of email IDs to unflag
116115 @return Set_args for unflagging emails *)
117116val unflag_emails :
118118- account_id:id ->
119119- id list ->
117117+ account_id:Jmap.Id.t ->
118118+ Jmap.Id.t list ->
120119 (Create.t, patch_object) Set_args.t
121120122121(** Move emails to a specific mailbox.
···125124 @param email_ids List of email IDs to move
126125 @return Set_args for moving emails *)
127126val move_to_mailbox :
128128- account_id:id ->
129129- mailbox_id:id ->
130130- id list ->
127127+ account_id:Jmap.Id.t ->
128128+ mailbox_id:Jmap.Id.t ->
129129+ Jmap.Id.t list ->
131130 (Create.t, patch_object) Set_args.t
132131133132(** Delete emails (destroy or move to trash).
···136135 @param email_ids List of email IDs to delete
137136 @return Set_args for deleting emails *)
138137val delete_emails :
139139- account_id:id ->
138138+ account_id:Jmap.Id.t ->
140139 ?destroy:bool ->
141141- id list ->
140140+ Jmap.Id.t list ->
142141 (Create.t, patch_object) Set_args.t
143142144143(** Batch update multiple emails with different patches.
···146145 @param updates List of (email_id, patch_object) pairs
147146 @return Set_args for batch updates *)
148147val batch_update :
149149- account_id:id ->
150150- (id * patch_object) list ->
148148+ account_id:Jmap.Id.t ->
149149+ (Jmap.Id.t * patch_object) list ->
151150 (Create.t, patch_object) Set_args.t
152151153152(** Create a draft email.
···163162 @param ?html_body Optional HTML body
164163 @return Set_args for creating a draft *)
165164val create_draft :
166166- account_id:id ->
167167- mailbox_ids:(id * bool) list ->
165165+ account_id:Jmap.Id.t ->
166166+ mailbox_ids:(Jmap.Id.t * bool) list ->
168167 ?keywords:(Keywords.keyword * bool) list ->
169168 ?subject:string ->
170169 ?from:string ->
+104-85
jmap/jmap-email/submission.ml
···77 @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-7> RFC 8621, Section 7: EmailSubmission
88*)
991010-open Jmap.Types
11101211(** {1 Internal Type Representations} *)
13121413(** Internal EmailSubmission representation *)
1514type submission_data = {
1616- id : id;
1717- identity_id : id;
1818- email_id : id;
1919- thread_id : id;
1515+ id : Jmap.Id.t;
1616+ identity_id : Jmap.Id.t;
1717+ email_id : Jmap.Id.t;
1818+ thread_id : Jmap.Id.t;
2019 envelope : envelope_data option;
2121- send_at : utc_date;
2020+ send_at : Jmap.Date.t;
2221 undo_status : [`Pending | `Final | `Canceled];
2323- delivery_status : delivery_status_data string_map option;
2424- dsn_blob_ids : id list;
2525- mdn_blob_ids : id list;
2222+ delivery_status : (string, delivery_status_data) Hashtbl.t option;
2323+ dsn_blob_ids : Jmap.Id.t list;
2424+ mdn_blob_ids : Jmap.Id.t list;
2625}
27262827(** Internal envelope representation *)
···3433(** Internal envelope address representation *)
3534and envelope_address_data = {
3635 email : string;
3737- parameters : Yojson.Safe.t string_map option;
3636+ parameters : (string, Yojson.Safe.t) Hashtbl.t option;
3837}
39384039(** Internal delivery status representation *)
···227226(** Convert submission to JSON *)
228227let to_json submission =
229228 let base = [
230230- ("id", `String submission.id);
231231- ("identityId", `String submission.identity_id);
232232- ("emailId", `String submission.email_id);
233233- ("threadId", `String submission.thread_id);
234234- ("sendAt", `Float submission.send_at);
229229+ ("id", `String (Jmap.Id.to_string submission.id));
230230+ ("identityId", `String (Jmap.Id.to_string submission.identity_id));
231231+ ("emailId", `String (Jmap.Id.to_string submission.email_id));
232232+ ("threadId", `String (Jmap.Id.to_string submission.thread_id));
233233+ ("sendAt", `Float (Jmap.Date.to_timestamp submission.send_at));
235234 ("undoStatus", `String (undo_status_to_string submission.undo_status));
236236- ("dsnBlobIds", `List (List.map (fun id -> `String id) submission.dsn_blob_ids));
237237- ("mdnBlobIds", `List (List.map (fun id -> `String id) submission.mdn_blob_ids));
235235+ ("dsnBlobIds", `List (List.map (fun id -> `String (Jmap.Id.to_string id)) submission.dsn_blob_ids));
236236+ ("mdnBlobIds", `List (List.map (fun id -> `String (Jmap.Id.to_string id)) submission.mdn_blob_ids));
238237 ] in
239238 let fields = match submission.envelope with
240239 | Some _env -> ("envelope", `Null) :: base (* Envelope serialization not implemented *)
···251250252251(** Format EmailSubmission for debugging *)
253252let pp ppf submission =
254254- let send_at_str = Printf.sprintf "%.0f" submission.send_at in
253253+ let send_at_str = Printf.sprintf "%.0f" (Jmap.Date.to_timestamp submission.send_at) in
255254 let undo_status_str = undo_status_to_string submission.undo_status in
256256- Format.fprintf ppf "EmailSubmission{id=%s; email_id=%s; thread_id=%s; identity_id=%s; send_at=%s; undo_status=%s}"
257257- submission.id
258258- submission.email_id
259259- submission.thread_id
260260- submission.identity_id
255255+ Format.fprintf ppf "EmailSubmission{Id.t=%s; email_id=%s; thread_id=%s; identity_id=%s; send_at=%s; undo_status=%s}"
256256+ (Jmap.Id.to_string submission.id)
257257+ (Jmap.Id.to_string submission.email_id)
258258+ (Jmap.Id.to_string submission.thread_id)
259259+ (Jmap.Id.to_string submission.identity_id)
261260 send_at_str
262261 undo_status_str
263262264263(** Format EmailSubmission for human reading *)
265264let pp_hum ppf submission =
266266- let send_at_str = Printf.sprintf "%.0f" submission.send_at in
265265+ let send_at_str = Printf.sprintf "%.0f" (Jmap.Date.to_timestamp submission.send_at) in
267266 let undo_status_str = undo_status_to_string submission.undo_status in
268267 let envelope_str = match submission.envelope with
269268 | None -> "none"
···273272 | None -> "none"
274273 | Some tbl -> Printf.sprintf "%d recipients" (Hashtbl.length tbl)
275274 in
276276- Format.fprintf ppf "EmailSubmission {\n id: %s\n email_id: %s\n thread_id: %s\n identity_id: %s\n send_at: %s\n undo_status: %s\n envelope: %s\n delivery_status: %s\n dsn_blob_ids: %d\n mdn_blob_ids: %d\n}"
277277- submission.id
278278- submission.email_id
279279- submission.thread_id
280280- submission.identity_id
275275+ Format.fprintf ppf "EmailSubmission {\n Id.t: %s\n email_id: %s\n thread_id: %s\n identity_id: %s\n send_at: %s\n undo_status: %s\n envelope: %s\n delivery_status: %s\n dsn_blob_ids: %d\n mdn_blob_ids: %d\n}"
276276+ (Jmap.Id.to_string submission.id)
277277+ (Jmap.Id.to_string submission.email_id)
278278+ (Jmap.Id.to_string submission.thread_id)
279279+ (Jmap.Id.to_string submission.identity_id)
281280 send_at_str
282281 undo_status_str
283282 envelope_str
···305304 in
306305 let get_optional_field name = try Some (get_field name) with Not_found -> None in
307306308308- let id = get_string_field "id" in
309309- let identity_id = get_string_field "identityId" in
310310- let email_id = get_string_field "emailId" in
311311- let thread_id = get_string_field "threadId" in
312312- let send_at = get_float_field "sendAt" in
307307+ let id = match Jmap.Id.of_string (get_string_field "id") with
308308+ | Ok id -> id | Error err -> failwith ("Invalid id: " ^ err) in
309309+ let identity_id = match Jmap.Id.of_string (get_string_field "identityId") with
310310+ | Ok id -> id | Error err -> failwith ("Invalid identityId: " ^ err) in
311311+ let email_id = match Jmap.Id.of_string (get_string_field "emailId") with
312312+ | Ok id -> id | Error err -> failwith ("Invalid emailId: " ^ err) in
313313+ let thread_id = match Jmap.Id.of_string (get_string_field "threadId") with
314314+ | Ok id -> id | Error err -> failwith ("Invalid threadId: " ^ err) in
315315+ let send_at = Jmap.Date.of_timestamp (get_float_field "sendAt") in
313316 let undo_status = undo_status_of_string (get_string_field "undoStatus") in
314317 let dsn_blob_ids = List.map (function
315315- | `String s -> s
318318+ | `String s -> (match Jmap.Id.of_string s with Ok id -> id | Error err -> failwith ("Invalid dsnBlobId: " ^ err))
316319 | _ -> failwith "Expected string in dsnBlobIds"
317320 ) (get_list_field "dsnBlobIds") in
318321 let mdn_blob_ids = List.map (function
319319- | `String s -> s
322322+ | `String s -> (match Jmap.Id.of_string s with Ok id -> id | Error err -> failwith ("Invalid mdnBlobId: " ^ err))
320323 | _ -> failwith "Expected string in mdnBlobIds"
321324 ) (get_list_field "mdnBlobIds") in
322325···361364(** Serialize to JSON with only specified properties *)
362365let to_json_with_properties ~properties submission =
363366 let all_fields = [
364364- ("id", `String submission.id);
365365- ("identityId", `String submission.identity_id);
366366- ("emailId", `String submission.email_id);
367367- ("threadId", `String submission.thread_id);
368368- ("sendAt", `Float submission.send_at);
367367+ ("id", `String (Jmap.Id.to_string submission.id));
368368+ ("identityId", `String (Jmap.Id.to_string submission.identity_id));
369369+ ("emailId", `String (Jmap.Id.to_string submission.email_id));
370370+ ("threadId", `String (Jmap.Id.to_string submission.thread_id));
371371+ ("sendAt", `Float (Jmap.Date.to_timestamp submission.send_at));
369372 ("undoStatus", `String (undo_status_to_string submission.undo_status));
370370- ("dsnBlobIds", `List (List.map (fun id -> `String id) submission.dsn_blob_ids));
371371- ("mdnBlobIds", `List (List.map (fun id -> `String id) submission.mdn_blob_ids));
373373+ ("dsnBlobIds", `List (List.map (fun id -> `String (Jmap.Id.to_string id)) submission.dsn_blob_ids));
374374+ ("mdnBlobIds", `List (List.map (fun id -> `String (Jmap.Id.to_string id)) submission.mdn_blob_ids));
372375 (* TODO: Add envelope and deliveryStatus when implemented *)
373376 ("envelope", match submission.envelope with Some _ -> `Null | None -> `Null);
374377 ("deliveryStatus", match submission.delivery_status with Some _ -> `Null | None -> `Null);
···418421module Create = struct
419422420423 type create_data = {
421421- identity_id : id;
422422- email_id : id;
424424+ identity_id : Jmap.Id.t;
425425+ email_id : Jmap.Id.t;
423426 envelope : envelope_data option;
424427 }
425428···427430428431 let to_json create =
429432 let base = [
430430- ("identityId", `String create.identity_id);
431431- ("emailId", `String create.email_id);
433433+ ("identityId", `String (Jmap.Id.to_string create.identity_id));
434434+ ("emailId", `String (Jmap.Id.to_string create.email_id));
432435 ] in
433436 let fields = match create.envelope with
434437 | Some _env -> ("envelope", `Null) :: base (* Envelope serialization not implemented *)
···443446 let get_field name = List.assoc name fields in
444447 let get_optional_field name = try Some (get_field name) with Not_found -> None in
445448 let identity_id = match get_field "identityId" with
446446- | `String s -> s
449449+ | `String s -> (match Jmap.Id.of_string s with
450450+ | Ok id -> id
451451+ | Error _ -> failwith ("Invalid identityId: " ^ s))
447452 | _ -> failwith "Expected string for identityId"
448453 in
449454 let email_id = match get_field "emailId" with
450450- | `String s -> s
455455+ | `String s -> (match Jmap.Id.of_string s with
456456+ | Ok id -> id
457457+ | Error _ -> failwith ("Invalid emailId: " ^ s))
451458 | _ -> failwith "Expected string for emailId"
452459 in
453460 let envelope = match get_optional_field "envelope" with
···472479 module Response = struct
473480474481 type response_data = {
475475- id : id;
476476- thread_id : id;
477477- send_at : utc_date;
482482+ id : Jmap.Id.t;
483483+ thread_id : Jmap.Id.t;
484484+ send_at : Jmap.Date.t;
478485 }
479486480487 type t = response_data
481488482489 let to_json response =
483490 `Assoc [
484484- ("id", `String response.id);
485485- ("threadId", `String response.thread_id);
486486- ("sendAt", `Float response.send_at);
491491+ ("id", `String (Jmap.Id.to_string response.id));
492492+ ("threadId", `String (Jmap.Id.to_string response.thread_id));
493493+ ("sendAt", `Float (Jmap.Date.to_timestamp response.send_at));
487494 ]
488495489496 let of_json json =
···492499 | `Assoc fields ->
493500 let get_field name = List.assoc name fields in
494501 let id = match get_field "id" with
495495- | `String s -> s
502502+ | `String s -> (match Jmap.Id.of_string s with
503503+ | Ok id -> id
504504+ | Error _ -> failwith ("Invalid id: " ^ s))
496505 | _ -> failwith "Expected string for id"
497506 in
498507 let thread_id = match get_field "threadId" with
499499- | `String s -> s
508508+ | `String s -> (match Jmap.Id.of_string s with
509509+ | Ok id -> id
510510+ | Error _ -> failwith ("Invalid threadId: " ^ s))
500511 | _ -> failwith "Expected string for threadId"
501512 in
502513 let send_at = match get_field "sendAt" with
503503- | `Float f -> f
514514+ | `Float f -> Jmap.Date.of_timestamp f
504515 | _ -> failwith "Expected float for sendAt"
505516 in
506517 Ok { id; thread_id; send_at }
···548559module Get_args = struct
549560550561 type get_args_data = {
551551- account_id : id;
552552- ids : id list option;
562562+ account_id : Jmap.Id.t;
563563+ ids : Jmap.Id.t list option;
553564 properties : string list option;
554565 }
555566556567 type t = get_args_data
557568558569 let to_json args =
559559- let base = [("accountId", `String args.account_id)] in
570570+ let base = [("accountId", `String (Jmap.Id.to_string args.account_id))] in
560571 let fields = match args.ids with
561561- | Some ids -> ("ids", `List (List.map (fun id -> `String id) ids)) :: base
572572+ | Some ids -> ("ids", `List (List.map (fun id -> `String (Jmap.Id.to_string id)) ids)) :: base
562573 | None -> base
563574 in
564575 let fields = match args.properties with
···574585 let get_field name = List.assoc name fields in
575586 let get_optional_field name = try Some (get_field name) with Not_found -> None in
576587 let account_id = match get_field "accountId" with
577577- | `String s -> s
588588+ | `String s -> (match Jmap.Id.of_string s with
589589+ | Ok id -> id
590590+ | Error _ -> failwith ("Invalid accountId: " ^ s))
578591 | _ -> failwith "Expected string for accountId"
579592 in
580593 let ids = match get_optional_field "ids" with
581594 | Some (`List id_list) -> Some (List.map (function
582582- | `String s -> s
595595+ | `String s -> (match Jmap.Id.of_string s with
596596+ | Ok id -> id
597597+ | Error _ -> failwith ("Invalid id: " ^ s))
583598 | _ -> failwith "Expected string in ids"
584599 ) id_list)
585600 | Some _ -> failwith "Expected list for ids"
···607622module Get_response = struct
608623609624 type get_response_data = {
610610- account_id : id;
625625+ account_id : Jmap.Id.t;
611626 state : string;
612627 list : email_submission_t list;
613613- not_found : id list;
628628+ not_found : Jmap.Id.t list;
614629 }
615630616631 type t = get_response_data
617632618633 let to_json response =
619634 `Assoc [
620620- ("accountId", `String response.account_id);
635635+ ("accountId", `String (Jmap.Id.to_string response.account_id));
621636 ("state", `String response.state);
622622- ("list", `List (List.map to_json response.list));
623623- ("notFound", `List (List.map (fun id -> `String id) response.not_found));
637637+ ("list", `List (List.map (fun submission -> to_json submission) response.list));
638638+ ("notFound", `List (List.map (fun id -> `String (Jmap.Id.to_string id)) response.not_found));
624639 ]
625640626641 let of_json json =
···629644 | `Assoc fields ->
630645 let get_field name = List.assoc name fields in
631646 let account_id = match get_field "accountId" with
632632- | `String s -> s
647647+ | `String s -> (match Jmap.Id.of_string s with
648648+ | Ok id -> id
649649+ | Error _ -> failwith ("Invalid accountId: " ^ s))
633650 | _ -> failwith "Expected string for accountId"
634651 in
635652 let state = match get_field "state" with
···639656 let list = match get_field "list" with
640657 | `List submission_list ->
641658 List.filter_map (fun item ->
642642- match of_json item with
659659+ match (of_json : Yojson.Safe.t -> (email_submission_t, string) result) item with
643660 | Ok submission -> Some submission
644661 | Error _ -> None (* Skip entries that fail to parse *)
645662 ) submission_list
646663 | _ -> failwith "Expected list for list"
647664 in
648665 let not_found = match get_field "notFound" with
649649- | `List id_list -> List.map (function
650650- | `String s -> s
651651- | _ -> failwith "Expected string in notFound"
666666+ | `List id_list -> List.filter_map (function
667667+ | `String s -> (match Jmap.Id.of_string s with
668668+ | Ok id -> Some id
669669+ | Error _ -> None)
670670+ | _ -> None
652671 ) id_list
653672 | _ -> failwith "Expected list for notFound"
654673 in
···681700 type t = unit (* Not implemented *)
682701 let to_json _ = `Assoc []
683702 let of_json _ = Ok ()
684684- let account_id _ = ""
703703+ let account_id _ = match Jmap.Id.of_string "stub-account-id" with Ok id -> id | Error _ -> failwith "Invalid stub id"
685704 let old_state _ = ""
686705 let new_state _ = ""
687706 let has_more_changes _ = false
···701720 type t = unit (* Not implemented *)
702721 let to_json _ = `Assoc []
703722 let of_json _ = Ok ()
704704- let account_id _ = ""
723723+ let account_id _ = match Jmap.Id.of_string "stub-account-id" with Ok id -> id | Error _ -> failwith "Invalid stub id"
705724 let query_state _ = ""
706725 let can_calculate_changes _ = false
707707- let position _ = 0
726726+ let position _ = match Jmap.UInt.of_int 0 with Ok v -> v | Error _ -> failwith "Invalid position"
708727 let total _ = None
709728 let ids _ = []
710729end
···720739 type t = unit (* Not implemented *)
721740 let to_json _ = `Assoc []
722741 let of_json _ = Ok ()
723723- let account_id _ = ""
742742+ let account_id _ = match Jmap.Id.of_string "stub-set-response-account-id" with Ok id -> id | Error _ -> failwith "Invalid stub id"
724743 let old_state _ = None
725744 let new_state _ = ""
726745 let created _ = Hashtbl.create 0
···736755module Filter = struct
737756738757 let identity_ids ids =
739739- let id_values = List.map (fun id -> `String id) ids in
758758+ let id_values = List.map (fun id -> `String (Jmap.Id.to_string id)) ids in
740759 Jmap.Methods.Filter.property_in "identityId" id_values
741760742761 let email_ids ids =
743743- let id_values = List.map (fun id -> `String id) ids in
762762+ let id_values = List.map (fun id -> `String (Jmap.Id.to_string id)) ids in
744763 Jmap.Methods.Filter.property_in "emailId" id_values
745764746765 let thread_ids ids =
747747- let id_values = List.map (fun id -> `String id) ids in
766766+ let id_values = List.map (fun id -> `String (Jmap.Id.to_string id)) ids in
748767 Jmap.Methods.Filter.property_in "threadId" id_values
749768750769 let undo_status status =
···752771 Jmap.Methods.Filter.property_equals "undoStatus" status_value
753772754773 let before date =
755755- Jmap.Methods.Filter.property_lt "sendAt" (`Float date)
774774+ Jmap.Methods.Filter.property_lt "sendAt" (`Float (Jmap.Date.to_timestamp date))
756775757776 let after date =
758758- Jmap.Methods.Filter.property_gt "sendAt" (`Float date)
777777+ Jmap.Methods.Filter.property_gt "sendAt" (`Float (Jmap.Date.to_timestamp date))
759778760779 let date_range ~after_date ~before_date =
761780 Jmap.Methods.Filter.and_ [
···802821 ]
803822804823 let to_string = function
805805- | `Id -> "id"
824824+ | `Id -> "Id.t"
806825 | `IdentityId -> "identityId"
807826 | `EmailId -> "emailId"
808827 | `ThreadId -> "threadId"
···814833 | `MdnBlobIds -> "mdnBlobIds"
815834816835 let of_string = function
817817- | "id" -> Some `Id
836836+ | "Id.t" -> Some `Id
818837 | "identityId" -> Some `IdentityId
819838 | "emailId" -> Some `EmailId
820839 | "threadId" -> Some `ThreadId
+76-79
jmap/jmap-email/submission.mli
···1212 @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-7> RFC 8621, Section 7: EmailSubmission
1313*)
14141515-open Jmap.Types
1616-1717-1815(** {1 Supporting Types} *)
19162017(** SMTP envelope address representation.
···4037 (** Get the optional SMTP parameters.
4138 @param address The envelope address object
4239 @return Optional SMTP parameters *)
4343- val parameters : t -> Yojson.Safe.t string_map option
4040+ val parameters : t -> (string, Yojson.Safe.t) Hashtbl.t option
44414542 (** Create an envelope address.
4643 @param email Email address for SMTP envelope
···4845 @return Ok with address object, or Error with validation message *)
4946 val create :
5047 email:string ->
5151- ?parameters:Yojson.Safe.t string_map ->
4848+ ?parameters:(string, Yojson.Safe.t) Hashtbl.t ->
5249 unit -> (t, string) result
5350end
5451···149146include Jmap_sigs.PRINTABLE with type t := t
150147151148(** JMAP object interface for property-based operations *)
152152-include Jmap_sigs.JMAP_OBJECT with type t := t and type id_type := id
149149+include Jmap_sigs.JMAP_OBJECT with type t := t and type id_type := string
153150154151(** {1 Property Accessors} *)
155152156153(** Get the server-assigned submission identifier.
157154 @param submission The email submission object
158155 @return Immutable server-assigned submission ID *)
159159-val id : t -> id option
156156+val id : t -> Jmap.Id.t option
160157161158(** Get the identity used for sending this email.
162159 @param submission The email submission object
163160 @return Immutable identity ID used for sending *)
164164-val identity_id : t -> id
161161+val identity_id : t -> Jmap.Id.t
165162166163(** Get the email being submitted.
167164 @param submission The email submission object
168165 @return Immutable email ID being submitted *)
169169-val email_id : t -> id
166166+val email_id : t -> Jmap.Id.t
170167171168(** Get the thread this email belongs to.
172169 @param submission The email submission object
173170 @return Immutable thread ID (server-set) *)
174174-val thread_id : t -> id
171171+val thread_id : t -> Jmap.Id.t
175172176173(** Get the SMTP envelope override.
177174 @param submission The email submission object
···181178(** Get the scheduled send time.
182179 @param submission The email submission object
183180 @return Immutable scheduled send time (server-set) *)
184184-val send_at : t -> utc_date
181181+val send_at : t -> Jmap.Date.t
185182186183(** Get the current undo/cancellation status.
187184 @param submission The email submission object
···191188(** Get the per-recipient delivery status.
192189 @param submission The email submission object
193190 @return Per-recipient delivery status (server-set) *)
194194-val delivery_status : t -> DeliveryStatus.t string_map option
191191+val delivery_status : t -> (string, DeliveryStatus.t) Hashtbl.t option
195192196193(** Get the delivery status notification blob IDs.
197194 @param submission The email submission object
198195 @return Delivery status notification blobs (server-set) *)
199199-val dsn_blob_ids : t -> id list
196196+val dsn_blob_ids : t -> Jmap.Id.t list
200197201198(** Get the message disposition notification blob IDs.
202199 @param submission The email submission object
203200 @return Message disposition notification blobs (server-set) *)
204204-val mdn_blob_ids : t -> id list
201201+val mdn_blob_ids : t -> Jmap.Id.t list
205202206203(** {1 Smart Constructors} *)
207204208205(** Create an EmailSubmission object from all properties.
209209- @param id Server-assigned submission ID
206206+ @param Jmap.Id.t Server-assigned submission ID
210207 @param identity_id Identity used for sending
211208 @param email_id Email being submitted
212209 @param thread_id Thread ID (server-set)
···218215 @param mdn_blob_ids Message disposition notification blobs (server-set)
219216 @return Ok with submission object, or Error with validation message *)
220217val create :
221221- id:id ->
222222- identity_id:id ->
223223- email_id:id ->
224224- thread_id:id ->
218218+ id:Jmap.Id.t ->
219219+ identity_id:Jmap.Id.t ->
220220+ email_id:Jmap.Id.t ->
221221+ thread_id:Jmap.Id.t ->
225222 ?envelope:Envelope.t ->
226226- send_at:utc_date ->
223223+ send_at:Jmap.Date.t ->
227224 undo_status:[`Pending | `Final | `Canceled] ->
228228- ?delivery_status:DeliveryStatus.t string_map ->
229229- ?dsn_blob_ids:id list ->
230230- ?mdn_blob_ids:id list ->
225225+ ?delivery_status:(string, DeliveryStatus.t) Hashtbl.t ->
226226+ ?dsn_blob_ids:Jmap.Id.t list ->
227227+ ?mdn_blob_ids:Jmap.Id.t list ->
231228 unit -> (t, string) result
232229233230(** {1 JMAP Method Operations} *)
···250247 (** Get the identity to use for sending.
251248 @param create The creation object
252249 @return Identity to use for sending *)
253253- val identity_id : t -> id
250250+ val identity_id : t -> Jmap.Id.t
254251255252 (** Get the email object to submit.
256253 @param create The creation object
257254 @return Email object to submit *)
258258- val email_id : t -> id
255255+ val email_id : t -> Jmap.Id.t
259256260257 (** Get the optional envelope override.
261258 @param create The creation object
···268265 @param envelope Optional envelope override
269266 @return Ok with creation object, or Error with validation message *)
270267 val create :
271271- identity_id:id ->
272272- email_id:id ->
268268+ identity_id:Jmap.Id.t ->
269269+ email_id:Jmap.Id.t ->
273270 ?envelope:Envelope.t ->
274271 unit -> (t, string) result
275272···290287 (** Get the server-assigned submission ID.
291288 @param response The creation response object
292289 @return Server-assigned submission ID *)
293293- val id : t -> id
290290+ val id : t -> Jmap.Id.t
294291295292 (** Get the thread ID the email belongs to.
296293 @param response The creation response object
297294 @return Thread ID the email belongs to *)
298298- val thread_id : t -> id
295295+ val thread_id : t -> Jmap.Id.t
299296300297 (** Get the actual/scheduled send timestamp.
301298 @param response The creation response object
302299 @return Actual/scheduled send timestamp *)
303303- val send_at : t -> utc_date
300300+ val send_at : t -> Jmap.Date.t
304301305302 (** Create a creation response.
306306- @param id Server-assigned submission ID
303303+ @param Jmap.Id.t Server-assigned submission ID
307304 @param thread_id Thread ID the email belongs to
308305 @param send_at Actual/scheduled send timestamp
309306 @return Ok with response object, or Error with validation message *)
310307 val create :
311311- id:id ->
312312- thread_id:id ->
313313- send_at:utc_date ->
308308+ id:Jmap.Id.t ->
309309+ thread_id:Jmap.Id.t ->
310310+ send_at:Jmap.Date.t ->
314311 (t, string) result
315312 end
316313end
···383380 @param properties Properties to include (None for all)
384381 @return Ok with get arguments, or Error with validation message *)
385382 val create :
386386- account_id:id ->
387387- ?ids:id list ->
383383+ account_id:Jmap.Id.t ->
384384+ ?ids:Jmap.Id.t list ->
388385 ?properties:string list ->
389386 unit -> (t, string) result
390387end
···406403 (** Get the account ID.
407404 @param response The get response object
408405 @return Account ID *)
409409- val account_id : t -> id
406406+ val account_id : t -> Jmap.Id.t
410407411408 (** Get the current state string.
412409 @param response The get response object
···421418 (** Get the list of submission IDs not found.
422419 @param response The get response object
423420 @return List of submission IDs not found *)
424424- val not_found : t -> id list
421421+ val not_found : t -> Jmap.Id.t list
425422end
426423427424(** Arguments for EmailSubmission/changes method.
···444441 @param max_changes Maximum number of changes to return
445442 @return Ok with changes arguments, or Error with validation message *)
446443 val create :
447447- account_id:id ->
444444+ account_id:Jmap.Id.t ->
448445 since_state:string ->
449449- ?max_changes:uint ->
446446+ ?max_changes:Jmap.UInt.t ->
450447 unit -> (t, string) result
451448end
452449···467464 (** Get the account ID.
468465 @param response The changes response object
469466 @return Account ID *)
470470- val account_id : t -> id
467467+ val account_id : t -> Jmap.Id.t
471468472469 (** Get the old state string.
473470 @param response The changes response object
···487484 (** Get the list of created submission IDs.
488485 @param response The changes response object
489486 @return List of created submission IDs *)
490490- val created : t -> id list
487487+ val created : t -> Jmap.Id.t list
491488492489 (** Get the list of updated submission IDs.
493490 @param response The changes response object
494491 @return List of updated submission IDs *)
495495- val updated : t -> id list
492492+ val updated : t -> Jmap.Id.t list
496493497494 (** Get the list of destroyed submission IDs.
498495 @param response The changes response object
499496 @return List of destroyed submission IDs *)
500500- val destroyed : t -> id list
497497+ val destroyed : t -> Jmap.Id.t list
501498end
502499503500(** Arguments for EmailSubmission/query method.
···525522 @param calculate_total Whether to calculate total count
526523 @return Ok with query arguments, or Error with validation message *)
527524 val create :
528528- account_id:id ->
525525+ account_id:Jmap.Id.t ->
529526 ?filter:Jmap.Methods.Filter.t ->
530527 ?sort:Jmap.Methods.Comparator.t list ->
531531- ?position:uint ->
532532- ?anchor:id ->
528528+ ?position:Jmap.UInt.t ->
529529+ ?anchor:Jmap.Id.t ->
533530 ?anchor_offset:int ->
534534- ?limit:uint ->
531531+ ?limit:Jmap.UInt.t ->
535532 ?calculate_total:bool ->
536533 unit -> (t, string) result
537534end
···553550 (** Get the account ID.
554551 @param response The query response object
555552 @return Account ID *)
556556- val account_id : t -> id
553553+ val account_id : t -> Jmap.Id.t
557554558555 (** Get the query state string.
559556 @param response The query response object
···568565 (** Get the starting position of results.
569566 @param response The query response object
570567 @return Starting position of results *)
571571- val position : t -> uint
568568+ val position : t -> Jmap.UInt.t
572569573570 (** Get the total number of matching objects.
574571 @param response The query response object
575572 @return Total number of matching objects (if calculated) *)
576576- val total : t -> uint option
573573+ val total : t -> Jmap.UInt.t option
577574578575 (** Get the list of matching submission IDs.
579576 @param response The query response object
580577 @return List of matching submission IDs *)
581581- val ids : t -> id list
578578+ val ids : t -> Jmap.Id.t list
582579end
583580584581(** Arguments for EmailSubmission/set method.
···605602 @param on_success_destroy_email Emails to destroy after successful submission
606603 @return Ok with set arguments, or Error with validation message *)
607604 val create :
608608- account_id:id ->
605605+ account_id:Jmap.Id.t ->
609606 ?if_in_state:string ->
610610- ?create:(id * Create.t) list ->
611611- ?update:(id * Update.t) list ->
612612- ?destroy:id list ->
613613- ?on_success_destroy_email:id list ->
607607+ ?create:(Jmap.Id.t * Create.t) list ->
608608+ ?update:(Jmap.Id.t * Update.t) list ->
609609+ ?destroy:Jmap.Id.t list ->
610610+ ?on_success_destroy_email:Jmap.Id.t list ->
614611 unit -> (t, string) result
615612end
616613···632629 (** Get the account ID.
633630 @param response The set response object
634631 @return Account ID *)
635635- val account_id : t -> id
632632+ val account_id : t -> Jmap.Id.t
636633637634 (** Get the old state string.
638635 @param response The set response object
···647644 (** Get the created submissions with server-computed properties.
648645 @param response The set response object
649646 @return Created submissions with server-computed properties *)
650650- val created : t -> Create.Response.t id_map
647647+ val created : t -> (string, Create.Response.t) Hashtbl.t
651648652649 (** Get the updated submissions with server-computed properties.
653650 @param response The set response object
654651 @return Updated submissions with server-computed properties *)
655655- val updated : t -> Update.Response.t id_map option
652652+ val updated : t -> (string, Update.Response.t) Hashtbl.t option
656653657654 (** Get the destroyed submission IDs.
658655 @param response The set response object
659656 @return Destroyed submission IDs *)
660660- val destroyed : t -> id list option
657657+ val destroyed : t -> Jmap.Id.t list option
661658662659 (** Get the submission IDs that could not be created.
663660 @param response The set response object
664661 @return Submission IDs that could not be created *)
665665- val not_created : t -> Jmap.Error.Set_error.t id_map option
662662+ val not_created : t -> (string, Jmap.Error.Set_error.t) Hashtbl.t option
666663667664 (** Get the submission IDs that could not be updated.
668665 @param response The set response object
669666 @return Submission IDs that could not be updated *)
670670- val not_updated : t -> Jmap.Error.Set_error.t id_map option
667667+ val not_updated : t -> (string, Jmap.Error.Set_error.t) Hashtbl.t option
671668672669 (** Get the submission IDs that could not be destroyed.
673670 @param response The set response object
674671 @return Submission IDs that could not be destroyed *)
675675- val not_destroyed : t -> Jmap.Error.Set_error.t id_map option
672672+ val not_destroyed : t -> (string, Jmap.Error.Set_error.t) Hashtbl.t option
676673end
677674678675(** {1 Filter Helper Functions} *)
···688685 (** Create filter for specific identity IDs.
689686 @param ids List of identity IDs to match
690687 @return Filter that matches submissions using any of these identities *)
691691- val identity_ids : id list -> Jmap.Methods.Filter.t
688688+ val identity_ids : Jmap.Id.t list -> Jmap.Methods.Filter.t
692689693690 (** Create filter for specific email IDs.
694691 @param ids List of email IDs to match
695692 @return Filter that matches submissions for any of these emails *)
696696- val email_ids : id list -> Jmap.Methods.Filter.t
693693+ val email_ids : Jmap.Id.t list -> Jmap.Methods.Filter.t
697694698695 (** Create filter for specific thread IDs.
699696 @param ids List of thread IDs to match
700697 @return Filter that matches submissions in any of these threads *)
701701- val thread_ids : id list -> Jmap.Methods.Filter.t
698698+ val thread_ids : Jmap.Id.t list -> Jmap.Methods.Filter.t
702699703700 (** Create filter for undo status.
704701 @param status Undo status to match
705702 @return Filter that matches submissions with this undo status *)
706703 val undo_status : [`Pending | `Final | `Canceled] -> Jmap.Methods.Filter.t
707704708708- (** Create filter for submissions sent before a specific date.
709709- @param date UTC timestamp to compare against
710710- @return Filter that matches submissions sent before this date *)
711711- val before : utc_date -> Jmap.Methods.Filter.t
705705+ (** Create filter for submissions sent before a specific Date.t.
706706+ @param Date.t UTC timestamp to compare against
707707+ @return Filter that matches submissions sent before this Date.t *)
708708+ val before : Jmap.Date.t -> Jmap.Methods.Filter.t
712709713713- (** Create filter for submissions sent after a specific date.
714714- @param date UTC timestamp to compare against
715715- @return Filter that matches submissions sent after this date *)
716716- val after : utc_date -> Jmap.Methods.Filter.t
710710+ (** Create filter for submissions sent after a specific Date.t.
711711+ @param Date.t UTC timestamp to compare against
712712+ @return Filter that matches submissions sent after this Date.t *)
713713+ val after : Jmap.Date.t -> Jmap.Methods.Filter.t
717714718718- (** Create filter for submissions sent within a date range.
719719- @param after_date Start of date range
720720- @param before_date End of date range
715715+ (** Create filter for submissions sent within a Date.t range.
716716+ @param after_date Start of Date.t range
717717+ @param before_date End of Date.t range
721718 @return Filter that matches submissions sent within this range *)
722722- val date_range : after_date:utc_date -> before_date:utc_date -> Jmap.Methods.Filter.t
719719+ val date_range : after_date:Jmap.Date.t -> before_date:Jmap.Date.t -> Jmap.Methods.Filter.t
723720end
724721725722(** {1 Sort Helper Functions} *)
+99-67
jmap/jmap-email/thread.ml
···77 @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-3> RFC 8621, Section 3: Threads
88*)
991010-open Jmap.Types
1110open Jmap.Method_names
1211open Jmap.Methods
13121413module Thread = struct
1514 type t = {
1616- id : id option;
1717- email_ids : id list;
1515+ id : Jmap.Id.t option;
1616+ email_ids : Jmap.Id.t list;
1817 }
19182019 let id t = t.id
···25242625 (* JMAP_OBJECT implementation *)
2726 let create ?id () =
2828- { id; email_ids = [] }
2727+ let id_opt = match id with
2828+ | None -> None
2929+ | Some id_str ->
3030+ (match Jmap.Id.of_string id_str with
3131+ | Ok jmap_id -> Some jmap_id
3232+ | Error _ -> failwith ("Invalid thread id: " ^ id_str)) in
3333+ { id = id_opt; email_ids = [] }
29343035 let to_json_with_properties ~properties t =
3136 let all_fields = [
3232- ("id", (match t.id with Some id -> `String id | None -> `Null));
3333- ("emailIds", `List (List.map (fun id -> `String id) t.email_ids));
3737+ ("id", (match t.id with Some id -> `String (Jmap.Id.to_string id) | None -> `Null));
3838+ ("emailIds", `List (List.map (fun id -> `String (Jmap.Id.to_string id)) t.email_ids));
3439 ] in
3540 let filtered_fields = List.filter (fun (name, _) ->
3641 List.mem name properties
···4247 (* JSONABLE implementation *)
4348 let to_json t =
4449 `Assoc [
4545- ("id", (match t.id with Some id -> `String id | None -> `Null));
4646- ("emailIds", `List (List.map (fun id -> `String id) t.email_ids));
5050+ ("id", (match t.id with Some id -> `String (Jmap.Id.to_string id) | None -> `Null));
5151+ ("emailIds", `List (List.map (fun id -> `String (Jmap.Id.to_string id)) t.email_ids));
4752 ]
48534954 let of_json json =
···6570 in
6671 let id_str = get_string "id" "" in
6772 let email_ids = get_string_list "emailIds" in
7373+ let id = if id_str = "" then None else (match Jmap.Id.of_string id_str with Ok id -> Some id | Error e -> failwith e) in
7474+ let email_ids = List.map (fun s -> match Jmap.Id.of_string s with Ok id -> id | Error e -> failwith e) email_ids in
6875 Ok {
6969- id = (if id_str = "" then None else Some id_str);
7676+ id;
7077 email_ids;
7178 }
7279 | _ -> Error "Thread must be a JSON object"
···7986 let email_ids_str = match t.email_ids with
8087 | [] -> "[]"
8188 | ids when List.length ids <= 3 ->
8282- "[" ^ String.concat "; " ids ^ "]"
8989+ "[" ^ String.concat "; " (List.map Jmap.Id.to_string ids) ^ "]"
8390 | a :: b :: c :: _ ->
8484- "[" ^ String.concat "; " [a; b; c] ^ "; ...]"
9191+ "[" ^ String.concat "; " (List.map Jmap.Id.to_string [a; b; c]) ^ "; ...]"
8592 | ids ->
8686- "[" ^ String.concat "; " ids ^ "]"
9393+ "[" ^ String.concat "; " (List.map Jmap.Id.to_string ids) ^ "]"
8794 in
8888- let id_str = match t.id with Some id -> id | None -> "(no-id)" in
9595+ let id_str = match t.id with Some id -> Jmap.Id.to_string id | None -> "(no-id)" in
8996 Format.fprintf ppf "Thread{id=%s; emails=%d; email_ids=%s}"
9097 id_str email_count email_ids_str
9198···100107 ]
101108102109 let to_string = function
103103- | `Id -> "id"
110110+ | `Id -> "Jmap.Id.t"
104111 | `EmailIds -> "emailIds"
105112106113 let of_string = function
107107- | "id" -> Some `Id
114114+ | "Jmap.Id.t" -> Some `Id
108115 | "emailIds" -> Some `EmailIds
109116 | _ -> None
110117···118125119126module Query_args = struct
120127 type t = {
121121- account_id : id;
128128+ account_id : Jmap.Id.t;
122129 filter : Filter.t option;
123130 sort : Comparator.t list option;
124131 position : int option;
125125- anchor : id option;
132132+ anchor : Jmap.Id.t option;
126133 anchor_offset : int option;
127127- limit : uint option;
134134+ limit : Jmap.UInt.t option;
128135 calculate_total : bool option;
129136 }
130137···144151145152 let to_json t =
146153 let json_fields = [
147147- ("accountId", `String t.account_id);
154154+ ("accountId", `String (Jmap.Id.to_string t.account_id));
148155 ] in
149156 let json_fields = match t.filter with
150157 | None -> json_fields
···160167 in
161168 let json_fields = match t.anchor with
162169 | None -> json_fields
163163- | Some anchor -> ("anchor", `String anchor) :: json_fields
170170+ | Some anchor -> ("anchor", `String (Jmap.Id.to_string anchor)) :: json_fields
164171 in
165172 let json_fields = match t.anchor_offset with
166173 | None -> json_fields
···168175 in
169176 let json_fields = match t.limit with
170177 | None -> json_fields
171171- | Some limit -> ("limit", `Int limit) :: json_fields
178178+ | Some limit -> ("limit", `Int (Jmap.UInt.to_int limit)) :: json_fields
172179 in
173180 let json_fields = match t.calculate_total with
174181 | None -> json_fields
···181188 match json with
182189 | `Assoc fields ->
183190 let account_id = match List.assoc_opt "accountId" fields with
184184- | Some (`String id) -> id
191191+ | Some (`String id) -> (match Jmap.Id.of_string id with
192192+ | Ok id -> id
193193+ | Error err -> failwith ("Invalid accountId: " ^ err))
185194 | _ -> failwith "Missing or invalid accountId"
186195 in
187196 let filter = match List.assoc_opt "filter" fields with
···197206 | exn -> Error (Printexc.to_string exn)
198207199208 let pp fmt t =
200200- Format.fprintf fmt "Thread.Query_args{account=%s}" t.account_id
209209+ Format.fprintf fmt "Thread.Query_args{account=%s}" (Jmap.Id.to_string t.account_id)
201210202211 let pp_hum fmt t = pp fmt t
203212···208217209218module Query_response = struct
210219 type t = {
211211- account_id : id;
220220+ account_id : Jmap.Id.t;
212221 query_state : string;
213222 can_calculate_changes : bool;
214223 position : int;
215215- ids : id list;
216216- total : uint option;
217217- limit : uint option;
224224+ ids : Jmap.Id.t list;
225225+ total : Jmap.UInt.t option;
226226+ limit : Jmap.UInt.t option;
218227 }
219228220229 let account_id t = t.account_id
···232241233242 let to_json t =
234243 let fields = [
235235- ("accountId", `String t.account_id);
244244+ ("accountId", `String (Jmap.Id.to_string t.account_id));
236245 ("queryState", `String t.query_state);
237246 ("canCalculateChanges", `Bool t.can_calculate_changes);
238247 ("position", `Int t.position);
239239- ("ids", `List (List.map (fun id -> `String id) t.ids));
248248+ ("ids", `List (List.map (fun id -> `String (Jmap.Id.to_string id)) t.ids));
240249 ] in
241250 let fields = match t.total with
242242- | Some total -> ("total", `Int total) :: fields
251251+ | Some total -> ("total", `Int (Jmap.UInt.to_int total)) :: fields
243252 | None -> fields
244253 in
245254 let fields = match t.limit with
246246- | Some limit -> ("limit", `Int limit) :: fields
255255+ | Some limit -> ("limit", `Int (Jmap.UInt.to_int limit)) :: fields
247256 | None -> fields
248257 in
249258 `Assoc fields
···253262 match json with
254263 | `Assoc fields ->
255264 let account_id = match List.assoc_opt "accountId" fields with
256256- | Some (`String id) -> id
265265+ | Some (`String id_str) -> (match Jmap.Id.of_string id_str with
266266+ | Ok id -> id
267267+ | Error _ -> failwith ("Invalid accountId: " ^ id_str))
257268 | _ -> failwith "Missing or invalid accountId"
258269 in
259270 Ok { account_id; query_state = ""; can_calculate_changes = false;
···265276266277 let pp fmt t =
267278 Format.fprintf fmt "Thread.Query_response{account=%s;ids=%d}"
268268- t.account_id (List.length t.ids)
279279+ (Jmap.Id.to_string t.account_id) (List.length t.ids)
269280270281 let pp_hum fmt t = pp fmt t
271282···276287277288module Get_args = struct
278289 type t = {
279279- account_id : id;
280280- ids : id list option;
290290+ account_id : Jmap.Id.t;
291291+ ids : Jmap.Id.t list option;
281292 properties : string list option;
282293 }
283294···290301291302 let to_json t =
292303 let json_fields = [
293293- ("accountId", `String t.account_id);
304304+ ("accountId", `String (Jmap.Id.to_string t.account_id));
294305 ] in
295306 let json_fields = match t.ids with
296307 | None -> json_fields
297297- | Some ids -> ("ids", `List (List.map (fun id -> `String id) ids)) :: json_fields
308308+ | Some ids -> ("ids", `List (List.map (fun id -> `String (Jmap.Id.to_string id)) ids)) :: json_fields
298309 in
299310 let json_fields = match t.properties with
300311 | None -> json_fields
···307318 match json with
308319 | `Assoc fields ->
309320 let account_id = match List.assoc_opt "accountId" fields with
310310- | Some (`String id) -> id
321321+ | Some (`String id_str) -> (match Jmap.Id.of_string id_str with
322322+ | Ok id -> id
323323+ | Error _ -> failwith ("Invalid accountId: " ^ id_str))
311324 | _ -> failwith "Missing or invalid accountId"
312325 in
313326 Ok { account_id; ids = None; properties = None }
···317330 | exn -> Error (Printexc.to_string exn)
318331319332 let pp fmt t =
320320- Format.fprintf fmt "Thread.Get_args{account=%s}" t.account_id
333333+ Format.fprintf fmt "Thread.Get_args{account=%s}" (Jmap.Id.to_string t.account_id)
321334322335 let pp_hum fmt t = pp fmt t
323336···328341329342module Get_response = struct
330343 type t = {
331331- account_id : id;
344344+ account_id : Jmap.Id.t;
332345 state : string;
333346 list : Thread.t list;
334334- not_found : id list;
347347+ not_found : Jmap.Id.t list;
335348 }
336349337350 let account_id t = t.account_id
···344357345358 let to_json t =
346359 `Assoc [
347347- ("accountId", `String t.account_id);
360360+ ("accountId", `String (Jmap.Id.to_string t.account_id));
348361 ("state", `String t.state);
349362 ("list", `List (List.map Thread.to_json t.list));
350350- ("notFound", `List (List.map (fun id -> `String id) t.not_found));
363363+ ("notFound", `List (List.map (fun id -> `String (Jmap.Id.to_string id)) t.not_found));
351364 ]
352365353366 let of_json json =
···355368 match json with
356369 | `Assoc fields ->
357370 let account_id = match List.assoc_opt "accountId" fields with
358358- | Some (`String id) -> id
371371+ | Some (`String id_str) -> (match Jmap.Id.of_string id_str with
372372+ | Ok id -> id
373373+ | Error _ -> failwith ("Invalid accountId: " ^ id_str))
359374 | _ -> failwith "Missing or invalid accountId"
360375 in
361376 Ok { account_id; state = ""; list = []; not_found = [] }
···366381367382 let pp fmt t =
368383 Format.fprintf fmt "Thread.Get_response{account=%s;threads=%d}"
369369- t.account_id (List.length t.list)
384384+ (Jmap.Id.to_string t.account_id) (List.length t.list)
370385371386 let pp_hum fmt t = pp fmt t
372387···375390376391module Changes_args = struct
377392 type t = {
378378- account_id : id;
393393+ account_id : Jmap.Id.t;
379394 since_state : string;
380380- max_changes : uint option;
395395+ max_changes : Jmap.UInt.t option;
381396 }
382397383398 let account_id t = t.account_id
···388403 { account_id; since_state; max_changes }
389404390405 let to_json t =
391391- let fields = [("accountId", `String t.account_id); ("sinceState", `String t.since_state)] in
406406+ let fields = [("accountId", `String (Jmap.Id.to_string t.account_id)); ("sinceState", `String t.since_state)] in
392407 let fields = match t.max_changes with
393408 | None -> fields
394394- | Some n -> ("maxChanges", `Int n) :: fields
409409+ | Some n -> ("maxChanges", `Int (Jmap.UInt.to_int n)) :: fields
395410 in
396411 `Assoc fields
397412···400415 match json with
401416 | `Assoc fields ->
402417 let account_id = match List.assoc_opt "accountId" fields with
403403- | Some (`String id) -> id
418418+ | Some (`String id_str) -> (match Jmap.Id.of_string id_str with
419419+ | Ok id -> id
420420+ | Error _ -> failwith ("Invalid accountId: " ^ id_str))
404421 | _ -> failwith "Missing or invalid accountId"
405422 in
406423 Ok { account_id; since_state = ""; max_changes = None }
···411428412429 let pp fmt t =
413430 Format.fprintf fmt "Thread.Changes_args{account=%s;since=%s}"
414414- t.account_id t.since_state
431431+ (Jmap.Id.to_string t.account_id) t.since_state
415432416433 let pp_hum fmt t = pp fmt t
417434···422439423440module Changes_response = struct
424441 type t = {
425425- account_id : id;
442442+ account_id : Jmap.Id.t;
426443 old_state : string;
427444 new_state : string;
428445 has_more_changes : bool;
429429- created : id list;
430430- updated : id list;
431431- destroyed : id list;
446446+ created : Jmap.Id.t list;
447447+ updated : Jmap.Id.t list;
448448+ destroyed : Jmap.Id.t list;
432449 }
433450434451 let account_id t = t.account_id
···453470 @return JSON object with accountId, states, hasMoreChanges, and change arrays *)
454471 let to_json t =
455472 `Assoc [
456456- ("accountId", `String t.account_id);
473473+ ("accountId", `String (Jmap.Id.to_string t.account_id));
457474 ("oldState", `String t.old_state);
458475 ("newState", `String t.new_state);
459476 ("hasMoreChanges", `Bool t.has_more_changes);
460460- ("created", `List (List.map (fun id -> `String id) t.created));
461461- ("updated", `List (List.map (fun id -> `String id) t.updated));
462462- ("destroyed", `List (List.map (fun id -> `String id) t.destroyed));
477477+ ("created", `List (List.map (fun id -> `String (Jmap.Id.to_string id)) t.created));
478478+ ("updated", `List (List.map (fun id -> `String (Jmap.Id.to_string id)) t.updated));
479479+ ("destroyed", `List (List.map (fun id -> `String (Jmap.Id.to_string id)) t.destroyed));
463480 ]
464481465482 (** Parse Thread/changes response from JSON.
···472489 let of_json json =
473490 try
474491 let open Yojson.Safe.Util in
475475- let account_id = json |> member "accountId" |> to_string in
492492+ let account_id_str = json |> member "accountId" |> to_string in
493493+ let account_id = match Jmap.Id.of_string account_id_str with
494494+ | Ok id -> id
495495+ | Error _ -> failwith ("Invalid accountId: " ^ account_id_str) in
476496 let old_state = json |> member "oldState" |> to_string in
477497 let new_state = json |> member "newState" |> to_string in
478498 let has_more_changes = json |> member "hasMoreChanges" |> to_bool in
479479- let created = json |> member "created" |> to_list |> List.map to_string in
480480- let updated = json |> member "updated" |> to_list |> List.map to_string in
481481- let destroyed = json |> member "destroyed" |> to_list |> List.map to_string in
499499+ let created = json |> member "created" |> to_list |> List.map (fun item ->
500500+ let id_str = to_string item in
501501+ match Jmap.Id.of_string id_str with
502502+ | Ok id -> id
503503+ | Error _ -> failwith ("Invalid created id: " ^ id_str)) in
504504+ let updated = json |> member "updated" |> to_list |> List.map (fun item ->
505505+ let id_str = to_string item in
506506+ match Jmap.Id.of_string id_str with
507507+ | Ok id -> id
508508+ | Error _ -> failwith ("Invalid updated id: " ^ id_str)) in
509509+ let destroyed = json |> member "destroyed" |> to_list |> List.map (fun item ->
510510+ let id_str = to_string item in
511511+ match Jmap.Id.of_string id_str with
512512+ | Ok id -> id
513513+ | Error _ -> failwith ("Invalid destroyed id: " ^ id_str)) in
482514 Ok {
483515 account_id;
484516 old_state;
···493525 | exn -> Error ("Thread Changes_response JSON parse error: " ^ Printexc.to_string exn)
494526495527 let pp fmt t =
496496- Format.fprintf fmt "Thread.Changes_response{account=%s}" t.account_id
528528+ Format.fprintf fmt "Thread.Changes_response{account=%s}" (Jmap.Id.to_string t.account_id)
497529498530 let pp_hum fmt t = pp fmt t
499531···503535end
504536505537let filter_has_email email_id =
506506- Filter.property_equals "emailIds" (`String email_id)
538538+ Filter.property_equals "emailIds" (`String (Jmap.Id.to_string email_id))
507539508540let filter_from sender =
509541 Filter.text_contains "from" sender
···515547 Filter.text_contains "subject" subject
516548517549let filter_before date =
518518- Filter.property_lt "receivedAt" (`Float date)
550550+ Filter.property_lt "receivedAt" (`Float (Jmap.Date.to_timestamp date))
519551520552let filter_after date =
521521- Filter.property_gt "receivedAt" (`Float date)553553+ Filter.property_gt "receivedAt" (`Float (Jmap.Date.to_timestamp date))
+54-55
jmap/jmap-email/thread.mli
···1212 @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-3> RFC 8621, Section 3: Threads
1313*)
14141515-open Jmap.Types
1615open Jmap.Methods
17161817(** Thread object representation.
···3534 include Jmap_sigs.PRINTABLE with type t := t
36353736 (** JMAP object interface for property selection and object creation *)
3838- include Jmap_sigs.JMAP_OBJECT with type t := t and type id_type := id
3737+ include Jmap_sigs.JMAP_OBJECT with type t := t and type id_type := string
39384039 (** Get the server-assigned thread identifier.
4140 @return Unique thread ID (Some for all persisted threads, None only for unsaved objects) *)
4242- val id : t -> id option
4141+ val id : t -> Jmap.Id.t option
43424443 (** Get the list of email IDs belonging to this thread.
4544 @return List of email IDs in conversation order *)
4646- val email_ids : t -> id list
4545+ val email_ids : t -> Jmap.Id.t list
47464847 (** Create a new Thread object.
4949- @param id Server-assigned thread identifier
4848+ @param Jmap.Id.t Server-assigned thread identifier
5049 @param email_ids List of email IDs in the thread
5150 @return New thread object *)
5252- val v : id:id -> email_ids:id list -> t
5151+ val v : id:Jmap.Id.t -> email_ids:Jmap.Id.t list -> t
5352end
54535554···7675 include Jmap_sigs.JSONABLE with type t := t
77767877 (** JMAP method arguments interface *)
7979- include Jmap_sigs.METHOD_ARGS with type t := t and type account_id := id
7878+ include Jmap_sigs.METHOD_ARGS with type t := t and type account_id := string
80798180 (** Get the account ID for the operation.
8281 @return Account identifier where threads will be queried *)
8383- val account_id : t -> id
8282+ val account_id : t -> Jmap.Id.t
84838584 (** Validate query arguments according to JMAP method constraints.
8685 @param t Query arguments to validate
···105104106105 (** Get the anchor thread ID for relative positioning.
107106 @return Thread ID to anchor results from, or None *)
108108- val anchor : t -> id option
107107+ val anchor : t -> Jmap.Id.t option
109108110109 (** Get the offset from the anchor position.
111110 @return Number of positions to offset from anchor *)
···113112114113 (** Get the maximum number of results to return.
115114 @return Result limit, or None for server default *)
116116- val limit : t -> uint option
115115+ val limit : t -> Jmap.UInt.t option
117116118117 (** Check if total count should be calculated.
119118 @return true to calculate total result count *)
···130129 @param calculate_total Optional flag to calculate totals
131130 @return Thread/query arguments object *)
132131 val v :
133133- account_id:id ->
132132+ account_id:Jmap.Id.t ->
134133 ?filter:Filter.t ->
135134 ?sort:Comparator.t list ->
136135 ?position:int ->
137137- ?anchor:id ->
136136+ ?anchor:Jmap.Id.t ->
138137 ?anchor_offset:int ->
139139- ?limit:uint ->
138138+ ?limit:Jmap.UInt.t ->
140139 ?calculate_total:bool ->
141140 unit -> t
142141···161160 include Jmap_sigs.JSONABLE with type t := t
162161163162 (** JMAP method response interface *)
164164- include Jmap_sigs.METHOD_RESPONSE with type t := t and type account_id := id and type state := string
163163+ include Jmap_sigs.METHOD_RESPONSE with type t := t and type account_id := string and type state := string
165164166165 (** Get the account ID from the response.
167166 @return Account identifier where threads were queried *)
168168- val account_id : t -> id
167167+ val account_id : t -> Jmap.Id.t
169168170169 (** Get the query state string for change tracking.
171170 @return State string for use in queryChanges *)
···189188190189 (** Get the list of matching thread IDs.
191190 @return Ordered list of thread IDs matching the query *)
192192- val ids : t -> id list
191191+ val ids : t -> Jmap.Id.t list
193192194193 (** Get the total number of matching threads.
195194 @return Total result count if calculateTotal was requested *)
196196- val total : t -> uint option
195195+ val total : t -> Jmap.UInt.t option
197196198197 (** Get the limit that was applied to the results.
199198 @return Number of results returned, or None if no limit *)
200200- val limit : t -> uint option
199199+ val limit : t -> Jmap.UInt.t option
201200202201 (** Create Thread/query response.
203202 @param account_id Account where threads were queried
···209208 @param limit Optional result limit applied
210209 @return Thread/query response object *)
211210 val v :
212212- account_id:id ->
211211+ account_id:Jmap.Id.t ->
213212 query_state:string ->
214213 can_calculate_changes:bool ->
215214 position:int ->
216216- ids:id list ->
217217- ?total:uint ->
218218- ?limit:uint ->
215215+ ids:Jmap.Id.t list ->
216216+ ?total:Jmap.UInt.t ->
217217+ ?limit:Jmap.UInt.t ->
219218 unit -> t
220219end
221220···234233 include Jmap_sigs.JSONABLE with type t := t
235234236235 (** JMAP method arguments interface *)
237237- include Jmap_sigs.METHOD_ARGS with type t := t and type account_id := id
236236+ include Jmap_sigs.METHOD_ARGS with type t := t and type account_id := string
238237239238 (** Get the account ID for the operation.
240239 @return Account identifier where threads will be retrieved *)
241241- val account_id : t -> id
240240+ val account_id : t -> Jmap.Id.t
242241243242 (** Validate get arguments according to JMAP method constraints.
244243 @param t Get arguments to validate
···251250252251 (** Get the specific thread IDs to retrieve.
253252 @return List of thread IDs, or None to retrieve all threads *)
254254- val ids : t -> id list option
253253+ val ids : t -> Jmap.Id.t list option
255254256255 (** Get the properties to include in the response.
257256 @return List of property names, or None for all properties *)
···263262 @param properties Optional list of properties to include
264263 @return Thread/get arguments object *)
265264 val v :
266266- account_id:id ->
267267- ?ids:id list ->
265265+ account_id:Jmap.Id.t ->
266266+ ?ids:Jmap.Id.t list ->
268267 ?properties:string list ->
269268 unit -> t
270269···289288 include Jmap_sigs.JSONABLE with type t := t
290289291290 (** JMAP method response interface *)
292292- include Jmap_sigs.METHOD_RESPONSE with type t := t and type account_id := id and type state := string
291291+ include Jmap_sigs.METHOD_RESPONSE with type t := t and type account_id := string and type state := string
293292294293 (** Get the account ID from the response.
295294 @return Account identifier where threads were retrieved *)
296296- val account_id : t -> id
295295+ val account_id : t -> Jmap.Id.t
297296298297 (** Get the current state string for change tracking.
299298 @return State string for use in Thread/changes *)
···309308310309 (** Get the list of thread IDs that were not found.
311310 @return List of requested IDs that don't exist *)
312312- val not_found : t -> id list
311311+ val not_found : t -> Jmap.Id.t list
313312314313 (** Create Thread/get response.
315314 @param account_id Account where threads were retrieved
···318317 @param not_found IDs that were not found
319318 @return Thread/get response object *)
320319 val v :
321321- account_id:id ->
320320+ account_id:Jmap.Id.t ->
322321 state:string ->
323322 list:Thread.t list ->
324324- not_found:id list ->
323323+ not_found:Jmap.Id.t list ->
325324 unit -> t
326325end
327326···341340 include Jmap_sigs.JSONABLE with type t := t
342341343342 (** JMAP method arguments interface *)
344344- include Jmap_sigs.METHOD_ARGS with type t := t and type account_id := id
343343+ include Jmap_sigs.METHOD_ARGS with type t := t and type account_id := string
345344346345 (** Get the account ID for the operation.
347346 @return Account identifier where thread changes are tracked *)
348348- val account_id : t -> id
347347+ val account_id : t -> Jmap.Id.t
349348350349 (** Validate changes arguments according to JMAP method constraints.
351350 @param t Changes arguments to validate
···362361363362 (** Get the maximum number of changes to return.
364363 @return Change limit, or None for server default *)
365365- val max_changes : t -> uint option
364364+ val max_changes : t -> Jmap.UInt.t option
366365367366 (** Create Thread/changes arguments.
368367 @param account_id Account where thread changes are tracked
···370369 @param max_changes Optional limit on number of changes returned
371370 @return Thread/changes arguments object *)
372371 val v :
373373- account_id:id ->
372372+ account_id:Jmap.Id.t ->
374373 since_state:string ->
375375- ?max_changes:uint ->
374374+ ?max_changes:Jmap.UInt.t ->
376375 unit -> t
377376end
378377···392391 include Jmap_sigs.JSONABLE with type t := t
393392394393 (** JMAP method response interface *)
395395- include Jmap_sigs.METHOD_RESPONSE with type t := t and type account_id := id and type state := string
394394+ include Jmap_sigs.METHOD_RESPONSE with type t := t and type account_id := string and type state := string
396395397396 (** Get the account ID from the response.
398397 @return Account identifier where changes occurred *)
399399- val account_id : t -> id
398398+ val account_id : t -> Jmap.Id.t
400399401400 (** Get the old state string that was compared against.
402401 @return The since_state parameter from the request *)
···420419421420 (** Get the list of newly created thread IDs.
422421 @return Thread IDs that were created since the old state *)
423423- val created : t -> id list
422422+ val created : t -> Jmap.Id.t list
424423425424 (** Get the list of updated thread IDs.
426425 @return Thread IDs whose email lists changed since the old state *)
427427- val updated : t -> id list
426426+ val updated : t -> Jmap.Id.t list
428427429428 (** Get the list of destroyed thread IDs.
430429 @return Thread IDs that were deleted since the old state *)
431431- val destroyed : t -> id list
430430+ val destroyed : t -> Jmap.Id.t list
432431433432 (** Create Thread/changes response.
434433 @param account_id Account where changes occurred
···440439 @param destroyed List of destroyed thread IDs
441440 @return Thread/changes response object *)
442441 val v :
443443- account_id:id ->
442442+ account_id:Jmap.Id.t ->
444443 old_state:string ->
445444 new_state:string ->
446445 has_more_changes:bool ->
447447- created:id list ->
448448- updated:id list ->
449449- destroyed:id list ->
446446+ created:Jmap.Id.t list ->
447447+ updated:Jmap.Id.t list ->
448448+ destroyed:Jmap.Id.t list ->
450449 unit -> t
451450end
452451···461460(** Create a filter to find threads containing a specific email.
462461 @param email_id The email ID to search for in threads
463462 @return Filter condition for Email/query to find related emails *)
464464-val filter_has_email : id -> Filter.t
463463+val filter_has_email : Jmap.Id.t -> Filter.t
465464466465(** Create a filter to find threads containing emails from a sender.
467466 @param sender Email address or name to search for in From fields
···478477 @return Filter condition for finding threads containing the subject text *)
479478val filter_subject : string -> Filter.t
480479481481-(** Create a filter to find threads with emails received before a date.
482482- @param date Cutoff date for filtering
483483- @return Filter condition for threads with emails before the date *)
484484-val filter_before : date -> Filter.t
480480+(** Create a filter to find threads with emails received before a Date.t.
481481+ @param Date.t Cutoff Date.t for filtering
482482+ @return Filter condition for threads with emails before the Date.t *)
483483+val filter_before : Jmap.Date.t -> Filter.t
485484486486-(** Create a filter to find threads with emails received after a date.
487487- @param date Start date for filtering
488488- @return Filter condition for threads with emails after the date *)
489489-val filter_after : date -> Filter.t
485485+(** Create a filter to find threads with emails received after a Date.t.
486486+ @param Date.t Start Date.t for filtering
487487+ @return Filter condition for threads with emails after the Date.t *)
488488+val filter_after : Jmap.Date.t -> Filter.t
490489491490(** {1 Property System} *)
492491
-807
jmap/jmap-email/types.ml
···11-(** JMAP Mail Types Implementation.
22-33- This module implements the common types for JMAP Mail as specified in RFC 8621.
44- It provides concrete implementations of email addresses, body parts, keywords,
55- and email objects with their associated operations.
66-77- @see <https://www.rfc-editor.org/rfc/rfc8621.html> RFC 8621: JMAP for Mail
88-*)
99-1010-open Jmap.Types
1111-1212-module Email_address = struct
1313- type t = {
1414- name : string option;
1515- email : string;
1616- }
1717-1818- let name t = t.name
1919- let email t = t.email
2020-2121- let v ?name ~email () = { name; email }
2222-2323- let to_json t =
2424- let fields = [("email", `String t.email)] in
2525- let fields = match t.name with
2626- | Some name -> ("name", `String name) :: fields
2727- | None -> fields
2828- in
2929- `Assoc fields
3030-3131- let of_json json =
3232- try
3333- match json with
3434- | `Assoc fields ->
3535- let email = match List.assoc_opt "email" fields with
3636- | Some (`String email) -> email
3737- | _ -> failwith "Email_address.of_json: missing or invalid email field"
3838- in
3939- let name = match List.assoc_opt "name" fields with
4040- | Some (`String name) -> Some name
4141- | Some `Null | None -> None
4242- | _ -> failwith "Email_address.of_json: invalid name field"
4343- in
4444- Ok { name; email }
4545- | _ -> failwith "Email_address.of_json: expected JSON object"
4646- with
4747- | Failure msg -> Error msg
4848- | exn -> Error (Printexc.to_string exn)
4949-5050- let pp fmt t =
5151- match t.name with
5252- | Some name -> Format.fprintf fmt "%s <%s>" name t.email
5353- | None -> Format.fprintf fmt "%s" t.email
5454-5555- let pp_hum fmt t = pp fmt t
5656-end
5757-5858-module Email_address_group = struct
5959- type t = {
6060- name : string option;
6161- addresses : Email_address.t list;
6262- }
6363-6464- let name t = t.name
6565- let addresses t = t.addresses
6666-6767- let v ?name ~addresses () = { name; addresses }
6868-end
6969-7070-module Email_header = struct
7171- type t = {
7272- name : string;
7373- value : string;
7474- }
7575-7676- let name t = t.name
7777- let value t = t.value
7878-7979- let v ~name ~value () = { name; value }
8080-8181- let to_json t =
8282- `Assoc [
8383- ("name", `String t.name);
8484- ("value", `String t.value);
8585- ]
8686-8787- let of_json = function
8888- | `Assoc fields ->
8989- let name = match List.assoc_opt "name" fields with
9090- | Some (`String name) -> name
9191- | _ -> failwith "Email_header.of_json: missing or invalid name field"
9292- in
9393- let value = match List.assoc_opt "value" fields with
9494- | Some (`String value) -> value
9595- | _ -> failwith "Email_header.of_json: missing or invalid value field"
9696- in
9797- { name; value }
9898- | _ -> failwith "Email_header.of_json: expected JSON object"
9999-end
100100-101101-module Email_body_part = struct
102102- type t = {
103103- id : string option;
104104- blob_id : id option;
105105- size : uint;
106106- headers : Email_header.t list;
107107- name : string option;
108108- mime_type : string;
109109- charset : string option;
110110- disposition : string option;
111111- cid : string option;
112112- language : string list option;
113113- location : string option;
114114- sub_parts : t list option;
115115- other_headers : Yojson.Safe.t string_map;
116116- }
117117-118118- let id t = t.id
119119- let blob_id t = t.blob_id
120120- let size t = t.size
121121- let headers t = t.headers
122122- let name t = t.name
123123- let mime_type t = t.mime_type
124124- let charset t = t.charset
125125- let disposition t = t.disposition
126126- let cid t = t.cid
127127- let language t = t.language
128128- let location t = t.location
129129- let sub_parts t = t.sub_parts
130130- let other_headers t = t.other_headers
131131-132132- let v ?id ?blob_id ~size ~headers ?name ~mime_type ?charset
133133- ?disposition ?cid ?language ?location ?sub_parts
134134- ?(other_headers = Hashtbl.create 0) () =
135135- { id; blob_id; size; headers; name; mime_type; charset;
136136- disposition; cid; language; location; sub_parts; other_headers }
137137-138138- let rec to_json t =
139139- let fields = [
140140- ("size", `Int t.size);
141141- ("headers", `List (List.map Email_header.to_json t.headers));
142142- ("type", `String t.mime_type);
143143- ] in
144144- let fields = match t.id with
145145- | Some id -> ("partId", `String id) :: fields
146146- | None -> fields
147147- in
148148- let fields = match t.blob_id with
149149- | Some blob_id -> ("blobId", `String blob_id) :: fields
150150- | None -> fields
151151- in
152152- let fields = match t.name with
153153- | Some name -> ("name", `String name) :: fields
154154- | None -> fields
155155- in
156156- let fields = match t.charset with
157157- | Some charset -> ("charset", `String charset) :: fields
158158- | None -> fields
159159- in
160160- let fields = match t.disposition with
161161- | Some disposition -> ("disposition", `String disposition) :: fields
162162- | None -> fields
163163- in
164164- let fields = match t.cid with
165165- | Some cid -> ("cid", `String cid) :: fields
166166- | None -> fields
167167- in
168168- let fields = match t.language with
169169- | Some langs -> ("language", `List (List.map (fun l -> `String l) langs)) :: fields
170170- | None -> fields
171171- in
172172- let fields = match t.location with
173173- | Some location -> ("location", `String location) :: fields
174174- | None -> fields
175175- in
176176- let fields = match t.sub_parts with
177177- | Some sub_parts -> ("subParts", `List (List.map to_json sub_parts)) :: fields
178178- | None -> fields
179179- in
180180- let fields = Hashtbl.fold (fun k v acc -> (k, v) :: acc) t.other_headers fields in
181181- `Assoc fields
182182-183183- let rec of_json json =
184184- match json with
185185- | `Assoc fields ->
186186- let size = match List.assoc_opt "size" fields with
187187- | Some (`Int size) -> size
188188- | _ -> failwith "Email_body_part.of_json: missing or invalid size field"
189189- in
190190- let mime_type = match List.assoc_opt "type" fields with
191191- | Some (`String mime_type) -> mime_type
192192- | _ -> failwith "Email_body_part.of_json: missing or invalid type field"
193193- in
194194- let headers = match List.assoc_opt "headers" fields with
195195- | Some (`List header_list) -> List.map Email_header.of_json header_list
196196- | _ -> failwith "Email_body_part.of_json: missing or invalid headers field"
197197- in
198198- let id = match List.assoc_opt "partId" fields with
199199- | Some (`String id) -> Some id
200200- | Some `Null | None -> None
201201- | _ -> failwith "Email_body_part.of_json: invalid partId field"
202202- in
203203- let blob_id = match List.assoc_opt "blobId" fields with
204204- | Some (`String blob_id) -> Some blob_id
205205- | Some `Null | None -> None
206206- | _ -> failwith "Email_body_part.of_json: invalid blobId field"
207207- in
208208- let name = match List.assoc_opt "name" fields with
209209- | Some (`String name) -> Some name
210210- | Some `Null | None -> None
211211- | _ -> failwith "Email_body_part.of_json: invalid name field"
212212- in
213213- let charset = match List.assoc_opt "charset" fields with
214214- | Some (`String charset) -> Some charset
215215- | Some `Null | None -> None
216216- | _ -> failwith "Email_body_part.of_json: invalid charset field"
217217- in
218218- let disposition = match List.assoc_opt "disposition" fields with
219219- | Some (`String disposition) -> Some disposition
220220- | Some `Null | None -> None
221221- | _ -> failwith "Email_body_part.of_json: invalid disposition field"
222222- in
223223- let cid = match List.assoc_opt "cid" fields with
224224- | Some (`String cid) -> Some cid
225225- | Some `Null | None -> None
226226- | _ -> failwith "Email_body_part.of_json: invalid cid field"
227227- in
228228- let language = match List.assoc_opt "language" fields with
229229- | Some (`List lang_list) ->
230230- Some (List.map (function
231231- | `String l -> l
232232- | _ -> failwith "Email_body_part.of_json: invalid language list item"
233233- ) lang_list)
234234- | Some `Null | None -> None
235235- | _ -> failwith "Email_body_part.of_json: invalid language field"
236236- in
237237- let location = match List.assoc_opt "location" fields with
238238- | Some (`String location) -> Some location
239239- | Some `Null | None -> None
240240- | _ -> failwith "Email_body_part.of_json: invalid location field"
241241- in
242242- let sub_parts = match List.assoc_opt "subParts" fields with
243243- | Some (`List sub_part_list) -> Some (List.map of_json sub_part_list)
244244- | Some `Null | None -> None
245245- | _ -> failwith "Email_body_part.of_json: invalid subParts field"
246246- in
247247- let other_headers = Hashtbl.create 0 in
248248- let standard_fields = [
249249- "partId"; "blobId"; "size"; "headers"; "name"; "type";
250250- "charset"; "disposition"; "cid"; "language"; "location"; "subParts"
251251- ] in
252252- List.iter (fun (k, v) ->
253253- if not (List.mem k standard_fields) then
254254- Hashtbl.add other_headers k v
255255- ) fields;
256256- { id; blob_id; size; headers; name; mime_type; charset;
257257- disposition; cid; language; location; sub_parts; other_headers }
258258- | _ -> failwith "Email_body_part.of_json: expected JSON object"
259259-end
260260-261261-module Email_body_value = struct
262262- type t = {
263263- value : string;
264264- has_encoding_problem : bool;
265265- is_truncated : bool;
266266- }
267267-268268- let value t = t.value
269269- let has_encoding_problem t = t.has_encoding_problem
270270- let is_truncated t = t.is_truncated
271271-272272- let v ~value ?(encoding_problem = false) ?(truncated = false) () =
273273- { value; has_encoding_problem = encoding_problem; is_truncated = truncated }
274274-end
275275-276276-module Keywords = struct
277277- type keyword =
278278- | Draft
279279- | Seen
280280- | Flagged
281281- | Answered
282282- | Forwarded
283283- | Phishing
284284- | Junk
285285- | NotJunk
286286- | Notify
287287- | Muted
288288- | Followed
289289- | Memo
290290- | HasMemo
291291- | Autosent
292292- | Unsubscribed
293293- | CanUnsubscribe
294294- | Imported
295295- | IsTrusted
296296- | MaskedEmail
297297- | New
298298- | MailFlagBit0
299299- | MailFlagBit1
300300- | MailFlagBit2
301301- | Custom of string
302302-303303- type t = keyword list
304304-305305- let is_draft t = List.mem Draft t
306306- let is_seen t = List.mem Seen t
307307- let is_unread t = not (is_seen t) && not (is_draft t)
308308- let is_flagged t = List.mem Flagged t
309309- let is_answered t = List.mem Answered t
310310- let is_forwarded t = List.mem Forwarded t
311311- let is_phishing t = List.mem Phishing t
312312- let is_junk t = List.mem Junk t
313313- let is_not_junk t = List.mem NotJunk t
314314-315315- let has_keyword t kw =
316316- List.exists (function Custom k -> k = kw | _ -> false) t
317317-318318- let custom_keywords t =
319319- List.filter_map (function Custom k -> Some k | _ -> None) t
320320-321321- let add t kw =
322322- if List.mem kw t then t else kw :: t
323323-324324- let remove t kw =
325325- List.filter (fun k -> k <> kw) t
326326-327327- let empty () = []
328328-329329- let of_list kws = kws
330330-331331- let to_string = function
332332- | Draft -> "$draft"
333333- | Seen -> "$seen"
334334- | Flagged -> "$flagged"
335335- | Answered -> "$answered"
336336- | Forwarded -> "$forwarded"
337337- | Phishing -> "$phishing"
338338- | Junk -> "$junk"
339339- | NotJunk -> "$notjunk"
340340- | Notify -> "$notify"
341341- | Muted -> "$muted"
342342- | Followed -> "$followed"
343343- | Memo -> "$memo"
344344- | HasMemo -> "$hasmemo"
345345- | Autosent -> "$autosent"
346346- | Unsubscribed -> "$unsubscribed"
347347- | CanUnsubscribe -> "$canunsubscribe"
348348- | Imported -> "$imported"
349349- | IsTrusted -> "$istrusted"
350350- | MaskedEmail -> "$maskedemail"
351351- | New -> "$new"
352352- | MailFlagBit0 -> "$MailFlagBit0"
353353- | MailFlagBit1 -> "$MailFlagBit1"
354354- | MailFlagBit2 -> "$MailFlagBit2"
355355- | Custom s -> s
356356-357357- let of_string = function
358358- | "$draft" -> Draft
359359- | "$seen" -> Seen
360360- | "$flagged" -> Flagged
361361- | "$answered" -> Answered
362362- | "$forwarded" -> Forwarded
363363- | "$phishing" -> Phishing
364364- | "$junk" -> Junk
365365- | "$notjunk" -> NotJunk
366366- | "$notify" -> Notify
367367- | "$muted" -> Muted
368368- | "$followed" -> Followed
369369- | "$memo" -> Memo
370370- | "$hasmemo" -> HasMemo
371371- | "$autosent" -> Autosent
372372- | "$unsubscribed" -> Unsubscribed
373373- | "$canunsubscribe" -> CanUnsubscribe
374374- | "$imported" -> Imported
375375- | "$istrusted" -> IsTrusted
376376- | "$maskedemail" -> MaskedEmail
377377- | "$new" -> New
378378- | "$MailFlagBit0" -> MailFlagBit0
379379- | "$MailFlagBit1" -> MailFlagBit1
380380- | "$MailFlagBit2" -> MailFlagBit2
381381- | s -> Custom s
382382-383383- let to_map t =
384384- let map = Hashtbl.create (List.length t) in
385385- List.iter (fun kw -> Hashtbl.add map (to_string kw) true) t;
386386- map
387387-388388- let to_json t =
389389- let map_json = to_map t in
390390- let assoc_list = Hashtbl.fold (fun k v acc -> (k, `Bool v) :: acc) map_json [] in
391391- `Assoc assoc_list
392392-393393- let of_json = function
394394- | `Assoc fields ->
395395- List.fold_left (fun acc (key, value) ->
396396- match value with
397397- | `Bool true -> (of_string key) :: acc
398398- | `Bool false -> acc (* Keywords with false value are not present *)
399399- | _ -> failwith ("Keywords.of_json: invalid keyword value for " ^ key)
400400- ) [] fields
401401- | _ -> failwith "Keywords.of_json: expected JSON object"
402402-end
403403-404404-405405-module Email = struct
406406- type t = {
407407- id : id option;
408408- blob_id : id option;
409409- thread_id : id option;
410410- mailbox_ids : bool id_map option;
411411- keywords : Keywords.t option;
412412- size : uint option;
413413- received_at : date option;
414414- subject : string option;
415415- preview : string option;
416416- from : Email_address.t list option;
417417- to_ : Email_address.t list option;
418418- cc : Email_address.t list option;
419419- message_id : string list option;
420420- has_attachment : bool option;
421421- text_body : Email_body_part.t list option;
422422- html_body : Email_body_part.t list option;
423423- attachments : Email_body_part.t list option;
424424- }
425425-426426- let id t = t.id
427427- let blob_id t = t.blob_id
428428- let thread_id t = t.thread_id
429429- let mailbox_ids t = t.mailbox_ids
430430- let keywords t = t.keywords
431431- let size t = t.size
432432- let received_at t = t.received_at
433433- let subject t = t.subject
434434- let preview t = t.preview
435435- let from t = t.from
436436- let to_ t = t.to_
437437- let cc t = t.cc
438438- let message_id t = t.message_id
439439- let has_attachment t = t.has_attachment
440440- let text_body t = t.text_body
441441- let html_body t = t.html_body
442442- let attachments t = t.attachments
443443-444444- let create ?id ?blob_id ?thread_id ?mailbox_ids ?keywords ?size
445445- ?received_at ?subject ?preview ?from ?to_ ?cc ?message_id
446446- ?has_attachment ?text_body ?html_body ?attachments () =
447447- { id; blob_id; thread_id; mailbox_ids; keywords; size;
448448- received_at; subject; preview; from; to_; cc; message_id;
449449- has_attachment; text_body; html_body; attachments }
450450-451451- let make_patch ?add_keywords ?remove_keywords ?add_mailboxes ?remove_mailboxes () =
452452- let patches = [] in
453453- let patches = match add_keywords with
454454- | Some kws ->
455455- List.fold_left (fun acc kw ->
456456- ("/keywords/" ^ Keywords.to_string kw, `Bool true) :: acc
457457- ) patches kws
458458- | None -> patches
459459- in
460460- let patches = match remove_keywords with
461461- | Some kws ->
462462- List.fold_left (fun acc kw ->
463463- ("/keywords/" ^ Keywords.to_string kw, `Null) :: acc
464464- ) patches kws
465465- | None -> patches
466466- in
467467- let patches = match add_mailboxes with
468468- | Some ids ->
469469- List.fold_left (fun acc id ->
470470- ("/mailboxIds/" ^ id, `Bool true) :: acc
471471- ) patches ids
472472- | None -> patches
473473- in
474474- let patches = match remove_mailboxes with
475475- | Some ids ->
476476- List.fold_left (fun acc id ->
477477- ("/mailboxIds/" ^ id, `Null) :: acc
478478- ) patches ids
479479- | None -> patches
480480- in
481481- patches
482482-483483- let get_id t =
484484- match t.id with
485485- | Some id -> Ok id
486486- | None -> Error "Email has no ID"
487487-488488- let take_id t =
489489- match t.id with
490490- | Some id -> id
491491- | None -> failwith "Email has no ID"
492492-493493- (* Helper function to convert mailbox ID map to JSON *)
494494- let mailbox_ids_to_json mailbox_ids =
495495- let assoc_list = Hashtbl.fold (fun k v acc -> (k, `Bool v) :: acc) mailbox_ids [] in
496496- `Assoc assoc_list
497497-498498- (* Helper function to parse mailbox ID map from JSON *)
499499- let mailbox_ids_of_json = function
500500- | `Assoc fields ->
501501- let map = Hashtbl.create (List.length fields) in
502502- List.iter (fun (k, v) ->
503503- match v with
504504- | `Bool b -> Hashtbl.add map k b
505505- | _ -> failwith ("Email.mailbox_ids_of_json: invalid mailbox ID value for " ^ k)
506506- ) fields;
507507- map
508508- | _ -> failwith "Email.mailbox_ids_of_json: expected JSON object"
509509-510510- let to_json t =
511511- let fields = [] in
512512- let fields = match t.id with
513513- | Some id -> ("id", `String id) :: fields
514514- | None -> fields
515515- in
516516- let fields = match t.blob_id with
517517- | Some blob_id -> ("blobId", `String blob_id) :: fields
518518- | None -> fields
519519- in
520520- let fields = match t.thread_id with
521521- | Some thread_id -> ("threadId", `String thread_id) :: fields
522522- | None -> fields
523523- in
524524- let fields = match t.mailbox_ids with
525525- | Some mailbox_ids -> ("mailboxIds", mailbox_ids_to_json mailbox_ids) :: fields
526526- | None -> fields
527527- in
528528- let fields = match t.keywords with
529529- | Some keywords -> ("keywords", Keywords.to_json keywords) :: fields
530530- | None -> fields
531531- in
532532- let fields = match t.size with
533533- | Some size -> ("size", `Int size) :: fields
534534- | None -> fields
535535- in
536536- let fields = match t.received_at with
537537- | Some date -> ("receivedAt", `Float date) :: fields
538538- | None -> fields
539539- in
540540- let fields = match t.subject with
541541- | Some subject -> ("subject", `String subject) :: fields
542542- | None -> fields
543543- in
544544- let fields = match t.preview with
545545- | Some preview -> ("preview", `String preview) :: fields
546546- | None -> fields
547547- in
548548- let fields = match t.from with
549549- | Some from -> ("from", `List (List.map Email_address.to_json from)) :: fields
550550- | None -> fields
551551- in
552552- let fields = match t.to_ with
553553- | Some to_ -> ("to", `List (List.map Email_address.to_json to_)) :: fields
554554- | None -> fields
555555- in
556556- let fields = match t.cc with
557557- | Some cc -> ("cc", `List (List.map Email_address.to_json cc)) :: fields
558558- | None -> fields
559559- in
560560- let fields = match t.message_id with
561561- | Some message_ids -> ("messageId", `List (List.map (fun s -> `String s) message_ids)) :: fields
562562- | None -> fields
563563- in
564564- let fields = match t.has_attachment with
565565- | Some has_attachment -> ("hasAttachment", `Bool has_attachment) :: fields
566566- | None -> fields
567567- in
568568- let fields = match t.text_body with
569569- | Some text_body -> ("textBody", `List (List.map Email_body_part.to_json text_body)) :: fields
570570- | None -> fields
571571- in
572572- let fields = match t.html_body with
573573- | Some html_body -> ("htmlBody", `List (List.map Email_body_part.to_json html_body)) :: fields
574574- | None -> fields
575575- in
576576- let fields = match t.attachments with
577577- | Some attachments -> ("attachments", `List (List.map Email_body_part.to_json attachments)) :: fields
578578- | None -> fields
579579- in
580580- `Assoc fields
581581-582582- let of_json json =
583583- match json with
584584- | `Assoc fields ->
585585- let id = match List.assoc_opt "id" fields with
586586- | Some (`String id) -> Some id
587587- | Some `Null | None -> None
588588- | _ -> failwith "Email.of_json: invalid id field"
589589- in
590590- let blob_id = match List.assoc_opt "blobId" fields with
591591- | Some (`String blob_id) -> Some blob_id
592592- | Some `Null | None -> None
593593- | _ -> failwith "Email.of_json: invalid blobId field"
594594- in
595595- let thread_id = match List.assoc_opt "threadId" fields with
596596- | Some (`String thread_id) -> Some thread_id
597597- | Some `Null | None -> None
598598- | _ -> failwith "Email.of_json: invalid threadId field"
599599- in
600600- let mailbox_ids = match List.assoc_opt "mailboxIds" fields with
601601- | Some json_obj -> Some (mailbox_ids_of_json json_obj)
602602- | None -> None
603603- in
604604- let keywords = match List.assoc_opt "keywords" fields with
605605- | Some json_obj -> Some (Keywords.of_json json_obj)
606606- | None -> None
607607- in
608608- let size = match List.assoc_opt "size" fields with
609609- | Some (`Int size) -> Some size
610610- | Some `Null | None -> None
611611- | _ -> failwith "Email.of_json: invalid size field"
612612- in
613613- let received_at = match List.assoc_opt "receivedAt" fields with
614614- | Some (`Float date) -> Some date
615615- | Some (`String date_str) ->
616616- (* Parse ISO 8601 date string to Unix timestamp *)
617617- (try
618618- (* Simple ISO 8601 parser for "YYYY-MM-DDTHH:MM:SSZ" format *)
619619- let parse_iso8601 s =
620620- if String.length s >= 19 && s.[10] = 'T' then
621621- let year = int_of_string (String.sub s 0 4) in
622622- let month = int_of_string (String.sub s 5 2) in
623623- let day = int_of_string (String.sub s 8 2) in
624624- let hour = int_of_string (String.sub s 11 2) in
625625- let minute = int_of_string (String.sub s 14 2) in
626626- let second = int_of_string (String.sub s 17 2) in
627627- (* Convert to Unix timestamp - approximate conversion *)
628628- let days_since_epoch =
629629- (year - 1970) * 365 + (year - 1969) / 4 - (year - 1901) / 100 + (year - 1601) / 400 +
630630- [|0; 31; 59; 90; 120; 151; 181; 212; 243; 273; 304; 334|].(month - 1) + day - 1 in
631631- let seconds_in_day = hour * 3600 + minute * 60 + second in
632632- float_of_int (days_since_epoch * 86400 + seconds_in_day)
633633- else
634634- failwith "Invalid ISO 8601 format"
635635- in
636636- Some (parse_iso8601 date_str)
637637- with _ -> failwith "Email.of_json: invalid receivedAt date format")
638638- | Some `Null | None -> None
639639- | _ -> failwith "Email.of_json: invalid receivedAt field"
640640- in
641641- let subject = match List.assoc_opt "subject" fields with
642642- | Some (`String subject) -> Some subject
643643- | Some `Null | None -> None
644644- | _ -> failwith "Email.of_json: invalid subject field"
645645- in
646646- let preview = match List.assoc_opt "preview" fields with
647647- | Some (`String preview) -> Some preview
648648- | Some `Null | None -> None
649649- | _ -> failwith "Email.of_json: invalid preview field"
650650- in
651651- let from = match List.assoc_opt "from" fields with
652652- | Some (`List from_list) ->
653653- let rec process_addresses acc = function
654654- | [] -> Some (List.rev acc)
655655- | addr :: rest ->
656656- (match Email_address.of_json addr with
657657- | Ok a -> process_addresses (a :: acc) rest
658658- | Error _ -> failwith "Email.of_json: invalid address in from field")
659659- in
660660- process_addresses [] from_list
661661- | Some `Null | None -> None
662662- | _ -> failwith "Email.of_json: invalid from field"
663663- in
664664- let to_ = match List.assoc_opt "to" fields with
665665- | Some (`List to_list) ->
666666- let rec process_addresses acc = function
667667- | [] -> Some (List.rev acc)
668668- | addr :: rest ->
669669- (match Email_address.of_json addr with
670670- | Ok a -> process_addresses (a :: acc) rest
671671- | Error _ -> failwith "Email.of_json: invalid address in to field")
672672- in
673673- process_addresses [] to_list
674674- | Some `Null | None -> None
675675- | _ -> failwith "Email.of_json: invalid to field"
676676- in
677677- let cc = match List.assoc_opt "cc" fields with
678678- | Some (`List cc_list) ->
679679- let rec process_addresses acc = function
680680- | [] -> Some (List.rev acc)
681681- | addr :: rest ->
682682- (match Email_address.of_json addr with
683683- | Ok a -> process_addresses (a :: acc) rest
684684- | Error _ -> failwith "Email.of_json: invalid address in cc field")
685685- in
686686- process_addresses [] cc_list
687687- | Some `Null | None -> None
688688- | _ -> failwith "Email.of_json: invalid cc field"
689689- in
690690- let message_id = match List.assoc_opt "messageId" fields with
691691- | Some (`List msg_id_list) ->
692692- Some (List.map (function
693693- | `String s -> s
694694- | _ -> failwith "Email.of_json: invalid messageId list item"
695695- ) msg_id_list)
696696- | Some `Null | None -> None
697697- | _ -> failwith "Email.of_json: invalid messageId field"
698698- in
699699- let has_attachment = match List.assoc_opt "hasAttachment" fields with
700700- | Some (`Bool has_attachment) -> Some has_attachment
701701- | Some `Null | None -> None
702702- | _ -> failwith "Email.of_json: invalid hasAttachment field"
703703- in
704704- let text_body = match List.assoc_opt "textBody" fields with
705705- | Some (`List body_list) -> Some (List.map Email_body_part.of_json body_list)
706706- | Some `Null | None -> None
707707- | _ -> failwith "Email.of_json: invalid textBody field"
708708- in
709709- let html_body = match List.assoc_opt "htmlBody" fields with
710710- | Some (`List body_list) -> Some (List.map Email_body_part.of_json body_list)
711711- | Some `Null | None -> None
712712- | _ -> failwith "Email.of_json: invalid htmlBody field"
713713- in
714714- let attachments = match List.assoc_opt "attachments" fields with
715715- | Some (`List att_list) -> Some (List.map Email_body_part.of_json att_list)
716716- | Some `Null | None -> None
717717- | _ -> failwith "Email.of_json: invalid attachments field"
718718- in
719719- { id; blob_id; thread_id; mailbox_ids; keywords; size;
720720- received_at; subject; preview; from; to_; cc; message_id;
721721- has_attachment; text_body; html_body; attachments }
722722- | _ -> failwith "Email.of_json: expected JSON object"
723723-end
724724-725725-module Import = struct
726726- type args = {
727727- account_id : id;
728728- blob_ids : id list;
729729- mailbox_ids : id id_map;
730730- keywords : Keywords.t option;
731731- received_at : date option;
732732- }
733733-734734- let create_args ~account_id ~blob_ids ~mailbox_ids ?keywords ?received_at () =
735735- { account_id; blob_ids; mailbox_ids; keywords; received_at }
736736-737737- type email_import_result = {
738738- blob_id : id;
739739- email : Email.t;
740740- }
741741-742742- let create_result ~blob_id ~email () = { blob_id; email }
743743-744744- type response = {
745745- account_id : id;
746746- created : email_import_result id_map;
747747- not_created : Jmap.Error.Set_error.t id_map;
748748- }
749749-750750- let create_response ~account_id ~created ~not_created () =
751751- { account_id; created; not_created }
752752-end
753753-754754-module Parse = struct
755755- type args = {
756756- account_id : id;
757757- blob_ids : id list;
758758- properties : string list option;
759759- }
760760-761761- let create_args ~account_id ~blob_ids ?properties () =
762762- { account_id; blob_ids; properties }
763763-764764- type email_parse_result = {
765765- blob_id : id;
766766- parsed : Email.t;
767767- }
768768-769769- let create_result ~blob_id ~parsed () = { blob_id; parsed }
770770-771771- type response = {
772772- account_id : id;
773773- parsed : email_parse_result id_map;
774774- not_parsed : string id_map;
775775- }
776776-777777- let create_response ~account_id ~parsed ~not_parsed () =
778778- { account_id; parsed; not_parsed }
779779-end
780780-781781-782782-module Copy = struct
783783- type args = {
784784- from_account_id : id;
785785- account_id : id;
786786- create : (id * id id_map) id_map;
787787- on_success_destroy_original : bool option;
788788- destroy_from_if_in_state : string option;
789789- }
790790-791791- let create_args ~from_account_id ~account_id ~create
792792- ?on_success_destroy_original ?destroy_from_if_in_state () =
793793- { from_account_id; account_id; create;
794794- on_success_destroy_original; destroy_from_if_in_state }
795795-796796- type response = {
797797- from_account_id : id;
798798- account_id : id;
799799- created : Email.t id_map option;
800800- not_created : Jmap.Error.Set_error.t id_map option;
801801- }
802802-803803- let create_response ~from_account_id ~account_id ?created ?not_created () =
804804- { from_account_id; account_id; created; not_created }
805805-end
806806-807807-
-764
jmap/jmap-email/types.mli
···11-(** Common types for JMAP Mail (RFC 8621).
22-33- This module defines the core data types and structures used throughout the JMAP Mail
44- specification. These types represent email objects, addresses, body parts, keywords,
55- and methods for importing, parsing, and copying email messages.
66-77- All types follow the JMAP specification for immutable, server-synchronized objects
88- with appropriate property access patterns.
99-1010- @see <https://www.rfc-editor.org/rfc/rfc8621.html> RFC 8621: The JSON Meta Application Protocol (JMAP) for Mail
1111-*)
1212-1313-open Jmap.Types
1414-1515-(** Email address representation.
1616-1717- Represents an email address as specified in RFC 8621 Section 4.1.2.3.
1818- An email address consists of an email field (required) and an optional
1919- name field for display purposes. This follows the standard format used
2020- in email headers like "From", "To", "Cc", etc.
2121-2222- The email field MUST be a valid RFC 5322 addr-spec and the name field,
2323- if present, provides a human-readable display name.
2424-2525- @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-4.1.2.3> RFC 8621, Section 4.1.2.3
2626-*)
2727-module Email_address : sig
2828- type t
2929-3030- (** JSON serialization interface *)
3131- include Jmap_sigs.JSONABLE with type t := t
3232-3333- (** Pretty-printing interface *)
3434- include Jmap_sigs.PRINTABLE with type t := t
3535-3636- (** Get the display name for the address.
3737- @return The human-readable display name, or None if not set *)
3838- val name : t -> string option
3939-4040- (** Get the actual email address.
4141- @return The RFC 5322 addr-spec email address *)
4242- val email : t -> string
4343-4444- (** Create a new email address object.
4545- @param name Optional human-readable display name
4646- @param email Required RFC 5322 addr-spec email address
4747- @return New email address object *)
4848- val v :
4949- ?name:string ->
5050- email:string ->
5151- unit -> t
5252-5353-end
5454-5555-(** Email address group representation.
5656-5757- Represents a named group of email addresses as specified in RFC 8621 Section 4.1.2.4.
5858- This corresponds to RFC 5322 group syntax in email headers, allowing multiple
5959- addresses to be grouped under a common name.
6060-6161- Groups are used in headers like "To", "Cc", and "Bcc" when addresses need to be
6262- organized or when mailing list functionality is involved.
6363-6464- @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-4.1.2.4> RFC 8621, Section 4.1.2.4
6565-*)
6666-module Email_address_group : sig
6767- type t
6868-6969- (** Get the name of the address group.
7070- @return The group name, or None if not set *)
7171- val name : t -> string option
7272-7373- (** Get the list of email addresses in the group.
7474- @return List of email addresses belonging to this group *)
7575- val addresses : t -> Email_address.t list
7676-7777- (** Create a new email address group.
7878- @param name Optional group name
7979- @param addresses List of email addresses in the group
8080- @return New address group object *)
8181- val v :
8282- ?name:string ->
8383- addresses:Email_address.t list ->
8484- unit -> t
8585-end
8686-8787-(** Email header field representation.
8888-8989- Represents a single email header field as specified in RFC 8621 Section 4.1.3.
9090- Each header consists of a field name and its raw, unprocessed value as it
9191- appears in the original email message.
9292-9393- Header fields follow RFC 5322 syntax and are used to provide access to
9494- both standard headers (Subject, From, To, etc.) and custom headers that
9595- may not be parsed into specific Email object properties.
9696-9797- @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-4.1.3> RFC 8621, Section 4.1.3
9898-*)
9999-module Email_header : sig
100100- type t
101101-102102- (** Get the header field name.
103103- @return The header field name (e.g., "Subject", "X-Custom-Header") *)
104104- val name : t -> string
105105-106106- (** Get the raw header field value.
107107- @return The unprocessed header value as it appears in the message *)
108108- val value : t -> string
109109-110110- (** Create a new header field.
111111- @param name The header field name
112112- @param value The raw header field value
113113- @return New header field object *)
114114- val v :
115115- name:string ->
116116- value:string ->
117117- unit -> t
118118-119119- (** Convert header field to JSON representation.
120120- @param t The header field to convert
121121- @return JSON object with 'name' and 'value' fields *)
122122- val to_json : t -> Yojson.Safe.t
123123-124124- (** Parse header field from JSON representation.
125125- @param json JSON object with 'name' and 'value' fields
126126- @return Parsed header field object
127127- @raise Failure if JSON structure is invalid *)
128128- val of_json : Yojson.Safe.t -> t
129129-end
130130-131131-(** Email body part representation.
132132-133133- Represents a single part within an email's MIME structure as specified in
134134- RFC 8621 Section 4.1.4. Each body part can be either a leaf part containing
135135- actual content or a multipart container holding sub-parts.
136136-137137- Body parts include information about MIME type, encoding, disposition,
138138- size, and other RFC 2045-2047 MIME attributes. For multipart types,
139139- the sub_parts field contains nested body parts.
140140-141141- @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-4.1.4> RFC 8621, Section 4.1.4
142142-*)
143143-module Email_body_part : sig
144144- type t
145145-146146- (** Get the part ID for referencing this specific part.
147147- @return Part identifier, or None for multipart container types *)
148148- val id : t -> string option
149149-150150- (** Get the blob ID for downloading the part content.
151151- @return Blob identifier for content access, or None for multipart types *)
152152- val blob_id : t -> id option
153153-154154- (** Get the size of the part in bytes.
155155- @return Size in bytes of the decoded content *)
156156- val size : t -> uint
157157-158158- (** Get the list of MIME headers for this part.
159159- @return List of header fields specific to this body part *)
160160- val headers : t -> Email_header.t list
161161-162162- (** Get the filename parameter from Content-Disposition or Content-Type.
163163- @return Filename if present, None otherwise *)
164164- val name : t -> string option
165165-166166- (** Get the MIME content type.
167167- @return MIME type (e.g., "text/plain", "image/jpeg") *)
168168- val mime_type : t -> string
169169-170170- (** Get the character set parameter.
171171- @return Character encoding (e.g., "utf-8", "iso-8859-1"), None if not specified *)
172172- val charset : t -> string option
173173-174174- (** Get the Content-Disposition header value.
175175- @return Disposition type (e.g., "attachment", "inline"), None if not specified *)
176176- val disposition : t -> string option
177177-178178- (** Get the Content-ID header value for referencing within HTML content.
179179- @return Content identifier for inline references, None if not specified *)
180180- val cid : t -> string option
181181-182182- (** Get the Content-Language header values.
183183- @return List of language codes (e.g., ["en"; "fr"]), None if not specified *)
184184- val language : t -> string list option
185185-186186- (** Get the Content-Location header value.
187187- @return URI reference for content location, None if not specified *)
188188- val location : t -> string option
189189-190190- (** Get nested parts for multipart content types.
191191- @return List of sub-parts for multipart types, None for leaf parts *)
192192- val sub_parts : t -> t list option
193193-194194- (** Get additional headers requested via header properties.
195195- @return Map of header names to their JSON values for extended header access *)
196196- val other_headers : t -> Yojson.Safe.t string_map
197197-198198- (** Create a new body part object.*)
199199- val v :
200200- ?id:string ->
201201- ?blob_id:id ->
202202- size:uint ->
203203- headers:Email_header.t list ->
204204- ?name:string ->
205205- mime_type:string ->
206206- ?charset:string ->
207207- ?disposition:string ->
208208- ?cid:string ->
209209- ?language:string list ->
210210- ?location:string ->
211211- ?sub_parts:t list ->
212212- ?other_headers:Yojson.Safe.t string_map ->
213213- unit -> t
214214-215215- (** Convert body part to JSON representation.
216216- @param t The body part to convert
217217- @return JSON object with all body part fields *)
218218- val to_json : t -> Yojson.Safe.t
219219-220220- (** Parse body part from JSON representation.
221221- @param json JSON object representing a body part
222222- @return Parsed body part object
223223- @raise Failure if JSON structure is invalid *)
224224- val of_json : Yojson.Safe.t -> t
225225-end
226226-227227-(** Decoded email body content.
228228-229229- Represents the decoded text content of a body part as specified in RFC 8621
230230- Section 4.1.4. This provides access to the actual text content after MIME
231231- decoding, along with metadata about potential encoding issues or truncation.
232232-233233- Used primarily for text/plain and text/html parts where the decoded content
234234- is needed for display or processing.
235235-236236- @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-4.1.4> RFC 8621, Section 4.1.4
237237-*)
238238-module Email_body_value : sig
239239- type t
240240-241241- (** Get the decoded text content.
242242- @return The decoded text content of the body part *)
243243- val value : t -> string
244244-245245- (** Check if there was an encoding problem during decoding.
246246- @return true if encoding issues were encountered during decoding *)
247247- val has_encoding_problem : t -> bool
248248-249249- (** Check if the content was truncated by the server.
250250- @return true if the content was truncated to fit size limits *)
251251- val is_truncated : t -> bool
252252-253253- (** Create a new body value object.
254254- @param value The decoded text content
255255- @param encoding_problem Whether encoding problems were encountered (default: false)
256256- @param truncated Whether the content was truncated (default: false)
257257- @return New body value object *)
258258- val v :
259259- value:string ->
260260- ?encoding_problem:bool ->
261261- ?truncated:bool ->
262262- unit -> t
263263-end
264264-265265-(** Email keywords and flags system.
266266-267267- Represents the JMAP email keywords system as specified in RFC 8621 Section 4.1.1.
268268- Keywords are used to store message flags and labels, providing compatibility with
269269- IMAP flags while extending functionality for modern email clients.
270270-271271- The system supports standard IMAP system flags ($seen, $draft, etc.) as well as
272272- vendor extensions (particularly Apple Mail extensions) and custom user-defined
273273- keywords. Keywords are stored as a set and provide both boolean checks and
274274- conversion functions for protocol serialization.
275275-276276- @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-4.1.1> RFC 8621, Section 4.1.1
277277- @see <https://datatracker.ietf.org/doc/draft-ietf-mailmaint-messageflag-mailboxattribute/> Draft for vendor extensions
278278-*)
279279-module Keywords : sig
280280- (** Keyword type representing various email flags and labels.
281281-282282- Covers standard IMAP system flags, common extensions, vendor-specific
283283- flags (particularly Apple Mail), and custom user-defined keywords.
284284- *)
285285- type keyword =
286286- | Draft (** "$draft": Email is a draft being composed by the user *)
287287- | Seen (** "$seen": Email has been read/viewed by the user *)
288288- | Flagged (** "$flagged": Email has been flagged for urgent or special attention *)
289289- | Answered (** "$answered": Email has been replied to *)
290290-291291- (* Common extension keywords from RFC 5788 and others *)
292292- | Forwarded (** "$forwarded": Email has been forwarded to others *)
293293- | Phishing (** "$phishing": Email is flagged as potential phishing attempt *)
294294- | Junk (** "$junk": Email is classified as spam/junk mail *)
295295- | NotJunk (** "$notjunk": Email is explicitly marked as legitimate (not spam) *)
296296-297297- (* Apple Mail and other vendor extension keywords *)
298298- | Notify (** "$notify": User requests notification when email receives replies *)
299299- | Muted (** "$muted": Email thread is muted (notifications disabled) *)
300300- | Followed (** "$followed": Email thread is followed for special notifications *)
301301- | Memo (** "$memo": Email has an associated memo or note *)
302302- | HasMemo (** "$hasmemo": Email contains memo, annotation or note properties *)
303303- | Autosent (** "$autosent": Email was automatically generated or sent *)
304304- | Unsubscribed (** "$unsubscribed": User has unsubscribed from this sender *)
305305- | CanUnsubscribe (** "$canunsubscribe": Email contains unsubscribe links/information *)
306306- | Imported (** "$imported": Email was imported from another email system *)
307307- | IsTrusted (** "$istrusted": Email sender is verified/trusted *)
308308- | MaskedEmail (** "$maskedemail": Email uses masked/anonymous addressing *)
309309- | New (** "$new": Email was recently delivered to the mailbox *)
310310-311311- (* Apple Mail color flag bit system for visual categorization *)
312312- | MailFlagBit0 (** "$MailFlagBit0": First color flag bit (used for red) *)
313313- | MailFlagBit1 (** "$MailFlagBit1": Second color flag bit (used for orange) *)
314314- | MailFlagBit2 (** "$MailFlagBit2": Third color flag bit (used for yellow) *)
315315- | Custom of string (** Custom user-defined keyword with arbitrary name *)
316316-317317- (** A set of keywords applied to an email.
318318-319319- Represents the collection of all flags and labels associated with a specific
320320- email message. Keywords are stored as a list but logically represent a set
321321- (duplicates are handled appropriately by the manipulation functions).
322322- *)
323323- type t = keyword list
324324-325325- (** Check if email is marked as a draft.
326326- @return true if the Draft keyword is present *)
327327- val is_draft : t -> bool
328328-329329- (** Check if email has been read.
330330- @return true if the Seen keyword is present *)
331331- val is_seen : t -> bool
332332-333333- (** Check if email is unread (not seen and not a draft).
334334- @return true if email is neither seen nor a draft *)
335335- val is_unread : t -> bool
336336-337337- (** Check if email is flagged for attention.
338338- @return true if the Flagged keyword is present *)
339339- val is_flagged : t -> bool
340340-341341- (** Check if email has been replied to.
342342- @return true if the Answered keyword is present *)
343343- val is_answered : t -> bool
344344-345345- (** Check if email has been forwarded.
346346- @return true if the Forwarded keyword is present *)
347347- val is_forwarded : t -> bool
348348-349349- (** Check if email is flagged as potential phishing.
350350- @return true if the Phishing keyword is present *)
351351- val is_phishing : t -> bool
352352-353353- (** Check if email is classified as junk/spam.
354354- @return true if the Junk keyword is present *)
355355- val is_junk : t -> bool
356356-357357- (** Check if email is explicitly marked as legitimate.
358358- @return true if the NotJunk keyword is present *)
359359- val is_not_junk : t -> bool
360360-361361- (** Check if a specific custom keyword is present.
362362- @param keywords The keyword set to check
363363- @param keyword The custom keyword string to look for
364364- @return true if the custom keyword is present *)
365365- val has_keyword : t -> string -> bool
366366-367367- (** Get all custom keywords, excluding standard system keywords.
368368- @return List of custom keyword strings *)
369369- val custom_keywords : t -> string list
370370-371371- (** Add a keyword to the set (avoiding duplicates).
372372- @param keywords The current keyword set
373373- @param keyword The keyword to add
374374- @return New keyword set with the keyword added *)
375375- val add : t -> keyword -> t
376376-377377- (** Remove a keyword from the set.
378378- @param keywords The current keyword set
379379- @param keyword The keyword to remove
380380- @return New keyword set with the keyword removed *)
381381- val remove : t -> keyword -> t
382382-383383- (** Create an empty keyword set.
384384- @return Empty keyword set *)
385385- val empty : unit -> t
386386-387387- (** Create a keyword set from a list of keywords.
388388- @param keywords List of keywords to include
389389- @return New keyword set containing the specified keywords *)
390390- val of_list : keyword list -> t
391391-392392- (** Convert a keyword to its JMAP protocol string representation.
393393- @param keyword The keyword to convert
394394- @return JMAP protocol string (e.g., "$seen", "$draft") *)
395395- val to_string : keyword -> string
396396-397397- (** Parse a JMAP protocol string into a keyword.
398398- @param str The protocol string to parse
399399- @return Corresponding keyword variant *)
400400- val of_string : string -> keyword
401401-402402- (** Convert keyword set to JMAP wire format (string -> bool map).
403403- @param keywords The keyword set to convert
404404- @return Hash table mapping keyword strings to true values *)
405405- val to_map : t -> bool string_map
406406-407407- (** Convert keyword set to JSON representation.
408408- @param t The keyword set to convert
409409- @return JSON object mapping keyword strings to boolean values *)
410410- val to_json : t -> Yojson.Safe.t
411411-412412- (** Parse keyword set from JSON representation.
413413- @param json JSON object mapping keyword strings to boolean values
414414- @return Parsed keyword set
415415- @raise Failure if JSON structure is invalid *)
416416- val of_json : Yojson.Safe.t -> t
417417-end
418418-419419-420420-(** Email object representation and operations.
421421-422422- The Email object represents a single email message as defined in RFC 8621
423423- Section 4.1. It provides access to message metadata, headers, body structure,
424424- and content through a property-based API that supports partial object loading.
425425-426426- Email objects are immutable and server-controlled. All modifications must
427427- be performed through the Email/set method using patch objects.
428428-429429- @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-4.1> RFC 8621, Section 4.1
430430-*)
431431-module Email : sig
432432- (** Immutable email object type *)
433433- type t
434434-435435- (** Get the server-assigned email identifier.
436436- @return Email ID if present in the object *)
437437- val id : t -> id option
438438-439439- (** Get the blob ID for downloading the complete raw message.
440440- @return Blob identifier for RFC 5322 message access *)
441441- val blob_id : t -> id option
442442-443443- (** Get the thread identifier linking related messages.
444444- @return Thread ID for conversation grouping *)
445445- val thread_id : t -> id option
446446-447447- (** Get the set of mailboxes containing this email.
448448- @return Map of mailbox IDs to boolean values (always true when present) *)
449449- val mailbox_ids : t -> bool id_map option
450450-451451- (** Get the keywords/flags applied to this email.
452452- @return Set of keywords if included in the retrieved properties *)
453453- val keywords : t -> Keywords.t option
454454-455455- (** Get the total size of the raw message.
456456- @return Message size in octets *)
457457- val size : t -> uint option
458458-459459- (** Get the server timestamp when the message was received.
460460- @return Reception timestamp *)
461461- val received_at : t -> date option
462462-463463- (** Get the email subject line.
464464- @return Subject text if the Subject property was requested *)
465465- val subject : t -> string option
466466-467467- (** Get the server-generated preview text for display.
468468- @return Preview text if the Preview property was requested *)
469469- val preview : t -> string option
470470-471471- (** Get the From header addresses.
472472- @return List of sender addresses if the From property was requested *)
473473- val from : t -> Email_address.t list option
474474-475475- (** Get the To header addresses.
476476- @return List of primary recipient addresses if the To property was requested *)
477477- val to_ : t -> Email_address.t list option
478478-479479- (** Get the Cc header addresses.
480480- @return List of carbon copy addresses if the Cc property was requested *)
481481- val cc : t -> Email_address.t list option
482482-483483- (** Get the Message-ID header values.
484484- @return List of message identifiers if the MessageId property was requested *)
485485- val message_id : t -> string list option
486486-487487- (** Check if the email has non-inline attachments.
488488- @return true if attachments are present, if the HasAttachment property was requested *)
489489- val has_attachment : t -> bool option
490490-491491- (** Get text/plain body parts suitable for display.
492492- @return List of text body parts if the TextBody property was requested *)
493493- val text_body : t -> Email_body_part.t list option
494494-495495- (** Get text/html body parts suitable for display.
496496- @return List of HTML body parts if the HtmlBody property was requested *)
497497- val html_body : t -> Email_body_part.t list option
498498-499499- (** Get attachment body parts.
500500- @return List of attachment parts if the Attachments property was requested *)
501501- val attachments : t -> Email_body_part.t list option
502502-503503- (** Create a new Email object.
504504-505505- Used primarily for constructing Email objects from server responses or
506506- for testing purposes. In normal operation, Email objects are returned
507507- by Email/get and related methods.
508508- *)
509509- val create :
510510- ?id:id ->
511511- ?blob_id:id ->
512512- ?thread_id:id ->
513513- ?mailbox_ids:bool id_map ->
514514- ?keywords:Keywords.t ->
515515- ?size:uint ->
516516- ?received_at:date ->
517517- ?subject:string ->
518518- ?preview:string ->
519519- ?from:Email_address.t list ->
520520- ?to_:Email_address.t list ->
521521- ?cc:Email_address.t list ->
522522- ?message_id:string list ->
523523- ?has_attachment:bool ->
524524- ?text_body:Email_body_part.t list ->
525525- ?html_body:Email_body_part.t list ->
526526- ?attachments:Email_body_part.t list ->
527527- unit -> t
528528-529529- (** Create a patch object for Email/set operations.
530530-531531- Generates JSON Patch operations for modifying email properties.
532532- Only keywords and mailbox membership can be modified after creation.
533533-534534- @param add_keywords Keywords to add to the email
535535- @param remove_keywords Keywords to remove from the email
536536- @param add_mailboxes Mailboxes to add the email to
537537- @param remove_mailboxes Mailboxes to remove the email from
538538- @return JSON Patch operations for Email/set
539539- *)
540540- val make_patch :
541541- ?add_keywords:Keywords.t ->
542542- ?remove_keywords:Keywords.t ->
543543- ?add_mailboxes:id list ->
544544- ?remove_mailboxes:id list ->
545545- unit -> Jmap.Methods.patch_object
546546-547547- (** Safely extract the email ID.
548548- @return Ok with the ID, or Error with message if not present *)
549549- val get_id : t -> (id, string) result
550550-551551- (** Extract the email ID, raising an exception if not present.
552552- @return The email ID
553553- @raise Failure if the email has no ID *)
554554- val take_id : t -> id
555555-556556- (** Convert email to JSON representation.
557557- @param t The email to convert
558558- @return JSON object with all email fields that are present *)
559559- val to_json : t -> Yojson.Safe.t
560560-561561- (** Parse email from JSON representation.
562562- @param json JSON object representing an email
563563- @return Parsed email object
564564- @raise Failure if JSON structure is invalid *)
565565- val of_json : Yojson.Safe.t -> t
566566-end
567567-568568-(** Email import functionality.
569569-570570- Provides types and operations for the Email/import method as specified in
571571- RFC 8621 Section 4.8. This method allows importing email messages from
572572- blob storage (typically uploaded via the Blob/upload method) into mailboxes
573573- as Email objects.
574574-575575- The import process converts raw RFC 5322 message data into structured
576576- Email objects with appropriate metadata and places them in specified mailboxes.
577577-578578- @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-4.8> RFC 8621, Section 4.8
579579-*)
580580-module Import : sig
581581- (** Arguments for Email/import method. *)
582582- type args = {
583583- account_id : id; (** Account where emails will be imported *)
584584- blob_ids : id list; (** List of blob IDs containing RFC 5322 messages *)
585585- mailbox_ids : id id_map; (** Map specifying target mailboxes for each blob *)
586586- keywords : Keywords.t option; (** Default keywords to apply to imported emails *)
587587- received_at : date option; (** Override timestamp for import (default: current time) *)
588588- }
589589-590590- (** Create Email/import arguments.
591591- @param account_id Target account for the import
592592- @param blob_ids List of blob IDs containing message data
593593- @param mailbox_ids Mapping of blob IDs to target mailbox sets
594594- @param keywords Optional default keywords to apply
595595- @param received_at Optional timestamp override
596596- @return Import arguments object *)
597597- val create_args :
598598- account_id:id ->
599599- blob_ids:id list ->
600600- mailbox_ids:id id_map ->
601601- ?keywords:Keywords.t ->
602602- ?received_at:date ->
603603- unit -> args
604604-605605- (** Result for a single successfully imported email. *)
606606- type email_import_result = {
607607- blob_id : id; (** Original blob ID that was imported *)
608608- email : Email.t; (** Created Email object with server-assigned properties *)
609609- }
610610-611611- (** Create an import result object.
612612- @param blob_id The blob ID that was successfully imported
613613- @param email The created Email object
614614- @return Import result object *)
615615- val create_result :
616616- blob_id:id ->
617617- email:Email.t ->
618618- unit -> email_import_result
619619-620620- (** Complete response for Email/import method. *)
621621- type response = {
622622- account_id : id; (** Account where import was attempted *)
623623- created : email_import_result id_map; (** Successfully imported emails by blob ID *)
624624- not_created : Jmap.Error.Set_error.t id_map; (** Failed imports with error details *)
625625- }
626626-627627- (** Create an import response object.
628628- @param account_id Account where import was performed
629629- @param created Map of successfully imported results
630630- @param not_created Map of failed imports with errors
631631- @return Import response object *)
632632- val create_response :
633633- account_id:id ->
634634- created:email_import_result id_map ->
635635- not_created:Jmap.Error.Set_error.t id_map ->
636636- unit -> response
637637-end
638638-639639-(** Email parsing functionality.
640640-641641- Provides types and operations for the Email/parse method as specified in
642642- RFC 8621 Section 4.9. This method parses RFC 5322 message data from
643643- blob storage into Email objects without importing them into mailboxes.
644644-645645- Parsing allows inspection of message structure and properties before
646646- deciding whether to import messages, and provides access to Email object
647647- properties for messages that may not be stored in the account.
648648-649649- @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-4.9> RFC 8621, Section 4.9
650650-*)
651651-module Parse : sig
652652- (** Arguments for Email/parse method. *)
653653- type args = {
654654- account_id : id; (** Account context for parsing *)
655655- blob_ids : id list; (** List of blob IDs to parse *)
656656- properties : string list option; (** Email properties to include in results *)
657657- }
658658-659659- (** Create Email/parse arguments.
660660- @param account_id Account context for the parsing operation
661661- @param blob_ids List of blob IDs containing RFC 5322 messages
662662- @param properties Optional list of Email properties to include
663663- @return Parse arguments object *)
664664- val create_args :
665665- account_id:id ->
666666- blob_ids:id list ->
667667- ?properties:string list ->
668668- unit -> args
669669-670670- (** Result for a single successfully parsed email. *)
671671- type email_parse_result = {
672672- blob_id : id; (** Original blob ID that was parsed *)
673673- parsed : Email.t; (** Parsed Email object (not stored in any mailbox) *)
674674- }
675675-676676- (** Create a parse result object.
677677- @param blob_id The blob ID that was successfully parsed
678678- @param parsed The parsed Email object
679679- @return Parse result object *)
680680- val create_result :
681681- blob_id:id ->
682682- parsed:Email.t ->
683683- unit -> email_parse_result
684684-685685- (** Complete response for Email/parse method. *)
686686- type response = {
687687- account_id : id; (** Account where parsing was performed *)
688688- parsed : email_parse_result id_map; (** Successfully parsed emails by blob ID *)
689689- not_parsed : string id_map; (** Failed parses with error messages *)
690690- }
691691-692692- (** Create a parse response object.
693693- @param account_id Account where parsing was performed
694694- @param parsed Map of successfully parsed results
695695- @param not_parsed Map of failed parses with error messages
696696- @return Parse response object *)
697697- val create_response :
698698- account_id:id ->
699699- parsed:email_parse_result id_map ->
700700- not_parsed:string id_map ->
701701- unit -> response
702702-end
703703-704704-705705-(** Email copying functionality.
706706-707707- Provides types and operations for the Email/copy method as specified in
708708- RFC 8621 Section 4.7. This method allows copying existing Email objects
709709- from one account to another, with optional mailbox placement and the
710710- ability to destroy originals on success (for move operations).
711711-712712- Cross-account copying maintains email content and properties while
713713- assigning new IDs in the target account.
714714-715715- @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-4.7> RFC 8621, Section 4.7
716716-*)
717717-module Copy : sig
718718- (** Arguments for Email/copy method. *)
719719- type args = {
720720- from_account_id : id; (** Source account containing emails to copy *)
721721- account_id : id; (** Destination account for copied emails *)
722722- create : (id * id id_map) id_map; (** Map of creation IDs to (email ID, mailbox set) pairs *)
723723- on_success_destroy_original : bool option; (** Whether to destroy originals after successful copy *)
724724- destroy_from_if_in_state : string option; (** Only destroy if source account is in this state *)
725725- }
726726-727727- (** Create Email/copy arguments.
728728- @param from_account_id Source account ID
729729- @param account_id Destination account ID
730730- @param create Map of creation IDs to (source email ID, target mailboxes)
731731- @param on_success_destroy_original Whether to destroy originals (move operation)
732732- @param destroy_from_if_in_state Only destroy if source state matches
733733- @return Copy arguments object *)
734734- val create_args :
735735- from_account_id:id ->
736736- account_id:id ->
737737- create:(id * id id_map) id_map ->
738738- ?on_success_destroy_original:bool ->
739739- ?destroy_from_if_in_state:string ->
740740- unit -> args
741741-742742- (** Complete response for Email/copy method. *)
743743- type response = {
744744- from_account_id : id; (** Source account ID *)
745745- account_id : id; (** Destination account ID *)
746746- created : Email.t id_map option; (** Successfully created emails by creation ID *)
747747- not_created : Jmap.Error.Set_error.t id_map option; (** Failed copies with error details *)
748748- }
749749-750750- (** Create a copy response object.
751751- @param from_account_id Source account ID
752752- @param account_id Destination account ID
753753- @param created Optional map of successfully copied emails
754754- @param not_created Optional map of failed copies with errors
755755- @return Copy response object *)
756756- val create_response :
757757- from_account_id:id ->
758758- account_id:id ->
759759- ?created:Email.t id_map ->
760760- ?not_created:Jmap.Error.Set_error.t id_map ->
761761- unit -> response
762762-end
763763-764764-
+87-62
jmap/jmap-email/vacation.ml
···11(** JMAP Vacation Response Implementation.
2233 This module implements the JMAP VacationResponse singleton data type
44- for managing automatic out-of-office email replies with date ranges,
44+ for managing automatic out-of-office email replies with Date.t ranges,
55 custom messages, and enable/disable functionality.
6677 @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-8> RFC 8621, Section 8: VacationResponse
88*)
991010-open Jmap.Types
1110open Jmap.Error
1211open Yojson.Safe.Util
1312···16151716(** VacationResponse object *)
1817type t = {
1919- id : id;
1818+ id : Jmap.Id.t;
2019 is_enabled : bool;
2121- from_date : utc_date option;
2222- to_date : utc_date option;
2020+ from_date : Jmap.Date.t option;
2121+ to_date : Jmap.Date.t option;
2322 subject : string option;
2423 text_body : string option;
2524 html_body : string option;
···3635(** Create a minimal VacationResponse object.
3736 VacationResponse always has ID "singleton" per JMAP spec *)
3837let create ?id () =
3939- let actual_id = match id with Some id -> id | None -> Jmap.Types.Constants.vacation_response_id in
3838+ let actual_id = match id with Some id -> id | None -> "singleton" in
3939+ let id_result = match Jmap.Id.of_string actual_id with
4040+ | Ok id -> id
4141+ | Error e -> failwith ("Invalid vacation response ID: " ^ e) in
4042 {
4141- id = actual_id;
4343+ id = id_result;
4244 is_enabled = false;
4345 from_date = None;
4446 to_date = None;
···5052(** Serialize to JSON with only specified properties *)
5153let to_json_with_properties ~properties t =
5254 let all_fields = [
5353- ("id", `String t.id);
5555+ ("id", `String (Jmap.Id.to_string t.id));
5456 ("isEnabled", `Bool t.is_enabled);
5555- ("fromDate", match t.from_date with Some date -> `Float date | None -> `Null);
5656- ("toDate", match t.to_date with Some date -> `Float date | None -> `Null);
5757+ ("fromDate", match t.from_date with Some date -> Jmap.Date.to_json date | None -> `Null);
5858+ ("toDate", match t.to_date with Some date -> Jmap.Date.to_json date | None -> `Null);
5759 ("subject", match t.subject with Some subj -> `String subj | None -> `Null);
5860 ("textBody", match t.text_body with Some text -> `String text | None -> `Null);
5961 ("htmlBody", match t.html_body with Some html -> `String html | None -> `Null);
···63656466(** Get list of all valid property names *)
6567let valid_properties () = [
6666- "id"; "isEnabled"; "fromDate"; "toDate"; "subject"; "textBody"; "htmlBody"
6868+ "Id.t"; "isEnabled"; "fromDate"; "toDate"; "subject"; "textBody"; "htmlBody"
6769] (* TODO: Use Property.to_string_list Property.all_properties when module ordering is fixed *)
68706971(** {1 Property Accessors} *)
···8890(* JSON serialization for VacationResponse *)
8991let to_json t =
9092 let json_fields = [
9191- ("id", `String t.id);
9393+ ("id", `String (Jmap.Id.to_string t.id));
9294 ("isEnabled", `Bool t.is_enabled);
9395 ] in
9496 let json_fields = match t.from_date with
9597 | None -> json_fields
9696- | Some date -> ("fromDate", `Float date) :: json_fields
9898+ | Some date -> ("fromDate", `Float (Jmap.Date.to_timestamp date)) :: json_fields
9799 in
98100 let json_fields = match t.to_date with
99101 | None -> json_fields
100100- | Some date -> ("toDate", `Float date) :: json_fields
102102+ | Some date -> ("toDate", `Float (Jmap.Date.to_timestamp date)) :: json_fields
101103 in
102104 let json_fields = match t.subject with
103105 | None -> json_fields
···120122 let enabled_str = string_of_bool vacation.is_enabled in
121123 let from_date_str = match vacation.from_date with
122124 | None -> "none"
123123- | Some date -> Printf.sprintf "%.0f" date
125125+ | Some date -> Printf.sprintf "%.0f" (Jmap.Date.to_timestamp date)
124126 in
125127 let to_date_str = match vacation.to_date with
126128 | None -> "none"
127127- | Some date -> Printf.sprintf "%.0f" date
129129+ | Some date -> Printf.sprintf "%.0f" (Jmap.Date.to_timestamp date)
128130 in
129131 let subject_str = match vacation.subject with
130132 | None -> "default"
131133 | Some subj -> Printf.sprintf "\"%s\"" (String.sub subj 0 (min 20 (String.length subj)))
132134 in
133135 Format.fprintf ppf "VacationResponse{id=%s; is_enabled=%s; from_date=%s; to_date=%s; subject=%s}"
134134- vacation.id
136136+ (Jmap.Id.to_string vacation.id)
135137 enabled_str
136138 from_date_str
137139 to_date_str
···142144 let enabled_str = string_of_bool vacation.is_enabled in
143145 let from_date_str = match vacation.from_date with
144146 | None -> "none"
145145- | Some date -> Printf.sprintf "%.0f" date
147147+ | Some date -> Printf.sprintf "%.0f" (Jmap.Date.to_timestamp date)
146148 in
147149 let to_date_str = match vacation.to_date with
148150 | None -> "none"
149149- | Some date -> Printf.sprintf "%.0f" date
151151+ | Some date -> Printf.sprintf "%.0f" (Jmap.Date.to_timestamp date)
150152 in
151153 let subject_str = match vacation.subject with
152154 | None -> "default subject"
···161163 | Some html -> Printf.sprintf "%d chars" (String.length html)
162164 in
163165 Format.fprintf ppf "VacationResponse {\n id: %s\n is_enabled: %s\n from_date: %s\n to_date: %s\n subject: %s\n text_body: %s\n html_body: %s\n}"
164164- vacation.id
166166+ (Jmap.Id.to_string vacation.id)
165167 enabled_str
166168 from_date_str
167169 to_date_str
···172174(* JSON deserialization for VacationResponse *)
173175let of_json json =
174176 try
175175- let id = json |> member "id" |> to_string in
177177+ let id = match Jmap.Id.of_string (json |> member "id" |> to_string) with
178178+ | Ok id -> id
179179+ | Error err -> failwith ("Invalid ID: " ^ err) in
176180 let is_enabled = json |> member "isEnabled" |> to_bool in
177181 let from_date =
178182 match json |> member "fromDate" with
179179- | `Float date -> Some date
183183+ | `Float date -> Some (Jmap.Date.of_timestamp date)
180184 | `String date_str ->
181181- (* Parse ISO 8601 date string to Unix timestamp - simplified *)
182182- (try Some (float_of_string date_str)
185185+ (* Parse ISO 8601 Date.t string to Unix timestamp - simplified *)
186186+ (try Some (Jmap.Date.of_timestamp (float_of_string date_str))
183187 with _ -> None)
184188 | `Null | _ -> None
185189 in
186190 let to_date =
187191 match json |> member "toDate" with
188188- | `Float date -> Some date
192192+ | `Float date -> Some (Jmap.Date.of_timestamp date)
189193 | `String date_str ->
190190- (* Parse ISO 8601 date string to Unix timestamp - simplified *)
191191- (try Some (float_of_string date_str)
194194+ (* Parse ISO 8601 Date.t string to Unix timestamp - simplified *)
195195+ (try Some (Jmap.Date.of_timestamp (float_of_string date_str))
192196 with _ -> None)
193197 | `Null | _ -> None
194198 in
···204208module Update = struct
205209 type t = {
206210 is_enabled : bool option;
207207- from_date : utc_date option option;
208208- to_date : utc_date option option;
211211+ from_date : Jmap.Date.t option option;
212212+ to_date : Jmap.Date.t option option;
209213 subject : string option option;
210214 text_body : string option option;
211215 html_body : string option option;
···255259 let json_fields = match t.from_date with
256260 | None -> json_fields
257261 | Some None -> ("fromDate", `Null) :: json_fields
258258- | Some (Some date) -> ("fromDate", `Float date) :: json_fields
262262+ | Some (Some date) -> ("fromDate", `Float (Jmap.Date.to_timestamp date)) :: json_fields
259263 in
260264 let json_fields = match t.to_date with
261265 | None -> json_fields
262266 | Some None -> ("toDate", `Null) :: json_fields
263263- | Some (Some date) -> ("toDate", `Float date) :: json_fields
267267+ | Some (Some date) -> ("toDate", `Float (Jmap.Date.to_timestamp date)) :: json_fields
264268 in
265269 let json_fields = match t.subject with
266270 | None -> json_fields
···290294 let from_date =
291295 match json |> member "fromDate" with
292296 | `Null -> Some None
293293- | `Float date -> Some (Some date)
294294- | `String date_str -> Some (Some (try float_of_string date_str with _ -> 0.0))
297297+ | `Float date -> Some (Some (Jmap.Date.of_timestamp date))
298298+ | `String date_str -> Some (Some (try Jmap.Date.of_timestamp (float_of_string date_str) with _ -> Jmap.Date.of_timestamp 0.0))
295299 | _ -> None
296300 in
297301 let to_date =
298302 match json |> member "toDate" with
299303 | `Null -> Some None
300300- | `Float date -> Some (Some date)
301301- | `String date_str -> Some (Some (try float_of_string date_str with _ -> 0.0))
304304+ | `Float date -> Some (Some (Jmap.Date.of_timestamp date))
305305+ | `String date_str -> Some (Some (try Jmap.Date.of_timestamp (float_of_string date_str) with _ -> Jmap.Date.of_timestamp 0.0))
302306 | _ -> None
303307 in
304308 let subject =
···328332(** Arguments for VacationResponse/get method *)
329333module Get_args = struct
330334 type t = {
331331- account_id : id;
332332- ids : id list option;
335335+ account_id : Jmap.Id.t;
336336+ ids : Jmap.Id.t list option;
333337 properties : string list option;
334338 }
335339···341345 { account_id; ids; properties }
342346343347 let singleton ~account_id ?properties () =
344344- { account_id; ids = Some [Jmap.Types.Constants.vacation_response_id]; properties }
348348+ { account_id; ids = Some [Jmap.Id.of_string "singleton" |> Result.get_ok]; properties }
345349346350 let to_json t =
347351 let json_fields = [
348348- ("accountId", `String t.account_id);
352352+ ("accountId", `String (Jmap.Id.to_string t.account_id));
349353 ] in
350354 let json_fields = match t.ids with
351355 | None -> json_fields
352352- | Some ids -> ("ids", `List (List.map (fun id -> `String id) ids)) :: json_fields
356356+ | Some ids -> ("ids", `List (List.map (fun id -> `String (Jmap.Id.to_string id)) ids)) :: json_fields
353357 in
354358 let json_fields = match t.properties with
355359 | None -> json_fields
···359363360364 let of_json json =
361365 try
362362- let account_id = json |> member "accountId" |> to_string in
366366+ let account_id_str = json |> member "accountId" |> to_string in
367367+ let account_id = match Jmap.Id.of_string account_id_str with
368368+ | Ok id -> id
369369+ | Error _ -> failwith ("Invalid accountId: " ^ account_id_str) in
363370 let ids =
364371 match json |> member "ids" with
365365- | `List items -> Some (List.map (fun item -> to_string item) items)
372372+ | `List items ->
373373+ Some (List.map (fun item ->
374374+ let id_str = to_string item in
375375+ match Jmap.Id.of_string id_str with
376376+ | Ok id -> id
377377+ | Error _ -> failwith ("Invalid id: " ^ id_str)) items)
366378 | _ -> None
367379 in
368380 let properties =
···381393 type vacation_response = t
382394383395 type t = {
384384- account_id : id;
396396+ account_id : Jmap.Id.t;
385397 state : string;
386398 list : vacation_response list;
387387- not_found : id list;
399399+ not_found : Jmap.Id.t list;
388400 }
389401390402 let account_id t = t.account_id
···401413402414 let to_json t =
403415 `Assoc [
404404- ("accountId", `String t.account_id);
416416+ ("accountId", `String (Jmap.Id.to_string t.account_id));
405417 ("state", `String t.state);
406418 ("list", `List (List.map (fun item -> (to_json item : Yojson.Safe.t)) t.list));
407407- ("notFound", `List (List.map (fun id -> `String id) t.not_found));
419419+ ("notFound", `List (List.map (fun id -> `String (Jmap.Id.to_string id)) t.not_found));
408420 ]
409421410422 let of_json json =
411423 try
412412- let account_id = json |> member "accountId" |> to_string in
424424+ let account_id_str = json |> member "accountId" |> to_string in
425425+ let account_id = match Jmap.Id.of_string account_id_str with
426426+ | Ok id -> id
427427+ | Error _ -> failwith ("Invalid accountId: " ^ account_id_str) in
413428 let state = json |> member "state" |> to_string in
414429 let list_json = json |> member "list" |> to_list in
415430 let list =
···419434 | Error _ -> acc (* Skip invalid items *)
420435 ) [] list_json |> List.rev
421436 in
422422- let not_found = json |> member "notFound" |> to_list |> List.map to_string in
437437+ let not_found = json |> member "notFound" |> to_list |> List.filter_map (fun item ->
438438+ let str = to_string item in
439439+ match Jmap.Id.of_string str with
440440+ | Ok id -> Some id
441441+ | Error _ -> None) in
423442 Ok { account_id; state; list; not_found }
424443 with
425444 | Type_error (msg, _) -> Error ("Invalid VacationResponse/get response JSON: " ^ msg)
···429448(** VacationResponse/set: Args type *)
430449module Set_args = struct
431450 type t = {
432432- account_id : id;
451451+ account_id : Jmap.Id.t;
433452 if_in_state : string option;
434434- update : Update.t id_map option;
453453+ update : (string, Update.t) Hashtbl.t option;
435454 }
436455437456 let account_id t = t.account_id
···447466 let singleton ~account_id ?if_in_state ~update () = {
448467 account_id;
449468 if_in_state;
450450- update = Some (Hashtbl.create 1 |> fun tbl -> Hashtbl.add tbl Jmap.Types.Constants.vacation_response_id update; tbl);
469469+ update = Some (Hashtbl.create 1 |> fun tbl -> Hashtbl.add tbl "singleton" update; tbl);
451470 }
452471453472 let to_json t =
454473 let json_fields = [
455455- ("accountId", `String t.account_id);
474474+ ("accountId", `String (Jmap.Id.to_string t.account_id));
456475 ] in
457476 let json_fields = match t.if_in_state with
458477 | None -> json_fields
···468487469488 let of_json json =
470489 try
471471- let account_id = json |> member "accountId" |> to_string in
490490+ let account_id_str = json |> member "accountId" |> to_string in
491491+ let account_id = match Jmap.Id.of_string account_id_str with
492492+ | Ok id -> id
493493+ | Error _ -> failwith ("Invalid accountId: " ^ account_id_str) in
472494 let if_in_state = json |> member "ifInState" |> to_string_option in
473495 let update =
474496 match json |> member "update" with
···493515 type vacation_response = t
494516495517 type t = {
496496- account_id : id;
518518+ account_id : Jmap.Id.t;
497519 old_state : string option;
498520 new_state : string;
499499- updated : vacation_response option id_map option;
500500- not_updated : Set_error.t id_map option;
521521+ updated : (string, vacation_response option) Hashtbl.t option;
522522+ not_updated : (string, Set_error.t) Hashtbl.t option;
501523 }
502524503525 let account_id t = t.account_id
···510532 match t.updated with
511533 | None -> None
512534 | Some updated_map ->
513513- try Hashtbl.find updated_map Jmap.Types.Constants.vacation_response_id
535535+ try Hashtbl.find updated_map "singleton"
514536 with Not_found -> None
515537516538 let singleton_error t =
517539 match t.not_updated with
518540 | None -> None
519541 | Some error_map ->
520520- try Some (Hashtbl.find error_map Jmap.Types.Constants.vacation_response_id)
542542+ try Some (Hashtbl.find error_map "singleton")
521543 with Not_found -> None
522544523545 let v ~account_id ?old_state ~new_state ?updated ?not_updated () = {
···530552531553 let to_json t =
532554 let json_fields = [
533533- ("accountId", `String t.account_id);
555555+ ("accountId", `String (Jmap.Id.to_string t.account_id));
534556 ("newState", `String t.new_state);
535557 ] in
536558 let json_fields = match t.old_state with
···559581560582 let of_json json =
561583 try
562562- let account_id = json |> member "accountId" |> to_string in
584584+ let account_id_str = json |> member "accountId" |> to_string in
585585+ let account_id = match Jmap.Id.of_string account_id_str with
586586+ | Ok id -> id
587587+ | Error _ -> failwith ("Invalid accountId: " ^ account_id_str) in
563588 let old_state = json |> member "oldState" |> to_string_option in
564589 let new_state = json |> member "newState" |> to_string in
565590 let updated =
···608633 ]
609634610635 let to_string = function
611611- | `Id -> "id"
636636+ | `Id -> "Id.t"
612637 | `IsEnabled -> "isEnabled"
613638 | `FromDate -> "fromDate"
614639 | `ToDate -> "toDate"
···617642 | `HtmlBody -> "htmlBody"
618643619644 let of_string = function
620620- | "id" -> Some `Id
645645+ | "Id.t" -> Some `Id
621646 | "isEnabled" -> Some `IsEnabled
622647 | "fromDate" -> Some `FromDate
623648 | "toDate" -> Some `ToDate
+51-52
jmap/jmap-email/vacation.mli
···1212 @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-8> RFC 8621, Section 8: VacationResponse
1313*)
14141515-open Jmap.Types
1615open Jmap.Error
17161817(** Complete VacationResponse object representation.
···2221 exactly one VacationResponse per account.
23222423 The vacation response can be enabled/disabled and configured with
2525- date ranges, custom subject, and message content in both text and HTML.
2424+ Date.t ranges, custom subject, and message content in both text and HTML.
26252726 @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-8> RFC 8621, Section 8
2827*)
···3837include Jmap_sigs.PRINTABLE with type t := t
39384039(** JMAP object interface for property-based operations *)
4141-include Jmap_sigs.JMAP_OBJECT with type t := t and type id_type := id
4040+include Jmap_sigs.JMAP_OBJECT with type t := t and type id_type := string
42414342(** Get the vacation response ID.
4443 @return Always returns "singleton" for VacationResponse objects *)
4545-val id : t -> id option
4444+val id : t -> Jmap.Id.t option
46454746(** Check if the vacation response is currently enabled.
4847 @return true if auto-replies are active *)
4948val is_enabled : t -> bool
50495151-(** Get the start date for the vacation period.
5252- @return Optional start date, None means no start constraint *)
5353-val from_date : t -> utc_date option
5050+(** Get the start Date.t for the vacation period.
5151+ @return Optional start Date.t, None means no start constraint *)
5252+val from_date : t -> Jmap.Date.t option
54535555-(** Get the end date for the vacation period.
5656- @return Optional end date, None means no end constraint *)
5757-val to_date : t -> utc_date option
5454+(** Get the end Date.t for the vacation period.
5555+ @return Optional end Date.t, None means no end constraint *)
5656+val to_date : t -> Jmap.Date.t option
58575958(** Get the custom subject line for vacation replies.
6059 @return Optional subject override, None uses default subject *)
···6968val html_body : t -> string option
70697170(** Create a VacationResponse object.
7272- @param id Must be "singleton" for VacationResponse objects
7171+ @param Jmap.Id.t Must be "singleton" for VacationResponse objects
7372 @param is_enabled Whether vacation replies are active
7474- @param from_date Optional start date for vacation period
7575- @param to_date Optional end date for vacation period
7373+ @param from_date Optional start Date.t for vacation period
7474+ @param to_date Optional end Date.t for vacation period
7675 @param subject Optional custom subject line
7776 @param text_body Optional plain text message content
7877 @param html_body Optional HTML message content
7978 @return New VacationResponse object *)
8079val v :
8181- id:id ->
8080+ id:Jmap.Id.t ->
8281 is_enabled:bool ->
8383- ?from_date:utc_date ->
8484- ?to_date:utc_date ->
8282+ ?from_date:Jmap.Date.t ->
8383+ ?to_date:Jmap.Date.t ->
8584 ?subject:string ->
8685 ?text_body:string ->
8786 ?html_body:string ->
···108107 @return Optional enabled flag for update *)
109108 val is_enabled : t -> bool option
110109111111- (** Get the start date update.
112112- @return Optional start date change *)
113113- val from_date : t -> utc_date option option
110110+ (** Get the start Date.t update.
111111+ @return Optional start Date.t change *)
112112+ val from_date : t -> Jmap.Date.t option option
114113115115- (** Get the end date update.
116116- @return Optional end date change *)
117117- val to_date : t -> utc_date option option
114114+ (** Get the end Date.t update.
115115+ @return Optional end Date.t change *)
116116+ val to_date : t -> Jmap.Date.t option option
118117119118 (** Get the subject line update.
120119 @return Optional subject change *)
···130129131130 (** Create VacationResponse update parameters.
132131 @param is_enabled Optional enabled flag update
133133- @param from_date Optional start date update
134134- @param to_date Optional end date update
132132+ @param from_date Optional start Date.t update
133133+ @param to_date Optional end Date.t update
135134 @param subject Optional subject update
136135 @param text_body Optional text body update
137136 @param html_body Optional HTML body update
138137 @return Update parameters *)
139138 val v :
140139 ?is_enabled:bool ->
141141- ?from_date:utc_date option ->
142142- ?to_date:utc_date option ->
140140+ ?from_date:Jmap.Date.t option ->
141141+ ?to_date:Jmap.Date.t option ->
143142 ?subject:string option ->
144143 ?text_body:string option ->
145144 ?html_body:string option ->
146145 unit -> t
147146148147 (** Create an update to enable vacation responses.
149149- @param from_date Optional start date for vacation period
150150- @param to_date Optional end date for vacation period
148148+ @param from_date Optional start Date.t for vacation period
149149+ @param to_date Optional end Date.t for vacation period
151150 @param subject Optional custom subject line
152151 @param text_body Optional text message content
153152 @param html_body Optional HTML message content
154153 @return Update to enable vacation with specified settings *)
155154 val enable :
156156- ?from_date:utc_date ->
157157- ?to_date:utc_date ->
155155+ ?from_date:Jmap.Date.t ->
156156+ ?to_date:Jmap.Date.t ->
158157 ?subject:string ->
159158 ?text_body:string ->
160159 ?html_body:string ->
···188187189188 (** Get the account ID for the operation.
190189 @return Account identifier where vacation response will be retrieved *)
191191- val account_id : t -> id
190190+ val account_id : t -> Jmap.Id.t
192191193192 (** Get the specific vacation response IDs to retrieve.
194193 @return List should be ["singleton"] or None for the singleton object *)
195195- val ids : t -> id list option
194194+ val ids : t -> Jmap.Id.t list option
196195197196 (** Get the properties to include in the response.
198197 @return List of property names, or None for all properties *)
···204203 @param properties Optional list of properties to retrieve
205204 @return VacationResponse/get arguments *)
206205 val v :
207207- account_id:id ->
208208- ?ids:id list ->
206206+ account_id:Jmap.Id.t ->
207207+ ?ids:Jmap.Id.t list ->
209208 ?properties:string list ->
210209 unit -> t
211210···214213 @param properties Optional list of properties to retrieve
215214 @return Arguments configured for singleton retrieval *)
216215 val singleton :
217217- account_id:id ->
216216+ account_id:Jmap.Id.t ->
218217 ?properties:string list ->
219218 unit -> t
220219end
···235234236235 (** Get the account ID from the response.
237236 @return Account identifier where vacation response was retrieved *)
238238- val account_id : t -> id
237237+ val account_id : t -> Jmap.Id.t
239238240239 (** Get the current state string for change tracking.
241240 @return State string for use in VacationResponse/set *)
···247246248247 (** Get the list of vacation response IDs that were not found.
249248 @return List of requested IDs that don't exist *)
250250- val not_found : t -> id list
249249+ val not_found : t -> Jmap.Id.t list
251250252251 (** Create VacationResponse/get response.
253252 @param account_id Account where vacation response was retrieved
···256255 @param not_found List of requested IDs that were not found
257256 @return VacationResponse/get response *)
258257 val v :
259259- account_id:id ->
258258+ account_id:Jmap.Id.t ->
260259 state:string ->
261260 list:vacation_response list ->
262262- not_found:id list ->
261261+ not_found:Jmap.Id.t list ->
263262 unit -> t
264263265264 (** Get the singleton vacation response if present.
···283282284283 (** Get the account ID for the set operation.
285284 @return Account where vacation response will be updated *)
286286- val account_id : t -> id
285285+ val account_id : t -> Jmap.Id.t
287286288287 (** Get the conditional state for the update.
289288 @return Optional state string for conditional updates *)
···291290292291 (** Get the update operations to perform.
293292 @return Map of "singleton" to update patch object *)
294294- val update : t -> Update.t id_map option
293293+ val update : t -> (string, Update.t) Hashtbl.t option
295294296295 (** Create VacationResponse/set arguments.
297296 @param account_id Account where vacation response will be updated
···299298 @param update Map containing "singleton" -> update object
300299 @return VacationResponse/set arguments *)
301300 val v :
302302- account_id:id ->
301301+ account_id:Jmap.Id.t ->
303302 ?if_in_state:string ->
304304- ?update:Update.t id_map ->
303303+ ?update:(string, Update.t) Hashtbl.t ->
305304 unit ->
306305 t
307306···311310 @param update Update parameters for the singleton
312311 @return Arguments configured for singleton update *)
313312 val singleton :
314314- account_id:id ->
313313+ account_id:Jmap.Id.t ->
315314 ?if_in_state:string ->
316315 update:Update.t ->
317316 unit -> t
···333332334333 (** Get the account ID from the response.
335334 @return Account where vacation response was updated *)
336336- val account_id : t -> id
335335+ val account_id : t -> Jmap.Id.t
337336338337 (** Get the old state string.
339338 @return Previous state if available *)
···345344346345 (** Get the successfully updated VacationResponse objects.
347346 @return Map of "singleton" to updated VacationResponse (if successful) *)
348348- val updated : t -> vacation_response option id_map option
347347+ val updated : t -> (string, vacation_response option) Hashtbl.t option
349348350349 (** Get the vacation responses that failed to update.
351350 @return Map of IDs to error information for failed updates *)
352352- val not_updated : t -> Set_error.t id_map option
351351+ val not_updated : t -> (string, Set_error.t) Hashtbl.t option
353352354353 (** Create VacationResponse/set response.
355354 @param account_id Account where vacation response was updated
···359358 @param not_updated Map of failed updates with errors
360359 @return VacationResponse/set response *)
361360 val v :
362362- account_id:id ->
361361+ account_id:Jmap.Id.t ->
363362 ?old_state:string ->
364363 new_state:string ->
365365- ?updated:vacation_response option id_map ->
366366- ?not_updated:Set_error.t id_map ->
364364+ ?updated:(string, vacation_response option) Hashtbl.t ->
365365+ ?not_updated:(string, Set_error.t) Hashtbl.t ->
367366 unit ->
368367 t
369368···395394 type t = [
396395 | `Id (** Server-assigned unique identifier (always "singleton") (immutable, server-set) *)
397396 | `IsEnabled (** Whether vacation response is currently active *)
398398- | `FromDate (** Start date for vacation response activation *)
399399- | `ToDate (** End date for vacation response activation *)
397397+ | `FromDate (** Start Date.t for vacation response activation *)
398398+ | `ToDate (** End Date.t for vacation response activation *)
400399 | `Subject (** Subject line for vacation response messages *)
401400 | `TextBody (** Plain text body for vacation responses *)
402401 | `HtmlBody (** HTML body for vacation responses *)
+11-11
jmap/jmap-unix/client.mli
···119119val get_emails :
120120 t ->
121121 ?account_id:string ->
122122- Jmap.Types.id list ->
122122+ string list ->
123123 ?properties:Jmap_email.Property.t list ->
124124 unit ->
125125 (Jmap_email.Email.t list, Jmap.Error.error) result
···137137 t ->
138138 account_id:string ->
139139 raw_message:bytes ->
140140- mailbox_ids:Jmap.Types.id list ->
140140+ mailbox_ids:string list ->
141141 ?keywords:string list ->
142142 ?received_at:Jmap.Types.date ->
143143 unit ->
···152152val destroy_email :
153153 t ->
154154 account_id:string ->
155155- email_id:Jmap.Types.id ->
155155+ email_id:string ->
156156 (unit, Jmap.Error.error) result
157157158158(** Set email keywords (flags) - replaces all existing keywords.
···165165val set_email_keywords :
166166 t ->
167167 account_id:string ->
168168- email_id:Jmap.Types.id ->
168168+ email_id:string ->
169169 keywords:string list ->
170170 (unit, Jmap.Error.error) result
171171···179179val set_email_mailboxes :
180180 t ->
181181 account_id:string ->
182182- email_id:Jmap.Types.id ->
183183- mailbox_ids:Jmap.Types.id list ->
182182+ email_id:string ->
183183+ mailbox_ids:string list ->
184184 (unit, Jmap.Error.error) result
185185186186(** {1 Mailbox Operations} *)
···212212 t ->
213213 account_id:string ->
214214 name:string ->
215215- ?parent_id:Jmap.Types.id ->
215215+ ?parent_id:string ->
216216 ?role:Jmap_email.Mailbox.Role.t ->
217217 unit ->
218218- (Jmap.Types.id, Jmap.Error.error) result
218218+ (string, Jmap.Error.error) result
219219220220(** Destroy mailbox.
221221···227227val destroy_mailbox :
228228 t ->
229229 account_id:string ->
230230- mailbox_id:Jmap.Types.id ->
230230+ mailbox_id:string ->
231231 ?on_destroy_remove_emails:bool ->
232232 unit ->
233233 (unit, Jmap.Error.error) result
···262262 ?sort:Jmap_email.Query.Sort.t list ->
263263 ?limit:int ->
264264 unit ->
265265- Jmap.Types.id list batch_operation
265265+ string list batch_operation
266266267267 (** Add email get operation using result reference from query *)
268268 val get_emails_ref :
269269 batch_builder ->
270270- Jmap.Types.id list batch_operation ->
270270+ string list batch_operation ->
271271 ?properties:Jmap_email.Property.t list ->
272272 unit ->
273273 Jmap_email.Email.t list batch_operation
+32-22
jmap/jmap-unix/jmap_unix.ml
···138138 let all_headers =
139139 let base_headers = [
140140 ("Host", host);
141141- ("User-Agent", Option.value ctx.config.user_agent ~default:Jmap.Types.Constants.User_agent.eio_client);
142142- ("Accept", Jmap.Types.Constants.Content_type.json);
143143- ("Content-Type", Jmap.Types.Constants.Content_type.json);
141141+ ("User-Agent", Option.value ctx.config.user_agent ~default:"jmap-eio-client/1.0");
142142+ ("Accept", "application/json");
143143+ ("Content-Type", "application/json");
144144 ] in
145145 let auth_hdrs = auth_headers ctx.auth in
146146 List.rev_append auth_hdrs (List.rev_append headers base_headers)
···362362 | Ok _response_body ->
363363 (* Simple response construction - in a real implementation would parse JSON *)
364364 let response = Jmap.Binary.Upload_response.v
365365- ~account_id
366366- ~blob_id:("blob-" ^ account_id ^ "-" ^ string_of_int (Random.int 1000000))
365365+ ~account_string:account_id
366366+ ~blob_string:("blob-" ^ account_id ^ "-" ^ string_of_int (Random.int 1000000))
367367 ~type_:content_type
368368 ~size:1000
369369 ()
···411411 let copied = Hashtbl.create (List.length blob_ids) in
412412 List.iter (fun id -> Hashtbl.add copied id id) blob_ids;
413413 let copy_response = Jmap.Binary.Blob_copy_response.v
414414- ~from_account_id
415415- ~account_id
414414+ ~from_account_string:from_account_id
415415+ ~account_string:account_id
416416 ~copied
417417 ()
418418 in
419419 Ok copy_response
420420 | Error e -> Error e)
421421422422-let connect_event_source env ctx ?(types=[]) ?(close_after=`No) ?(ping=30) () =
422422+let connect_event_source env ctx ?(types=[]) ?(close_after=`No) ?(ping=(match Jmap.UInt.of_int 30 with Ok v -> v | Error _ -> failwith "Invalid default ping")) () =
423423 let _ = ignore env in
424424 let _ = ignore ctx in
425425 let _ = ignore types in
···543543 (* Bridge to jmap-email query functionality *)
544544 module Query_args = struct
545545 type t = {
546546- account_id : Jmap.Types.id;
546546+ account_id : string;
547547 filter : Jmap.Methods.Filter.t option;
548548 sort : Jmap.Methods.Comparator.t list option;
549549 position : int option;
550550- limit : Jmap.Types.uint option;
550550+ limit : Jmap.UInt.t option;
551551 calculate_total : bool option;
552552 collapse_threads : bool option;
553553 }
···574574 | None -> args
575575 in
576576 let args = match t.limit with
577577- | Some lim -> ("limit", `Int lim) :: args
577577+ | Some lim -> ("limit", `Int (Jmap.UInt.to_int lim)) :: args
578578 | None -> args
579579 in
580580 let args = match t.calculate_total with
···590590591591 module Get_args = struct
592592 type ids_source =
593593- | Specific_ids of Jmap.Types.id list
593593+ | Specific_ids of string list
594594 | Result_reference of {
595595 result_of : string;
596596 name : string;
···598598 }
599599600600 type t = {
601601- account_id : Jmap.Types.id;
601601+ account_id : string;
602602 ids_source : ids_source;
603603 properties : string list option;
604604 }
···665665 | None -> `Bool false);
666666 ]) s)
667667 | None -> `Null);
668668- ("limit", match limit with Some l -> `Int l | None -> `Null);
668668+ ("limit", match limit with Some l -> `Int (Jmap.UInt.to_int l) | None -> `Null);
669669 ("position", match position with Some p -> `Int p | None -> `Null);
670670 ] in
671671 let builder = build ctx
···729729 | Error e -> Error e
730730731731 let move_emails env ctx ~account_id ~email_ids ~mailbox_id ?remove_from_mailboxes () =
732732+ (* Convert string IDs to Jmap.Id.t *)
733733+ let mailbox_id_t = match Jmap.Id.of_string mailbox_id with Ok id -> id | Error _ -> failwith ("Invalid mailbox_id: " ^ mailbox_id) in
734734+ let remove_from_mailboxes_t = match remove_from_mailboxes with
735735+ | Some mailbox_ids -> Some (List.map (fun id_str -> match Jmap.Id.of_string id_str with Ok id -> id | Error _ -> failwith ("Invalid remove_from_mailboxes id: " ^ id_str)) mailbox_ids)
736736+ | None -> None
737737+ in
732738 (* Create Email/set request with mailbox patches *)
733733- let patch = match remove_from_mailboxes with
739739+ let patch = match remove_from_mailboxes_t with
734740 | Some mailbox_ids_to_remove ->
735741 (* Move to new mailbox and remove from specified ones *)
736742 JmapEmail.Email.Patch.create
737737- ~add_mailboxes:[mailbox_id]
743743+ ~add_mailboxes:[mailbox_id_t]
738744 ~remove_mailboxes:mailbox_ids_to_remove
739745 ()
740746 | None ->
741747 (* Move to single mailbox (replace all existing) *)
742742- JmapEmail.Email.Patch.move_to_mailboxes [mailbox_id]
748748+ JmapEmail.Email.Patch.move_to_mailboxes [mailbox_id_t]
743749 in
744750 let updates = List.fold_left (fun acc email_id ->
745751 (email_id, patch) :: acc
···779785 | Error e -> Error e
780786781787 let import_email env ctx ~account_id ~rfc822 ~mailbox_ids ?keywords ?received_at () =
782782- let _ = ignore rfc822 in
788788+ let _rfc822_content = (rfc822 : string) in
783789 let blob_id = "blob-" ^ account_id ^ "-" ^ string_of_int (Random.int 1000000) in
784790 (* Note: Email/import uses different argument structure, keeping manual for now *)
785791 let args = `Assoc [
···787793 ("blobIds", `List [`String blob_id]);
788794 ("mailboxIds", `Assoc (List.map (fun id -> (id, `String id)) mailbox_ids));
789795 ("keywords", match keywords with
790790- | Some _kws -> `Assoc [] (* Simplified for now *)
796796+ | Some kws -> Jmap_email.Keywords.to_json kws
791797 | None -> `Null);
792798 ("receivedAt", match received_at with
793793- | Some d -> `Float d
799799+ | Some d -> `Float (Jmap.Date.to_timestamp d)
794800 | None -> `Null);
795801 ] in
796802 let builder = build ctx
···933939 | None -> args
934940 in
935941 let args = match limit with
936936- | Some l -> ("limit", `Int l) :: args
942942+ | Some l -> ("limit", `Int (Jmap.UInt.to_int l)) :: args
937943 | None -> args
938944 in
939945 let args = match position with
···10021008 end
1003100910041010 let email_query ?account_id ?filter ?sort ?limit ?position builder =
10051005- let args = EmailQuery.build_args ?account_id ?filter ?sort ?limit ?position () in
10111011+ let limit_uint = match limit with
10121012+ | Some i -> Some (match Jmap.UInt.of_int i with Ok u -> u | Error _ -> failwith ("Invalid limit: " ^ string_of_int i))
10131013+ | None -> None
10141014+ in
10151015+ let args = EmailQuery.build_args ?account_id ?filter ?sort ?limit:limit_uint ?position () in
10061016 let call_id = "email-query-" ^ string_of_int (Random.int 10000) in
10071017 { builder with methods = (Jmap.Method_names.method_to_string `Email_query, args, call_id) :: builder.methods }
10081018
+54-54
jmap/jmap-unix/jmap_unix.mli
···140140val upload :
141141 < net : 'a Eio.Net.t ; .. > ->
142142 context ->
143143- account_id:Jmap.Types.id ->
143143+ account_id:string ->
144144 content_type:string ->
145145 data_stream:string Seq.t ->
146146 Jmap.Binary.Upload_response.t Jmap.Error.result
···157157val download :
158158 < net : 'a Eio.Net.t ; .. > ->
159159 context ->
160160- account_id:Jmap.Types.id ->
161161- blob_id:Jmap.Types.id ->
160160+ account_id:string ->
161161+ blob_id:string ->
162162 ?content_type:string ->
163163 ?name:string ->
164164 unit ->
···175175val copy_blobs :
176176 < net : 'a Eio.Net.t ; .. > ->
177177 context ->
178178- from_account_id:Jmap.Types.id ->
179179- account_id:Jmap.Types.id ->
180180- blob_ids:Jmap.Types.id list ->
178178+ from_account_id:string ->
179179+ account_id:string ->
180180+ blob_ids:string list ->
181181 Jmap.Binary.Blob_copy_response.t Jmap.Error.result
182182183183(** Connect to the EventSource for push notifications.
···193193 context ->
194194 ?types:string list ->
195195 ?close_after:[`State | `No] ->
196196- ?ping:Jmap.Types.uint ->
196196+ ?ping:Jmap.UInt.t ->
197197 unit ->
198198 (event_source_connection *
199199 ([`State of Jmap.Push.State_change.t | `Ping of Jmap.Push.Event_source_ping_data.t ] Seq.t)) Jmap.Error.result
···246246 < net : 'a Eio.Net.t ; .. > ->
247247 context ->
248248 method_name:Jmap.Method_names.jmap_method ->
249249- account_id:Jmap.Types.id ->
250250- object_id:Jmap.Types.id ->
249249+ account_id:string ->
250250+ object_id:string ->
251251 ?properties:string list ->
252252 unit ->
253253 Yojson.Safe.t Jmap.Error.result
···333333 val add_get_with_reference :
334334 t ->
335335 method_name:Jmap.Method_names.jmap_method ->
336336- account_id:Jmap.Types.id ->
336336+ account_id:string ->
337337 result_reference:Jmap.Wire.Result_reference.t ->
338338 ?properties:string list ->
339339 method_call_id:string ->
···370370 @param ?collapse_threads Whether to collapse threads (None = false)
371371 @return Email query arguments object *)
372372 val create :
373373- account_id:Jmap.Types.id ->
373373+ account_id:string ->
374374 ?filter:Jmap.Methods.Filter.t ->
375375 ?sort:Jmap.Methods.Comparator.t list ->
376376 ?position:int ->
377377- ?limit:Jmap.Types.uint ->
377377+ ?limit:Jmap.UInt.t ->
378378 ?calculate_total:bool ->
379379 ?collapse_threads:bool ->
380380 unit ->
···401401 @param ?properties Optional list of properties to return (None = all properties)
402402 @return Email get arguments object *)
403403 val create :
404404- account_id:Jmap.Types.id ->
405405- ids:Jmap.Types.id list ->
404404+ account_id:string ->
405405+ ids:string list ->
406406 ?properties:string list ->
407407 unit ->
408408 t
···416416 @param ?properties Optional list of properties to return (None = all properties)
417417 @return Email get arguments object *)
418418 val create_with_reference :
419419- account_id:Jmap.Types.id ->
419419+ account_id:string ->
420420 result_of:string ->
421421 name:string ->
422422 path:string ->
···441441 val get_email :
442442 < net : 'a Eio.Net.t ; .. > ->
443443 context ->
444444- account_id:Jmap.Types.id ->
445445- email_id:Jmap.Types.id ->
444444+ account_id:string ->
445445+ email_id:string ->
446446 ?properties:string list ->
447447 unit ->
448448 Jmap_email.Email.t Jmap.Error.result
···460460 val search_emails :
461461 < net : 'a Eio.Net.t ; .. > ->
462462 context ->
463463- account_id:Jmap.Types.id ->
463463+ account_id:string ->
464464 filter:Jmap.Methods.Filter.t ->
465465 ?sort:Jmap.Methods.Comparator.t list ->
466466- ?limit:Jmap.Types.uint ->
466466+ ?limit:Jmap.UInt.t ->
467467 ?position:int ->
468468 ?properties:string list ->
469469 unit ->
470470- (Jmap.Types.id list * Jmap_email.Email.t list option) Jmap.Error.result
470470+ (string list * Jmap_email.Email.t list option) Jmap.Error.result
471471472472 (** Mark multiple emails with a keyword
473473 @param env The Eio environment for network operations
···480480 val mark_emails :
481481 < net : 'a Eio.Net.t ; .. > ->
482482 context ->
483483- account_id:Jmap.Types.id ->
484484- email_ids:Jmap.Types.id list ->
483483+ account_id:string ->
484484+ email_ids:string list ->
485485 keyword:Jmap_email.Keywords.keyword ->
486486 unit ->
487487 unit Jmap.Error.result
···496496 val mark_as_seen :
497497 < net : 'a Eio.Net.t ; .. > ->
498498 context ->
499499- account_id:Jmap.Types.id ->
500500- email_ids:Jmap.Types.id list ->
499499+ account_id:string ->
500500+ email_ids:string list ->
501501 unit ->
502502 unit Jmap.Error.result
503503···511511 val mark_as_unseen :
512512 < net : 'a Eio.Net.t ; .. > ->
513513 context ->
514514- account_id:Jmap.Types.id ->
515515- email_ids:Jmap.Types.id list ->
514514+ account_id:string ->
515515+ email_ids:string list ->
516516 unit ->
517517 unit Jmap.Error.result
518518···528528 val move_emails :
529529 < net : 'a Eio.Net.t ; .. > ->
530530 context ->
531531- account_id:Jmap.Types.id ->
532532- email_ids:Jmap.Types.id list ->
533533- mailbox_id:Jmap.Types.id ->
534534- ?remove_from_mailboxes:Jmap.Types.id list ->
531531+ account_id:string ->
532532+ email_ids:string list ->
533533+ mailbox_id:string ->
534534+ ?remove_from_mailboxes:string list ->
535535 unit ->
536536 unit Jmap.Error.result
537537···548548 val import_email :
549549 < net : 'a Eio.Net.t ; .. > ->
550550 context ->
551551- account_id:Jmap.Types.id ->
551551+ account_id:string ->
552552 rfc822:string ->
553553- mailbox_ids:Jmap.Types.id list ->
553553+ mailbox_ids:string list ->
554554 ?keywords:Jmap_email.Keywords.t ->
555555- ?received_at:Jmap.Types.date ->
555555+ ?received_at:Jmap.Date.t ->
556556 unit ->
557557- Jmap.Types.id Jmap.Error.result
557557+ string Jmap.Error.result
558558559559 (** {2 JSON Parsing Functions} *)
560560···611611 Falls back to the first available account if no primary mail account is found.
612612 @param session The JMAP session
613613 @return The account ID to use for mail operations *)
614614- val get_primary_mail_account : Jmap.Session.Session.t -> Jmap.Types.id
614614+ val get_primary_mail_account : Jmap.Session.Session.t -> string
615615end
616616617617(** Response utilities for extracting data from JMAP responses *)
···651651652652 (** Add Email/query method *)
653653 val email_query :
654654- ?account_id:Jmap.Types.id ->
654654+ ?account_id:string ->
655655 ?filter:Yojson.Safe.t ->
656656 ?sort:Jmap.Methods.Comparator.t list ->
657657 ?limit:int ->
···660660661661 (** Add Email/get method with automatic result reference *)
662662 val email_get :
663663- ?account_id:Jmap.Types.id ->
664664- ?ids:Jmap.Types.Id.t list ->
663663+ ?account_id:string ->
664664+ ?ids:Jmap.Id.t list ->
665665 ?properties:string list ->
666666 ?reference_from:string -> (* Call ID to reference *)
667667 t -> t
668668669669 (** Add Email/set method *)
670670 val email_set :
671671- ?account_id:Jmap.Types.id ->
671671+ ?account_id:string ->
672672 ?create:(string * Yojson.Safe.t) list ->
673673- ?update:(Jmap.Types.Id.t * Jmap.Types.Patch.t) list ->
674674- ?destroy:Jmap.Types.Id.t list ->
673673+ ?update:(Jmap.Id.t * Jmap.Patch.t) list ->
674674+ ?destroy:Jmap.Id.t list ->
675675 t -> t
676676677677 (** Add Thread/get method *)
678678 val thread_get :
679679- ?account_id:Jmap.Types.id ->
680680- ?ids:Jmap.Types.Id.t list ->
679679+ ?account_id:string ->
680680+ ?ids:Jmap.Id.t list ->
681681 t -> t
682682683683 (** Add Mailbox/query method *)
684684 val mailbox_query :
685685- ?account_id:Jmap.Types.id ->
685685+ ?account_id:string ->
686686 ?filter:Yojson.Safe.t ->
687687 ?sort:Jmap.Methods.Comparator.t list ->
688688 t -> t
689689690690 (** Add Mailbox/get method *)
691691 val mailbox_get :
692692- ?account_id:Jmap.Types.id ->
693693- ?ids:Jmap.Types.Id.t list ->
692692+ ?account_id:string ->
693693+ ?ids:Jmap.Id.t list ->
694694 t -> t
695695696696 (** Execute the built request *)
···742742 < net : 'a Eio.Net.t ; .. > ->
743743 ctx:context ->
744744 session:Jmap.Session.Session.t ->
745745- ?account_id:Jmap.Types.id ->
745745+ ?account_id:string ->
746746 ?filter:Yojson.Safe.t ->
747747 ?sort:Jmap.Methods.Comparator.t list ->
748748 ?limit:int ->
···755755 < net : 'a Eio.Net.t ; .. > ->
756756 ctx:context ->
757757 session:Jmap.Session.Session.t ->
758758- ?account_id:Jmap.Types.id ->
758758+ ?account_id:string ->
759759 ?properties:string list ->
760760- Jmap.Types.Id.t list ->
760760+ Jmap.Id.t list ->
761761 (Yojson.Safe.t list, Jmap.Error.error) result
762762763763 (** Get all mailboxes *)
···765765 < net : 'a Eio.Net.t ; .. > ->
766766 ctx:context ->
767767 session:Jmap.Session.Session.t ->
768768- ?account_id:Jmap.Types.id ->
768768+ ?account_id:string ->
769769 unit ->
770770 (Yojson.Safe.t list, Jmap.Error.error) result
771771···774774 < net : 'a Eio.Net.t ; .. > ->
775775 ctx:context ->
776776 session:Jmap.Session.Session.t ->
777777- ?account_id:Jmap.Types.id ->
777777+ ?account_id:string ->
778778 string ->
779779 (Yojson.Safe.t option, Jmap.Error.error) result
780780end
···812812 < net : 'a Eio.Net.t ; .. > ->
813813 ctx:context ->
814814 session:Jmap.Session.Session.t ->
815815- ?account_id:Jmap.Types.id ->
815815+ ?account_id:string ->
816816 Yojson.Safe.t ->
817817 (Yojson.Safe.t, Jmap.Error.error) result
818818···823823 < net : 'a Eio.Net.t ; .. > ->
824824 ctx:context ->
825825 session:Jmap.Session.Session.t ->
826826- email_ids:Jmap.Types.Id.t list ->
826826+ email_ids:Jmap.Id.t list ->
827827 (Yojson.Safe.t, Jmap.Error.error) result
828828829829 (** Bulk delete spam/trash emails older than N days *)
···855855 < net : 'a Eio.Net.t ; .. > ->
856856 ctx:context ->
857857 session:Jmap.Session.Session.t ->
858858- ?account_id:Jmap.Types.id ->
858858+ ?account_id:string ->
859859 progress_fn:(progress -> unit) ->
860860 Yojson.Safe.t ->
861861 (Yojson.Safe.t, Jmap.Error.error) result
+122
jmap/jmap/date.ml
···11+(** JMAP Date Implementation *)
22+33+type t = float (* Unix timestamp *)
44+55+(* Basic RFC 3339 parsing - simplified for JMAP usage *)
66+let parse_rfc3339 str =
77+ try
88+ (* Use Unix.strptime if available, otherwise simplified parsing *)
99+ let len = String.length str in
1010+ if len < 19 then failwith "Too short for RFC 3339";
1111+1212+ (* Extract year, month, day, hour, minute, second *)
1313+ let year = int_of_string (String.sub str 0 4) in
1414+ let month = int_of_string (String.sub str 5 2) in
1515+ let day = int_of_string (String.sub str 8 2) in
1616+ let hour = int_of_string (String.sub str 11 2) in
1717+ let minute = int_of_string (String.sub str 14 2) in
1818+ let second = int_of_string (String.sub str 17 2) in
1919+2020+ (* Basic validation *)
2121+ if year < 1970 || year > 9999 then failwith "Invalid year";
2222+ if month < 1 || month > 12 then failwith "Invalid month";
2323+ if day < 1 || day > 31 then failwith "Invalid day";
2424+ if hour < 0 || hour > 23 then failwith "Invalid hour";
2525+ if minute < 0 || minute > 59 then failwith "Invalid minute";
2626+ if second < 0 || second > 59 then failwith "Invalid second";
2727+2828+ (* Convert to Unix timestamp using built-in functions *)
2929+ let tm = {
3030+ Unix.tm_year = year - 1900;
3131+ tm_mon = month - 1;
3232+ tm_mday = day;
3333+ tm_hour = hour;
3434+ tm_min = minute;
3535+ tm_sec = second;
3636+ tm_wday = 0;
3737+ tm_yday = 0;
3838+ tm_isdst = false;
3939+ } in
4040+4141+ (* Handle timezone - simplified to assume UTC for 'Z' suffix *)
4242+ let timestamp =
4343+ if len >= 20 && str.[len-1] = 'Z' then
4444+ (* UTC time - convert to UTC timestamp *)
4545+ let local_time = fst (Unix.mktime tm) in
4646+ let gm_tm = Unix.gmtime local_time in
4747+ let utc_time = fst (Unix.mktime gm_tm) in
4848+ utc_time
4949+ else if len >= 25 && (str.[len-6] = '+' || str.[len-6] = '-') then
5050+ (* Timezone offset specified *)
5151+ let sign = if str.[len-6] = '+' then -1.0 else 1.0 in
5252+ let tz_hours = int_of_string (String.sub str (len-5) 2) in
5353+ let tz_minutes = int_of_string (String.sub str (len-2) 2) in
5454+ let offset = sign *. (float_of_int tz_hours *. 3600.0 +. float_of_int tz_minutes *. 60.0) in
5555+ fst (Unix.mktime tm) +. offset
5656+ else
5757+ (* No timezone - assume local time *)
5858+ fst (Unix.mktime tm)
5959+ in
6060+ Ok timestamp
6161+ with
6262+ | Failure msg -> Error ("Invalid RFC 3339 format: " ^ msg)
6363+ | Invalid_argument _ -> Error "Invalid RFC 3339 format: parsing error"
6464+ | _ -> Error "Invalid RFC 3339 format"
6565+6666+let format_rfc3339 timestamp =
6767+ let tm = Unix.gmtime timestamp in
6868+ Printf.sprintf "%04d-%02d-%02dT%02d:%02d:%02dZ"
6969+ (tm.tm_year + 1900)
7070+ (tm.tm_mon + 1)
7171+ tm.tm_mday
7272+ tm.tm_hour
7373+ tm.tm_min
7474+ tm.tm_sec
7575+7676+let of_timestamp timestamp = timestamp
7777+7878+let to_timestamp date = date
7979+8080+let of_rfc3339 str = parse_rfc3339 str
8181+8282+let to_rfc3339 date = format_rfc3339 date
8383+8484+let now () = Unix.time ()
8585+8686+let validate date =
8787+ if date >= 0.0 && date <= 253402300799.0 (* 9999-12-31T23:59:59Z *) then
8888+ Ok ()
8989+ else
9090+ Error "Date timestamp out of valid range"
9191+9292+let equal date1 date2 =
9393+ (* Equal within 1 second precision *)
9494+ abs_float (date1 -. date2) < 1.0
9595+9696+let compare date1 date2 =
9797+ if date1 < date2 then -1
9898+ else if date1 > date2 then 1
9999+ else 0
100100+101101+let is_before date1 date2 = date1 < date2
102102+103103+let is_after date1 date2 = date1 > date2
104104+105105+let pp ppf date = Format.fprintf ppf "%s" (to_rfc3339 date)
106106+107107+let pp_hum ppf date = Format.fprintf ppf "Date(%s)" (to_rfc3339 date)
108108+109109+let pp_debug ppf date =
110110+ Format.fprintf ppf "Date(%s)" (to_rfc3339 date)
111111+112112+let to_string_debug date =
113113+ Printf.sprintf "Date(%s)" (to_rfc3339 date)
114114+115115+(* JSON serialization *)
116116+let to_json date = `String (to_rfc3339 date)
117117+118118+let of_json = function
119119+ | `String str -> of_rfc3339 str
120120+ | json ->
121121+ let json_str = Yojson.Safe.to_string json in
122122+ Error (Printf.sprintf "Expected JSON string for Date, got: %s" json_str)
+98
jmap/jmap/date.mli
···11+(** JMAP Date data type with RFC 3339 support and JSON serialization.
22+33+ The Date data type is a string in RFC 3339 "date-time" format, optionally
44+ with timezone information. For example: "2014-10-30T14:12:00+08:00" or
55+ "2014-10-30T06:12:00Z".
66+77+ In this OCaml implementation, dates are internally represented as Unix
88+ timestamps (float) for efficient computation, with conversion to/from
99+ RFC 3339 string format handled by the serialization functions.
1010+1111+ {b Note}: When represented as a float, precision may be lost for sub-second
1212+ values. The implementation preserves second-level precision.
1313+1414+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-1.4> RFC 8620, Section 1.4
1515+ @see <https://www.rfc-editor.org/rfc/rfc3339.html> RFC 3339 *)
1616+1717+(** Abstract type representing a JMAP Date. *)
1818+type t
1919+2020+(** JSON serialization interface *)
2121+include Jmap_sigs.JSONABLE with type t := t
2222+2323+(** Pretty-printing interface *)
2424+include Jmap_sigs.PRINTABLE with type t := t
2525+2626+(** {2 Construction and Access} *)
2727+2828+(** Create a Date from a Unix timestamp.
2929+ @param timestamp The Unix timestamp (seconds since epoch).
3030+ @return A Date representing the timestamp. *)
3131+val of_timestamp : float -> t
3232+3333+(** Convert a Date to a Unix timestamp.
3434+ @param date The Date to convert.
3535+ @return The Unix timestamp (seconds since epoch). *)
3636+val to_timestamp : t -> float
3737+3838+(** Create a Date from an RFC 3339 string.
3939+ @param str The RFC 3339 formatted string.
4040+ @return Ok with the parsed Date, or Error if the string is not valid RFC 3339. *)
4141+val of_rfc3339 : string -> (t, string) result
4242+4343+(** Convert a Date to an RFC 3339 string.
4444+ @param date The Date to convert.
4545+ @return The RFC 3339 formatted string. *)
4646+val to_rfc3339 : t -> string
4747+4848+(** Create a Date representing the current time.
4949+ @return A Date set to the current time. *)
5050+val now : unit -> t
5151+5252+(** {2 Validation} *)
5353+5454+(** Validate a Date according to JMAP constraints.
5555+ @param date The Date to validate.
5656+ @return Ok () if valid, Error with description if invalid. *)
5757+val validate : t -> (unit, string) result
5858+5959+(** {2 Comparison and Utilities} *)
6060+6161+(** Compare two Dates for equality.
6262+ @param date1 First Date.
6363+ @param date2 Second Date.
6464+ @return True if equal (within 1 second precision), false otherwise. *)
6565+val equal : t -> t -> bool
6666+6767+(** Compare two Dates chronologically.
6868+ @param date1 First Date.
6969+ @param date2 Second Date.
7070+ @return Negative if date1 < date2, zero if equal, positive if date1 > date2. *)
7171+val compare : t -> t -> int
7272+7373+(** Check if first Date is before second Date.
7474+ @param date1 First Date.
7575+ @param date2 Second Date.
7676+ @return True if date1 is before date2. *)
7777+val is_before : t -> t -> bool
7878+7979+(** Check if first Date is after second Date.
8080+ @param date1 First Date.
8181+ @param date2 Second Date.
8282+ @return True if date1 is after date2. *)
8383+val is_after : t -> t -> bool
8484+8585+(** Pretty-print a Date in RFC3339 format.
8686+ @param ppf The formatter.
8787+ @param date The Date to print. *)
8888+val pp : Format.formatter -> t -> unit
8989+9090+(** Pretty-print a Date for debugging.
9191+ @param ppf The formatter.
9292+ @param date The Date to format. *)
9393+val pp_debug : Format.formatter -> t -> unit
9494+9595+(** Convert a Date to a human-readable string for debugging.
9696+ @param date The Date to format.
9797+ @return A debug string representation. *)
9898+val to_string_debug : t -> string
+4-1
jmap/jmap/dune
···44 (libraries yojson uri unix base64 jmap-sigs)
55 (modules
66 jmap
77- types
77+ id
88+ date
99+ uint
1010+ patch
811 wire
912 session
1013 error
+7-7
jmap/jmap/error.ml
···11-open Types
11+(* Use underlying types directly to avoid circular dependency with Jmap module *)
22open Yojson.Safe.Util
3344type method_error_type = [
···101101 | `Network_error of network_error_kind * string * bool (** kind * message * retryable *)
102102 | `Parse_error of parse_error_kind * string (** kind * context *)
103103 | `Method_error of string * string * method_error_type * string option (** method_name * call_id * type * description *)
104104- | `Set_error of string * id * set_error_type * string option (** method_name * object_id * type * description *)
104104+ | `Set_error of string * string * set_error_type * string option (** method_name * object_id * type * description *)
105105 | `Auth_error of auth_error_kind * string (** kind * message *)
106106 | `Server_error of server_error_kind * string (** kind * message *)
107107 | `Timeout_error of timeout_context * string (** context * message *)
···243243 status : int option;
244244 detail : string option;
245245 limit : string option;
246246- other_fields : Yojson.Safe.t string_map;
246246+ other_fields : (string, Yojson.Safe.t) Hashtbl.t;
247247 }
248248249249 let problem_type t = t.problem_type
···377377 type_ : set_error_type;
378378 description : string option;
379379 properties : string list option;
380380- existing_id : id option;
381381- max_recipients : uint option;
380380+ existing_id : string option;
381381+ max_recipients : int option;
382382 invalid_recipients : string list option;
383383- max_size : uint option;
384384- not_found_blob_ids : id list option;
383383+ max_size : int option;
384384+ not_found_blob_ids : string list option;
385385 }
386386387387 let type_ t = t.type_
+14-14
jmap/jmap/error.mli
···6677 @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-3.6> RFC 8620, Section 3.6 *)
8899-open Types
99+(* Use underlying types directly to avoid circular dependency with Jmap module *)
10101111(** {1 Method-Level Error Types} *)
1212···223223 | `Network_error of network_error_kind * string * bool (** kind * message * retryable *)
224224 | `Parse_error of parse_error_kind * string (** kind * context *)
225225 | `Method_error of string * string * method_error_type * string option (** method_name * call_id * type * description *)
226226- | `Set_error of string * id * set_error_type * string option (** method_name * object_id * type * description *)
226226+ | `Set_error of string * string * set_error_type * string option (** method_name * object_id * type * description *)
227227 | `Auth_error of auth_error_kind * string (** kind * message *)
228228 | `Server_error of server_error_kind * string (** kind * message *)
229229 | `Timeout_error of timeout_context * string (** context * message *)
···296296 val status : t -> int option
297297 val detail : t -> string option
298298 val limit : t -> string option
299299- val other_fields : t -> Yojson.Safe.t string_map
299299+ val other_fields : t -> (string, Yojson.Safe.t) Hashtbl.t
300300301301 val v :
302302 ?status:int ->
303303 ?detail:string ->
304304 ?limit:string ->
305305- ?other_fields:Yojson.Safe.t string_map ->
305305+ ?other_fields:(string, Yojson.Safe.t) Hashtbl.t ->
306306 string ->
307307 t
308308end
···348348 val type_ : t -> set_error_type
349349 val description : t -> string option
350350 val properties : t -> string list option
351351- val existing_id : t -> id option
352352- val max_recipients : t -> uint option
351351+ val existing_id : t -> string option
352352+ val max_recipients : t -> int option
353353 val invalid_recipients : t -> string list option
354354- val max_size : t -> uint option
355355- val not_found_blob_ids : t -> id list option
354354+ val max_size : t -> int option
355355+ val not_found_blob_ids : t -> string list option
356356357357 val v :
358358 ?description:string ->
359359 ?properties:string list ->
360360- ?existing_id:id ->
361361- ?max_recipients:uint ->
360360+ ?existing_id:string ->
361361+ ?max_recipients:int ->
362362 ?invalid_recipients:string list ->
363363- ?max_size:uint ->
364364- ?not_found_blob_ids:id list ->
363363+ ?max_size:int ->
364364+ ?not_found_blob_ids:string list ->
365365 set_error_type ->
366366 t
367367···400400val method_error : ?description:string -> method_error_type -> error
401401402402(** Create a SetItem error *)
403403-val set_item_error : id -> ?description:string -> set_error_type -> error
403403+val set_item_error : string -> ?description:string -> set_error_type -> error
404404405405(** Create an auth error *)
406406val auth_error : string -> error
···412412val of_method_error : Method_error.t -> error
413413414414(** Convert a Set_error.t to error for a specific ID *)
415415-val of_set_error : id -> Set_error.t -> error
415415+val of_set_error : string -> Set_error.t -> error
416416417417(** Create a parse error (alias) *)
418418val parse : string -> error
+55
jmap/jmap/id.ml
···11+(** JMAP Id Implementation *)
22+33+type t = string
44+type id = t
55+66+let is_base64url_char c =
77+ (c >= 'A' && c <= 'Z') ||
88+ (c >= 'a' && c <= 'z') ||
99+ (c >= '0' && c <= '9') ||
1010+ c = '-' || c = '_'
1111+1212+let is_valid_string str =
1313+ let len = String.length str in
1414+ len > 0 && len <= 255 &&
1515+ let rec check i =
1616+ if i >= len then true
1717+ else if is_base64url_char str.[i] then check (i + 1)
1818+ else false
1919+ in
2020+ check 0
2121+2222+let of_string str =
2323+ if is_valid_string str then Ok str
2424+ else
2525+ let len = String.length str in
2626+ if len = 0 then Error "Id cannot be empty"
2727+ else if len > 255 then Error "Id cannot be longer than 255 octets"
2828+ else Error "Id contains invalid characters (must be base64url alphabet only)"
2929+3030+let to_string id = id
3131+3232+let pp ppf id = Format.fprintf ppf "%s" id
3333+3434+let pp_hum ppf id = Format.fprintf ppf "Id(%s)" id
3535+3636+let validate id =
3737+ if is_valid_string id then Ok ()
3838+ else Error "Invalid Id format"
3939+4040+let equal = String.equal
4141+4242+let compare = String.compare
4343+4444+let pp_debug ppf id = Format.fprintf ppf "Id(%s)" id
4545+4646+let to_string_debug id = Printf.sprintf "Id(%s)" id
4747+4848+(* JSON serialization *)
4949+let to_json id = `String id
5050+5151+let of_json = function
5252+ | `String str -> of_string str
5353+ | json ->
5454+ let json_str = Yojson.Safe.to_string json in
5555+ Error (Printf.sprintf "Expected JSON string for Id, got: %s" json_str)
+75
jmap/jmap/id.mli
···11+(** JMAP Id data type with validation and JSON serialization.
22+33+ The Id data type is a string of 1 to 255 octets in length and MUST consist
44+ only of characters from the base64url alphabet, as defined in Section 5 of
55+ RFC 4648. This includes ASCII alphanumeric characters, plus the characters
66+ '-' and '_'.
77+88+ Ids are used to identify JMAP objects within an account. They are assigned
99+ by the server and are immutable once assigned. The same id MUST refer to
1010+ the same object throughout the lifetime of the object.
1111+1212+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-1.2> RFC 8620, Section 1.2 *)
1313+1414+(** Abstract type representing a JMAP Id. *)
1515+type t
1616+type id = t
1717+1818+(** JSON serialization interface *)
1919+include Jmap_sigs.JSONABLE with type t := t
2020+2121+(** Pretty-printing interface *)
2222+include Jmap_sigs.PRINTABLE with type t := t
2323+2424+(** {2 Construction and Access} *)
2525+2626+(** Create a new Id from a string.
2727+ @param str The string representation.
2828+ @return Ok with the created Id, or Error if the string violates Id constraints. *)
2929+val of_string : string -> (t, string) result
3030+3131+(** Convert an Id to its string representation.
3232+ @param id The Id to convert.
3333+ @return The string representation. *)
3434+val to_string : t -> string
3535+3636+(** Pretty-print an Id.
3737+ @param ppf The formatter.
3838+ @param id The Id to print. *)
3939+val pp : Format.formatter -> t -> unit
4040+4141+(** {2 Validation} *)
4242+4343+(** Check if a string is a valid JMAP Id.
4444+ @param str The string to validate.
4545+ @return True if the string meets Id requirements, false otherwise. *)
4646+val is_valid_string : string -> bool
4747+4848+(** Validate an Id according to JMAP constraints.
4949+ @param id The Id to validate.
5050+ @return Ok () if valid, Error with description if invalid. *)
5151+val validate : t -> (unit, string) result
5252+5353+(** {2 Comparison and Utilities} *)
5454+5555+(** Compare two Ids for equality.
5656+ @param id1 First Id.
5757+ @param id2 Second Id.
5858+ @return True if equal, false otherwise. *)
5959+val equal : t -> t -> bool
6060+6161+(** Compare two Ids lexicographically.
6262+ @param id1 First Id.
6363+ @param id2 Second Id.
6464+ @return Negative if id1 < id2, zero if equal, positive if id1 > id2. *)
6565+val compare : t -> t -> int
6666+6767+(** Pretty-print an Id for debugging.
6868+ @param ppf The formatter.
6969+ @param id The Id to format. *)
7070+val pp_debug : Format.formatter -> t -> unit
7171+7272+(** Convert an Id to a human-readable string for debugging.
7373+ @param id The Id to format.
7474+ @return A debug string representation. *)
7575+val to_string_debug : t -> string
+5-7
jmap/jmap/jmap.ml
···11-module Types = Types
22-33-(* Backwards compatibility aliases *)
44-module Id = Types.Id
55-module Date = Types.Date
66-module UInt = Types.UInt
77-module Patch = Types.Patch
11+(* Core type modules *)
22+module Id = Id
33+module Date = Date
44+module UInt = Uint
55+module Patch = Patch
8697module Capability = Jmap_capability
108
+10-19
jmap/jmap/jmap.mli
···25252626(** {1 Core Types and Methods} *)
27272828-(** JMAP core types with unified interface
2929-3030- This module consolidates all fundamental JMAP data types including Id, Date,
3131- UInt, Patch, and collection types. It provides both modern structured modules
3232- and legacy type aliases for compatibility.
3333-3434- @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-1> RFC 8620, Section 1 *)
3535-module Types = Types
2828+(** {2 Core Type Modules} *)
36293737-(** {2 Backwards Compatibility Aliases} *)
3838-3939-(** JMAP Id data type (alias to Types.Id)
3030+(** JMAP Id data type with validation and JSON serialization
4031 @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-1.2> RFC 8620, Section 1.2 *)
4141-module Id = Types.Id
3232+module Id = Id
42334343-(** JMAP Date data type (alias to Types.Date)
3434+(** JMAP Date data type with RFC 3339 support and JSON serialization
4435 @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-1.4> RFC 8620, Section 1.4 *)
4545-module Date = Types.Date
3636+module Date = Date
46374747-(** JMAP UnsignedInt data type (alias to Types.UInt)
3838+(** JMAP UnsignedInt data type with range validation and JSON serialization
4839 @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-1.3> RFC 8620, Section 1.3 *)
4949-module UInt = Types.UInt
4040+module UInt = Uint
50415151-(** JMAP Patch Object (alias to Types.Patch)
4242+(** JMAP Patch Object for property updates with JSON serialization
5243 @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-5.3> RFC 8620, Section 5.3 *)
5353-module Patch = Types.Patch
4444+module Patch = Patch
54455546(** JMAP Capability management (alias to Jmap_capability)
5647 @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-2> RFC 8620, Section 2 *)
···124115{[
125116 (* OCaml 5.1 required for Eio *)
126117 open Jmap
127127- open Jmap.Types
118118+ open Jmap.Id
128119 open Jmap.Wire
129120 open Jmap.Methods
130121
+24-24
jmap/jmap/jmap_binary.ml
···11-open Types
11+(* Use underlying types directly to avostring circular dependency with Jmap module *)
2233module Upload_response = struct
44 type t = {
55- account_id : id;
66- blob_id : id;
55+ account_string : string;
66+ blob_string : string;
77 type_ : string;
88- size : uint;
88+ size : int;
99 }
10101111- let account_id t = t.account_id
1212- let blob_id t = t.blob_id
1111+ let account_string t = t.account_string
1212+ let blob_string t = t.blob_string
1313 let type_ t = t.type_
1414 let size t = t.size
15151616- let v ~account_id ~blob_id ~type_ ~size () =
1717- { account_id; blob_id; type_; size }
1616+ let v ~account_string ~blob_string ~type_ ~size () =
1717+ { account_string; blob_string; type_; size }
1818end
19192020module Blob_copy_args = struct
2121 type t = {
2222- from_account_id : id;
2323- account_id : id;
2424- blob_ids : id list;
2222+ from_account_string : string;
2323+ account_string : string;
2424+ blob_strings : string list;
2525 }
26262727- let from_account_id t = t.from_account_id
2828- let account_id t = t.account_id
2929- let blob_ids t = t.blob_ids
2727+ let from_account_string t = t.from_account_string
2828+ let account_string t = t.account_string
2929+ let blob_strings t = t.blob_strings
30303131- let v ~from_account_id ~account_id ~blob_ids () =
3232- { from_account_id; account_id; blob_ids }
3131+ let v ~from_account_string ~account_string ~blob_strings () =
3232+ { from_account_string; account_string; blob_strings }
3333end
34343535module Blob_copy_response = struct
3636 type t = {
3737- from_account_id : id;
3838- account_id : id;
3939- copied : id id_map option;
4040- not_copied : Error.Set_error.t id_map option;
3737+ from_account_string : string;
3838+ account_string : string;
3939+ copied : (string, string) Hashtbl.t option;
4040+ not_copied : (string, Error.Set_error.t) Hashtbl.t option;
4141 }
42424343- let from_account_id t = t.from_account_id
4444- let account_id t = t.account_id
4343+ let from_account_string t = t.from_account_string
4444+ let account_string t = t.account_string
4545 let copied t = t.copied
4646 let not_copied t = t.not_copied
47474848- let v ~from_account_id ~account_id ?copied ?not_copied () =
4949- { from_account_id; account_id; copied; not_copied }
4848+ let v ~from_account_string ~account_string ?copied ?not_copied () =
4949+ { from_account_string; account_string; copied; not_copied }
5050end
+22-22
jmap/jmap/jmap_binary.mli
···11(** JMAP Binary Data Handling.
2233- This module provides types for handling binary data (blobs) in JMAP.
33+ This module provstringes types for handling binary data (blobs) in JMAP.
44 Binary data is uploaded and downloaded separately from regular JMAP
55 method calls, using dedicated HTTP endpoints.
66···12121313 @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-6> RFC 8620, Section 6 *)
14141515-open Types
1515+(* Use underlying types directly to avostring circular dependency with Jmap module *)
16161717(** Response from uploading binary data.
1818 @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-6.1> RFC 8620, Section 6.1 *)
1919module Upload_response : sig
2020 type t
21212222- val account_id : t -> id
2323- val blob_id : t -> id
2222+ val account_string : t -> string
2323+ val blob_string : t -> string
2424 val type_ : t -> string
2525- val size : t -> uint
2525+ val size : t -> int
26262727 val v :
2828- account_id:id ->
2929- blob_id:id ->
2828+ account_string:string ->
2929+ blob_string:string ->
3030 type_:string ->
3131- size:uint ->
3131+ size:int ->
3232 unit ->
3333 t
3434end
···3838module Blob_copy_args : sig
3939 type t
40404141- val from_account_id : t -> id
4242- val account_id : t -> id
4343- val blob_ids : t -> id list
4141+ val from_account_string : t -> string
4242+ val account_string : t -> string
4343+ val blob_strings : t -> string list
44444545 val v :
4646- from_account_id:id ->
4747- account_id:id ->
4848- blob_ids:id list ->
4646+ from_account_string:string ->
4747+ account_string:string ->
4848+ blob_strings:string list ->
4949 unit ->
5050 t
5151end
···5555module Blob_copy_response : sig
5656 type t
57575858- val from_account_id : t -> id
5959- val account_id : t -> id
6060- val copied : t -> id id_map option
6161- val not_copied : t -> Error.Set_error.t id_map option
5858+ val from_account_string : t -> string
5959+ val account_string : t -> string
6060+ val copied : t -> (string, string) Hashtbl.t option
6161+ val not_copied : t -> (string, Error.Set_error.t) Hashtbl.t option
62626363 val v :
6464- from_account_id:id ->
6565- account_id:id ->
6666- ?copied:id id_map ->
6767- ?not_copied:Error.Set_error.t id_map ->
6464+ from_account_string:string ->
6565+ account_string:string ->
6666+ ?copied:(string, string) Hashtbl.t ->
6767+ ?not_copied:(string, Error.Set_error.t) Hashtbl.t ->
6868 unit ->
6969 t
7070end
+8-8
jmap/jmap/jmap_client.mli
···6677 @see <https://www.rfc-editor.org/rfc/rfc8620.html> RFC 8620: Core JMAP *)
8899-open Types
99+(* Use underlying types directly to avoid circular dependency with Jmap module *)
1010open Jmap_protocol
11111212(** {1 Client Type} *)
···117117 @return The blob ID or an error. *)
118118val upload_blob :
119119 t ->
120120- account_id:id ->
120120+ account_id:string ->
121121 data:string ->
122122 ?content_type:string ->
123123 unit ->
124124- (id, error) result
124124+ (string, error) result
125125126126(** Download binary data from the server.
127127 @param t The client instance.
···131131 @return The binary data or an error. *)
132132val download_blob :
133133 t ->
134134- account_id:id ->
135135- blob_id:id ->
134134+ account_id:string ->
135135+ blob_id:string ->
136136 ?name:string ->
137137 unit ->
138138 (string, error) result
···148148 @return The download URL. *)
149149val get_download_url :
150150 t ->
151151- account_id:id ->
152152- blob_id:id ->
151151+ account_id:string ->
152152+ blob_id:string ->
153153 ?name:string ->
154154 ?content_type:string ->
155155 unit ->
···159159 @param t The client instance.
160160 @param account_id The account ID.
161161 @return The upload URL. *)
162162-val get_upload_url : t -> account_id:id -> Uri.t
162162+val get_upload_url : t -> account_id:string -> Uri.t
163163164164(** {1 Utilities} *)
165165
+53-53
jmap/jmap/jmap_methods.ml
···11-open Types
11+(* Use underlying types directly to avoid circular dependency with Jmap module *)
22open Jmap_method_names
3344type generic_record
5566module Get_args = struct
77 type 'record t = {
88- account_id : id;
99- ids : id list option;
88+ account_id : string;
99+ ids : string list option;
1010 properties : string list option;
1111 }
1212···3333 | Some ref_json -> ("#ids", ref_json) :: base_fields
3434 | None ->
3535 match t.ids with
3636- | Some id_list -> ("ids", (`List (List.map (fun id -> `String id) id_list) : Yojson.Safe.t)) :: base_fields
3636+ | Some id_list -> ("ids", (`List (List.map (fun string -> `String string) id_list) : Yojson.Safe.t)) :: base_fields
3737 | None -> base_fields
3838 in
3939 let fields = match t.properties with
···45454646module Get_response = struct
4747 type 'record t = {
4848- account_id : id;
4848+ account_id : string;
4949 state : string;
5050 list : 'record list;
5151- not_found : id list;
5151+ not_found : string list;
5252 }
53535454 let account_id t = t.account_id
···81818282module Changes_args = struct
8383 type t = {
8484- account_id : id;
8484+ account_id : string;
8585 since_state : string;
8686- max_changes : uint option;
8686+ max_changes : int option;
8787 }
88888989 let account_id t = t.account_id
···107107108108module Changes_response = struct
109109 type t = {
110110- account_id : id;
110110+ account_id : string;
111111 old_state : string;
112112 new_state : string;
113113 has_more_changes : bool;
114114- created : id list;
115115- updated : id list;
116116- destroyed : id list;
114114+ created : string list;
115115+ updated : string list;
116116+ destroyed : string list;
117117 updated_properties : string list option;
118118 }
119119···157157 | exn -> Error (Error.parse_error ("Changes_response parse error: " ^ Printexc.to_string exn))
158158end
159159160160-type patch_object = (json_pointer * Yojson.Safe.t) list
160160+type patch_object = (string * Yojson.Safe.t) list
161161162162module Set_args = struct
163163 type ('create_record, 'update_record) t = {
164164- account_id : id;
164164+ account_id : string;
165165 if_in_state : string option;
166166- create : 'create_record id_map option;
167167- update : 'update_record id_map option;
168168- destroy : id list option;
166166+ create : (string, 'create_record) Hashtbl.t option;
167167+ update : (string, 'update_record) Hashtbl.t option;
168168+ destroy : string list option;
169169 on_success_destroy_original : bool option;
170170 destroy_from_if_in_state : string option;
171171 on_destroy_remove_emails : bool option;
···212212 | None -> fields
213213 in
214214 let fields = match t.destroy with
215215- | Some destroy_list -> ("destroy", (`List (List.map (fun id -> `String id) destroy_list) : Yojson.Safe.t)) :: fields
215215+ | Some destroy_list -> ("destroy", (`List (List.map (fun string -> `String string) destroy_list) : Yojson.Safe.t)) :: fields
216216 | None -> fields
217217 in
218218 let fields = match t.on_success_destroy_original with
···232232233233module Set_response = struct
234234 type ('created_record_info, 'updated_record_info) t = {
235235- account_id : id;
235235+ account_id : string;
236236 old_state : string option;
237237 new_state : string;
238238- created : 'created_record_info id_map option;
239239- updated : 'updated_record_info option id_map option;
240240- destroyed : id list option;
241241- not_created : Error.Set_error.t id_map option;
242242- not_updated : Error.Set_error.t id_map option;
243243- not_destroyed : Error.Set_error.t id_map option;
238238+ created : (string, 'created_record_info) Hashtbl.t option;
239239+ updated : (string, 'updated_record_info option) Hashtbl.t option;
240240+ destroyed : string list option;
241241+ not_created : (string, Error.Set_error.t) Hashtbl.t option;
242242+ not_updated : (string, Error.Set_error.t) Hashtbl.t option;
243243+ not_destroyed : (string, Error.Set_error.t) Hashtbl.t option;
244244 }
245245246246 let account_id t = t.account_id
···356356357357module Copy_args = struct
358358 type 'copy_record_override t = {
359359- from_account_id : id;
359359+ from_account_id : string;
360360 if_from_in_state : string option;
361361- account_id : id;
361361+ account_id : string;
362362 if_in_state : string option;
363363- create : 'copy_record_override id_map;
363363+ create : (string, 'copy_record_override) Hashtbl.t;
364364 on_success_destroy_original : bool;
365365 destroy_from_if_in_state : string option;
366366 }
···382382383383module Copy_response = struct
384384 type 'created_record_info t = {
385385- from_account_id : id;
386386- account_id : id;
385385+ from_account_id : string;
386386+ account_id : string;
387387 old_state : string option;
388388 new_state : string;
389389- created : 'created_record_info id_map option;
390390- not_created : Error.Set_error.t id_map option;
389389+ created : (string, 'created_record_info) Hashtbl.t option;
390390+ not_created : (string, Error.Set_error.t) Hashtbl.t option;
391391 }
392392393393 let from_account_id t = t.from_account_id
···465465 is_ascending : bool option;
466466 collation : string option;
467467 keyword : string option;
468468- other_fields : Yojson.Safe.t string_map;
468468+ other_fields : (string, Yojson.Safe.t) Hashtbl.t;
469469 }
470470471471 let property t = t.property
···537537538538module Query_args = struct
539539 type t = {
540540- account_id : id;
540540+ account_id : string;
541541 filter : Filter.t option;
542542 sort : Comparator.t list option;
543543- position : jint option;
544544- anchor : id option;
545545- anchor_offset : jint option;
546546- limit : uint option;
543543+ position : int option;
544544+ anchor : string option;
545545+ anchor_offset : int option;
546546+ limit : int option;
547547 calculate_total : bool option;
548548 collapse_threads : bool option;
549549 sort_as_tree : bool option;
···618618619619module Query_response = struct
620620 type t = {
621621- account_id : id;
621621+ account_id : string;
622622 query_state : string;
623623 can_calculate_changes : bool;
624624- position : uint;
625625- ids : id list;
626626- total : uint option;
627627- limit : uint option;
624624+ position : int;
625625+ ids : string list;
626626+ total : int option;
627627+ limit : int option;
628628 }
629629630630 let account_id t = t.account_id
···669669670670module Added_item = struct
671671 type t = {
672672- id : id;
673673- index : uint;
672672+ string : string;
673673+ index : int;
674674 }
675675676676- let id t = t.id
676676+ let string t = t.string
677677 let index t = t.index
678678679679- let v ~id ~index () = { id; index }
679679+ let v ~string ~index () = { string; index }
680680end
681681682682module Query_changes_args = struct
683683 type t = {
684684- account_id : id;
684684+ account_id : string;
685685 filter : Filter.t option;
686686 sort : Comparator.t list option;
687687 since_query_state : string;
688688- max_changes : uint option;
689689- up_to_id : id option;
688688+ max_changes : int option;
689689+ up_to_id : string option;
690690 calculate_total : bool option;
691691 collapse_threads : bool option;
692692 }
···708708709709module Query_changes_response = struct
710710 type t = {
711711- account_id : id;
711711+ account_id : string;
712712 old_query_state : string;
713713 new_query_state : string;
714714- total : uint option;
715715- removed : id list;
714714+ total : int option;
715715+ removed : string list;
716716 added : Added_item.t list;
717717 }
718718
+102-102
jmap/jmap/jmap_methods.mli
···1919 @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-4> RFC 8620, Section 4 (Core/echo)
2020 @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-5> RFC 8620, Section 5 (Standard Methods) *)
21212222-open Types
2222+(* Use underlying types directly to avoid circular dependency with Jmap module *)
23232424(** {1 Generic Types} *)
2525···54545555 (** Get the account ID for this request.
5656 @return The account ID to retrieve objects from *)
5757- val account_id : 'record t -> id
5757+ val account_id : 'record t -> string
58585959 (** Get the list of object IDs to retrieve.
6060 @return Specific IDs to fetch, or None for all objects *)
6161- val ids : 'record t -> id list option
6161+ val ids : 'record t -> string list option
62626363 (** Get the list of properties to return.
6464 @return Specific properties to include, or None for all properties *)
···7070 @param ?properties Optional list of properties to return (None = all properties)
7171 @return New get arguments object *)
7272 val v :
7373- account_id:id ->
7474- ?ids:id list ->
7373+ account_id:string ->
7474+ ?ids:string list ->
7575 ?properties:string list ->
7676 unit ->
7777 'record t
···109109(** Response for /get methods.
110110111111 The /get method response contains the retrieved objects along with
112112- metadata about the current state and any objects that weren't found.
112112+ metadata about the current state and any objects that werent found.
113113114114 The response includes:
115115 - Retrieved objects in the same order as requested (or arbitrary order if all objects)
116116 - Current state string for change tracking
117117- - List of IDs that weren't found
117117+ - List of IDs that werent found
118118119119 @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-5.1> RFC 8620, Section 5.1 *)
120120module Get_response : sig
···122122123123 (** Get the account ID for this response.
124124 @return The account ID the objects were retrieved from *)
125125- val account_id : 'record t -> id
125125+ val account_id : 'record t -> string
126126127127 (** Get the current state string for the object type.
128128 @return State string for change tracking *)
···132132 @return List of objects in requested order (or arbitrary order if all) *)
133133 val list : 'record t -> 'record list
134134135135- (** Get the list of IDs that weren't found.
136136- @return IDs that don't exist or are not accessible *)
137137- val not_found : 'record t -> id list
135135+ (** Get the list of IDs that werent found.
136136+ @return IDs that dont exist or are not accessible *)
137137+ val not_found : 'record t -> string list
138138139139 (** Create a new get response.
140140 @param account_id The account ID
141141 @param state Current state string
142142 @param list Retrieved objects
143143- @param not_found IDs that weren't found
143143+ @param not_found IDs that werent found
144144 @return New get response object *)
145145 val v :
146146- account_id:id ->
146146+ account_id:string ->
147147 state:string ->
148148 list:'record list ->
149149- not_found:id list ->
149149+ not_found:string list ->
150150 unit ->
151151 'record t
152152···171171module Changes_args : sig
172172 type t
173173174174- val account_id : t -> id
174174+ val account_id : t -> string
175175 val since_state : t -> string
176176- val max_changes : t -> uint option
176176+ val max_changes : t -> int option
177177178178 val v :
179179- account_id:id ->
179179+ account_id:string ->
180180 since_state:string ->
181181- ?max_changes:uint ->
181181+ ?max_changes:int ->
182182 unit ->
183183 t
184184···193193module Changes_response : sig
194194 type t
195195196196- val account_id : t -> id
196196+ val account_id : t -> string
197197 val old_state : t -> string
198198 val new_state : t -> string
199199 val has_more_changes : t -> bool
200200- val created : t -> id list
201201- val updated : t -> id list
202202- val destroyed : t -> id list
200200+ val created : t -> string list
201201+ val updated : t -> string list
202202+ val destroyed : t -> string list
203203 val updated_properties : t -> string list option
204204205205 val v :
206206- account_id:id ->
206206+ account_id:string ->
207207 old_state:string ->
208208 new_state:string ->
209209 has_more_changes:bool ->
210210- created:id list ->
211211- updated:id list ->
212212- destroyed:id list ->
210210+ created:string list ->
211211+ updated:string list ->
212212+ destroyed:string list ->
213213 ?updated_properties:string list ->
214214 unit ->
215215 t
···231231(** Patch object for /set update.
232232 A list of (JSON Pointer path, value) pairs.
233233 @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-5.3> RFC 8620, Section 5.3 *)
234234-type patch_object = (json_pointer * Yojson.Safe.t) list
234234+type patch_object = (string * Yojson.Safe.t) list
235235236236(** Arguments for /set methods.
237237 ['create_record] is the record type without server-set/immutable fields.
···240240module Set_args : sig
241241 type ('create_record, 'update_record) t
242242243243- val account_id : ('a, 'b) t -> id
243243+ val account_id : ('a, 'b) t -> string
244244 val if_in_state : ('a, 'b) t -> string option
245245- val create : ('a, 'b) t -> 'a id_map option
246246- val update : ('a, 'b) t -> 'b id_map option
247247- val destroy : ('a, 'b) t -> id list option
245245+ val create : ('a, 'b) t -> (string, 'a) Hashtbl.t option
246246+ val update : ('a, 'b) t -> (string, 'b) Hashtbl.t option
247247+ val destroy : ('a, 'b) t -> string list option
248248 val on_success_destroy_original : ('a, 'b) t -> bool option
249249 val destroy_from_if_in_state : ('a, 'b) t -> string option
250250 val on_destroy_remove_emails : ('a, 'b) t -> bool option
251251252252 val v :
253253- account_id:id ->
253253+ account_id:string ->
254254 ?if_in_state:string ->
255255- ?create:'a id_map ->
256256- ?update:'b id_map ->
257257- ?destroy:id list ->
255255+ ?create:(string, 'a) Hashtbl.t ->
256256+ ?update:(string, 'b) Hashtbl.t ->
257257+ ?destroy:string list ->
258258 ?on_success_destroy_original:bool ->
259259 ?destroy_from_if_in_state:string ->
260260 ?on_destroy_remove_emails:bool ->
···281281module Set_response : sig
282282 type ('created_record_info, 'updated_record_info) t
283283284284- val account_id : ('a, 'b) t -> id
284284+ val account_id : ('a, 'b) t -> string
285285 val old_state : ('a, 'b) t -> string option
286286 val new_state : ('a, 'b) t -> string
287287- val created : ('a, 'b) t -> 'a id_map option
288288- val updated : ('a, 'b) t -> 'b option id_map option
289289- val destroyed : ('a, 'b) t -> id list option
290290- val not_created : ('a, 'b) t -> Error.Set_error.t id_map option
291291- val not_updated : ('a, 'b) t -> Error.Set_error.t id_map option
292292- val not_destroyed : ('a, 'b) t -> Error.Set_error.t id_map option
287287+ val created : ('a, 'b) t -> (string, 'a) Hashtbl.t option
288288+ val updated : ('a, 'b) t -> (string, 'b option) Hashtbl.t option
289289+ val destroyed : ('a, 'b) t -> string list option
290290+ val not_created : ('a, 'b) t -> (string, Error.Set_error.t) Hashtbl.t option
291291+ val not_updated : ('a, 'b) t -> (string, Error.Set_error.t) Hashtbl.t option
292292+ val not_destroyed : ('a, 'b) t -> (string, Error.Set_error.t) Hashtbl.t option
293293294294 val v :
295295- account_id:id ->
295295+ account_id:string ->
296296 ?old_state:string ->
297297 new_state:string ->
298298- ?created:'a id_map ->
299299- ?updated:'b option id_map ->
300300- ?destroyed:id list ->
301301- ?not_created:Error.Set_error.t id_map ->
302302- ?not_updated:Error.Set_error.t id_map ->
303303- ?not_destroyed:Error.Set_error.t id_map ->
298298+ ?created:(string, 'a) Hashtbl.t ->
299299+ ?updated:(string, 'b option) Hashtbl.t ->
300300+ ?destroyed:string list ->
301301+ ?not_created:(string, Error.Set_error.t) Hashtbl.t ->
302302+ ?not_updated:(string, Error.Set_error.t) Hashtbl.t ->
303303+ ?not_destroyed:(string, Error.Set_error.t) Hashtbl.t ->
304304 unit ->
305305 ('a, 'b) t
306306···323323end
324324325325(** Arguments for /copy methods.
326326- ['copy_record_override] contains the record id and override properties.
326326+ ['copy_record_override] contains the record string and override properties.
327327 @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-5.4> RFC 8620, Section 5.4 *)
328328module Copy_args : sig
329329 type 'copy_record_override t
330330331331- val from_account_id : 'a t -> id
331331+ val from_account_id : 'a t -> string
332332 val if_from_in_state : 'a t -> string option
333333- val account_id : 'a t -> id
333333+ val account_id : 'a t -> string
334334 val if_in_state : 'a t -> string option
335335- val create : 'a t -> 'a id_map
335335+ val create : 'a t -> (string, 'a) Hashtbl.t
336336 val on_success_destroy_original : 'a t -> bool
337337 val destroy_from_if_in_state : 'a t -> string option
338338339339 val v :
340340- from_account_id:id ->
340340+ from_account_id:string ->
341341 ?if_from_in_state:string ->
342342- account_id:id ->
342342+ account_id:string ->
343343 ?if_in_state:string ->
344344- create:'a id_map ->
344344+ create:(string, 'a) Hashtbl.t ->
345345 ?on_success_destroy_original:bool ->
346346 ?destroy_from_if_in_state:string ->
347347 unit ->
···354354module Copy_response : sig
355355 type 'created_record_info t
356356357357- val from_account_id : 'a t -> id
358358- val account_id : 'a t -> id
357357+ val from_account_id : 'a t -> string
358358+ val account_id : 'a t -> string
359359 val old_state : 'a t -> string option
360360 val new_state : 'a t -> string
361361- val created : 'a t -> 'a id_map option
362362- val not_created : 'a t -> Error.Set_error.t id_map option
361361+ val created : 'a t -> (string, 'a) Hashtbl.t option
362362+ val not_created : 'a t -> (string, Error.Set_error.t) Hashtbl.t option
363363364364 val v :
365365- from_account_id:id ->
366366- account_id:id ->
365365+ from_account_id:string ->
366366+ account_id:string ->
367367 ?old_state:string ->
368368 new_state:string ->
369369- ?created:'a id_map ->
370370- ?not_created:Error.Set_error.t id_map ->
369369+ ?created:(string, 'a) Hashtbl.t ->
370370+ ?not_created:(string, Error.Set_error.t) Hashtbl.t ->
371371 unit ->
372372 'a t
373373end
···478478 val is_ascending : t -> bool option
479479 val collation : t -> string option
480480 val keyword : t -> string option
481481- val other_fields : t -> Yojson.Safe.t string_map
481481+ val other_fields : t -> (string, Yojson.Safe.t) Hashtbl.t
482482483483 val v :
484484 property:string ->
485485 ?is_ascending:bool ->
486486 ?collation:string ->
487487 ?keyword:string ->
488488- ?other_fields:Yojson.Safe.t string_map ->
488488+ ?other_fields:(string, Yojson.Safe.t) Hashtbl.t ->
489489 unit ->
490490 t
491491···505505module Query_args : sig
506506 type t
507507508508- val account_id : t -> id
508508+ val account_id : t -> string
509509 val filter : t -> Filter.t option
510510 val sort : t -> Comparator.t list option
511511- val position : t -> jint option
512512- val anchor : t -> id option
513513- val anchor_offset : t -> jint option
514514- val limit : t -> uint option
511511+ val position : t -> int option
512512+ val anchor : t -> string option
513513+ val anchor_offset : t -> int option
514514+ val limit : t -> int option
515515 val calculate_total : t -> bool option
516516 val collapse_threads : t -> bool option
517517 val sort_as_tree : t -> bool option
518518 val filter_as_tree : t -> bool option
519519520520 val v :
521521- account_id:id ->
521521+ account_id:string ->
522522 ?filter:Filter.t ->
523523 ?sort:Comparator.t list ->
524524- ?position:jint ->
525525- ?anchor:id ->
526526- ?anchor_offset:jint ->
527527- ?limit:uint ->
524524+ ?position:int ->
525525+ ?anchor:string ->
526526+ ?anchor_offset:int ->
527527+ ?limit:int ->
528528 ?calculate_total:bool ->
529529 ?collapse_threads:bool ->
530530 ?sort_as_tree:bool ->
···543543module Query_response : sig
544544 type t
545545546546- val account_id : t -> id
546546+ val account_id : t -> string
547547 val query_state : t -> string
548548 val can_calculate_changes : t -> bool
549549- val position : t -> uint
550550- val ids : t -> id list
551551- val total : t -> uint option
552552- val limit : t -> uint option
549549+ val position : t -> int
550550+ val ids : t -> string list
551551+ val total : t -> int option
552552+ val limit : t -> int option
553553554554 val v :
555555- account_id:id ->
555555+ account_id:string ->
556556 query_state:string ->
557557 can_calculate_changes:bool ->
558558- position:uint ->
559559- ids:id list ->
560560- ?total:uint ->
561561- ?limit:uint ->
558558+ position:int ->
559559+ ids:string list ->
560560+ ?total:int ->
561561+ ?limit:int ->
562562 unit ->
563563 t
564564···581581module Added_item : sig
582582 type t
583583584584- val id : t -> id
585585- val index : t -> uint
584584+ val string : t -> string
585585+ val index : t -> int
586586587587 val v :
588588- id:id ->
589589- index:uint ->
588588+ string:string ->
589589+ index:int ->
590590 unit ->
591591 t
592592end
···596596module Query_changes_args : sig
597597 type t
598598599599- val account_id : t -> id
599599+ val account_id : t -> string
600600 val filter : t -> Filter.t option
601601 val sort : t -> Comparator.t list option
602602 val since_query_state : t -> string
603603- val max_changes : t -> uint option
604604- val up_to_id : t -> id option
603603+ val max_changes : t -> int option
604604+ val up_to_id : t -> string option
605605 val calculate_total : t -> bool option
606606 val collapse_threads : t -> bool option
607607608608 val v :
609609- account_id:id ->
609609+ account_id:string ->
610610 ?filter:Filter.t ->
611611 ?sort:Comparator.t list ->
612612 since_query_state:string ->
613613- ?max_changes:uint ->
614614- ?up_to_id:id ->
613613+ ?max_changes:int ->
614614+ ?up_to_id:string ->
615615 ?calculate_total:bool ->
616616 ?collapse_threads:bool ->
617617 unit ->
···623623module Query_changes_response : sig
624624 type t
625625626626- val account_id : t -> id
626626+ val account_id : t -> string
627627 val old_query_state : t -> string
628628 val new_query_state : t -> string
629629- val total : t -> uint option
630630- val removed : t -> id list
629629+ val total : t -> int option
630630+ val removed : t -> string list
631631 val added : t -> Added_item.t list
632632633633 val v :
634634- account_id:id ->
634634+ account_id:string ->
635635 old_query_state:string ->
636636 new_query_state:string ->
637637- ?total:uint ->
638638- removed:id list ->
637637+ ?total:int ->
638638+ removed:string list ->
639639 added:Added_item.t list ->
640640 unit ->
641641 t
+1-1
jmap/jmap/jmap_protocol.mli
···182182 @param session The session object.
183183 @param capability The capability.
184184 @return The account ID or an error if not found. *)
185185-val get_primary_account : session -> Jmap_capability.t -> (Types.id, error) result
185185+val get_primary_account : session -> Jmap_capability.t -> (string, error) result
186186187187(** Find a method response by its call ID.
188188 @param response The response object.
+27-27
jmap/jmap/jmap_push.ml
···11-open Types
11+(* Use underlying types directly to avoid circular dependency with Jmap module *)
22open Jmap_methods
3344-type type_state = string string_map
44+type type_state = (string, string) Hashtbl.t
5566module State_change = struct
77 type t = {
88- changed : type_state id_map;
88+ changed : (string, type_state) Hashtbl.t;
99 }
10101111 let changed t = t.changed
···27272828module Push_subscription = struct
2929 type t = {
3030- id : id;
3030+ string : string;
3131 device_client_id : string;
3232 url : Uri.t;
3333 keys : Push_encryption_keys.t option;
3434 verification_code : string option;
3535- expires : utc_date option;
3535+ expires : string option;
3636 types : string list option;
3737 }
38383939- let id t = t.id
3939+ let string t = t.string
4040 let device_client_id t = t.device_client_id
4141 let url t = t.url
4242 let keys t = t.keys
···4444 let expires t = t.expires
4545 let types t = t.types
46464747- let v ~id ~device_client_id ~url ?keys ?verification_code ?expires ?types () =
4848- { id; device_client_id; url; keys; verification_code; expires; types }
4747+ let v ~string ~device_client_id ~url ?keys ?verification_code ?expires ?types () =
4848+ { string; device_client_id; url; keys; verification_code; expires; types }
4949end
50505151module Push_subscription_create = struct
···5353 device_client_id : string;
5454 url : Uri.t;
5555 keys : Push_encryption_keys.t option;
5656- expires : utc_date option;
5656+ expires : string option;
5757 types : string list option;
5858 }
5959···71717272module Push_subscription_get_args = struct
7373 type t = {
7474- ids : id list option;
7474+ ids : string list option;
7575 properties : string list option;
7676 }
7777···8484module Push_subscription_get_response = struct
8585 type t = {
8686 list : Push_subscription.t list;
8787- not_found : id list;
8787+ not_found : string list;
8888 }
89899090 let list t = t.list
···95959696module Push_subscription_set_args = struct
9797 type t = {
9898- create : Push_subscription_create.t id_map option;
9999- update : push_subscription_update id_map option;
100100- destroy : id list option;
9898+ create : (string, Push_subscription_create.t) Hashtbl.t option;
9999+ update : (string, push_subscription_update) Hashtbl.t option;
100100+ destroy : string list option;
101101 }
102102103103 let create t = t.create
···109109110110module Push_subscription_created_info = struct
111111 type t = {
112112- id : id;
113113- expires : utc_date option;
112112+ string : string;
113113+ expires : string option;
114114 }
115115116116- let id t = t.id
116116+ let string t = t.string
117117 let expires t = t.expires
118118119119- let v ~id ?expires () = { id; expires }
119119+ let v ~string ?expires () = { string; expires }
120120end
121121122122module Push_subscription_updated_info = struct
123123 type t = {
124124- expires : utc_date option;
124124+ expires : string option;
125125 }
126126127127 let expires t = t.expires
···131131132132module Push_subscription_set_response = struct
133133 type t = {
134134- created : Push_subscription_created_info.t id_map option;
135135- updated : Push_subscription_updated_info.t option id_map option;
136136- destroyed : id list option;
137137- not_created : Error.Set_error.t id_map option;
138138- not_updated : Error.Set_error.t id_map option;
139139- not_destroyed : Error.Set_error.t id_map option;
134134+ created : (string, Push_subscription_created_info.t) Hashtbl.t option;
135135+ updated : (string, Push_subscription_updated_info.t option) Hashtbl.t option;
136136+ destroyed : string list option;
137137+ not_created : (string, Error.Set_error.t) Hashtbl.t option;
138138+ not_updated : (string, Error.Set_error.t) Hashtbl.t option;
139139+ not_destroyed : (string, Error.Set_error.t) Hashtbl.t option;
140140 }
141141142142 let created t = t.created
···152152153153module Push_verification = struct
154154 type t = {
155155- push_subscription_id : id;
155155+ push_subscription_id : string;
156156 verification_code : string;
157157 }
158158···165165166166module Event_source_ping_data = struct
167167 type t = {
168168- interval : uint;
168168+ interval : int;
169169 }
170170171171 let interval t = t.interval
+43-43
jmap/jmap/jmap_push.mli
···11(** JMAP Push Notifications.
22 @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-7> RFC 8620, Section 7 *)
3344-open Types
44+(* Use underlying types directly to avoid circular dependency with Jmap module *)
55open Jmap_methods
6677(** TypeState object map (TypeName -> StateString).
88 @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-7.1> RFC 8620, Section 7.1 *)
99-type type_state = string string_map
99+type type_state = (string, string) Hashtbl.t
10101111(** StateChange object.
1212 @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-7.1> RFC 8620, Section 7.1 *)
1313module State_change : sig
1414 type t
15151616- val changed : t -> type_state id_map
1616+ val changed : t -> (string, type_state) Hashtbl.t
17171818 val v :
1919- changed:type_state id_map ->
1919+ changed:(string, type_state) Hashtbl.t ->
2020 unit ->
2121 t
2222end
···4545 type t
46464747 (** Id of the subscription (server-set, immutable) *)
4848- val id : t -> id
4848+ val string : t -> string
49495050- (** Device client id (immutable) *)
5050+ (** Device client string (immutable) *)
5151 val device_client_id : t -> string
52525353 (** Notification URL (immutable) *)
···5656 (** Encryption keys (immutable) *)
5757 val keys : t -> Push_encryption_keys.t option
5858 val verification_code : t -> string option
5959- val expires : t -> utc_date option
5959+ val expires : t -> string option
6060 val types : t -> string list option
61616262 val v :
6363- id:id ->
6363+ string:string ->
6464 device_client_id:string ->
6565 url:Uri.t ->
6666 ?keys:Push_encryption_keys.t ->
6767 ?verification_code:string ->
6868- ?expires:utc_date ->
6868+ ?expires:string ->
6969 ?types:string list ->
7070 unit ->
7171 t
···7979 val device_client_id : t -> string
8080 val url : t -> Uri.t
8181 val keys : t -> Push_encryption_keys.t option
8282- val expires : t -> utc_date option
8282+ val expires : t -> string option
8383 val types : t -> string list option
84848585 val v :
8686 device_client_id:string ->
8787 url:Uri.t ->
8888 ?keys:Push_encryption_keys.t ->
8989- ?expires:utc_date ->
8989+ ?expires:string ->
9090 ?types:string list ->
9191 unit ->
9292 t
···104104module Push_subscription_get_args : sig
105105 type t
106106107107- val ids : t -> id list option
107107+ val ids : t -> string list option
108108 val properties : t -> string list option
109109110110 val v :
111111- ?ids:id list ->
111111+ ?ids:string list ->
112112 ?properties:string list ->
113113 unit ->
114114 t
···121121 type t
122122123123 val list : t -> Push_subscription.t list
124124- val not_found : t -> id list
124124+ val not_found : t -> string list
125125126126 val v :
127127 list:Push_subscription.t list ->
128128- not_found:id list ->
128128+ not_found:string list ->
129129 unit ->
130130 t
131131end
···136136module Push_subscription_set_args : sig
137137 type t
138138139139- val create : t -> Push_subscription_create.t id_map option
140140- val update : t -> push_subscription_update id_map option
141141- val destroy : t -> id list option
139139+ val create : t -> (string, Push_subscription_create.t) Hashtbl.t option
140140+ val update : t -> (string, push_subscription_update) Hashtbl.t option
141141+ val destroy : t -> string list option
142142143143 val v :
144144- ?create:Push_subscription_create.t id_map ->
145145- ?update:push_subscription_update id_map ->
146146- ?destroy:id list ->
144144+ ?create:(string, Push_subscription_create.t) Hashtbl.t ->
145145+ ?update:(string, push_subscription_update) Hashtbl.t ->
146146+ ?destroy:string list ->
147147 unit ->
148148 t
149149end
···153153module Push_subscription_created_info : sig
154154 type t
155155156156- val id : t -> id
157157- val expires : t -> utc_date option
156156+ val string : t -> string
157157+ val expires : t -> string option
158158159159 val v :
160160- id:id ->
161161- ?expires:utc_date ->
160160+ string:string ->
161161+ ?expires:string ->
162162 unit ->
163163 t
164164end
···168168module Push_subscription_updated_info : sig
169169 type t
170170171171- val expires : t -> utc_date option
171171+ val expires : t -> string option
172172173173 val v :
174174- ?expires:utc_date ->
174174+ ?expires:string ->
175175 unit ->
176176 t
177177end
···182182module Push_subscription_set_response : sig
183183 type t
184184185185- val created : t -> Push_subscription_created_info.t id_map option
186186- val updated : t -> Push_subscription_updated_info.t option id_map option
187187- val destroyed : t -> id list option
188188- val not_created : t -> Error.Set_error.t id_map option
189189- val not_updated : t -> Error.Set_error.t id_map option
190190- val not_destroyed : t -> Error.Set_error.t id_map option
185185+ val created : t -> (string, Push_subscription_created_info.t) Hashtbl.t option
186186+ val updated : t -> (string, Push_subscription_updated_info.t option) Hashtbl.t option
187187+ val destroyed : t -> string list option
188188+ val not_created : t -> (string, Error.Set_error.t) Hashtbl.t option
189189+ val not_updated : t -> (string, Error.Set_error.t) Hashtbl.t option
190190+ val not_destroyed : t -> (string, Error.Set_error.t) Hashtbl.t option
191191192192 val v :
193193- ?created:Push_subscription_created_info.t id_map ->
194194- ?updated:Push_subscription_updated_info.t option id_map ->
195195- ?destroyed:id list ->
196196- ?not_created:Error.Set_error.t id_map ->
197197- ?not_updated:Error.Set_error.t id_map ->
198198- ?not_destroyed:Error.Set_error.t id_map ->
193193+ ?created:(string, Push_subscription_created_info.t) Hashtbl.t ->
194194+ ?updated:(string, Push_subscription_updated_info.t option) Hashtbl.t ->
195195+ ?destroyed:string list ->
196196+ ?not_created:(string, Error.Set_error.t) Hashtbl.t ->
197197+ ?not_updated:(string, Error.Set_error.t) Hashtbl.t ->
198198+ ?not_destroyed:(string, Error.Set_error.t) Hashtbl.t ->
199199 unit ->
200200 t
201201end
···205205module Push_verification : sig
206206 type t
207207208208- val push_subscription_id : t -> id
208208+ val push_subscription_id : t -> string
209209 val verification_code : t -> string
210210211211 val v :
212212- push_subscription_id:id ->
212212+ push_subscription_id:string ->
213213 verification_code:string ->
214214 unit ->
215215 t
···220220module Event_source_ping_data : sig
221221 type t
222222223223- val interval : t -> uint
223223+ val interval : t -> int
224224225225 val v :
226226- interval:uint ->
226226+ interval:int ->
227227 unit ->
228228 t
229229end
+6-6
jmap/jmap/jmap_request.ml
···11(** Implementation of type-safe JMAP request building and management. *)
2233-open Types
33+(* Use underlying types directly to avoid circular dependency with Jmap module *)
4455(** Internal representation of a JMAP request under construction *)
66type t = {
77 using: string list;
88 methods: (Jmap_method.t * string) list; (* (method, call_id) pairs *)
99- created_ids: (string) id_map option;
99+ created_ids: (string, string) Hashtbl.t option;
1010 call_id_counter: int;
1111}
1212···4545 let rec find_index methods index =
4646 match methods with
4747 | [] -> None
4848- | (_, id) :: _ when id = call_id -> Some index
4848+ | (_, string) :: _ when string = call_id -> Some index
4949 | _ :: rest -> find_index rest (index + 1)
5050 in
5151 find_index (List.rev t.methods) 0 (* Reverse to maintain insertion order *)
···54545555let add_method t method_call =
5656 let call_id = match Jmap_method.call_id method_call with
5757- | Some id -> id
5757+ | Some string -> string
5858 | None ->
5959- let (id, _) = generate_call_id t in
6060- id
5959+ let (string, _) = generate_call_id t in
6060+ string
6161 in
6262 let method_with_id = Jmap_method.with_call_id method_call call_id in
6363 let (final_call_id, updated_t) = if Jmap_method.call_id method_with_id = Some call_id then
+3-3
jmap/jmap/jmap_request.mli
···2222 @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-3.3> RFC 8620, Section 3.3 (Request Object)
2323 @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-3.7> RFC 8620, Section 3.7 (Result References) *)
24242525-open Types
2525+(* Use underlying types directly to avoid circular dependency with Jmap module *)
26262727(** {1 Request Types} *)
2828···4747 @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-3.3> RFC 8620, Section 3.3 *)
4848val create :
4949 using:string list ->
5050- ?created_ids:(string) id_map ->
5050+ ?created_ids:(string, string) Hashtbl.t ->
5151 unit ->
5252 t
5353···6565 @return A new request with standard capabilities *)
6666val create_with_standard_capabilities :
6767 ?additional_capabilities:string list ->
6868- ?created_ids:(string) id_map ->
6868+ ?created_ids:(string, string) Hashtbl.t ->
6969 unit ->
7070 t
7171
+13-13
jmap/jmap/jmap_response.mli
···2525 @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-3.4> RFC 8620, Section 3.4 (Method Responses)
2626 @see <https://www.rfc-editor.org/rfc/rfc8621.html> RFC 8621 (Email Extensions) *)
27272828-open Types
2828+(* Use underlying types directly to avoid circular dependency with Jmap module *)
29293030(** {1 Response Types} *)
3131···142142 val query_state : t -> string
143143144144 (** Extract total count from response *)
145145- val total : t -> uint option
145145+ val total : t -> int option
146146147147 (** Extract current position from response *)
148148- val position : t -> uint
148148+ val position : t -> int
149149end
150150151151(** Email/get response - implements METHOD_RESPONSE for get operations *)
···164164 include Jmap_sigs.METHOD_RESPONSE with type t = (Yojson.Safe.t, Yojson.Safe.t) Jmap_methods.Set_response.t
165165166166 (** Extract created emails from response *)
167167- val created : t -> Yojson.Safe.t id_map option
167167+ val created : t -> (string, Yojson.Safe.t) Hashtbl.t option
168168169169 (** Extract updated emails from response *)
170170- val updated : t -> Yojson.Safe.t option id_map option
170170+ val updated : t -> (string, Yojson.Safe.t option) Hashtbl.t option
171171172172 (** Extract destroyed email IDs from response *)
173173 val destroyed : t -> string list option
···214214 include Jmap_sigs.METHOD_RESPONSE with type t = (Yojson.Safe.t, Yojson.Safe.t) Jmap_methods.Set_response.t
215215216216 (** Extract created mailboxes from response *)
217217- val created : t -> Yojson.Safe.t id_map option
217217+ val created : t -> (string, Yojson.Safe.t) Hashtbl.t option
218218219219 (** Extract updated mailboxes from response *)
220220- val updated : t -> Yojson.Safe.t option id_map option
220220+ val updated : t -> (string, Yojson.Safe.t option) Hashtbl.t option
221221222222 (** Extract destroyed mailbox IDs from response *)
223223 val destroyed : t -> string list option
···281281 include Jmap_sigs.METHOD_RESPONSE with type t = (Yojson.Safe.t, Yojson.Safe.t) Jmap_methods.Set_response.t
282282283283 (** Extract created identities from response *)
284284- val created : t -> Yojson.Safe.t id_map option
284284+ val created : t -> (string, Yojson.Safe.t) Hashtbl.t option
285285286286 (** Extract updated identities from response *)
287287- val updated : t -> Yojson.Safe.t option id_map option
287287+ val updated : t -> (string, Yojson.Safe.t option) Hashtbl.t option
288288289289 (** Extract destroyed identity IDs from response *)
290290 val destroyed : t -> string list option
···323323 include Jmap_sigs.METHOD_RESPONSE with type t = (Yojson.Safe.t, Yojson.Safe.t) Jmap_methods.Set_response.t
324324325325 (** Extract created email submissions from response *)
326326- val created : t -> Yojson.Safe.t id_map option
326326+ val created : t -> (string, Yojson.Safe.t) Hashtbl.t option
327327328328 (** Extract updated email submissions from response *)
329329- val updated : t -> Yojson.Safe.t option id_map option
329329+ val updated : t -> (string, Yojson.Safe.t option) Hashtbl.t option
330330331331 (** Extract destroyed email submission IDs from response *)
332332 val destroyed : t -> string list option
···373373 include Jmap_sigs.METHOD_RESPONSE with type t = (Yojson.Safe.t, Yojson.Safe.t) Jmap_methods.Set_response.t
374374375375 (** Extract created vacation responses from response *)
376376- val created : t -> Yojson.Safe.t id_map option
376376+ val created : t -> (string, Yojson.Safe.t) Hashtbl.t option
377377378378 (** Extract updated vacation responses from response *)
379379- val updated : t -> Yojson.Safe.t option id_map option
379379+ val updated : t -> (string, Yojson.Safe.t option) Hashtbl.t option
380380381381 (** Extract destroyed vacation response IDs from response *)
382382 val destroyed : t -> string list option
+132
jmap/jmap/patch.ml
···11+(* Internal representation as a hash table for efficient operations *)
22+type t = (string, Yojson.Safe.t) Hashtbl.t
33+44+(* JSON Pointer validation - simplified but covers common cases *)
55+let is_valid_property_path path =
66+ let len = String.length path in
77+ if len = 0 then true (* empty path is valid root *)
88+ else if path.[0] <> '/' then true (* simple property names are valid *)
99+ else
1010+ (* Check for valid JSON Pointer format *)
1111+ let rec check_escaping i =
1212+ if i >= len then true
1313+ else match path.[i] with
1414+ | '~' when i + 1 < len ->
1515+ (match path.[i + 1] with
1616+ | '0' | '1' -> check_escaping (i + 2)
1717+ | _ -> false)
1818+ | '/' -> check_escaping (i + 1)
1919+ | _ -> check_escaping (i + 1)
2020+ in
2121+ check_escaping 0
2222+2323+let empty = Hashtbl.create 8
2424+2525+let of_operations operations =
2626+ let patch = Hashtbl.create (List.length operations) in
2727+ let rec process = function
2828+ | [] -> Ok patch
2929+ | (property, value) :: rest ->
3030+ if is_valid_property_path property then (
3131+ Hashtbl.replace patch property value;
3232+ process rest
3333+ ) else
3434+ Error ("Invalid property path: " ^ property)
3535+ in
3636+ process operations
3737+3838+let to_operations patch =
3939+ Hashtbl.fold (fun property value acc ->
4040+ (property, value) :: acc
4141+ ) patch []
4242+4343+let of_json_object = function
4444+ | `Assoc pairs -> of_operations pairs
4545+ | json ->
4646+ let json_str = Yojson.Safe.to_string json in
4747+ Error (Printf.sprintf "Expected JSON object for Patch, got: %s" json_str)
4848+4949+let to_json_object patch =
5050+ let pairs = to_operations patch in
5151+ `Assoc pairs
5252+5353+let set_property patch property value =
5454+ if is_valid_property_path property then (
5555+ let new_patch = Hashtbl.copy patch in
5656+ Hashtbl.replace new_patch property value;
5757+ Ok new_patch
5858+ ) else
5959+ Error ("Invalid property path: " ^ property)
6060+6161+let remove_property patch property =
6262+ set_property patch property `Null
6363+6464+let has_property patch property =
6565+ Hashtbl.mem patch property
6666+6767+let get_property patch property =
6868+ try Some (Hashtbl.find patch property)
6969+ with Not_found -> None
7070+7171+let merge patch1 patch2 =
7272+ let result = Hashtbl.copy patch1 in
7373+ Hashtbl.iter (fun property value ->
7474+ Hashtbl.replace result property value
7575+ ) patch2;
7676+ result
7777+7878+let is_empty patch =
7979+ Hashtbl.length patch = 0
8080+8181+let size patch =
8282+ Hashtbl.length patch
8383+8484+let validate patch =
8585+ (* Validate all property paths *)
8686+ try
8787+ Hashtbl.iter (fun property _value ->
8888+ if not (is_valid_property_path property) then
8989+ failwith ("Invalid property path: " ^ property)
9090+ ) patch;
9191+ Ok ()
9292+ with
9393+ | Failure msg -> Error msg
9494+9595+let equal patch1 patch2 =
9696+ if Hashtbl.length patch1 <> Hashtbl.length patch2 then false
9797+ else
9898+ try
9999+ Hashtbl.iter (fun property value1 ->
100100+ match get_property patch2 property with
101101+ | None -> failwith "Property not found"
102102+ | Some value2 when Yojson.Safe.equal value1 value2 -> ()
103103+ | Some _ -> failwith "Property values differ"
104104+ ) patch1;
105105+ true
106106+ with
107107+ | Failure _ -> false
108108+109109+let pp ppf patch =
110110+ Format.fprintf ppf "%s" (Yojson.Safe.to_string (to_json_object patch))
111111+112112+let pp_hum ppf patch =
113113+ let operations = to_operations patch in
114114+ let op_count = List.length operations in
115115+ let key_list = List.map fst operations in
116116+ let key_str = match key_list with
117117+ | [] -> "none"
118118+ | keys -> String.concat ", " keys
119119+ in
120120+ Format.fprintf ppf "Patch{operations=%d; keys=[%s]}" op_count key_str
121121+122122+let to_string_debug patch =
123123+ let operations = to_operations patch in
124124+ let op_strings = List.map (fun (prop, value) ->
125125+ Printf.sprintf "%s: %s" prop (Yojson.Safe.to_string value)
126126+ ) operations in
127127+ Printf.sprintf "Patch({%s})" (String.concat "; " op_strings)
128128+129129+(* JSON serialization *)
130130+let to_json patch = to_json_object patch
131131+132132+let of_json json = of_json_object json
+122
jmap/jmap/patch.mli
···11+(** JMAP Patch Object for property updates with JSON serialization.
22+33+ A patch object is used to update properties of JMAP objects. It represents
44+ a JSON object where each key is a property path (using JSON Pointer syntax)
55+ and each value is the new value to set for that property, or null to remove
66+ the property.
77+88+ Patch objects are commonly used in /set method calls to update existing
99+ objects without having to send the complete object representation.
1010+1111+ Examples of patch operations:
1212+ - Setting a property: [{"name": "New Name"}]
1313+ - Removing a property: [{"oldProperty": null}]
1414+ - Setting nested properties: [{"address/street": "123 Main St"}]
1515+1616+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-5.3> RFC 8620, Section 5.3
1717+ @see <https://www.rfc-editor.org/rfc/rfc6901.html> RFC 6901 (JSON Pointer) *)
1818+1919+(** Abstract type representing a JMAP Patch Object. *)
2020+type t
2121+2222+(** JSON serialization interface *)
2323+include Jmap_sigs.JSONABLE with type t := t
2424+2525+(** Pretty-printing interface *)
2626+include Jmap_sigs.PRINTABLE with type t := t
2727+2828+(** {2 Construction and Access} *)
2929+3030+(** Create an empty patch object.
3131+ @return An empty patch with no operations. *)
3232+val empty : t
3333+3434+(** Create a patch from a list of property-value pairs.
3535+ @param operations List of (property_path, value) pairs.
3636+ @return Ok with the patch, or Error if any property path is invalid. *)
3737+val of_operations : (string * Yojson.Safe.t) list -> (t, string) result
3838+3939+(** Convert a patch to a list of property-value pairs.
4040+ @param patch The patch to convert.
4141+ @return List of (property_path, value) pairs. *)
4242+val to_operations : t -> (string * Yojson.Safe.t) list
4343+4444+(** Create a patch from a Yojson.Safe.t object directly.
4545+ @param json The JSON object.
4646+ @return Ok with the patch, or Error if the JSON is not a valid object. *)
4747+val of_json_object : Yojson.Safe.t -> (t, string) result
4848+4949+(** Convert a patch to a Yojson.Safe.t object directly.
5050+ @param patch The patch to convert.
5151+ @return The JSON object representation. *)
5252+val to_json_object : t -> Yojson.Safe.t
5353+5454+(** {2 Patch Operations} *)
5555+5656+(** Set a property in the patch.
5757+ @param patch The patch to modify.
5858+ @param property The property path (JSON Pointer format).
5959+ @param value The value to set.
6060+ @return Ok with the updated patch, or Error if the property path is invalid. *)
6161+val set_property : t -> string -> Yojson.Safe.t -> (t, string) result
6262+6363+(** Remove a property in the patch (set to null).
6464+ @param patch The patch to modify.
6565+ @param property The property path to remove.
6666+ @return Ok with the updated patch, or Error if the property path is invalid. *)
6767+val remove_property : t -> string -> (t, string) result
6868+6969+(** Check if a property is set in the patch.
7070+ @param patch The patch to check.
7171+ @param property The property path to check.
7272+ @return True if the property is explicitly set in the patch. *)
7373+val has_property : t -> string -> bool
7474+7575+(** Get a property value from the patch.
7676+ @param patch The patch to query.
7777+ @param property The property path to get.
7878+ @return Some value if the property is set, None if not present. *)
7979+val get_property : t -> string -> Yojson.Safe.t option
8080+8181+(** {2 Patch Composition} *)
8282+8383+(** Merge two patches, with the second patch taking precedence.
8484+ @param patch1 The first patch.
8585+ @param patch2 The second patch (higher precedence).
8686+ @return The merged patch. *)
8787+val merge : t -> t -> t
8888+8989+(** Check if a patch is empty (no operations).
9090+ @param patch The patch to check.
9191+ @return True if the patch has no operations. *)
9292+val is_empty : t -> bool
9393+9494+(** Get the number of operations in a patch.
9595+ @param patch The patch to count.
9696+ @return The number of property operations. *)
9797+val size : t -> int
9898+9999+(** {2 Validation} *)
100100+101101+(** Validate a patch according to JMAP constraints.
102102+ @param patch The patch to validate.
103103+ @return Ok () if valid, Error with description if invalid. *)
104104+val validate : t -> (unit, string) result
105105+106106+(** Validate a JSON Pointer path.
107107+ @param path The property path to validate.
108108+ @return True if the path is a valid JSON Pointer, false otherwise. *)
109109+val is_valid_property_path : string -> bool
110110+111111+(** {2 Comparison and Utilities} *)
112112+113113+(** Compare two patches for equality.
114114+ @param patch1 First patch.
115115+ @param patch2 Second patch.
116116+ @return True if patches have identical operations, false otherwise. *)
117117+val equal : t -> t -> bool
118118+119119+(** Convert a patch to a human-readable string for debugging.
120120+ @param patch The patch to format.
121121+ @return A debug string representation. *)
122122+val to_string_debug : t -> string
···11111212 @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-2> RFC 8620, Section 2 *)
13131414-open Types
1414+(* Use underlying types directly to avoid circular dependency with Jmap module *)
15151616(** {1 Capability Types} *)
1717···57575858 (** Maximum size in bytes for a single blob upload.
5959 @return Maximum upload size (typically 50MB or similar) *)
6060- val max_size_upload : t -> uint
6060+ val max_size_upload : t -> int
61616262 (** Maximum number of concurrent blob uploads allowed.
6363 @return Maximum concurrent uploads (typically 4-10) *)
6464- val max_concurrent_upload : t -> uint
6464+ val max_concurrent_upload : t -> int
65656666 (** Maximum size in bytes for a single JMAP request.
6767 @return Maximum request size (typically 10MB or similar) *)
6868- val max_size_request : t -> uint
6868+ val max_size_request : t -> int
69697070 (** Maximum number of concurrent JMAP requests allowed.
7171 @return Maximum concurrent requests (typically 4-10) *)
7272- val max_concurrent_requests : t -> uint
7272+ val max_concurrent_requests : t -> int
73737474 (** Maximum number of method calls allowed in a single request.
7575 @return Maximum method calls per request (typically 16-64) *)
7676- val max_calls_in_request : t -> uint
7676+ val max_calls_in_request : t -> int
77777878 (** Maximum number of objects that can be requested in a single /get call.
7979 @return Maximum objects per /get (typically 500-1000) *)
8080- val max_objects_in_get : t -> uint
8080+ val max_objects_in_get : t -> int
81818282 (** Maximum number of objects that can be processed in a single /set call.
8383 @return Maximum objects per /set (typically 500-1000) *)
8484- val max_objects_in_set : t -> uint
8484+ val max_objects_in_set : t -> int
85858686 (** List of supported collation algorithms for sorting.
8787 @return List of collation algorithm names (e.g., ["i;ascii-casemap", "i;unicode-casemap"]) *)
···9898 @param collation_algorithms Supported collation algorithms
9999 @return A new core capability object *)
100100 val v :
101101- max_size_upload:uint ->
102102- max_concurrent_upload:uint ->
103103- max_size_request:uint ->
104104- max_concurrent_requests:uint ->
105105- max_calls_in_request:uint ->
106106- max_objects_in_get:uint ->
107107- max_objects_in_set:uint ->
101101+ max_size_upload:int ->
102102+ max_concurrent_upload:int ->
103103+ max_size_request:int ->
104104+ max_concurrent_requests:int ->
105105+ max_calls_in_request:int ->
106106+ max_objects_in_get:int ->
107107+ max_objects_in_set:int ->
108108 collation_algorithms:string list ->
109109 unit ->
110110 t
···149149150150 (** Get the account-specific capability information.
151151 @return Map of capability URIs to their account-specific metadata *)
152152- val account_capabilities : t -> account_capability_value string_map
152152+ val account_capabilities : t -> (string, account_capability_value) Hashtbl.t
153153154154 (** Create a new account object.
155155 @param name Human-readable account name
···161161 name:string ->
162162 ?is_personal:bool ->
163163 ?is_read_only:bool ->
164164- ?account_capabilities:account_capability_value string_map ->
164164+ ?account_capabilities:(string, account_capability_value) Hashtbl.t ->
165165 unit ->
166166 t
167167···198198199199 (** Get the server capabilities.
200200 @return Map of capability URIs to server-specific capability metadata *)
201201- val capabilities : t -> server_capability_value string_map
201201+ val capabilities : t -> (string, server_capability_value) Hashtbl.t
202202203203 (** Get all accounts accessible to the authenticated user.
204204 @return Map of account IDs to account objects *)
205205- val accounts : t -> Account.t id_map
205205+ val accounts : t -> (string, Account.t) Hashtbl.t
206206207207 (** Get the primary account ID for each capability.
208208 @return Map from capability URI to primary account ID for that capability *)
209209- val primary_accounts : t -> id string_map
209209+ val primary_accounts : t -> (string, string) Hashtbl.t
210210211211 (** Get the authenticated username.
212212 @return Username or email address of the authenticated user *)
···244244 @param state Current session state string
245245 @return A new session object *)
246246 val v :
247247- capabilities:server_capability_value string_map ->
248248- accounts:Account.t id_map ->
249249- primary_accounts:id string_map ->
247247+ capabilities:(string, server_capability_value) Hashtbl.t ->
248248+ accounts:(string, Account.t) Hashtbl.t ->
249249+ primary_accounts:(string, string) Hashtbl.t ->
250250 username:string ->
251251 api_url:Uri.t ->
252252 download_url:Uri.t ->
···272272 (** Get the primary account ID for a given capability.
273273 @param capability The capability
274274 @return Primary account ID if found, None otherwise *)
275275- val get_primary_account : t -> Jmap_capability.t -> id option
275275+ val get_primary_account : t -> Jmap_capability.t -> string option
276276277277 (** Get account information by account ID.
278278 @param account_id The account ID to look up
279279 @return Account object if found, None otherwise *)
280280- val get_account : t -> id -> Account.t option
280280+ val get_account : t -> string -> Account.t option
281281282282 (** Get all personal accounts for the authenticated user.
283283 @return List of (account_id, account) pairs for personal accounts *)
284284- val get_personal_accounts : t -> (id * Account.t) list
284284+ val get_personal_accounts : t -> (string * Account.t) list
285285286286 (** Get all accounts that support a given capability.
287287 @param capability The capability
288288 @return List of (account_id, account) pairs that support the capability *)
289289- val get_capability_accounts : t -> Jmap_capability.t -> (id * Account.t) list
289289+ val get_capability_accounts : t -> Jmap_capability.t -> (string * Account.t) list
290290end
291291292292(** {1 Session Discovery and Retrieval} *)
-432
jmap/jmap/types.ml
···11-(** JMAP Core Types Implementation *)
22-33-(* Id module implementation *)
44-module Id = struct
55- type t = string
66-77- let is_base64url_char c =
88- (c >= 'A' && c <= 'Z') ||
99- (c >= 'a' && c <= 'z') ||
1010- (c >= '0' && c <= '9') ||
1111- c = '-' || c = '_'
1212-1313- let is_valid_string str =
1414- let len = String.length str in
1515- len > 0 && len <= 255 &&
1616- let rec check i =
1717- if i >= len then true
1818- else if is_base64url_char str.[i] then check (i + 1)
1919- else false
2020- in
2121- check 0
2222-2323- let of_string str =
2424- if is_valid_string str then Ok str
2525- else
2626- let len = String.length str in
2727- if len = 0 then Error "Id cannot be empty"
2828- else if len > 255 then Error "Id cannot be longer than 255 octets"
2929- else Error "Id contains invalid characters (must be base64url alphabet only)"
3030-3131- let to_string id = id
3232-3333- let pp ppf id = Format.fprintf ppf "%s" id
3434-3535- let pp_hum ppf id = Format.fprintf ppf "Id(%s)" id
3636-3737- let validate id =
3838- if is_valid_string id then Ok ()
3939- else Error "Invalid Id format"
4040-4141- let equal = String.equal
4242-4343- let compare = String.compare
4444-4545- let pp_debug ppf id = Format.fprintf ppf "Id(%s)" id
4646-4747- let to_string_debug id = Printf.sprintf "Id(%s)" id
4848-4949- (* JSON serialization *)
5050- let to_json id = `String id
5151-5252- let of_json = function
5353- | `String str -> of_string str
5454- | json ->
5555- let json_str = Yojson.Safe.to_string json in
5656- Error (Printf.sprintf "Expected JSON string for Id, got: %s" json_str)
5757-end
5858-5959-(* Date module implementation *)
6060-module Date = struct
6161- type t = float (* Unix timestamp *)
6262-6363- (* Basic RFC 3339 parsing - simplified for JMAP usage *)
6464- let parse_rfc3339 str =
6565- try
6666- (* Use Unix.strptime if available, otherwise simplified parsing *)
6767- let len = String.length str in
6868- if len < 19 then failwith "Too short for RFC 3339";
6969-7070- (* Extract year, month, day, hour, minute, second *)
7171- let year = int_of_string (String.sub str 0 4) in
7272- let month = int_of_string (String.sub str 5 2) in
7373- let day = int_of_string (String.sub str 8 2) in
7474- let hour = int_of_string (String.sub str 11 2) in
7575- let minute = int_of_string (String.sub str 14 2) in
7676- let second = int_of_string (String.sub str 17 2) in
7777-7878- (* Basic validation *)
7979- if year < 1970 || year > 9999 then failwith "Invalid year";
8080- if month < 1 || month > 12 then failwith "Invalid month";
8181- if day < 1 || day > 31 then failwith "Invalid day";
8282- if hour < 0 || hour > 23 then failwith "Invalid hour";
8383- if minute < 0 || minute > 59 then failwith "Invalid minute";
8484- if second < 0 || second > 59 then failwith "Invalid second";
8585-8686- (* Convert to Unix timestamp using built-in functions *)
8787- let tm = {
8888- Unix.tm_year = year - 1900;
8989- tm_mon = month - 1;
9090- tm_mday = day;
9191- tm_hour = hour;
9292- tm_min = minute;
9393- tm_sec = second;
9494- tm_wday = 0;
9595- tm_yday = 0;
9696- tm_isdst = false;
9797- } in
9898-9999- (* Handle timezone - simplified to assume UTC for 'Z' suffix *)
100100- let timestamp =
101101- if len >= 20 && str.[len-1] = 'Z' then
102102- (* UTC time - convert to UTC timestamp *)
103103- let local_time = fst (Unix.mktime tm) in
104104- let gm_tm = Unix.gmtime local_time in
105105- let utc_time = fst (Unix.mktime gm_tm) in
106106- utc_time
107107- else if len >= 25 && (str.[len-6] = '+' || str.[len-6] = '-') then
108108- (* Timezone offset specified *)
109109- let sign = if str.[len-6] = '+' then -1.0 else 1.0 in
110110- let tz_hours = int_of_string (String.sub str (len-5) 2) in
111111- let tz_minutes = int_of_string (String.sub str (len-2) 2) in
112112- let offset = sign *. (float_of_int tz_hours *. 3600.0 +. float_of_int tz_minutes *. 60.0) in
113113- fst (Unix.mktime tm) +. offset
114114- else
115115- (* No timezone - assume local time *)
116116- fst (Unix.mktime tm)
117117- in
118118- Ok timestamp
119119- with
120120- | Failure msg -> Error ("Invalid RFC 3339 format: " ^ msg)
121121- | Invalid_argument _ -> Error "Invalid RFC 3339 format: parsing error"
122122- | _ -> Error "Invalid RFC 3339 format"
123123-124124- let format_rfc3339 timestamp =
125125- let tm = Unix.gmtime timestamp in
126126- Printf.sprintf "%04d-%02d-%02dT%02d:%02d:%02dZ"
127127- (tm.tm_year + 1900)
128128- (tm.tm_mon + 1)
129129- tm.tm_mday
130130- tm.tm_hour
131131- tm.tm_min
132132- tm.tm_sec
133133-134134- let of_timestamp timestamp = timestamp
135135-136136- let to_timestamp date = date
137137-138138- let of_rfc3339 str = parse_rfc3339 str
139139-140140- let to_rfc3339 date = format_rfc3339 date
141141-142142- let now () = Unix.time ()
143143-144144- let validate date =
145145- if date >= 0.0 && date <= 253402300799.0 (* 9999-12-31T23:59:59Z *) then
146146- Ok ()
147147- else
148148- Error "Date timestamp out of valid range"
149149-150150- let equal date1 date2 =
151151- (* Equal within 1 second precision *)
152152- abs_float (date1 -. date2) < 1.0
153153-154154- let compare date1 date2 =
155155- if date1 < date2 then -1
156156- else if date1 > date2 then 1
157157- else 0
158158-159159- let is_before date1 date2 = date1 < date2
160160-161161- let is_after date1 date2 = date1 > date2
162162-163163- let pp ppf date = Format.fprintf ppf "%s" (to_rfc3339 date)
164164-165165- let pp_hum ppf date = Format.fprintf ppf "Date(%s)" (to_rfc3339 date)
166166-167167- let pp_debug ppf date =
168168- Format.fprintf ppf "Date(%s)" (to_rfc3339 date)
169169-170170- let to_string_debug date =
171171- Printf.sprintf "Date(%s)" (to_rfc3339 date)
172172-173173- (* JSON serialization *)
174174- let to_json date = `String (to_rfc3339 date)
175175-176176- let of_json = function
177177- | `String str -> of_rfc3339 str
178178- | json ->
179179- let json_str = Yojson.Safe.to_string json in
180180- Error (Printf.sprintf "Expected JSON string for Date, got: %s" json_str)
181181-end
182182-183183-(* UInt module implementation *)
184184-module UInt = struct
185185- type t = int
186186-187187- (* Maximum safe integer value for JavaScript: 2^53 - 1 *)
188188- let max_safe_value = 9007199254740991
189189-190190- let is_valid_int i = i >= 0 && i <= max_safe_value
191191-192192- let of_int i =
193193- if is_valid_int i then Ok i
194194- else if i < 0 then Error "UnsignedInt cannot be negative"
195195- else Error "UnsignedInt cannot exceed 2^53-1"
196196-197197- let to_int uint = uint
198198-199199- let of_string str =
200200- try
201201- let i = int_of_string str in
202202- of_int i
203203- with
204204- | Failure _ -> Error "Invalid integer string format"
205205- | Invalid_argument _ -> Error "Invalid integer string format"
206206-207207- let to_string uint = string_of_int uint
208208-209209- let pp ppf uint = Format.fprintf ppf "%d" uint
210210-211211- let pp_hum ppf uint = Format.fprintf ppf "UInt(%d)" uint
212212-213213- (* Constants *)
214214- let zero = 0
215215- let one = 1
216216- let max_safe = max_safe_value
217217-218218- let validate uint =
219219- if is_valid_int uint then Ok ()
220220- else Error "UnsignedInt value out of valid range"
221221-222222- (* Arithmetic operations with overflow checking *)
223223- let add uint1 uint2 =
224224- let result = uint1 + uint2 in
225225- if result >= uint1 && result >= uint2 && is_valid_int result then
226226- Ok result
227227- else
228228- Error "UnsignedInt addition overflow"
229229-230230- let sub uint1 uint2 =
231231- if uint1 >= uint2 then Ok (uint1 - uint2)
232232- else Error "UnsignedInt subtraction would result in negative value"
233233-234234- let mul uint1 uint2 =
235235- if uint1 = 0 || uint2 = 0 then Ok 0
236236- else if uint1 <= max_safe_value / uint2 then
237237- Ok (uint1 * uint2)
238238- else
239239- Error "UnsignedInt multiplication overflow"
240240-241241- (* Comparison and utilities *)
242242- let equal = (=)
243243-244244- let compare = compare
245245-246246- let min uint1 uint2 = if uint1 <= uint2 then uint1 else uint2
247247-248248- let max uint1 uint2 = if uint1 >= uint2 then uint1 else uint2
249249-250250- let pp_debug ppf uint = Format.fprintf ppf "UInt(%d)" uint
251251-252252- let to_string_debug uint = Printf.sprintf "UInt(%d)" uint
253253-254254- (* JSON serialization *)
255255- let to_json uint = `Int uint
256256-257257- let of_json = function
258258- | `Int i -> of_int i
259259- | `Float f ->
260260- (* Handle case where JSON parser represents integers as floats *)
261261- if f >= 0.0 && f <= float_of_int max_safe_value && f = Float.round f then
262262- of_int (int_of_float f)
263263- else
264264- Error "Float value is not a valid UnsignedInt"
265265- | json ->
266266- let json_str = Yojson.Safe.to_string json in
267267- Error (Printf.sprintf "Expected JSON number for UnsignedInt, got: %s" json_str)
268268-end
269269-270270-(* Patch module implementation *)
271271-module Patch = struct
272272- (* Internal representation as a hash table for efficient operations *)
273273- type t = (string, Yojson.Safe.t) Hashtbl.t
274274-275275- (* JSON Pointer validation - simplified but covers common cases *)
276276- let is_valid_property_path path =
277277- let len = String.length path in
278278- if len = 0 then true (* empty path is valid root *)
279279- else if path.[0] <> '/' then true (* simple property names are valid *)
280280- else
281281- (* Check for valid JSON Pointer format *)
282282- let rec check_escaping i =
283283- if i >= len then true
284284- else match path.[i] with
285285- | '~' when i + 1 < len ->
286286- (match path.[i + 1] with
287287- | '0' | '1' -> check_escaping (i + 2)
288288- | _ -> false)
289289- | '/' -> check_escaping (i + 1)
290290- | _ -> check_escaping (i + 1)
291291- in
292292- check_escaping 0
293293-294294- let empty = Hashtbl.create 8
295295-296296- let of_operations operations =
297297- let patch = Hashtbl.create (List.length operations) in
298298- let rec process = function
299299- | [] -> Ok patch
300300- | (property, value) :: rest ->
301301- if is_valid_property_path property then (
302302- Hashtbl.replace patch property value;
303303- process rest
304304- ) else
305305- Error ("Invalid property path: " ^ property)
306306- in
307307- process operations
308308-309309- let to_operations patch =
310310- Hashtbl.fold (fun property value acc ->
311311- (property, value) :: acc
312312- ) patch []
313313-314314- let of_json_object = function
315315- | `Assoc pairs -> of_operations pairs
316316- | json ->
317317- let json_str = Yojson.Safe.to_string json in
318318- Error (Printf.sprintf "Expected JSON object for Patch, got: %s" json_str)
319319-320320- let to_json_object patch =
321321- let pairs = to_operations patch in
322322- `Assoc pairs
323323-324324- let set_property patch property value =
325325- if is_valid_property_path property then (
326326- let new_patch = Hashtbl.copy patch in
327327- Hashtbl.replace new_patch property value;
328328- Ok new_patch
329329- ) else
330330- Error ("Invalid property path: " ^ property)
331331-332332- let remove_property patch property =
333333- set_property patch property `Null
334334-335335- let has_property patch property =
336336- Hashtbl.mem patch property
337337-338338- let get_property patch property =
339339- try Some (Hashtbl.find patch property)
340340- with Not_found -> None
341341-342342- let merge patch1 patch2 =
343343- let result = Hashtbl.copy patch1 in
344344- Hashtbl.iter (fun property value ->
345345- Hashtbl.replace result property value
346346- ) patch2;
347347- result
348348-349349- let is_empty patch =
350350- Hashtbl.length patch = 0
351351-352352- let size patch =
353353- Hashtbl.length patch
354354-355355- let validate patch =
356356- (* Validate all property paths *)
357357- try
358358- Hashtbl.iter (fun property _value ->
359359- if not (is_valid_property_path property) then
360360- failwith ("Invalid property path: " ^ property)
361361- ) patch;
362362- Ok ()
363363- with
364364- | Failure msg -> Error msg
365365-366366- let equal patch1 patch2 =
367367- if Hashtbl.length patch1 <> Hashtbl.length patch2 then false
368368- else
369369- try
370370- Hashtbl.iter (fun property value1 ->
371371- match get_property patch2 property with
372372- | None -> failwith "Property not found"
373373- | Some value2 when Yojson.Safe.equal value1 value2 -> ()
374374- | Some _ -> failwith "Property values differ"
375375- ) patch1;
376376- true
377377- with
378378- | Failure _ -> false
379379-380380- let pp ppf patch =
381381- Format.fprintf ppf "%s" (Yojson.Safe.to_string (to_json_object patch))
382382-383383- let pp_hum ppf patch =
384384- let operations = to_operations patch in
385385- let op_count = List.length operations in
386386- let key_list = List.map fst operations in
387387- let key_str = match key_list with
388388- | [] -> "none"
389389- | keys -> String.concat ", " keys
390390- in
391391- Format.fprintf ppf "Patch{operations=%d; keys=[%s]}" op_count key_str
392392-393393- let to_string_debug patch =
394394- let operations = to_operations patch in
395395- let op_strings = List.map (fun (prop, value) ->
396396- Printf.sprintf "%s: %s" prop (Yojson.Safe.to_string value)
397397- ) operations in
398398- Printf.sprintf "Patch({%s})" (String.concat "; " op_strings)
399399-400400- (* JSON serialization *)
401401- let to_json patch = to_json_object patch
402402-403403- let of_json json = of_json_object json
404404-end
405405-406406-(* Legacy type aliases *)
407407-type id = string
408408-type jint = int
409409-type uint = int
410410-type date = float
411411-type utc_date = float
412412-413413-(* Collection types *)
414414-type 'v string_map = (string, 'v) Hashtbl.t
415415-type 'v id_map = (id, 'v) Hashtbl.t
416416-417417-(* Protocol-specific types *)
418418-type json_pointer = string
419419-420420-(* Constants module *)
421421-module Constants = struct
422422- let vacation_response_id = "singleton"
423423-424424- module Content_type = struct
425425- let json = "application/json"
426426- end
427427-428428- module User_agent = struct
429429- let ocaml_jmap = "OCaml-JMAP/1.0"
430430- let eio_client = "OCaml JMAP Client/Eio"
431431- end
432432-end
-592
jmap/jmap/types.mli
···11-(** JMAP Core Types Library (RFC 8620)
22-33- This module provides all fundamental JMAP data types in a unified interface.
44- It consolidates the core primitives (Id, Date, UInt), data structures (Patch),
55- and collection types used throughout the JMAP protocol.
66-77- The module is organized into clear sections:
88- - {!Types.Id}: JMAP Id type with validation and JSON serialization
99- - {!Types.Date}: JMAP Date type with RFC 3339 support
1010- - {!Types.UInt}: JMAP UnsignedInt type with range validation
1111- - {!Types.Patch}: JMAP Patch objects for property updates
1212- - Legacy type aliases for backwards compatibility
1313-1414- @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-1> RFC 8620, Section 1 *)
1515-1616-(** {1 Core JMAP Types} *)
1717-1818-(** JMAP Id data type with validation and JSON serialization.
1919-2020- The Id data type is a string of 1 to 255 octets in length and MUST consist
2121- only of characters from the base64url alphabet, as defined in Section 5 of
2222- RFC 4648. This includes ASCII alphanumeric characters, plus the characters
2323- '-' and '_'.
2424-2525- Ids are used to identify JMAP objects within an account. They are assigned
2626- by the server and are immutable once assigned. The same id MUST refer to
2727- the same object throughout the lifetime of the object.
2828-2929- @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-1.2> RFC 8620, Section 1.2 *)
3030-module Id : sig
3131- (** Abstract type representing a JMAP Id. *)
3232- type t
3333-3434- (** JSON serialization interface *)
3535- include Jmap_sigs.JSONABLE with type t := t
3636-3737- (** Pretty-printing interface *)
3838- include Jmap_sigs.PRINTABLE with type t := t
3939-4040- (** {2 Construction and Access} *)
4141-4242- (** Create a new Id from a string.
4343- @param str The string representation.
4444- @return Ok with the created Id, or Error if the string violates Id constraints. *)
4545- val of_string : string -> (t, string) result
4646-4747- (** Convert an Id to its string representation.
4848- @param id The Id to convert.
4949- @return The string representation. *)
5050- val to_string : t -> string
5151-5252- (** Pretty-print an Id.
5353- @param ppf The formatter.
5454- @param id The Id to print. *)
5555- val pp : Format.formatter -> t -> unit
5656-5757- (** {2 Validation} *)
5858-5959- (** Check if a string is a valid JMAP Id.
6060- @param str The string to validate.
6161- @return True if the string meets Id requirements, false otherwise. *)
6262- val is_valid_string : string -> bool
6363-6464- (** Validate an Id according to JMAP constraints.
6565- @param id The Id to validate.
6666- @return Ok () if valid, Error with description if invalid. *)
6767- val validate : t -> (unit, string) result
6868-6969- (** {2 Comparison and Utilities} *)
7070-7171- (** Compare two Ids for equality.
7272- @param id1 First Id.
7373- @param id2 Second Id.
7474- @return True if equal, false otherwise. *)
7575- val equal : t -> t -> bool
7676-7777- (** Compare two Ids lexicographically.
7878- @param id1 First Id.
7979- @param id2 Second Id.
8080- @return Negative if id1 < id2, zero if equal, positive if id1 > id2. *)
8181- val compare : t -> t -> int
8282-8383- (** Pretty-print an Id for debugging.
8484- @param ppf The formatter.
8585- @param id The Id to format. *)
8686- val pp_debug : Format.formatter -> t -> unit
8787-8888- (** Convert an Id to a human-readable string for debugging.
8989- @param id The Id to format.
9090- @return A debug string representation. *)
9191- val to_string_debug : t -> string
9292-end
9393-9494-(** JMAP Date data type with RFC 3339 support and JSON serialization.
9595-9696- The Date data type is a string in RFC 3339 "date-time" format, optionally
9797- with timezone information. For example: "2014-10-30T14:12:00+08:00" or
9898- "2014-10-30T06:12:00Z".
9999-100100- In this OCaml implementation, dates are internally represented as Unix
101101- timestamps (float) for efficient computation, with conversion to/from
102102- RFC 3339 string format handled by the serialization functions.
103103-104104- {b Note}: When represented as a float, precision may be lost for sub-second
105105- values. The implementation preserves second-level precision.
106106-107107- @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-1.4> RFC 8620, Section 1.4
108108- @see <https://www.rfc-editor.org/rfc/rfc3339.html> RFC 3339 *)
109109-module Date : sig
110110- (** Abstract type representing a JMAP Date. *)
111111- type t
112112-113113- (** JSON serialization interface *)
114114- include Jmap_sigs.JSONABLE with type t := t
115115-116116- (** Pretty-printing interface *)
117117- include Jmap_sigs.PRINTABLE with type t := t
118118-119119- (** {2 Construction and Access} *)
120120-121121- (** Create a Date from a Unix timestamp.
122122- @param timestamp The Unix timestamp (seconds since epoch).
123123- @return A Date representing the timestamp. *)
124124- val of_timestamp : float -> t
125125-126126- (** Convert a Date to a Unix timestamp.
127127- @param date The Date to convert.
128128- @return The Unix timestamp (seconds since epoch). *)
129129- val to_timestamp : t -> float
130130-131131- (** Create a Date from an RFC 3339 string.
132132- @param str The RFC 3339 formatted string.
133133- @return Ok with the parsed Date, or Error if the string is not valid RFC 3339. *)
134134- val of_rfc3339 : string -> (t, string) result
135135-136136- (** Convert a Date to an RFC 3339 string.
137137- @param date The Date to convert.
138138- @return The RFC 3339 formatted string. *)
139139- val to_rfc3339 : t -> string
140140-141141- (** Create a Date representing the current time.
142142- @return A Date set to the current time. *)
143143- val now : unit -> t
144144-145145- (** {2 Validation} *)
146146-147147- (** Validate a Date according to JMAP constraints.
148148- @param date The Date to validate.
149149- @return Ok () if valid, Error with description if invalid. *)
150150- val validate : t -> (unit, string) result
151151-152152- (** {2 Comparison and Utilities} *)
153153-154154- (** Compare two Dates for equality.
155155- @param date1 First Date.
156156- @param date2 Second Date.
157157- @return True if equal (within 1 second precision), false otherwise. *)
158158- val equal : t -> t -> bool
159159-160160- (** Compare two Dates chronologically.
161161- @param date1 First Date.
162162- @param date2 Second Date.
163163- @return Negative if date1 < date2, zero if equal, positive if date1 > date2. *)
164164- val compare : t -> t -> int
165165-166166- (** Check if first Date is before second Date.
167167- @param date1 First Date.
168168- @param date2 Second Date.
169169- @return True if date1 is before date2. *)
170170- val is_before : t -> t -> bool
171171-172172- (** Check if first Date is after second Date.
173173- @param date1 First Date.
174174- @param date2 Second Date.
175175- @return True if date1 is after date2. *)
176176- val is_after : t -> t -> bool
177177-178178- (** Pretty-print a Date in RFC3339 format.
179179- @param ppf The formatter.
180180- @param date The Date to print. *)
181181- val pp : Format.formatter -> t -> unit
182182-183183- (** Pretty-print a Date for debugging.
184184- @param ppf The formatter.
185185- @param date The Date to format. *)
186186- val pp_debug : Format.formatter -> t -> unit
187187-188188- (** Convert a Date to a human-readable string for debugging.
189189- @param date The Date to format.
190190- @return A debug string representation. *)
191191- val to_string_debug : t -> string
192192-end
193193-194194-(** JMAP UnsignedInt data type with range validation and JSON serialization.
195195-196196- The UnsignedInt data type is an unsigned integer in the range [0, 2^53-1].
197197- This corresponds to the safe integer range for unsigned values in JavaScript
198198- and JSON implementations.
199199-200200- In OCaml, this is represented as a regular [int]. Note that OCaml's [int]
201201- on 64-bit platforms has a larger range, but JMAP protocol compliance
202202- requires staying within the specified range and ensuring non-negative values.
203203-204204- Common uses include counts, limits, positions, and sizes within the protocol.
205205-206206- @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-1.3> RFC 8620, Section 1.3 *)
207207-module UInt : sig
208208- (** Abstract type representing a JMAP UnsignedInt. *)
209209- type t
210210-211211- (** JSON serialization interface *)
212212- include Jmap_sigs.JSONABLE with type t := t
213213-214214- (** Pretty-printing interface *)
215215- include Jmap_sigs.PRINTABLE with type t := t
216216-217217- (** {2 Construction and Access} *)
218218-219219- (** Create an UnsignedInt from an int.
220220- @param i The int value.
221221- @return Ok with the UnsignedInt, or Error if the value is negative or too large. *)
222222- val of_int : int -> (t, string) result
223223-224224- (** Convert an UnsignedInt to an int.
225225- @param uint The UnsignedInt to convert.
226226- @return The int representation. *)
227227- val to_int : t -> int
228228-229229- (** Create an UnsignedInt from a string.
230230- @param str The string representation of a non-negative integer.
231231- @return Ok with the UnsignedInt, or Error if parsing fails or value is invalid. *)
232232- val of_string : string -> (t, string) result
233233-234234- (** Convert an UnsignedInt to a string.
235235- @param uint The UnsignedInt to convert.
236236- @return The string representation. *)
237237- val to_string : t -> string
238238-239239- (** Pretty-print an UnsignedInt.
240240- @param ppf The formatter.
241241- @param uint The UnsignedInt to print. *)
242242- val pp : Format.formatter -> t -> unit
243243-244244- (** {2 Constants} *)
245245-246246- (** Zero value. *)
247247- val zero : t
248248-249249- (** One value. *)
250250- val one : t
251251-252252- (** Maximum safe value (2^53 - 1). *)
253253- val max_safe : t
254254-255255- (** {2 Validation} *)
256256-257257- (** Check if an int is a valid UnsignedInt value.
258258- @param i The int to validate.
259259- @return True if the value is in valid range, false otherwise. *)
260260- val is_valid_int : int -> bool
261261-262262- (** Validate an UnsignedInt according to JMAP constraints.
263263- @param uint The UnsignedInt to validate.
264264- @return Ok () if valid, Error with description if invalid. *)
265265- val validate : t -> (unit, string) result
266266-267267- (** {2 Arithmetic Operations} *)
268268-269269- (** Add two UnsignedInts.
270270- @param uint1 First UnsignedInt.
271271- @param uint2 Second UnsignedInt.
272272- @return Ok with the sum, or Error if overflow would occur. *)
273273- val add : t -> t -> (t, string) result
274274-275275- (** Subtract two UnsignedInts.
276276- @param uint1 First UnsignedInt (minuend).
277277- @param uint2 Second UnsignedInt (subtrahend).
278278- @return Ok with the difference, or Error if result would be negative. *)
279279- val sub : t -> t -> (t, string) result
280280-281281- (** Multiply two UnsignedInts.
282282- @param uint1 First UnsignedInt.
283283- @param uint2 Second UnsignedInt.
284284- @return Ok with the product, or Error if overflow would occur. *)
285285- val mul : t -> t -> (t, string) result
286286-287287- (** {2 Comparison and Utilities} *)
288288-289289- (** Compare two UnsignedInts for equality.
290290- @param uint1 First UnsignedInt.
291291- @param uint2 Second UnsignedInt.
292292- @return True if equal, false otherwise. *)
293293- val equal : t -> t -> bool
294294-295295- (** Compare two UnsignedInts numerically.
296296- @param uint1 First UnsignedInt.
297297- @param uint2 Second UnsignedInt.
298298- @return Negative if uint1 < uint2, zero if equal, positive if uint1 > uint2. *)
299299- val compare : t -> t -> int
300300-301301- (** Get the minimum of two UnsignedInts.
302302- @param uint1 First UnsignedInt.
303303- @param uint2 Second UnsignedInt.
304304- @return The smaller value. *)
305305- val min : t -> t -> t
306306-307307- (** Get the maximum of two UnsignedInts.
308308- @param uint1 First UnsignedInt.
309309- @param uint2 Second UnsignedInt.
310310- @return The larger value. *)
311311- val max : t -> t -> t
312312-313313- (** Pretty-print an UnsignedInt for debugging.
314314- @param ppf The formatter.
315315- @param uint The UnsignedInt to format. *)
316316- val pp_debug : Format.formatter -> t -> unit
317317-318318- (** Convert an UnsignedInt to a human-readable string for debugging.
319319- @param uint The UnsignedInt to format.
320320- @return A debug string representation. *)
321321- val to_string_debug : t -> string
322322-end
323323-324324-(** JMAP Patch Object for property updates with JSON serialization.
325325-326326- A patch object is used to update properties of JMAP objects. It represents
327327- a JSON object where each key is a property path (using JSON Pointer syntax)
328328- and each value is the new value to set for that property, or null to remove
329329- the property.
330330-331331- Patch objects are commonly used in /set method calls to update existing
332332- objects without having to send the complete object representation.
333333-334334- Examples of patch operations:
335335- - Setting a property: [{"name": "New Name"}]
336336- - Removing a property: [{"oldProperty": null}]
337337- - Setting nested properties: [{"address/street": "123 Main St"}]
338338-339339- @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-5.3> RFC 8620, Section 5.3
340340- @see <https://www.rfc-editor.org/rfc/rfc6901.html> RFC 6901 (JSON Pointer) *)
341341-module Patch : sig
342342- (** Abstract type representing a JMAP Patch Object. *)
343343- type t
344344-345345- (** JSON serialization interface *)
346346- include Jmap_sigs.JSONABLE with type t := t
347347-348348- (** Pretty-printing interface *)
349349- include Jmap_sigs.PRINTABLE with type t := t
350350-351351- (** {2 Construction and Access} *)
352352-353353- (** Create an empty patch object.
354354- @return An empty patch with no operations. *)
355355- val empty : t
356356-357357- (** Create a patch from a list of property-value pairs.
358358- @param operations List of (property_path, value) pairs.
359359- @return Ok with the patch, or Error if any property path is invalid. *)
360360- val of_operations : (string * Yojson.Safe.t) list -> (t, string) result
361361-362362- (** Convert a patch to a list of property-value pairs.
363363- @param patch The patch to convert.
364364- @return List of (property_path, value) pairs. *)
365365- val to_operations : t -> (string * Yojson.Safe.t) list
366366-367367- (** Create a patch from a Yojson.Safe.t object directly.
368368- @param json The JSON object.
369369- @return Ok with the patch, or Error if the JSON is not a valid object. *)
370370- val of_json_object : Yojson.Safe.t -> (t, string) result
371371-372372- (** Convert a patch to a Yojson.Safe.t object directly.
373373- @param patch The patch to convert.
374374- @return The JSON object representation. *)
375375- val to_json_object : t -> Yojson.Safe.t
376376-377377- (** {2 Patch Operations} *)
378378-379379- (** Set a property in the patch.
380380- @param patch The patch to modify.
381381- @param property The property path (JSON Pointer format).
382382- @param value The value to set.
383383- @return Ok with the updated patch, or Error if the property path is invalid. *)
384384- val set_property : t -> string -> Yojson.Safe.t -> (t, string) result
385385-386386- (** Remove a property in the patch (set to null).
387387- @param patch The patch to modify.
388388- @param property The property path to remove.
389389- @return Ok with the updated patch, or Error if the property path is invalid. *)
390390- val remove_property : t -> string -> (t, string) result
391391-392392- (** Check if a property is set in the patch.
393393- @param patch The patch to check.
394394- @param property The property path to check.
395395- @return True if the property is explicitly set in the patch. *)
396396- val has_property : t -> string -> bool
397397-398398- (** Get a property value from the patch.
399399- @param patch The patch to query.
400400- @param property The property path to get.
401401- @return Some value if the property is set, None if not present. *)
402402- val get_property : t -> string -> Yojson.Safe.t option
403403-404404- (** {2 Patch Composition} *)
405405-406406- (** Merge two patches, with the second patch taking precedence.
407407- @param patch1 The first patch.
408408- @param patch2 The second patch (higher precedence).
409409- @return The merged patch. *)
410410- val merge : t -> t -> t
411411-412412- (** Check if a patch is empty (no operations).
413413- @param patch The patch to check.
414414- @return True if the patch has no operations. *)
415415- val is_empty : t -> bool
416416-417417- (** Get the number of operations in a patch.
418418- @param patch The patch to count.
419419- @return The number of property operations. *)
420420- val size : t -> int
421421-422422- (** {2 Validation} *)
423423-424424- (** Validate a patch according to JMAP constraints.
425425- @param patch The patch to validate.
426426- @return Ok () if valid, Error with description if invalid. *)
427427- val validate : t -> (unit, string) result
428428-429429- (** Validate a JSON Pointer path.
430430- @param path The property path to validate.
431431- @return True if the path is a valid JSON Pointer, false otherwise. *)
432432- val is_valid_property_path : string -> bool
433433-434434- (** {2 Comparison and Utilities} *)
435435-436436- (** Compare two patches for equality.
437437- @param patch1 First patch.
438438- @param patch2 Second patch.
439439- @return True if patches have identical operations, false otherwise. *)
440440- val equal : t -> t -> bool
441441-442442- (** Convert a patch to a human-readable string for debugging.
443443- @param patch The patch to format.
444444- @return A debug string representation. *)
445445- val to_string_debug : t -> string
446446-end
447447-448448-(** {1 Legacy Types and Collections}
449449-450450- This section provides type aliases and collection types for compatibility
451451- and common use cases throughout the JMAP protocol. These types maintain
452452- backwards compatibility with existing code while the core types above
453453- provide the preferred interface. *)
454454-455455-(** The Id data type (legacy alias - prefer {!Types.Id}).
456456-457457- A string of 1 to 255 octets in length and MUST consist only of characters
458458- from the base64url alphabet, as defined in Section 5 of RFC 4648. This
459459- includes ASCII alphanumeric characters, plus the characters '-' and '_'.
460460-461461- Ids are used to identify JMAP objects within an account. They are assigned
462462- by the server and are immutable once assigned. The same id MUST refer to
463463- the same object throughout the lifetime of the object.
464464-465465- {b Note}: In this OCaml implementation, ids are represented as regular strings.
466466- Validation of id format is the responsibility of the client/server implementation.
467467-468468- @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-1.2> RFC 8620, Section 1.2 *)
469469-type id = string
470470-471471-(** The Int data type.
472472-473473- A signed 53-bit integer in the range [-2^53+1, 2^53-1]. This corresponds
474474- to the safe integer range in JavaScript and JSON implementations.
475475-476476- In OCaml, this is represented as a regular [int]. Note that OCaml's [int]
477477- on 64-bit platforms has a larger range, but JMAP protocol compliance
478478- requires staying within the specified range.
479479-480480- @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-1.3> RFC 8620, Section 1.3 *)
481481-type jint = int
482482-483483-(** The UnsignedInt data type (legacy alias - prefer {!Types.UInt}).
484484-485485- An unsigned integer in the range [0, 2^53-1]. This is the same as [jint]
486486- but restricted to non-negative values.
487487-488488- Common uses include counts, limits, positions, and sizes within the protocol.
489489-490490- @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-1.3> RFC 8620, Section 1.3 *)
491491-type uint = int
492492-493493-(** The Date data type (legacy alias - prefer {!Types.Date}).
494494-495495- A string in RFC 3339 "date-time" format, optionally with timezone information.
496496- For example: "2014-10-30T14:12:00+08:00" or "2014-10-30T06:12:00Z".
497497-498498- In this OCaml implementation, dates are represented as Unix timestamps (float).
499499- Conversion to/from RFC 3339 string format is handled by the wire protocol
500500- serialization layer.
501501-502502- {b Note}: When represented as a float, precision may be lost for sub-second
503503- values. Consider the precision requirements of your application.
504504-505505- @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-1.4> RFC 8620, Section 1.4
506506- @see <https://www.rfc-editor.org/rfc/rfc3339.html> RFC 3339 *)
507507-type date = float
508508-509509-(** The UTCDate data type.
510510-511511- A string in RFC 3339 "date-time" format with timezone restricted to UTC
512512- (i.e., ending with "Z"). For example: "2014-10-30T06:12:00Z".
513513-514514- This is a more restrictive version of the [date] type, used in contexts
515515- where timezone normalization is required.
516516-517517- @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-1.4> RFC 8620, Section 1.4 *)
518518-type utc_date = float
519519-520520-(** {2 Collection Types} *)
521521-522522-(** Represents a JSON object used as a map from String to arbitrary values.
523523-524524- In JMAP, many objects are represented as maps with string keys. This type
525525- provides a convenient OCaml representation using hash tables for efficient
526526- lookup and modification.
527527-528528- {b Usage example}: Account capabilities, session capabilities, and various
529529- property maps throughout the protocol.
530530-531531- @param 'v The type of values stored in the map *)
532532-type 'v string_map = (string, 'v) Hashtbl.t
533533-534534-(** Represents a JSON object used as a map from Id to arbitrary values.
535535-536536- This is similar to [string_map] but specifically for JMAP Id keys. Common
537537- use cases include mapping object IDs to objects, errors, or update information.
538538-539539- {b Usage example}: The "create" argument in /set methods maps client-assigned
540540- IDs to objects to be created.
541541-542542- @param 'v The type of values stored in the map *)
543543-type 'v id_map = (id, 'v) Hashtbl.t
544544-545545-(** {2 Protocol-Specific Types} *)
546546-547547-(** Represents a JSON Pointer path with JMAP extensions.
548548-549549- A JSON Pointer is a string syntax for identifying specific values within
550550- a JSON document. JMAP extends this with additional syntax for referencing
551551- values from previous method calls within the same request.
552552-553553- Examples of valid JSON pointers in JMAP:
554554- - "/property" - References the "property" field in the root object
555555- - "/items/0" - References the first item in the "items" array
556556- - "*" - Represents all properties or all array elements
557557-558558- The pointer syntax is used extensively in result references and patch
559559- operations within JMAP.
560560-561561- @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-3.7> RFC 8620, Section 3.7
562562- @see <https://www.rfc-editor.org/rfc/rfc6901.html> RFC 6901 (JSON Pointer) *)
563563-type json_pointer = string
564564-565565-(** {2 Protocol Constants} *)
566566-567567-(** Protocol constants for common values.
568568-569569- This module contains commonly used constant values throughout the
570570- JMAP protocol, reducing hardcoded strings and providing type safety. *)
571571-module Constants : sig
572572- (** VacationResponse singleton object ID.
573573-574574- VacationResponse objects always use this fixed ID per JMAP specification.
575575- @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-7> RFC 8621, Section 7 *)
576576- val vacation_response_id : string
577577-578578- (** HTTP Content-Type values for JMAP protocol. *)
579579- module Content_type : sig
580580- (** JMAP protocol content type. *)
581581- val json : string
582582- end
583583-584584- (** Default User-Agent strings. *)
585585- module User_agent : sig
586586- (** Default OCaml JMAP client user agent. *)
587587- val ocaml_jmap : string
588588-589589- (** Eio-based client user agent. *)
590590- val eio_client : string
591591- end
592592-end
+85
jmap/jmap/uint.ml
···11+(** JMAP UnsignedInt Implementation *)
22+33+type t = int
44+55+(* Maximum safe integer value for JavaScript: 2^53 - 1 *)
66+let max_safe_value = 9007199254740991
77+88+let is_valid_int i = i >= 0 && i <= max_safe_value
99+1010+let of_int i =
1111+ if is_valid_int i then Ok i
1212+ else if i < 0 then Error "UnsignedInt cannot be negative"
1313+ else Error "UnsignedInt cannot exceed 2^53-1"
1414+1515+let to_int uint = uint
1616+1717+let of_string str =
1818+ try
1919+ let i = int_of_string str in
2020+ of_int i
2121+ with
2222+ | Failure _ -> Error "Invalid integer string format"
2323+ | Invalid_argument _ -> Error "Invalid integer string format"
2424+2525+let to_string uint = string_of_int uint
2626+2727+let pp ppf uint = Format.fprintf ppf "%d" uint
2828+2929+let pp_hum ppf uint = Format.fprintf ppf "UInt(%d)" uint
3030+3131+(* Constants *)
3232+let zero = 0
3333+let one = 1
3434+let max_safe = max_safe_value
3535+3636+let validate uint =
3737+ if is_valid_int uint then Ok ()
3838+ else Error "UnsignedInt value out of valid range"
3939+4040+(* Arithmetic operations with overflow checking *)
4141+let add uint1 uint2 =
4242+ let result = uint1 + uint2 in
4343+ if result >= uint1 && result >= uint2 && is_valid_int result then
4444+ Ok result
4545+ else
4646+ Error "UnsignedInt addition overflow"
4747+4848+let sub uint1 uint2 =
4949+ if uint1 >= uint2 then Ok (uint1 - uint2)
5050+ else Error "UnsignedInt subtraction would result in negative value"
5151+5252+let mul uint1 uint2 =
5353+ if uint1 = 0 || uint2 = 0 then Ok 0
5454+ else if uint1 <= max_safe_value / uint2 then
5555+ Ok (uint1 * uint2)
5656+ else
5757+ Error "UnsignedInt multiplication overflow"
5858+5959+(* Comparison and utilities *)
6060+let equal = (=)
6161+6262+let compare = compare
6363+6464+let min uint1 uint2 = if uint1 <= uint2 then uint1 else uint2
6565+6666+let max uint1 uint2 = if uint1 >= uint2 then uint1 else uint2
6767+6868+let pp_debug ppf uint = Format.fprintf ppf "UInt(%d)" uint
6969+7070+let to_string_debug uint = Printf.sprintf "UInt(%d)" uint
7171+7272+(* JSON serialization *)
7373+let to_json uint = `Int uint
7474+7575+let of_json = function
7676+ | `Int i -> of_int i
7777+ | `Float f ->
7878+ (* Handle case where JSON parser represents integers as floats *)
7979+ if f >= 0.0 && f <= float_of_int max_safe_value && f = Float.round f then
8080+ of_int (int_of_float f)
8181+ else
8282+ Error "Float value is not a valid UnsignedInt"
8383+ | json ->
8484+ let json_str = Yojson.Safe.to_string json in
8585+ Error (Printf.sprintf "Expected JSON number for UnsignedInt, got: %s" json_str)
+128
jmap/jmap/uint.mli
···11+(** JMAP UnsignedInt data type with range validation and JSON serialization.
22+33+ The UnsignedInt data type is an unsigned integer in the range [0, 2^53-1].
44+ This corresponds to the safe integer range for unsigned values in JavaScript
55+ and JSON implementations.
66+77+ In OCaml, this is represented as a regular [int]. Note that OCaml's [int]
88+ on 64-bit platforms has a larger range, but JMAP protocol compliance
99+ requires staying within the specified range and ensuring non-negative values.
1010+1111+ Common uses include counts, limits, positions, and sizes within the protocol.
1212+1313+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-1.3> RFC 8620, Section 1.3 *)
1414+1515+(** Abstract type representing a JMAP UnsignedInt. *)
1616+type t
1717+1818+(** JSON serialization interface *)
1919+include Jmap_sigs.JSONABLE with type t := t
2020+2121+(** Pretty-printing interface *)
2222+include Jmap_sigs.PRINTABLE with type t := t
2323+2424+(** {2 Construction and Access} *)
2525+2626+(** Create an UnsignedInt from an int.
2727+ @param i The int value.
2828+ @return Ok with the UnsignedInt, or Error if the value is negative or too large. *)
2929+val of_int : int -> (t, string) result
3030+3131+(** Convert an UnsignedInt to an int.
3232+ @param uint The UnsignedInt to convert.
3333+ @return The int representation. *)
3434+val to_int : t -> int
3535+3636+(** Create an UnsignedInt from a string.
3737+ @param str The string representation of a non-negative integer.
3838+ @return Ok with the UnsignedInt, or Error if parsing fails or value is invalid. *)
3939+val of_string : string -> (t, string) result
4040+4141+(** Convert an UnsignedInt to a string.
4242+ @param uint The UnsignedInt to convert.
4343+ @return The string representation. *)
4444+val to_string : t -> string
4545+4646+(** Pretty-print an UnsignedInt.
4747+ @param ppf The formatter.
4848+ @param uint The UnsignedInt to print. *)
4949+val pp : Format.formatter -> t -> unit
5050+5151+(** {2 Constants} *)
5252+5353+(** Zero value. *)
5454+val zero : t
5555+5656+(** One value. *)
5757+val one : t
5858+5959+(** Maximum safe value (2^53 - 1). *)
6060+val max_safe : t
6161+6262+(** {2 Validation} *)
6363+6464+(** Check if an int is a valid UnsignedInt value.
6565+ @param i The int to validate.
6666+ @return True if the value is in valid range, false otherwise. *)
6767+val is_valid_int : int -> bool
6868+6969+(** Validate an UnsignedInt according to JMAP constraints.
7070+ @param uint The UnsignedInt to validate.
7171+ @return Ok () if valid, Error with description if invalid. *)
7272+val validate : t -> (unit, string) result
7373+7474+(** {2 Arithmetic Operations} *)
7575+7676+(** Add two UnsignedInts.
7777+ @param uint1 First UnsignedInt.
7878+ @param uint2 Second UnsignedInt.
7979+ @return Ok with the sum, or Error if overflow would occur. *)
8080+val add : t -> t -> (t, string) result
8181+8282+(** Subtract two UnsignedInts.
8383+ @param uint1 First UnsignedInt (minuend).
8484+ @param uint2 Second UnsignedInt (subtrahend).
8585+ @return Ok with the difference, or Error if result would be negative. *)
8686+val sub : t -> t -> (t, string) result
8787+8888+(** Multiply two UnsignedInts.
8989+ @param uint1 First UnsignedInt.
9090+ @param uint2 Second UnsignedInt.
9191+ @return Ok with the product, or Error if overflow would occur. *)
9292+val mul : t -> t -> (t, string) result
9393+9494+(** {2 Comparison and Utilities} *)
9595+9696+(** Compare two UnsignedInts for equality.
9797+ @param uint1 First UnsignedInt.
9898+ @param uint2 Second UnsignedInt.
9999+ @return True if equal, false otherwise. *)
100100+val equal : t -> t -> bool
101101+102102+(** Compare two UnsignedInts numerically.
103103+ @param uint1 First UnsignedInt.
104104+ @param uint2 Second UnsignedInt.
105105+ @return Negative if uint1 < uint2, zero if equal, positive if uint1 > uint2. *)
106106+val compare : t -> t -> int
107107+108108+(** Get the minimum of two UnsignedInts.
109109+ @param uint1 First UnsignedInt.
110110+ @param uint2 Second UnsignedInt.
111111+ @return The smaller value. *)
112112+val min : t -> t -> t
113113+114114+(** Get the maximum of two UnsignedInts.
115115+ @param uint1 First UnsignedInt.
116116+ @param uint2 Second UnsignedInt.
117117+ @return The larger value. *)
118118+val max : t -> t -> t
119119+120120+(** Pretty-print an UnsignedInt for debugging.
121121+ @param ppf The formatter.
122122+ @param uint The UnsignedInt to format. *)
123123+val pp_debug : Format.formatter -> t -> unit
124124+125125+(** Convert an UnsignedInt to a human-readable string for debugging.
126126+ @param uint The UnsignedInt to format.
127127+ @return A debug string representation. *)
128128+val to_string_debug : t -> string
+4-4
jmap/jmap/wire.ml
···11-open Types
11+(* Use underlying types directly to avoid circular dependency with Jmap module *)
2233module Invocation = struct
44 type t = {
···2323 type t = {
2424 result_of : string;
2525 name : string;
2626- path : json_pointer;
2626+ path : string;
2727 }
28282929 let result_of t = t.result_of
···3838 type t = {
3939 using : string list;
4040 method_calls : Invocation.t list;
4141- created_ids : id id_map option;
4141+ created_ids : (string, string) Hashtbl.t option;
4242 }
43434444 let using t = t.using
···5252module Response = struct
5353 type t = {
5454 method_responses : response_invocation list;
5555- created_ids : id id_map option;
5555+ created_ids : (string, string) Hashtbl.t option;
5656 session_state : string;
5757 }
5858
+7-7
jmap/jmap/wire.mli
···12121313 @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-3> RFC 8620, Section 3 *)
14141515-open Types
1515+(* Use underlying types directly to avoid circular dependency with Jmap module *)
16161717(** {1 Method Invocations} *)
1818···119119120120 (** Get the JSON Pointer path within the referenced property.
121121 @return The JSON Pointer path (e.g., "/0", "/items/5/id") *)
122122- val path : t -> json_pointer
122122+ val path : t -> string
123123124124 (** Create a new result reference.
125125 @param result_of The method call ID to reference
···129129 val v :
130130 result_of:string ->
131131 name:string ->
132132- path:json_pointer ->
132132+ path:string ->
133133 unit ->
134134 t
135135end
···173173174174 (** Get the optional createdIds map.
175175 @return Map from client IDs to server IDs, if present *)
176176- val created_ids : t -> id id_map option
176176+ val created_ids : t -> (string, string) Hashtbl.t option
177177178178 (** Create a new request object.
179179 @param using List of capability URIs required for this request
···183183 val v :
184184 using:string list ->
185185 method_calls:Invocation.t list ->
186186- ?created_ids:id id_map ->
186186+ ?created_ids:(string, string) Hashtbl.t ->
187187 unit ->
188188 t
189189end
···222222223223 (** Get the optional createdIds map.
224224 @return Map from client IDs to server IDs, if present *)
225225- val created_ids : t -> id id_map option
225225+ val created_ids : t -> (string, string) Hashtbl.t option
226226227227 (** Get the current session state.
228228 @return Session state string for subsequent requests *)
···235235 @return A new response object *)
236236 val v :
237237 method_responses:response_invocation list ->
238238- ?created_ids:id id_map ->
238238+ ?created_ids:(string, string) Hashtbl.t ->
239239 session_state:string ->
240240 unit ->
241241 t
+3-3
jmap/test_method.ml
···88 let valid_id = Jmap.Types.Id.of_string "abc123-_xyz" in
99 match valid_id with
1010 | Ok id ->
1111- printf "✓ Created valid ID: %s\n" (Jmap.Types.Id.to_string id);
1212- printf "✓ Debug representation: %s\n" (Jmap.Types.Id.to_string_debug id)
1111+ printf "✓ Created valid ID: %s\n" (stringo_string id);
1212+ printf "✓ Debug representation: %s\n" (stringo_string_debug id)
1313 | Error msg ->
1414 printf "✗ Failed to create valid ID: %s\n" msg
1515···6464 (* Test Id JSON roundtrip *)
6565 (match Jmap.Types.Id.of_string "test123" with
6666 | Ok id ->
6767- let json = Jmap.Types.Id.to_json id in
6767+ let json = stringo_json id in
6868 let parsed = Jmap.Types.Id.of_json json in
6969 (match parsed with
7070 | Ok parsed_id when Jmap.Types.Id.equal id parsed_id ->