···11+(** JMAP Mail Extension Library (RFC 8621).
22+33+ This library extends the core JMAP protocol with email-specific
44+ functionality as defined in RFC 8621. It provides types and signatures
55+ for interacting with JMAP Mail data types: Mailbox, Thread, Email,
66+ SearchSnippet, Identity, EmailSubmission, and VacationResponse.
77+88+ Requires the core Jmap library and Jmap_unix library for network operations.
99+1010+ @see <https://www.rfc-editor.org/rfc/rfc8621.html> RFC 8621: JMAP for Mail
1111+*)
1212+1313+open Jmap.Types
1414+1515+(** {1 Core Types} *)
1616+module Types = Jmap_email_types
1717+1818+(** {1 Mailbox}
1919+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-2> RFC 8621, Section 2 *)
2020+module Mailbox = Jmap_mailbox
2121+2222+(** {1 Thread}
2323+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-3> RFC 8621, Section 3 *)
2424+module Thread = Jmap_thread
2525+2626+(** {1 Search Snippet}
2727+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-5> RFC 8621, Section 5 *)
2828+module SearchSnippet = Jmap_search_snippet
2929+3030+(** {1 Identity}
3131+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-6> RFC 8621, Section 6 *)
3232+module Identity = Jmap_identity
3333+3434+(** {1 Email Submission}
3535+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-7> RFC 8621, Section 7 *)
3636+module Submission = Jmap_submission
3737+3838+(** {1 Vacation Response}
3939+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-8> RFC 8621, Section 8 *)
4040+module Vacation = Jmap_vacation
4141+4242+(** {1 Example Usage}
4343+4444+ The following example demonstrates using the JMAP Email library to fetch unread emails
4545+ from a specific sender.
4646+4747+{[
4848+ (* OCaml 5.1 required for Lwt let operators *)
4949+ open Lwt.Syntax
5050+ open Jmap
5151+ open Jmap.Types
5252+ open Jmap.Wire
5353+ open Jmap.Methods
5454+ open Jmap_email
5555+ open Jmap.Unix
5656+5757+ let list_unread_from_sender ctx session sender_email =
5858+ (* Find the primary mail account *)
5959+ let primary_mail_account_id =
6060+ Hashtbl.find session.primary_accounts capability_mail
6161+ in
6262+ (* Construct the filter *)
6363+ let filter : filter =
6464+ Filter_operator (Filter_operator.v
6565+ ~operator:`AND
6666+ ~conditions:[
6767+ Filter_condition (Yojson.Safe.to_basic (`Assoc [
6868+ ("from", `String sender_email);
6969+ ]));
7070+ Filter_condition (Yojson.Safe.to_basic (`Assoc [
7171+ ("hasKeyword", `String keyword_seen);
7272+ ("value", `Bool false);
7373+ ]));
7474+ ]
7575+ ())
7676+ in
7777+ (* Prepare the Email/query invocation *)
7878+ let query_args = Query_args.v
7979+ ~account_id:primary_mail_account_id
8080+ ~filter
8181+ ~sort:[
8282+ Comparator.v
8383+ ~property:"receivedAt"
8484+ ~is_ascending:false
8585+ ()
8686+ ]
8787+ ~position:0
8888+ ~limit:20 (* Get latest 20 *)
8989+ ~calculate_total:false
9090+ ~collapse_threads:false
9191+ ()
9292+ in
9393+ let query_invocation = Invocation.v
9494+ ~method_name:"Email/query"
9595+ ~arguments:(* Yojson conversion of query_args needed here *)
9696+ ~method_call_id:"q1"
9797+ ()
9898+ in
9999+100100+ (* Prepare the Email/get invocation using a back-reference *)
101101+ let get_args = Get_args.v
102102+ ~account_id:primary_mail_account_id
103103+ ~properties:["id"; "subject"; "receivedAt"; "from"]
104104+ ()
105105+ in
106106+ let get_invocation = Invocation.v
107107+ ~method_name:"Email/get"
108108+ ~arguments:(* Yojson conversion of get_args, with ids replaced by a ResultReference to q1 needed here *)
109109+ ~method_call_id:"g1"
110110+ ()
111111+ in
112112+113113+ (* Prepare the JMAP request *)
114114+ let request = Request.v
115115+ ~using:[ Jmap.capability_core; capability_mail ]
116116+ ~method_calls:[ query_invocation; get_invocation ]
117117+ ()
118118+ in
119119+120120+ (* Send the request *)
121121+ let* response = Jmap.Unix.request ctx request in
122122+123123+ (* Process the response (extract Email/get results) *)
124124+ (* ... Omitted: find the Email/get response in response.method_responses ... *)
125125+ Lwt.return_unit
126126+]}
127127+*)
128128+129129+(** Capability URI for JMAP Mail.
130130+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-1.3.1> RFC 8621, Section 1.3.1 *)
131131+val capability_mail : string
132132+133133+(** Capability URI for JMAP Submission.
134134+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-1.3.2> RFC 8621, Section 1.3.2 *)
135135+val capability_submission : string
136136+137137+(** Capability URI for JMAP Vacation Response.
138138+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-1.3.3> RFC 8621, Section 1.3.3 *)
139139+val capability_vacationresponse : string
140140+141141+(** Type name for EmailDelivery push notifications.
142142+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-1.5> RFC 8621, Section 1.5 *)
143143+val push_event_type_email_delivery : string
144144+145145+(** JMAP keywords corresponding to IMAP system flags.
146146+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-4.1.1> RFC 8621, Section 4.1.1 *)
147147+val keyword_draft : string
148148+val keyword_seen : string
149149+val keyword_flagged : string
150150+val keyword_answered : string
151151+152152+(** Common JMAP keywords from RFC 5788.
153153+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-4.1.1> RFC 8621, Section 4.1.1 *)
154154+val keyword_forwarded : string
155155+val keyword_phishing : string
156156+val keyword_junk : string
157157+val keyword_notjunk : string
158158+159159+(** Functions to manipulate email flags/keywords *)
160160+module Keyword_ops : sig
161161+ (** Add a keyword/flag to an email *)
162162+ val add : Types.Email.t -> Types.Keywords.keyword -> Types.Email.t
163163+164164+ (** Remove a keyword/flag from an email *)
165165+ val remove : Types.Email.t -> Types.Keywords.keyword -> Types.Email.t
166166+167167+ (** Mark an email as seen/read *)
168168+ val mark_as_seen : Types.Email.t -> Types.Email.t
169169+170170+ (** Mark an email as unseen/unread *)
171171+ val mark_as_unseen : Types.Email.t -> Types.Email.t
172172+173173+ (** Mark an email as flagged/important *)
174174+ val mark_as_flagged : Types.Email.t -> Types.Email.t
175175+176176+ (** Remove flagged/important marking from an email *)
177177+ val unmark_flagged : Types.Email.t -> Types.Email.t
178178+179179+ (** Mark an email as a draft *)
180180+ val mark_as_draft : Types.Email.t -> Types.Email.t
181181+182182+ (** Remove draft marking from an email *)
183183+ val unmark_draft : Types.Email.t -> Types.Email.t
184184+185185+ (** Mark an email as answered/replied *)
186186+ val mark_as_answered : Types.Email.t -> Types.Email.t
187187+188188+ (** Remove answered/replied marking from an email *)
189189+ val unmark_answered : Types.Email.t -> Types.Email.t
190190+191191+ (** Mark an email as forwarded *)
192192+ val mark_as_forwarded : Types.Email.t -> Types.Email.t
193193+194194+ (** Mark an email as spam/junk *)
195195+ val mark_as_junk : Types.Email.t -> Types.Email.t
196196+197197+ (** Mark an email as not spam/junk *)
198198+ val mark_as_not_junk : Types.Email.t -> Types.Email.t
199199+200200+ (** Mark an email as phishing *)
201201+ val mark_as_phishing : Types.Email.t -> Types.Email.t
202202+203203+ (** Add a custom keyword to an email *)
204204+ val add_custom : Types.Email.t -> string -> Types.Email.t
205205+206206+ (** Remove a custom keyword from an email *)
207207+ val remove_custom : Types.Email.t -> string -> Types.Email.t
208208+209209+ (** Create a patch object to add a keyword to emails *)
210210+ val add_keyword_patch : Types.Keywords.keyword -> Jmap.Methods.patch_object
211211+212212+ (** Create a patch object to remove a keyword from emails *)
213213+ val remove_keyword_patch : Types.Keywords.keyword -> Jmap.Methods.patch_object
214214+215215+ (** Create a patch object to mark emails as seen/read *)
216216+ val mark_seen_patch : unit -> Jmap.Methods.patch_object
217217+218218+ (** Create a patch object to mark emails as unseen/unread *)
219219+ val mark_unseen_patch : unit -> Jmap.Methods.patch_object
220220+end
221221+222222+(** Conversion functions for JMAP/IMAP compatibility *)
223223+module Conversion : sig
224224+ (** Convert a JMAP keyword variant to IMAP flag *)
225225+ val keyword_to_imap_flag : Types.Keywords.keyword -> string
226226+227227+ (** Convert an IMAP flag to JMAP keyword variant *)
228228+ val imap_flag_to_keyword : string -> Types.Keywords.keyword
229229+230230+ (** Check if a string is valid for use as a custom keyword according to RFC 8621 *)
231231+ val is_valid_custom_keyword : string -> bool
232232+233233+ (** Get the JMAP protocol string representation of a keyword *)
234234+ val keyword_to_string : Types.Keywords.keyword -> string
235235+236236+ (** Parse a JMAP protocol string into a keyword variant *)
237237+ val string_to_keyword : string -> Types.Keywords.keyword
238238+end
239239+240240+(** {1 Helper Functions} *)
241241+242242+(** Email query filter helpers *)
243243+module Email_filter : sig
244244+ (** Create a filter to find messages in a specific mailbox *)
245245+ val in_mailbox : id -> Jmap.Methods.Filter.t
246246+247247+ (** Create a filter to find messages with a specific keyword/flag *)
248248+ val has_keyword : Types.Keywords.keyword -> Jmap.Methods.Filter.t
249249+250250+ (** Create a filter to find messages without a specific keyword/flag *)
251251+ val not_has_keyword : Types.Keywords.keyword -> Jmap.Methods.Filter.t
252252+253253+ (** Create a filter to find unread messages *)
254254+ val unread : unit -> Jmap.Methods.Filter.t
255255+256256+ (** Create a filter to find messages with a specific subject *)
257257+ val subject : string -> Jmap.Methods.Filter.t
258258+259259+ (** Create a filter to find messages from a specific sender *)
260260+ val from : string -> Jmap.Methods.Filter.t
261261+262262+ (** Create a filter to find messages sent to a specific recipient *)
263263+ val to_ : string -> Jmap.Methods.Filter.t
264264+265265+ (** Create a filter to find messages with attachments *)
266266+ val has_attachment : unit -> Jmap.Methods.Filter.t
267267+268268+ (** Create a filter to find messages received before a date *)
269269+ val before : date -> Jmap.Methods.Filter.t
270270+271271+ (** Create a filter to find messages received after a date *)
272272+ val after : date -> Jmap.Methods.Filter.t
273273+274274+ (** Create a filter to find messages with size larger than the given bytes *)
275275+ val larger_than : uint -> Jmap.Methods.Filter.t
276276+277277+ (** Create a filter to find messages with size smaller than the given bytes *)
278278+ val smaller_than : uint -> Jmap.Methods.Filter.t
279279+end
280280+281281+(** Common email sorting comparators *)
282282+module Email_sort : sig
283283+ (** Sort by received date (most recent first) *)
284284+ val received_newest_first : unit -> Jmap.Methods.Comparator.t
285285+286286+ (** Sort by received date (oldest first) *)
287287+ val received_oldest_first : unit -> Jmap.Methods.Comparator.t
288288+289289+ (** Sort by sent date (most recent first) *)
290290+ val sent_newest_first : unit -> Jmap.Methods.Comparator.t
291291+292292+ (** Sort by sent date (oldest first) *)
293293+ val sent_oldest_first : unit -> Jmap.Methods.Comparator.t
294294+295295+ (** Sort by subject (A-Z) *)
296296+ val subject_asc : unit -> Jmap.Methods.Comparator.t
297297+298298+ (** Sort by subject (Z-A) *)
299299+ val subject_desc : unit -> Jmap.Methods.Comparator.t
300300+301301+ (** Sort by size (largest first) *)
302302+ val size_largest_first : unit -> Jmap.Methods.Comparator.t
303303+304304+ (** Sort by size (smallest first) *)
305305+ val size_smallest_first : unit -> Jmap.Methods.Comparator.t
306306+307307+ (** Sort by from address (A-Z) *)
308308+ val from_asc : unit -> Jmap.Methods.Comparator.t
309309+310310+ (** Sort by from address (Z-A) *)
311311+ val from_desc : unit -> Jmap.Methods.Comparator.t
312312+end
313313+314314+(** High-level email operations are implemented in the Jmap.Unix.Email module *)
+405
jmap-email/jmap_email_types.ml
···11+(* Common types for JMAP Mail (RFC 8621). *)
22+33+open Jmap.Types
44+55+(* Represents an email address with an optional name. *)
66+module Email_address = struct
77+ type t = {
88+ name: string option;
99+ email: string;
1010+ }
1111+1212+ let name t = t.name
1313+ let email t = t.email
1414+1515+ let v ?name ~email () = { name; email }
1616+end
1717+1818+(* Represents a group of email addresses. *)
1919+module Email_address_group = struct
2020+ type t = {
2121+ name: string option;
2222+ addresses: Email_address.t list;
2323+ }
2424+2525+ let name t = t.name
2626+ let addresses t = t.addresses
2727+2828+ let v ?name ~addresses () = { name; addresses }
2929+end
3030+3131+(* Represents a header field (name and raw value). *)
3232+module Email_header = struct
3333+ type t = {
3434+ name: string;
3535+ value: string;
3636+ }
3737+3838+ let name t = t.name
3939+ let value t = t.value
4040+4141+ let v ~name ~value () = { name; value }
4242+end
4343+4444+(* Represents a body part within an Email's MIME structure. *)
4545+module Email_body_part = struct
4646+ type t = {
4747+ id: string option;
4848+ blob_id: id option;
4949+ size: uint;
5050+ headers: Email_header.t list;
5151+ name: string option;
5252+ mime_type: string;
5353+ charset: string option;
5454+ disposition: string option;
5555+ cid: string option;
5656+ language: string list option;
5757+ location: string option;
5858+ sub_parts: t list option;
5959+ other_headers: Yojson.Safe.t string_map;
6060+ }
6161+6262+ let id t = t.id
6363+ let blob_id t = t.blob_id
6464+ let size t = t.size
6565+ let headers t = t.headers
6666+ let name t = t.name
6767+ let mime_type t = t.mime_type
6868+ let charset t = t.charset
6969+ let disposition t = t.disposition
7070+ let cid t = t.cid
7171+ let language t = t.language
7272+ let location t = t.location
7373+ let sub_parts t = t.sub_parts
7474+ let other_headers t = t.other_headers
7575+7676+ let v ?id ?blob_id ~size ~headers ?name ~mime_type ?charset
7777+ ?disposition ?cid ?language ?location ?sub_parts
7878+ ?(other_headers=Hashtbl.create 0) () =
7979+ { id; blob_id; size; headers; name; mime_type; charset;
8080+ disposition; cid; language; location; sub_parts; other_headers }
8181+end
8282+8383+(* Represents the decoded value of a text body part. *)
8484+module Email_body_value = struct
8585+ type t = {
8686+ value: string;
8787+ has_encoding_problem: bool;
8888+ is_truncated: bool;
8989+ }
9090+9191+ let value t = t.value
9292+ let has_encoding_problem t = t.has_encoding_problem
9393+ let is_truncated t = t.is_truncated
9494+9595+ let v ~value ?(encoding_problem=false) ?(truncated=false) () =
9696+ { value; has_encoding_problem = encoding_problem; is_truncated = truncated }
9797+end
9898+9999+(* Type to represent email message flags/keywords. *)
100100+module Keywords = struct
101101+ type keyword =
102102+ | Draft (* "$draft": The Email is a draft the user is composing *)
103103+ | Seen (* "$seen": The Email has been read *)
104104+ | Flagged (* "$flagged": The Email has been flagged for urgent/special attention *)
105105+ | Answered (* "$answered": The Email has been replied to *)
106106+107107+ (* Common extension keywords from RFC 5788 *)
108108+ | Forwarded (* "$forwarded": The Email has been forwarded *)
109109+ | Phishing (* "$phishing": The Email is likely to be phishing *)
110110+ | Junk (* "$junk": The Email is spam/junk *)
111111+ | NotJunk (* "$notjunk": The Email is explicitly marked as not spam/junk *)
112112+ | Custom of string (* Arbitrary user-defined keyword *)
113113+114114+ type t = keyword list
115115+116116+ let is_draft keywords =
117117+ List.exists (function Draft -> true | _ -> false) keywords
118118+119119+ let is_seen keywords =
120120+ List.exists (function Seen -> true | _ -> false) keywords
121121+122122+ let is_unread keywords =
123123+ not (is_seen keywords || is_draft keywords)
124124+125125+ let is_flagged keywords =
126126+ List.exists (function Flagged -> true | _ -> false) keywords
127127+128128+ let is_answered keywords =
129129+ List.exists (function Answered -> true | _ -> false) keywords
130130+131131+ let is_forwarded keywords =
132132+ List.exists (function Forwarded -> true | _ -> false) keywords
133133+134134+ let is_phishing keywords =
135135+ List.exists (function Phishing -> true | _ -> false) keywords
136136+137137+ let is_junk keywords =
138138+ List.exists (function Junk -> true | _ -> false) keywords
139139+140140+ let is_not_junk keywords =
141141+ List.exists (function NotJunk -> true | _ -> false) keywords
142142+143143+ let has_keyword keywords custom_keyword =
144144+ List.exists (function Custom k when k = custom_keyword -> true | _ -> false) keywords
145145+146146+ let custom_keywords keywords =
147147+ List.fold_left (fun acc kw ->
148148+ match kw with
149149+ | Custom k -> k :: acc
150150+ | _ -> acc
151151+ ) [] keywords
152152+153153+ let add keywords keyword =
154154+ if List.exists (fun k -> k = keyword) keywords then
155155+ keywords
156156+ else
157157+ keyword :: keywords
158158+159159+ let remove keywords keyword =
160160+ List.filter (fun k -> k <> keyword) keywords
161161+162162+ let empty () = []
163163+164164+ let of_list keywords = keywords
165165+166166+ let to_string = function
167167+ | Draft -> "$draft"
168168+ | Seen -> "$seen"
169169+ | Flagged -> "$flagged"
170170+ | Answered -> "$answered"
171171+ | Forwarded -> "$forwarded"
172172+ | Phishing -> "$phishing"
173173+ | Junk -> "$junk"
174174+ | NotJunk -> "$notjunk"
175175+ | Custom k -> k
176176+177177+ let of_string s =
178178+ match s with
179179+ | "$draft" -> Draft
180180+ | "$seen" -> Seen
181181+ | "$flagged" -> Flagged
182182+ | "$answered" -> Answered
183183+ | "$forwarded" -> Forwarded
184184+ | "$phishing" -> Phishing
185185+ | "$junk" -> Junk
186186+ | "$notjunk" -> NotJunk
187187+ | k -> Custom k
188188+189189+ let to_map keywords =
190190+ let map = Hashtbl.create (List.length keywords) in
191191+ List.iter (fun kw ->
192192+ Hashtbl.add map (to_string kw) true
193193+ ) keywords;
194194+ map
195195+end
196196+197197+(* Email properties enum. *)
198198+type email_property =
199199+ | Id (* The id of the email *)
200200+ | BlobId (* The id of the blob containing the raw message *)
201201+ | ThreadId (* The id of the thread this email belongs to *)
202202+ | MailboxIds (* The mailboxes this email belongs to *)
203203+ | Keywords (* The keywords/flags for this email *)
204204+ | Size (* Size of the message in bytes *)
205205+ | ReceivedAt (* When the message was received by the server *)
206206+ | MessageId (* Value of the Message-ID header *)
207207+ | InReplyTo (* Value of the In-Reply-To header *)
208208+ | References (* Value of the References header *)
209209+ | Sender (* Value of the Sender header *)
210210+ | From (* Value of the From header *)
211211+ | To (* Value of the To header *)
212212+ | Cc (* Value of the Cc header *)
213213+ | Bcc (* Value of the Bcc header *)
214214+ | ReplyTo (* Value of the Reply-To header *)
215215+ | Subject (* Value of the Subject header *)
216216+ | SentAt (* Value of the Date header *)
217217+ | HasAttachment (* Whether the email has attachments *)
218218+ | Preview (* Preview text of the email *)
219219+ | BodyStructure (* MIME structure of the email *)
220220+ | BodyValues (* Decoded body part values *)
221221+ | TextBody (* Text body parts *)
222222+ | HtmlBody (* HTML body parts *)
223223+ | Attachments (* Attachments *)
224224+ | Header of string (* Specific header *)
225225+ | Other of string (* Extension property *)
226226+227227+(* Represents an Email object. *)
228228+module Email = struct
229229+ type t = {
230230+ id: id option;
231231+ blob_id: id option;
232232+ thread_id: id option;
233233+ mailbox_ids: bool id_map option;
234234+ keywords: Keywords.t option;
235235+ size: uint option;
236236+ received_at: date option;
237237+ subject: string option;
238238+ preview: string option;
239239+ from: Email_address.t list option;
240240+ to_: Email_address.t list option;
241241+ cc: Email_address.t list option;
242242+ message_id: string list option;
243243+ has_attachment: bool option;
244244+ text_body: Email_body_part.t list option;
245245+ html_body: Email_body_part.t list option;
246246+ attachments: Email_body_part.t list option;
247247+ }
248248+249249+ let id t = t.id
250250+ let blob_id t = t.blob_id
251251+ let thread_id t = t.thread_id
252252+ let mailbox_ids t = t.mailbox_ids
253253+ let keywords t = t.keywords
254254+ let size t = t.size
255255+ let received_at t = t.received_at
256256+ let subject t = t.subject
257257+ let preview t = t.preview
258258+ let from t = t.from
259259+ let to_ t = t.to_
260260+ let cc t = t.cc
261261+ let message_id t = t.message_id
262262+ let has_attachment t = t.has_attachment
263263+ let text_body t = t.text_body
264264+ let html_body t = t.html_body
265265+ let attachments t = t.attachments
266266+267267+ let create ?id ?blob_id ?thread_id ?mailbox_ids ?keywords ?size
268268+ ?received_at ?subject ?preview ?from ?to_ ?cc ?message_id
269269+ ?has_attachment ?text_body ?html_body ?attachments () =
270270+ { id; blob_id; thread_id; mailbox_ids; keywords; size;
271271+ received_at; subject; preview; from; to_; cc; message_id;
272272+ has_attachment; text_body; html_body; attachments }
273273+274274+ let make_patch ?add_keywords ?remove_keywords ?add_mailboxes ?remove_mailboxes () =
275275+ let patch = [] in
276276+ let patch = match add_keywords with
277277+ | Some kw ->
278278+ ("keywords/", `Assoc (List.map (fun k ->
279279+ (Keywords.to_string k, `Bool true)
280280+ ) kw)) :: patch
281281+ | None -> patch
282282+ in
283283+ let patch = match remove_keywords with
284284+ | Some kw ->
285285+ List.fold_left (fun p k ->
286286+ ("keywords/" ^ Keywords.to_string k, `Null) :: p
287287+ ) patch kw
288288+ | None -> patch
289289+ in
290290+ let patch = match add_mailboxes with
291291+ | Some mboxes ->
292292+ List.fold_left (fun p mbx ->
293293+ ("mailboxIds/" ^ mbx, `Bool true) :: p
294294+ ) patch mboxes
295295+ | None -> patch
296296+ in
297297+ let patch = match remove_mailboxes with
298298+ | Some mboxes ->
299299+ List.fold_left (fun p mbx ->
300300+ ("mailboxIds/" ^ mbx, `Null) :: p
301301+ ) patch mboxes
302302+ | None -> patch
303303+ in
304304+ patch
305305+306306+ let get_id t =
307307+ match t.id with
308308+ | Some id -> Ok id
309309+ | None -> Error "Email missing ID"
310310+311311+ let take_id t =
312312+ match t.id with
313313+ | Some id -> id
314314+ | None -> failwith "Email missing ID"
315315+end
316316+317317+(* Email import options. *)
318318+type email_import_options = {
319319+ import_to_mailboxes : id list;
320320+ import_keywords : Keywords.t option;
321321+ import_received_at : date option;
322322+}
323323+324324+(* Email copy options. *)
325325+type email_copy_options = {
326326+ copy_to_account_id : id;
327327+ copy_to_mailboxes : id list;
328328+ copy_on_success_destroy_original : bool option;
329329+}
330330+331331+(* Convert a property variant to its string representation *)
332332+let email_property_to_string = function
333333+ | Id -> "id"
334334+ | BlobId -> "blobId"
335335+ | ThreadId -> "threadId"
336336+ | MailboxIds -> "mailboxIds"
337337+ | Keywords -> "keywords"
338338+ | Size -> "size"
339339+ | ReceivedAt -> "receivedAt"
340340+ | MessageId -> "messageId"
341341+ | InReplyTo -> "inReplyTo"
342342+ | References -> "references"
343343+ | Sender -> "sender"
344344+ | From -> "from"
345345+ | To -> "to"
346346+ | Cc -> "cc"
347347+ | Bcc -> "bcc"
348348+ | ReplyTo -> "replyTo"
349349+ | Subject -> "subject"
350350+ | SentAt -> "sentAt"
351351+ | HasAttachment -> "hasAttachment"
352352+ | Preview -> "preview"
353353+ | BodyStructure -> "bodyStructure"
354354+ | BodyValues -> "bodyValues"
355355+ | TextBody -> "textBody"
356356+ | HtmlBody -> "htmlBody"
357357+ | Attachments -> "attachments"
358358+ | Header h -> "header:" ^ h
359359+ | Other s -> s
360360+361361+(* Parse a string into a property variant *)
362362+let string_to_email_property s =
363363+ match s with
364364+ | "id" -> Id
365365+ | "blobId" -> BlobId
366366+ | "threadId" -> ThreadId
367367+ | "mailboxIds" -> MailboxIds
368368+ | "keywords" -> Keywords
369369+ | "size" -> Size
370370+ | "receivedAt" -> ReceivedAt
371371+ | "messageId" -> MessageId
372372+ | "inReplyTo" -> InReplyTo
373373+ | "references" -> References
374374+ | "sender" -> Sender
375375+ | "from" -> From
376376+ | "to" -> To
377377+ | "cc" -> Cc
378378+ | "bcc" -> Bcc
379379+ | "replyTo" -> ReplyTo
380380+ | "subject" -> Subject
381381+ | "sentAt" -> SentAt
382382+ | "hasAttachment" -> HasAttachment
383383+ | "preview" -> Preview
384384+ | "bodyStructure" -> BodyStructure
385385+ | "bodyValues" -> BodyValues
386386+ | "textBody" -> TextBody
387387+ | "htmlBody" -> HtmlBody
388388+ | "attachments" -> Attachments
389389+ | s when String.length s > 7 && String.sub s 0 7 = "header:" ->
390390+ Header (String.sub s 7 (String.length s - 7))
391391+ | s -> Other s
392392+393393+(* Get a list of common properties useful for displaying email lists *)
394394+let common_email_properties = [
395395+ Id; ThreadId; MailboxIds; Keywords; Size; ReceivedAt;
396396+ From; Subject; Preview; HasAttachment; SentAt;
397397+]
398398+399399+(* Get a list of common properties for detailed email view *)
400400+let detailed_email_properties = [
401401+ Id; ThreadId; MailboxIds; Keywords; Size; ReceivedAt;
402402+ MessageId; InReplyTo; References; Sender; From; To; Cc;
403403+ ReplyTo; Subject; SentAt; HasAttachment; Preview;
404404+ TextBody; HtmlBody; Attachments;
405405+]
+368
jmap-email/jmap_email_types.mli
···11+(** Common types for JMAP Mail (RFC 8621). *)
22+33+open Jmap.Types
44+55+(** Represents an email address with an optional name.
66+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-4.1.2.3> RFC 8621, Section 4.1.2.3 *)
77+module Email_address : sig
88+ type t
99+1010+ (** Get the display name for the address (if any) *)
1111+ val name : t -> string option
1212+1313+ (** Get the email address *)
1414+ val email : t -> string
1515+1616+ (** Create a new email address *)
1717+ val v :
1818+ ?name:string ->
1919+ email:string ->
2020+ unit -> t
2121+end
2222+2323+(** Represents a group of email addresses.
2424+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-4.1.2.4> RFC 8621, Section 4.1.2.4 *)
2525+module Email_address_group : sig
2626+ type t
2727+2828+ (** Get the name of the group (if any) *)
2929+ val name : t -> string option
3030+3131+ (** Get the list of addresses in the group *)
3232+ val addresses : t -> Email_address.t list
3333+3434+ (** Create a new address group *)
3535+ val v :
3636+ ?name:string ->
3737+ addresses:Email_address.t list ->
3838+ unit -> t
3939+end
4040+4141+(** Represents a header field (name and raw value).
4242+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-4.1.3> RFC 8621, Section 4.1.3 *)
4343+module Email_header : sig
4444+ type t
4545+4646+ (** Get the header field name *)
4747+ val name : t -> string
4848+4949+ (** Get the raw header field value *)
5050+ val value : t -> string
5151+5252+ (** Create a new header field *)
5353+ val v :
5454+ name:string ->
5555+ value:string ->
5656+ unit -> t
5757+end
5858+5959+(** Represents a body part within an Email's MIME structure.
6060+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-4.1.4> RFC 8621, Section 4.1.4 *)
6161+module Email_body_part : sig
6262+ type t
6363+6464+ (** Get the part ID (null only for multipart types) *)
6565+ val id : t -> string option
6666+6767+ (** Get the blob ID (null only for multipart types) *)
6868+ val blob_id : t -> id option
6969+7070+ (** Get the size of the part in bytes *)
7171+ val size : t -> uint
7272+7373+ (** Get the list of headers for this part *)
7474+ val headers : t -> Email_header.t list
7575+7676+ (** Get the filename (if any) *)
7777+ val name : t -> string option
7878+7979+ (** Get the MIME type *)
8080+ val mime_type : t -> string
8181+8282+ (** Get the charset (if any) *)
8383+ val charset : t -> string option
8484+8585+ (** Get the content disposition (if any) *)
8686+ val disposition : t -> string option
8787+8888+ (** Get the content ID (if any) *)
8989+ val cid : t -> string option
9090+9191+ (** Get the list of languages (if any) *)
9292+ val language : t -> string list option
9393+9494+ (** Get the content location (if any) *)
9595+ val location : t -> string option
9696+9797+ (** Get the sub-parts (only for multipart types) *)
9898+ val sub_parts : t -> t list option
9999+100100+ (** Get any other requested headers (header properties) *)
101101+ val other_headers : t -> Yojson.Safe.t string_map
102102+103103+ (** Create a new body part *)
104104+ val v :
105105+ ?id:string ->
106106+ ?blob_id:id ->
107107+ size:uint ->
108108+ headers:Email_header.t list ->
109109+ ?name:string ->
110110+ mime_type:string ->
111111+ ?charset:string ->
112112+ ?disposition:string ->
113113+ ?cid:string ->
114114+ ?language:string list ->
115115+ ?location:string ->
116116+ ?sub_parts:t list ->
117117+ ?other_headers:Yojson.Safe.t string_map ->
118118+ unit -> t
119119+end
120120+121121+(** Represents the decoded value of a text body part.
122122+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-4.1.4> RFC 8621, Section 4.1.4 *)
123123+module Email_body_value : sig
124124+ type t
125125+126126+ (** Get the decoded text content *)
127127+ val value : t -> string
128128+129129+ (** Check if there was an encoding problem *)
130130+ val has_encoding_problem : t -> bool
131131+132132+ (** Check if the content was truncated *)
133133+ val is_truncated : t -> bool
134134+135135+ (** Create a new body value *)
136136+ val v :
137137+ value:string ->
138138+ ?encoding_problem:bool ->
139139+ ?truncated:bool ->
140140+ unit -> t
141141+end
142142+143143+(** Type to represent email message flags/keywords.
144144+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-4.1.1> RFC 8621, Section 4.1.1 *)
145145+module Keywords : sig
146146+ (** Represents different types of JMAP keywords *)
147147+ type keyword =
148148+ | Draft (** "$draft": The Email is a draft the user is composing *)
149149+ | Seen (** "$seen": The Email has been read *)
150150+ | Flagged (** "$flagged": The Email has been flagged for urgent/special attention *)
151151+ | Answered (** "$answered": The Email has been replied to *)
152152+153153+ (* Common extension keywords from RFC 5788 *)
154154+ | Forwarded (** "$forwarded": The Email has been forwarded *)
155155+ | Phishing (** "$phishing": The Email is likely to be phishing *)
156156+ | Junk (** "$junk": The Email is spam/junk *)
157157+ | NotJunk (** "$notjunk": The Email is explicitly marked as not spam/junk *)
158158+ | Custom of string (** Arbitrary user-defined keyword *)
159159+160160+ (** A set of keywords applied to an email *)
161161+ type t = keyword list
162162+163163+ (** Check if an email has the draft flag *)
164164+ val is_draft : t -> bool
165165+166166+ (** Check if an email has been read *)
167167+ val is_seen : t -> bool
168168+169169+ (** Check if an email has neither been read nor is a draft *)
170170+ val is_unread : t -> bool
171171+172172+ (** Check if an email has been flagged *)
173173+ val is_flagged : t -> bool
174174+175175+ (** Check if an email has been replied to *)
176176+ val is_answered : t -> bool
177177+178178+ (** Check if an email has been forwarded *)
179179+ val is_forwarded : t -> bool
180180+181181+ (** Check if an email is marked as likely phishing *)
182182+ val is_phishing : t -> bool
183183+184184+ (** Check if an email is marked as junk/spam *)
185185+ val is_junk : t -> bool
186186+187187+ (** Check if an email is explicitly marked as not junk/spam *)
188188+ val is_not_junk : t -> bool
189189+190190+ (** Check if a specific custom keyword is set *)
191191+ val has_keyword : t -> string -> bool
192192+193193+ (** Get a list of all custom keywords (excluding system keywords) *)
194194+ val custom_keywords : t -> string list
195195+196196+ (** Add a keyword to the set *)
197197+ val add : t -> keyword -> t
198198+199199+ (** Remove a keyword from the set *)
200200+ val remove : t -> keyword -> t
201201+202202+ (** Create an empty keyword set *)
203203+ val empty : unit -> t
204204+205205+ (** Create a new keyword set with the specified keywords *)
206206+ val of_list : keyword list -> t
207207+208208+ (** Get the string representation of a keyword as used in the JMAP protocol *)
209209+ val to_string : keyword -> string
210210+211211+ (** Parse a string into a keyword *)
212212+ val of_string : string -> keyword
213213+214214+ (** Convert keyword set to string map representation as used in JMAP *)
215215+ val to_map : t -> bool string_map
216216+end
217217+218218+(** Email properties enum.
219219+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-4.1> RFC 8621, Section 4.1 *)
220220+type email_property =
221221+ | Id (** The id of the email *)
222222+ | BlobId (** The id of the blob containing the raw message *)
223223+ | ThreadId (** The id of the thread this email belongs to *)
224224+ | MailboxIds (** The mailboxes this email belongs to *)
225225+ | Keywords (** The keywords/flags for this email *)
226226+ | Size (** Size of the message in bytes *)
227227+ | ReceivedAt (** When the message was received by the server *)
228228+ | MessageId (** Value of the Message-ID header *)
229229+ | InReplyTo (** Value of the In-Reply-To header *)
230230+ | References (** Value of the References header *)
231231+ | Sender (** Value of the Sender header *)
232232+ | From (** Value of the From header *)
233233+ | To (** Value of the To header *)
234234+ | Cc (** Value of the Cc header *)
235235+ | Bcc (** Value of the Bcc header *)
236236+ | ReplyTo (** Value of the Reply-To header *)
237237+ | Subject (** Value of the Subject header *)
238238+ | SentAt (** Value of the Date header *)
239239+ | HasAttachment (** Whether the email has attachments *)
240240+ | Preview (** Preview text of the email *)
241241+ | BodyStructure (** MIME structure of the email *)
242242+ | BodyValues (** Decoded body part values *)
243243+ | TextBody (** Text body parts *)
244244+ | HtmlBody (** HTML body parts *)
245245+ | Attachments (** Attachments *)
246246+ | Header of string (** Specific header *)
247247+ | Other of string (** Extension property *)
248248+249249+(** Represents an Email object.
250250+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-4.1> RFC 8621, Section 4.1 *)
251251+module Email : sig
252252+ (** Email type *)
253253+ type t
254254+255255+ (** ID of the email *)
256256+ val id : t -> id option
257257+258258+ (** ID of the blob containing the raw message *)
259259+ val blob_id : t -> id option
260260+261261+ (** ID of the thread this email belongs to *)
262262+ val thread_id : t -> id option
263263+264264+ (** The set of mailbox IDs this email belongs to *)
265265+ val mailbox_ids : t -> bool id_map option
266266+267267+ (** The set of keywords/flags for this email *)
268268+ val keywords : t -> Keywords.t option
269269+270270+ (** Size of the message in bytes *)
271271+ val size : t -> uint option
272272+273273+ (** When the message was received by the server *)
274274+ val received_at : t -> date option
275275+276276+ (** Subject of the email (if requested) *)
277277+ val subject : t -> string option
278278+279279+ (** Preview text of the email (if requested) *)
280280+ val preview : t -> string option
281281+282282+ (** From addresses (if requested) *)
283283+ val from : t -> Email_address.t list option
284284+285285+ (** To addresses (if requested) *)
286286+ val to_ : t -> Email_address.t list option
287287+288288+ (** CC addresses (if requested) *)
289289+ val cc : t -> Email_address.t list option
290290+291291+ (** Message ID values (if requested) *)
292292+ val message_id : t -> string list option
293293+294294+ (** Get whether the email has attachments (if requested) *)
295295+ val has_attachment : t -> bool option
296296+297297+ (** Get text body parts (if requested) *)
298298+ val text_body : t -> Email_body_part.t list option
299299+300300+ (** Get HTML body parts (if requested) *)
301301+ val html_body : t -> Email_body_part.t list option
302302+303303+ (** Get attachments (if requested) *)
304304+ val attachments : t -> Email_body_part.t list option
305305+306306+ (** Create a new Email object from a server response or for a new email *)
307307+ val create :
308308+ ?id:id ->
309309+ ?blob_id:id ->
310310+ ?thread_id:id ->
311311+ ?mailbox_ids:bool id_map ->
312312+ ?keywords:Keywords.t ->
313313+ ?size:uint ->
314314+ ?received_at:date ->
315315+ ?subject:string ->
316316+ ?preview:string ->
317317+ ?from:Email_address.t list ->
318318+ ?to_:Email_address.t list ->
319319+ ?cc:Email_address.t list ->
320320+ ?message_id:string list ->
321321+ ?has_attachment:bool ->
322322+ ?text_body:Email_body_part.t list ->
323323+ ?html_body:Email_body_part.t list ->
324324+ ?attachments:Email_body_part.t list ->
325325+ unit -> t
326326+327327+ (** Create a patch object for updating email properties *)
328328+ val make_patch :
329329+ ?add_keywords:Keywords.t ->
330330+ ?remove_keywords:Keywords.t ->
331331+ ?add_mailboxes:id list ->
332332+ ?remove_mailboxes:id list ->
333333+ unit -> Jmap.Methods.patch_object
334334+335335+ (** Extract the ID from an email, returning a Result *)
336336+ val get_id : t -> (id, string) result
337337+338338+ (** Take the ID from an email (fails with an exception if not present) *)
339339+ val take_id : t -> id
340340+end
341341+342342+(** Email import options.
343343+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-4.5> RFC 8621, Section 4.5 *)
344344+type email_import_options = {
345345+ import_to_mailboxes : id list;
346346+ import_keywords : Keywords.t option;
347347+ import_received_at : date option;
348348+}
349349+350350+(** Email copy options.
351351+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-4.6> RFC 8621, Section 4.6 *)
352352+type email_copy_options = {
353353+ copy_to_account_id : id;
354354+ copy_to_mailboxes : id list;
355355+ copy_on_success_destroy_original : bool option;
356356+}
357357+358358+(** Convert a property variant to its string representation *)
359359+val email_property_to_string : email_property -> string
360360+361361+(** Parse a string into a property variant *)
362362+val string_to_email_property : string -> email_property
363363+364364+(** Get a list of common properties useful for displaying email lists *)
365365+val common_email_properties : email_property list
366366+367367+(** Get a list of common properties for detailed email view *)
368368+val detailed_email_properties : email_property list
+130
jmap-email/jmap_identity.ml
···11+(* JMAP Identity. *)
22+33+open Jmap.Types
44+open Jmap.Methods
55+66+(* Identity object. *)
77+type t = {
88+ id_value: id;
99+ name_value: string;
1010+ email_value: string;
1111+ reply_to_value: Jmap_email_types.Email_address.t list option;
1212+ bcc_value: Jmap_email_types.Email_address.t list option;
1313+ text_signature_value: string;
1414+ html_signature_value: string;
1515+ may_delete_value: bool;
1616+}
1717+1818+(* Get the identity ID (immutable, server-set) *)
1919+let id t = t.id_value
2020+2121+(* Get the display name (defaults to "") *)
2222+let name t = t.name_value
2323+2424+(* Get the email address (immutable) *)
2525+let email t = t.email_value
2626+2727+(* Get the reply-to addresses (if any) *)
2828+let reply_to t = t.reply_to_value
2929+3030+(* Get the bcc addresses (if any) *)
3131+let bcc t = t.bcc_value
3232+3333+(* Get the plain text signature (defaults to "") *)
3434+let text_signature t = t.text_signature_value
3535+3636+(* Get the HTML signature (defaults to "") *)
3737+let html_signature t = t.html_signature_value
3838+3939+(* Check if this identity may be deleted (server-set) *)
4040+let may_delete t = t.may_delete_value
4141+4242+(* Create a new identity object *)
4343+let v ~id ?(name="") ~email ?reply_to ?bcc ?(text_signature="") ?(html_signature="") ~may_delete () = {
4444+ id_value = id;
4545+ name_value = name;
4646+ email_value = email;
4747+ reply_to_value = reply_to;
4848+ bcc_value = bcc;
4949+ text_signature_value = text_signature;
5050+ html_signature_value = html_signature;
5151+ may_delete_value = may_delete;
5252+}
5353+5454+(* Types and functions for identity creation and updates *)
5555+module Create = struct
5656+ type t = {
5757+ name_value: string option;
5858+ email_value: string;
5959+ reply_to_value: Jmap_email_types.Email_address.t list option;
6060+ bcc_value: Jmap_email_types.Email_address.t list option;
6161+ text_signature_value: string option;
6262+ html_signature_value: string option;
6363+ }
6464+6565+ (* Get the name (if specified) *)
6666+ let name t = t.name_value
6767+6868+ (* Get the email address *)
6969+ let email t = t.email_value
7070+7171+ (* Get the reply-to addresses (if any) *)
7272+ let reply_to t = t.reply_to_value
7373+7474+ (* Get the bcc addresses (if any) *)
7575+ let bcc t = t.bcc_value
7676+7777+ (* Get the plain text signature (if specified) *)
7878+ let text_signature t = t.text_signature_value
7979+8080+ (* Get the HTML signature (if specified) *)
8181+ let html_signature t = t.html_signature_value
8282+8383+ (* Create a new identity creation object *)
8484+ let v ?name ~email ?reply_to ?bcc ?text_signature ?html_signature () = {
8585+ name_value = name;
8686+ email_value = email;
8787+ reply_to_value = reply_to;
8888+ bcc_value = bcc;
8989+ text_signature_value = text_signature;
9090+ html_signature_value = html_signature;
9191+ }
9292+9393+ (* Server response with info about the created identity *)
9494+ module Response = struct
9595+ type t = {
9696+ id_value: id;
9797+ may_delete_value: bool;
9898+ }
9999+100100+ (* Get the server-assigned ID for the created identity *)
101101+ let id t = t.id_value
102102+103103+ (* Check if this identity may be deleted *)
104104+ let may_delete t = t.may_delete_value
105105+106106+ (* Create a new response object *)
107107+ let v ~id ~may_delete () = {
108108+ id_value = id;
109109+ may_delete_value = may_delete;
110110+ }
111111+ end
112112+end
113113+114114+(* Identity object for update.
115115+ Patch object, specific structure not enforced here. *)
116116+type update = patch_object
117117+118118+(* Server-set/computed info for updated identity.
119119+ Contains only changed server-set props. *)
120120+module Update_response = struct
121121+ (* We use the same type as main identity *)
122122+ type identity_update = t
123123+ type t = identity_update
124124+125125+ (* Convert to a full Identity object (contains only changed server-set props) *)
126126+ let to_identity t = (t : t :> t)
127127+128128+ (* Create from a full Identity object *)
129129+ let of_identity t = (t : t :> t)
130130+end
+114
jmap-email/jmap_identity.mli
···11+(** JMAP Identity.
22+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-6> RFC 8621, Section 6 *)
33+44+open Jmap.Types
55+open Jmap.Methods
66+77+(** Identity object.
88+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-6> RFC 8621, Section 6 *)
99+type t
1010+1111+(** Get the identity ID (immutable, server-set) *)
1212+val id : t -> id
1313+1414+(** Get the display name (defaults to "") *)
1515+val name : t -> string
1616+1717+(** Get the email address (immutable) *)
1818+val email : t -> string
1919+2020+(** Get the reply-to addresses (if any) *)
2121+val reply_to : t -> Jmap_email_types.Email_address.t list option
2222+2323+(** Get the bcc addresses (if any) *)
2424+val bcc : t -> Jmap_email_types.Email_address.t list option
2525+2626+(** Get the plain text signature (defaults to "") *)
2727+val text_signature : t -> string
2828+2929+(** Get the HTML signature (defaults to "") *)
3030+val html_signature : t -> string
3131+3232+(** Check if this identity may be deleted (server-set) *)
3333+val may_delete : t -> bool
3434+3535+(** Create a new identity object *)
3636+val v :
3737+ id:id ->
3838+ ?name:string ->
3939+ email:string ->
4040+ ?reply_to:Jmap_email_types.Email_address.t list ->
4141+ ?bcc:Jmap_email_types.Email_address.t list ->
4242+ ?text_signature:string ->
4343+ ?html_signature:string ->
4444+ may_delete:bool ->
4545+ unit -> t
4646+4747+(** Types and functions for identity creation and updates *)
4848+module Create : sig
4949+ type t
5050+5151+ (** Get the name (if specified) *)
5252+ val name : t -> string option
5353+5454+ (** Get the email address *)
5555+ val email : t -> string
5656+5757+ (** Get the reply-to addresses (if any) *)
5858+ val reply_to : t -> Jmap_email_types.Email_address.t list option
5959+6060+ (** Get the bcc addresses (if any) *)
6161+ val bcc : t -> Jmap_email_types.Email_address.t list option
6262+6363+ (** Get the plain text signature (if specified) *)
6464+ val text_signature : t -> string option
6565+6666+ (** Get the HTML signature (if specified) *)
6767+ val html_signature : t -> string option
6868+6969+ (** Create a new identity creation object *)
7070+ val v :
7171+ ?name:string ->
7272+ email:string ->
7373+ ?reply_to:Jmap_email_types.Email_address.t list ->
7474+ ?bcc:Jmap_email_types.Email_address.t list ->
7575+ ?text_signature:string ->
7676+ ?html_signature:string ->
7777+ unit -> t
7878+7979+ (** Server response with info about the created identity *)
8080+ module Response : sig
8181+ type t
8282+8383+ (** Get the server-assigned ID for the created identity *)
8484+ val id : t -> id
8585+8686+ (** Check if this identity may be deleted *)
8787+ val may_delete : t -> bool
8888+8989+ (** Create a new response object *)
9090+ val v :
9191+ id:id ->
9292+ may_delete:bool ->
9393+ unit -> t
9494+ end
9595+end
9696+9797+(** Identity object for update.
9898+ Patch object, specific structure not enforced here.
9999+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-6.3> RFC 8621, Section 6.3 *)
100100+type update = patch_object
101101+102102+(** Server-set/computed info for updated identity.
103103+ Contains only changed server-set props.
104104+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-6.3> RFC 8621, Section 6.3 *)
105105+module Update_response : sig
106106+ type t
107107+108108+ (** Convert to a full Identity object (contains only changed server-set props) *)
109109+ val to_identity : t -> t
110110+111111+ (** Create from a full Identity object *)
112112+ val of_identity : t -> t
113113+end
114114+
+282
jmap-email/jmap_mailbox.ml
···11+(* JMAP Mailbox. *)
22+33+open Jmap.Types
44+open Jmap.Methods
55+66+(* Standard mailbox roles as defined in RFC 8621. *)
77+type role =
88+ | Inbox (* Messages in the primary inbox *)
99+ | Archive (* Archived messages *)
1010+ | Drafts (* Draft messages being composed *)
1111+ | Sent (* Messages that have been sent *)
1212+ | Trash (* Messages that have been deleted *)
1313+ | Junk (* Messages determined to be spam *)
1414+ | Important (* Messages deemed important *)
1515+ | Other of string (* Custom or non-standard role *)
1616+ | None (* No specific role assigned *)
1717+1818+(* Mailbox property identifiers. *)
1919+type property =
2020+ | Id (* The id of the mailbox *)
2121+ | Name (* The name of the mailbox *)
2222+ | ParentId (* The id of the parent mailbox *)
2323+ | Role (* The role of the mailbox *)
2424+ | SortOrder (* The sort order of the mailbox *)
2525+ | TotalEmails (* The total number of emails in the mailbox *)
2626+ | UnreadEmails (* The number of unread emails in the mailbox *)
2727+ | TotalThreads (* The total number of threads in the mailbox *)
2828+ | UnreadThreads (* The number of unread threads in the mailbox *)
2929+ | MyRights (* The rights the user has for the mailbox *)
3030+ | IsSubscribed (* Whether the mailbox is subscribed to *)
3131+ | Other of string (* Any server-specific extension properties *)
3232+3333+(* Mailbox access rights. *)
3434+type mailbox_rights = {
3535+ may_read_items : bool;
3636+ may_add_items : bool;
3737+ may_remove_items : bool;
3838+ may_set_seen : bool;
3939+ may_set_keywords : bool;
4040+ may_create_child : bool;
4141+ may_rename : bool;
4242+ may_delete : bool;
4343+ may_submit : bool;
4444+}
4545+4646+(* Mailbox object. *)
4747+type mailbox = {
4848+ mailbox_id : id; (* immutable, server-set *)
4949+ name : string;
5050+ parent_id : id option;
5151+ role : role option;
5252+ sort_order : uint; (* default: 0 *)
5353+ total_emails : uint; (* server-set *)
5454+ unread_emails : uint; (* server-set *)
5555+ total_threads : uint; (* server-set *)
5656+ unread_threads : uint; (* server-set *)
5757+ my_rights : mailbox_rights; (* server-set *)
5858+ is_subscribed : bool;
5959+}
6060+6161+(* Mailbox object for creation.
6262+ Excludes server-set fields. *)
6363+type mailbox_create = {
6464+ mailbox_create_name : string;
6565+ mailbox_create_parent_id : id option;
6666+ mailbox_create_role : role option;
6767+ mailbox_create_sort_order : uint option;
6868+ mailbox_create_is_subscribed : bool option;
6969+}
7070+7171+(* Mailbox object for update.
7272+ Patch object, specific structure not enforced here. *)
7373+type mailbox_update = patch_object
7474+7575+(* Server-set info for created mailbox. *)
7676+type mailbox_created_info = {
7777+ mailbox_created_id : id;
7878+ mailbox_created_role : role option; (* If default used *)
7979+ mailbox_created_sort_order : uint; (* If default used *)
8080+ mailbox_created_total_emails : uint;
8181+ mailbox_created_unread_emails : uint;
8282+ mailbox_created_total_threads : uint;
8383+ mailbox_created_unread_threads : uint;
8484+ mailbox_created_my_rights : mailbox_rights;
8585+ mailbox_created_is_subscribed : bool; (* If default used *)
8686+}
8787+8888+(* Server-set/computed info for updated mailbox. *)
8989+type mailbox_updated_info = mailbox (* Contains only changed server-set props *)
9090+9191+(* FilterCondition for Mailbox/query. *)
9292+type mailbox_filter_condition = {
9393+ filter_parent_id : id option option; (* Use option option for explicit null *)
9494+ filter_name : string option;
9595+ filter_role : role option option; (* Use option option for explicit null *)
9696+ filter_has_any_role : bool option;
9797+ filter_is_subscribed : bool option;
9898+}
9999+100100+(* Role and Property Conversion Functions *)
101101+102102+(* Role conversion utilities *)
103103+let role_to_string = function
104104+ | Inbox -> "inbox"
105105+ | Archive -> "archive"
106106+ | Drafts -> "drafts"
107107+ | Sent -> "sent"
108108+ | Trash -> "trash"
109109+ | Junk -> "junk"
110110+ | Important -> "important"
111111+ | Other s -> s
112112+ | None -> ""
113113+114114+let string_to_role = function
115115+ | "inbox" -> Inbox
116116+ | "archive" -> Archive
117117+ | "drafts" -> Drafts
118118+ | "sent" -> Sent
119119+ | "trash" -> Trash
120120+ | "junk" -> Junk
121121+ | "important" -> Important
122122+ | "" -> None
123123+ | s -> Other s
124124+125125+(* Property conversion utilities *)
126126+let property_to_string = function
127127+ | Id -> "id"
128128+ | Name -> "name"
129129+ | ParentId -> "parentId"
130130+ | Role -> "role"
131131+ | SortOrder -> "sortOrder"
132132+ | TotalEmails -> "totalEmails"
133133+ | UnreadEmails -> "unreadEmails"
134134+ | TotalThreads -> "totalThreads"
135135+ | UnreadThreads -> "unreadThreads"
136136+ | MyRights -> "myRights"
137137+ | IsSubscribed -> "isSubscribed"
138138+ | Other s -> s
139139+140140+let string_to_property = function
141141+ | "id" -> Id
142142+ | "name" -> Name
143143+ | "parentId" -> ParentId
144144+ | "role" -> Role
145145+ | "sortOrder" -> SortOrder
146146+ | "totalEmails" -> TotalEmails
147147+ | "unreadEmails" -> UnreadEmails
148148+ | "totalThreads" -> TotalThreads
149149+ | "unreadThreads" -> UnreadThreads
150150+ | "myRights" -> MyRights
151151+ | "isSubscribed" -> IsSubscribed
152152+ | s -> Other s
153153+154154+(* Get a list of common properties useful for displaying mailboxes *)
155155+let common_properties = [
156156+ Id; Name; ParentId; Role;
157157+ TotalEmails; UnreadEmails;
158158+ IsSubscribed
159159+]
160160+161161+(* Get a list of all standard properties *)
162162+let all_properties = [
163163+ Id; Name; ParentId; Role; SortOrder;
164164+ TotalEmails; UnreadEmails; TotalThreads; UnreadThreads;
165165+ MyRights; IsSubscribed
166166+]
167167+168168+(* Check if a property is a count property (TotalEmails, UnreadEmails, etc.) *)
169169+let is_count_property = function
170170+ | TotalEmails | UnreadEmails | TotalThreads | UnreadThreads -> true
171171+ | _ -> false
172172+173173+(* Mailbox Creation and Manipulation *)
174174+175175+(* Create a set of default rights with all permissions *)
176176+let default_rights () = {
177177+ may_read_items = true;
178178+ may_add_items = true;
179179+ may_remove_items = true;
180180+ may_set_seen = true;
181181+ may_set_keywords = true;
182182+ may_create_child = true;
183183+ may_rename = true;
184184+ may_delete = true;
185185+ may_submit = true;
186186+}
187187+188188+(* Create a set of read-only rights *)
189189+let readonly_rights () = {
190190+ may_read_items = true;
191191+ may_add_items = false;
192192+ may_remove_items = false;
193193+ may_set_seen = false;
194194+ may_set_keywords = false;
195195+ may_create_child = false;
196196+ may_rename = false;
197197+ may_delete = false;
198198+ may_submit = false;
199199+}
200200+201201+(* Create a new mailbox object with minimal required fields *)
202202+let create ~name ?parent_id ?role ?sort_order ?is_subscribed () = {
203203+ mailbox_create_name = name;
204204+ mailbox_create_parent_id = parent_id;
205205+ mailbox_create_role = role;
206206+ mailbox_create_sort_order = sort_order;
207207+ mailbox_create_is_subscribed = is_subscribed;
208208+}
209209+210210+(* Build a patch object for updating mailbox properties *)
211211+let update ?name ?parent_id ?role ?sort_order ?is_subscribed () =
212212+ let patches = [] in
213213+ let patches =
214214+ match name with
215215+ | Some new_name -> ("name", `String new_name) :: patches
216216+ | None -> patches
217217+ in
218218+ let patches =
219219+ match parent_id with
220220+ | Some (Some pid) -> ("parentId", `String pid) :: patches
221221+ | Some None -> ("parentId", `Null) :: patches
222222+ | None -> patches
223223+ in
224224+ let patches =
225225+ match role with
226226+ | Some (Some r) -> ("role", `String (role_to_string r)) :: patches
227227+ | Some None -> ("role", `Null) :: patches
228228+ | None -> patches
229229+ in
230230+ let patches =
231231+ match sort_order with
232232+ | Some order -> ("sortOrder", `Int order) :: patches
233233+ | None -> patches
234234+ in
235235+ let patches =
236236+ match is_subscribed with
237237+ | Some subscribed -> ("isSubscribed", `Bool subscribed) :: patches
238238+ | None -> patches
239239+ in
240240+ patches
241241+242242+(* Get the list of standard role names and their string representations *)
243243+let standard_role_names = [
244244+ (Inbox, "inbox");
245245+ (Archive, "archive");
246246+ (Drafts, "drafts");
247247+ (Sent, "sent");
248248+ (Trash, "trash");
249249+ (Junk, "junk");
250250+ (Important, "important");
251251+ (None, "");
252252+]
253253+254254+(* Filter Construction *)
255255+256256+(* Create a filter to match mailboxes with a specific role *)
257257+let filter_has_role role =
258258+ Filter.property_equals "role" (`String (role_to_string role))
259259+260260+(* Create a filter to match mailboxes with no role *)
261261+let filter_has_no_role () =
262262+ Filter.property_equals "role" `Null
263263+264264+(* Create a filter to match mailboxes that are child of a given parent *)
265265+let filter_has_parent parent_id =
266266+ Filter.property_equals "parentId" (`String parent_id)
267267+268268+(* Create a filter to match mailboxes at the root level (no parent) *)
269269+let filter_is_root () =
270270+ Filter.property_equals "parentId" `Null
271271+272272+(* Create a filter to match subscribed mailboxes *)
273273+let filter_is_subscribed () =
274274+ Filter.property_equals "isSubscribed" (`Bool true)
275275+276276+(* Create a filter to match unsubscribed mailboxes *)
277277+let filter_is_not_subscribed () =
278278+ Filter.property_equals "isSubscribed" (`Bool false)
279279+280280+(* Create a filter to match mailboxes by name (using case-insensitive substring matching) *)
281281+let filter_name_contains name =
282282+ Filter.text_contains "name" name
+183
jmap-email/jmap_mailbox.mli
···11+(** JMAP Mailbox.
22+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-2> RFC 8621, Section 2 *)
33+44+open Jmap.Types
55+open Jmap.Methods
66+77+(** Standard mailbox roles as defined in RFC 8621.
88+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-2> RFC 8621, Section 2 *)
99+type role =
1010+ | Inbox (** Messages in the primary inbox *)
1111+ | Archive (** Archived messages *)
1212+ | Drafts (** Draft messages being composed *)
1313+ | Sent (** Messages that have been sent *)
1414+ | Trash (** Messages that have been deleted *)
1515+ | Junk (** Messages determined to be spam *)
1616+ | Important (** Messages deemed important *)
1717+ | Other of string (** Custom or non-standard role *)
1818+ | None (** No specific role assigned *)
1919+2020+(** Mailbox property identifiers.
2121+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-2> RFC 8621, Section 2 *)
2222+type property =
2323+ | Id (** The id of the mailbox *)
2424+ | Name (** The name of the mailbox *)
2525+ | ParentId (** The id of the parent mailbox *)
2626+ | Role (** The role of the mailbox *)
2727+ | SortOrder (** The sort order of the mailbox *)
2828+ | TotalEmails (** The total number of emails in the mailbox *)
2929+ | UnreadEmails (** The number of unread emails in the mailbox *)
3030+ | TotalThreads (** The total number of threads in the mailbox *)
3131+ | UnreadThreads (** The number of unread threads in the mailbox *)
3232+ | MyRights (** The rights the user has for the mailbox *)
3333+ | IsSubscribed (** Whether the mailbox is subscribed to *)
3434+ | Other of string (** Any server-specific extension properties *)
3535+3636+(** Mailbox access rights.
3737+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-2> RFC 8621, Section 2 *)
3838+type mailbox_rights = {
3939+ may_read_items : bool;
4040+ may_add_items : bool;
4141+ may_remove_items : bool;
4242+ may_set_seen : bool;
4343+ may_set_keywords : bool;
4444+ may_create_child : bool;
4545+ may_rename : bool;
4646+ may_delete : bool;
4747+ may_submit : bool;
4848+}
4949+5050+(** Mailbox object.
5151+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-2> RFC 8621, Section 2 *)
5252+type mailbox = {
5353+ mailbox_id : id; (** immutable, server-set *)
5454+ name : string;
5555+ parent_id : id option;
5656+ role : role option;
5757+ sort_order : uint; (* default: 0 *)
5858+ total_emails : uint; (** server-set *)
5959+ unread_emails : uint; (** server-set *)
6060+ total_threads : uint; (** server-set *)
6161+ unread_threads : uint; (** server-set *)
6262+ my_rights : mailbox_rights; (** server-set *)
6363+ is_subscribed : bool;
6464+}
6565+6666+(** Mailbox object for creation.
6767+ Excludes server-set fields.
6868+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-2> RFC 8621, Section 2 *)
6969+type mailbox_create = {
7070+ mailbox_create_name : string;
7171+ mailbox_create_parent_id : id option;
7272+ mailbox_create_role : role option;
7373+ mailbox_create_sort_order : uint option;
7474+ mailbox_create_is_subscribed : bool option;
7575+}
7676+7777+(** Mailbox object for update.
7878+ Patch object, specific structure not enforced here.
7979+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-2.5> RFC 8621, Section 2.5 *)
8080+type mailbox_update = patch_object
8181+8282+(** Server-set info for created mailbox.
8383+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-2.5> RFC 8621, Section 2.5 *)
8484+type mailbox_created_info = {
8585+ mailbox_created_id : id;
8686+ mailbox_created_role : role option; (** If default used *)
8787+ mailbox_created_sort_order : uint; (** If default used *)
8888+ mailbox_created_total_emails : uint;
8989+ mailbox_created_unread_emails : uint;
9090+ mailbox_created_total_threads : uint;
9191+ mailbox_created_unread_threads : uint;
9292+ mailbox_created_my_rights : mailbox_rights;
9393+ mailbox_created_is_subscribed : bool; (** If default used *)
9494+}
9595+9696+(** Server-set/computed info for updated mailbox.
9797+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-2.5> RFC 8621, Section 2.5 *)
9898+type mailbox_updated_info = mailbox (* Contains only changed server-set props *)
9999+100100+(** FilterCondition for Mailbox/query.
101101+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-2.3> RFC 8621, Section 2.3 *)
102102+type mailbox_filter_condition = {
103103+ filter_parent_id : id option option; (* Use option option for explicit null *)
104104+ filter_name : string option;
105105+ filter_role : role option option; (* Use option option for explicit null *)
106106+ filter_has_any_role : bool option;
107107+ filter_is_subscribed : bool option;
108108+}
109109+110110+(** {2 Role and Property Conversion Functions} *)
111111+112112+(** Convert a role variant to its string representation *)
113113+val role_to_string : role -> string
114114+115115+(** Parse a string into a role variant *)
116116+val string_to_role : string -> role
117117+118118+(** Convert a property variant to its string representation *)
119119+val property_to_string : property -> string
120120+121121+(** Parse a string into a property variant *)
122122+val string_to_property : string -> property
123123+124124+(** Get a list of common properties useful for displaying mailboxes *)
125125+val common_properties : property list
126126+127127+(** Get a list of all standard properties *)
128128+val all_properties : property list
129129+130130+(** Check if a property is a count property (TotalEmails, UnreadEmails, etc.) *)
131131+val is_count_property : property -> bool
132132+133133+(** {2 Mailbox Creation and Manipulation} *)
134134+135135+(** Create a set of default rights with all permissions *)
136136+val default_rights : unit -> mailbox_rights
137137+138138+(** Create a set of read-only rights *)
139139+val readonly_rights : unit -> mailbox_rights
140140+141141+(** Create a new mailbox object with minimal required fields *)
142142+val create :
143143+ name:string ->
144144+ ?parent_id:id ->
145145+ ?role:role ->
146146+ ?sort_order:uint ->
147147+ ?is_subscribed:bool ->
148148+ unit -> mailbox_create
149149+150150+(** Build a patch object for updating mailbox properties *)
151151+val update :
152152+ ?name:string ->
153153+ ?parent_id:id option ->
154154+ ?role:role option ->
155155+ ?sort_order:uint ->
156156+ ?is_subscribed:bool ->
157157+ unit -> mailbox_update
158158+159159+(** Get the list of standard role names and their string representations *)
160160+val standard_role_names : (role * string) list
161161+162162+(** {2 Filter Construction} *)
163163+164164+(** Create a filter to match mailboxes with a specific role *)
165165+val filter_has_role : role -> Jmap.Methods.Filter.t
166166+167167+(** Create a filter to match mailboxes with no role *)
168168+val filter_has_no_role : unit -> Jmap.Methods.Filter.t
169169+170170+(** Create a filter to match mailboxes that are child of a given parent *)
171171+val filter_has_parent : id -> Jmap.Methods.Filter.t
172172+173173+(** Create a filter to match mailboxes at the root level (no parent) *)
174174+val filter_is_root : unit -> Jmap.Methods.Filter.t
175175+176176+(** Create a filter to match subscribed mailboxes *)
177177+val filter_is_subscribed : unit -> Jmap.Methods.Filter.t
178178+179179+(** Create a filter to match unsubscribed mailboxes *)
180180+val filter_is_not_subscribed : unit -> Jmap.Methods.Filter.t
181181+182182+(** Create a filter to match mailboxes by name (using case-insensitive substring matching) *)
183183+val filter_name_contains : string -> Jmap.Methods.Filter.t
+9
jmap-email/jmap_search_snippet.ml
···11+(* JMAP Search Snippet. *)
22+33+(* SearchSnippet object.
44+ Note: Does not have an 'id' property. *)
55+type t = {
66+ email_id : Jmap.Types.id;
77+ subject : string option;
88+ preview : string option;
99+}
+11
jmap-email/jmap_search_snippet.mli
···11+(** JMAP Search Snippet.
22+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-5> RFC 8621, Section 5 *)
33+44+(** SearchSnippet object.
55+ Note: Does not have an 'id' property.
66+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-5> RFC 8621, Section 5 *)
77+type t = {
88+ email_id : Jmap.Types.id;
99+ subject : string option;
1010+ preview : string option;
1111+}
+125
jmap-email/jmap_submission.ml
···11+(* JMAP Email Submission. *)
22+33+open Jmap.Types
44+open Jmap.Methods
55+66+(* Address object for Envelope. *)
77+type envelope_address = {
88+ env_addr_email : string;
99+ env_addr_parameters : Yojson.Safe.t string_map option;
1010+}
1111+1212+(* Envelope object. *)
1313+type envelope = {
1414+ env_mail_from : envelope_address;
1515+ env_rcpt_to : envelope_address list;
1616+}
1717+1818+(* Delivery status for a recipient. *)
1919+type delivery_status = {
2020+ delivery_smtp_reply : string;
2121+ delivery_delivered : [ `Queued | `Yes | `No | `Unknown ];
2222+ delivery_displayed : [ `Yes | `Unknown ];
2323+}
2424+2525+(* EmailSubmission object. *)
2626+type email_submission = {
2727+ email_sub_id : id; (* immutable, server-set *)
2828+ identity_id : id; (* immutable *)
2929+ email_id : id; (* immutable *)
3030+ thread_id : id; (* immutable, server-set *)
3131+ envelope : envelope option; (* immutable *)
3232+ send_at : utc_date; (* immutable, server-set *)
3333+ undo_status : [ `Pending | `Final | `Canceled ];
3434+ delivery_status : delivery_status string_map option; (* server-set *)
3535+ dsn_blob_ids : id list; (* server-set *)
3636+ mdn_blob_ids : id list; (* server-set *)
3737+}
3838+3939+(* EmailSubmission object for creation.
4040+ Excludes server-set fields. *)
4141+type email_submission_create = {
4242+ email_sub_create_identity_id : id;
4343+ email_sub_create_email_id : id;
4444+ email_sub_create_envelope : envelope option;
4545+}
4646+4747+(* EmailSubmission object for update.
4848+ Only undoStatus can be updated (to 'canceled'). *)
4949+type email_submission_update = patch_object
5050+5151+(* Server-set info for created email submission. *)
5252+type email_submission_created_info = {
5353+ email_sub_created_id : id;
5454+ email_sub_created_thread_id : id;
5555+ email_sub_created_send_at : utc_date;
5656+}
5757+5858+(* Server-set/computed info for updated email submission. *)
5959+type email_submission_updated_info = email_submission (* Contains only changed server-set props *)
6060+6161+(* FilterCondition for EmailSubmission/query. *)
6262+type email_submission_filter_condition = {
6363+ filter_identity_ids : id list option;
6464+ filter_email_ids : id list option;
6565+ filter_thread_ids : id list option;
6666+ filter_undo_status : [ `Pending | `Final | `Canceled ] option;
6767+ filter_before : utc_date option;
6868+ filter_after : utc_date option;
6969+}
7070+7171+(* EmailSubmission/get: Args type (specialized from ['record Get_args.t]). *)
7272+module Email_submission_get_args = struct
7373+ type t = email_submission Get_args.t
7474+end
7575+7676+(* EmailSubmission/get: Response type (specialized from ['record Get_response.t]). *)
7777+module Email_submission_get_response = struct
7878+ type t = email_submission Get_response.t
7979+end
8080+8181+(* EmailSubmission/changes: Args type (specialized from [Changes_args.t]). *)
8282+module Email_submission_changes_args = struct
8383+ type t = Changes_args.t
8484+end
8585+8686+(* EmailSubmission/changes: Response type (specialized from [Changes_response.t]). *)
8787+module Email_submission_changes_response = struct
8888+ type t = Changes_response.t
8989+end
9090+9191+(* EmailSubmission/query: Args type (specialized from [Query_args.t]). *)
9292+module Email_submission_query_args = struct
9393+ type t = Query_args.t
9494+end
9595+9696+(* EmailSubmission/query: Response type (specialized from [Query_response.t]). *)
9797+module Email_submission_query_response = struct
9898+ type t = Query_response.t
9999+end
100100+101101+(* EmailSubmission/queryChanges: Args type (specialized from [Query_changes_args.t]). *)
102102+module Email_submission_query_changes_args = struct
103103+ type t = Query_changes_args.t
104104+end
105105+106106+(* EmailSubmission/queryChanges: Response type (specialized from [Query_changes_response.t]). *)
107107+module Email_submission_query_changes_response = struct
108108+ type t = Query_changes_response.t
109109+end
110110+111111+(* EmailSubmission/set: Args type (specialized from [('c, 'u) set_args]).
112112+ Includes onSuccess arguments. *)
113113+type email_submission_set_args = {
114114+ set_account_id : id;
115115+ set_if_in_state : string option;
116116+ set_create : email_submission_create id_map option;
117117+ set_update : email_submission_update id_map option;
118118+ set_destroy : id list option;
119119+ set_on_success_destroy_email : id list option;
120120+}
121121+122122+(* EmailSubmission/set: Response type (specialized from [('c, 'u) Set_response.t]). *)
123123+module Email_submission_set_response = struct
124124+ type t = (email_submission_created_info, email_submission_updated_info) Set_response.t
125125+end
+136
jmap-email/jmap_submission.mli
···11+(** JMAP Email Submission.
22+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-7> RFC 8621, Section 7 *)
33+44+open Jmap.Types
55+open Jmap.Methods
66+77+(** Address object for Envelope.
88+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-7> RFC 8621, Section 7 *)
99+type envelope_address = {
1010+ env_addr_email : string;
1111+ env_addr_parameters : Yojson.Safe.t string_map option;
1212+}
1313+1414+(** Envelope object.
1515+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-7> RFC 8621, Section 7 *)
1616+type envelope = {
1717+ env_mail_from : envelope_address;
1818+ env_rcpt_to : envelope_address list;
1919+}
2020+2121+(** Delivery status for a recipient.
2222+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-7> RFC 8621, Section 7 *)
2323+type delivery_status = {
2424+ delivery_smtp_reply : string;
2525+ delivery_delivered : [ `Queued | `Yes | `No | `Unknown ];
2626+ delivery_displayed : [ `Yes | `Unknown ];
2727+}
2828+2929+(** EmailSubmission object.
3030+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-7> RFC 8621, Section 7 *)
3131+type email_submission = {
3232+ email_sub_id : id; (** immutable, server-set *)
3333+ identity_id : id; (** immutable *)
3434+ email_id : id; (** immutable *)
3535+ thread_id : id; (** immutable, server-set *)
3636+ envelope : envelope option; (** immutable *)
3737+ send_at : utc_date; (** immutable, server-set *)
3838+ undo_status : [ `Pending | `Final | `Canceled ];
3939+ delivery_status : delivery_status string_map option; (** server-set *)
4040+ dsn_blob_ids : id list; (** server-set *)
4141+ mdn_blob_ids : id list; (** server-set *)
4242+}
4343+4444+(** EmailSubmission object for creation.
4545+ Excludes server-set fields.
4646+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-7> RFC 8621, Section 7 *)
4747+type email_submission_create = {
4848+ email_sub_create_identity_id : id;
4949+ email_sub_create_email_id : id;
5050+ email_sub_create_envelope : envelope option;
5151+}
5252+5353+(** EmailSubmission object for update.
5454+ Only undoStatus can be updated (to 'canceled').
5555+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-7> RFC 8621, Section 7 *)
5656+type email_submission_update = patch_object
5757+5858+(** Server-set info for created email submission.
5959+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-7.5> RFC 8621, Section 7.5 *)
6060+type email_submission_created_info = {
6161+ email_sub_created_id : id;
6262+ email_sub_created_thread_id : id;
6363+ email_sub_created_send_at : utc_date;
6464+}
6565+6666+(** Server-set/computed info for updated email submission.
6767+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-7.5> RFC 8621, Section 7.5 *)
6868+type email_submission_updated_info = email_submission (* Contains only changed server-set props *)
6969+7070+(** FilterCondition for EmailSubmission/query.
7171+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-7.3> RFC 8621, Section 7.3 *)
7272+type email_submission_filter_condition = {
7373+ filter_identity_ids : id list option;
7474+ filter_email_ids : id list option;
7575+ filter_thread_ids : id list option;
7676+ filter_undo_status : [ `Pending | `Final | `Canceled ] option;
7777+ filter_before : utc_date option;
7878+ filter_after : utc_date option;
7979+}
8080+8181+(** EmailSubmission/get: Args type (specialized from ['record Get_args.t]). *)
8282+module Email_submission_get_args : sig
8383+ type t = email_submission Get_args.t
8484+end
8585+8686+(** EmailSubmission/get: Response type (specialized from ['record Get_response.t]). *)
8787+module Email_submission_get_response : sig
8888+ type t = email_submission Get_response.t
8989+end
9090+9191+(** EmailSubmission/changes: Args type (specialized from [Changes_args.t]). *)
9292+module Email_submission_changes_args : sig
9393+ type t = Changes_args.t
9494+end
9595+9696+(** EmailSubmission/changes: Response type (specialized from [Changes_response.t]). *)
9797+module Email_submission_changes_response : sig
9898+ type t = Changes_response.t
9999+end
100100+101101+(** EmailSubmission/query: Args type (specialized from [Query_args.t]). *)
102102+module Email_submission_query_args : sig
103103+ type t = Query_args.t
104104+end
105105+106106+(** EmailSubmission/query: Response type (specialized from [Query_response.t]). *)
107107+module Email_submission_query_response : sig
108108+ type t = Query_response.t
109109+end
110110+111111+(** EmailSubmission/queryChanges: Args type (specialized from [Query_changes_args.t]). *)
112112+module Email_submission_query_changes_args : sig
113113+ type t = Query_changes_args.t
114114+end
115115+116116+(** EmailSubmission/queryChanges: Response type (specialized from [Query_changes_response.t]). *)
117117+module Email_submission_query_changes_response : sig
118118+ type t = Query_changes_response.t
119119+end
120120+121121+(** EmailSubmission/set: Args type (specialized from [('c, 'u) set_args]).
122122+ Includes onSuccess arguments.
123123+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-7.5> RFC 8621, Section 7.5 *)
124124+type email_submission_set_args = {
125125+ set_account_id : id;
126126+ set_if_in_state : string option;
127127+ set_create : email_submission_create id_map option;
128128+ set_update : email_submission_update id_map option;
129129+ set_destroy : id list option;
130130+ set_on_success_destroy_email : id list option;
131131+}
132132+133133+(** EmailSubmission/set: Response type (specialized from [('c, 'u) Set_response.t]). *)
134134+module Email_submission_set_response : sig
135135+ type t = (email_submission_created_info, email_submission_updated_info) Set_response.t
136136+end
+19
jmap-email/jmap_thread.ml
···11+(* JMAP Thread. *)
22+33+open Jmap.Types
44+55+(* Thread object. *)
66+module Thread = struct
77+ type t = {
88+ id_value: id;
99+ email_ids_value: id list;
1010+ }
1111+1212+ let id t = t.id_value
1313+ let email_ids t = t.email_ids_value
1414+1515+ let v ~id ~email_ids = {
1616+ id_value = id;
1717+ email_ids_value = email_ids;
1818+ }
1919+end
+15
jmap-email/jmap_thread.mli
···11+(** JMAP Thread.
22+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-3> RFC 8621, Section 3 *)
33+44+open Jmap.Types
55+66+(** Thread object.
77+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-3> RFC 8621, Section 3 *)
88+module Thread : sig
99+ type t
1010+1111+ val id : t -> id
1212+ val email_ids : t -> id list
1313+1414+ val v : id:id -> email_ids:id list -> t
1515+end
+103
jmap-email/jmap_vacation.ml
···11+(* JMAP Vacation Response. *)
22+33+open Jmap.Types
44+open Jmap.Methods
55+open Jmap.Error
66+77+(* VacationResponse object.
88+ Note: id is always "singleton". *)
99+module Vacation_response = struct
1010+ type t = {
1111+ id_value: id;
1212+ is_enabled_value: bool;
1313+ from_date_value: utc_date option;
1414+ to_date_value: utc_date option;
1515+ subject_value: string option;
1616+ text_body_value: string option;
1717+ html_body_value: string option;
1818+ }
1919+2020+ (* Id of the vacation response (immutable, server-set, MUST be "singleton") *)
2121+ let id t = t.id_value
2222+ let is_enabled t = t.is_enabled_value
2323+ let from_date t = t.from_date_value
2424+ let to_date t = t.to_date_value
2525+ let subject t = t.subject_value
2626+ let text_body t = t.text_body_value
2727+ let html_body t = t.html_body_value
2828+2929+ let v ~id ~is_enabled ?from_date ?to_date ?subject ?text_body ?html_body () = {
3030+ id_value = id;
3131+ is_enabled_value = is_enabled;
3232+ from_date_value = from_date;
3333+ to_date_value = to_date;
3434+ subject_value = subject;
3535+ text_body_value = text_body;
3636+ html_body_value = html_body;
3737+ }
3838+end
3939+4040+(* VacationResponse object for update.
4141+ Patch object, specific structure not enforced here. *)
4242+type vacation_response_update = patch_object
4343+4444+(* VacationResponse/get: Args type (specialized from ['record get_args]). *)
4545+module Vacation_response_get_args = struct
4646+ type t = Vacation_response.t Get_args.t
4747+4848+ let v ~account_id ?ids ?properties () =
4949+ Get_args.v ~account_id ?ids ?properties ()
5050+end
5151+5252+(* VacationResponse/get: Response type (specialized from ['record get_response]). *)
5353+module Vacation_response_get_response = struct
5454+ type t = Vacation_response.t Get_response.t
5555+5656+ let v ~account_id ~state ~list ~not_found () =
5757+ Get_response.v ~account_id ~state ~list ~not_found ()
5858+end
5959+6060+(* VacationResponse/set: Args type.
6161+ Only allows update, id must be "singleton". *)
6262+module Vacation_response_set_args = struct
6363+ type t = {
6464+ account_id_value: id;
6565+ if_in_state_value: string option;
6666+ update_value: vacation_response_update id_map option;
6767+ }
6868+6969+ let account_id t = t.account_id_value
7070+ let if_in_state t = t.if_in_state_value
7171+ let update t = t.update_value
7272+7373+ let v ~account_id ?if_in_state ?update () = {
7474+ account_id_value = account_id;
7575+ if_in_state_value = if_in_state;
7676+ update_value = update;
7777+ }
7878+end
7979+8080+(* VacationResponse/set: Response type. *)
8181+module Vacation_response_set_response = struct
8282+ type t = {
8383+ account_id_value: id;
8484+ old_state_value: string option;
8585+ new_state_value: string;
8686+ updated_value: Vacation_response.t option id_map option;
8787+ not_updated_value: Set_error.t id_map option;
8888+ }
8989+9090+ let account_id t = t.account_id_value
9191+ let old_state t = t.old_state_value
9292+ let new_state t = t.new_state_value
9393+ let updated t = t.updated_value
9494+ let not_updated t = t.not_updated_value
9595+9696+ let v ~account_id ?old_state ~new_state ?updated ?not_updated () = {
9797+ account_id_value = account_id;
9898+ old_state_value = old_state;
9999+ new_state_value = new_state;
100100+ updated_value = updated;
101101+ not_updated_value = not_updated;
102102+ }
103103+end
+102
jmap-email/jmap_vacation.mli
···11+(** JMAP Vacation Response.
22+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-8> RFC 8621, Section 8 *)
33+44+open Jmap.Types
55+open Jmap.Methods
66+open Jmap.Error
77+88+(** VacationResponse object.
99+ Note: id is always "singleton".
1010+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-8> RFC 8621, Section 8 *)
1111+module Vacation_response : sig
1212+ type t
1313+1414+ (** Id of the vacation response (immutable, server-set, MUST be "singleton") *)
1515+ val id : t -> id
1616+ val is_enabled : t -> bool
1717+ val from_date : t -> utc_date option
1818+ val to_date : t -> utc_date option
1919+ val subject : t -> string option
2020+ val text_body : t -> string option
2121+ val html_body : t -> string option
2222+2323+ val v :
2424+ id:id ->
2525+ is_enabled:bool ->
2626+ ?from_date:utc_date ->
2727+ ?to_date:utc_date ->
2828+ ?subject:string ->
2929+ ?text_body:string ->
3030+ ?html_body:string ->
3131+ unit ->
3232+ t
3333+end
3434+3535+(** VacationResponse object for update.
3636+ Patch object, specific structure not enforced here.
3737+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-8.2> RFC 8621, Section 8.2 *)
3838+type vacation_response_update = patch_object
3939+4040+(** VacationResponse/get: Args type (specialized from ['record get_args]). *)
4141+module Vacation_response_get_args : sig
4242+ type t = Vacation_response.t Get_args.t
4343+4444+ val v :
4545+ account_id:id ->
4646+ ?ids:id list ->
4747+ ?properties:string list ->
4848+ unit ->
4949+ t
5050+end
5151+5252+(** VacationResponse/get: Response type (specialized from ['record get_response]). *)
5353+module Vacation_response_get_response : sig
5454+ type t = Vacation_response.t Get_response.t
5555+5656+ val v :
5757+ account_id:id ->
5858+ state:string ->
5959+ list:Vacation_response.t list ->
6060+ not_found:id list ->
6161+ unit ->
6262+ t
6363+end
6464+6565+(** VacationResponse/set: Args type.
6666+ Only allows update, id must be "singleton".
6767+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-8.2> RFC 8621, Section 8.2 *)
6868+module Vacation_response_set_args : sig
6969+ type t
7070+7171+ val account_id : t -> id
7272+ val if_in_state : t -> string option
7373+ val update : t -> vacation_response_update id_map option
7474+7575+ val v :
7676+ account_id:id ->
7777+ ?if_in_state:string ->
7878+ ?update:vacation_response_update id_map ->
7979+ unit ->
8080+ t
8181+end
8282+8383+(** VacationResponse/set: Response type.
8484+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-8.2> RFC 8621, Section 8.2 *)
8585+module Vacation_response_set_response : sig
8686+ type t
8787+8888+ val account_id : t -> id
8989+ val old_state : t -> string option
9090+ val new_state : t -> string
9191+ val updated : t -> Vacation_response.t option id_map option
9292+ val not_updated : t -> Set_error.t id_map option
9393+9494+ val v :
9595+ account_id:id ->
9696+ ?old_state:string ->
9797+ new_state:string ->
9898+ ?updated:Vacation_response.t option id_map ->
9999+ ?not_updated:Set_error.t id_map ->
100100+ unit ->
101101+ t
102102+end
···11+(* Unix-specific JMAP client implementation interface. *)
22+33+open Jmap
44+open Jmap.Types
55+open Jmap.Error
66+open Jmap.Session
77+open Jmap.Wire
88+99+(* Configuration options for a JMAP client context *)
1010+type client_config = {
1111+ connect_timeout : float option; (* Connection timeout in seconds *)
1212+ request_timeout : float option; (* Request timeout in seconds *)
1313+ max_concurrent_requests : int option; (* Maximum concurrent requests *)
1414+ max_request_size : int option; (* Maximum request size in bytes *)
1515+ user_agent : string option; (* User-Agent header value *)
1616+ authentication_header : string option; (* Custom Authentication header name *)
1717+}
1818+1919+(* Authentication method options *)
2020+type auth_method =
2121+ | Basic of string * string (* Basic auth with username and password *)
2222+ | Bearer of string (* Bearer token auth *)
2323+ | Custom of (string * string) (* Custom header name and value *)
2424+ | Session_cookie of (string * string) (* Session cookie name and value *)
2525+ | No_auth (* No authentication *)
2626+2727+(* The internal state of a JMAP client connection *)
2828+type context = {
2929+ config: client_config;
3030+ mutable session_url: Uri.t option;
3131+ mutable session: Session.t option;
3232+ mutable auth: auth_method;
3333+}
3434+3535+(* Represents an active EventSource connection *)
3636+type event_source_connection = {
3737+ event_url: Uri.t;
3838+ mutable is_connected: bool;
3939+}
4040+4141+(* A request builder for constructing and sending JMAP requests *)
4242+type request_builder = {
4343+ ctx: context;
4444+ mutable using: string list;
4545+ mutable method_calls: Invocation.t list;
4646+}
4747+4848+(* Create default configuration options *)
4949+let default_config () = {
5050+ connect_timeout = Some 30.0;
5151+ request_timeout = Some 300.0;
5252+ max_concurrent_requests = Some 4;
5353+ max_request_size = Some (1024 * 1024 * 10); (* 10 MB *)
5454+ user_agent = Some "OCaml JMAP Unix Client/1.0";
5555+ authentication_header = None;
5656+}
5757+5858+(* Create a client context with the specified configuration *)
5959+let create_client ?(config = default_config ()) () = {
6060+ config;
6161+ session_url = None;
6262+ session = None;
6363+ auth = No_auth;
6464+}
6565+6666+(* Mock implementation for the Unix connection *)
6767+let connect ctx ?session_url ?username ~host ?port ?auth_method () =
6868+ (* In a real implementation, this would use Unix HTTP functions *)
6969+ let auth = match auth_method with
7070+ | Some auth -> auth
7171+ | None -> No_auth
7272+ in
7373+7474+ (* Store the auth method for future requests *)
7575+ ctx.auth <- auth;
7676+7777+ (* Set session URL, either directly or after discovery *)
7878+ let session_url = match session_url with
7979+ | Some url -> url
8080+ | None ->
8181+ (* In a real implementation, this would perform RFC 8620 discovery *)
8282+ let proto = "https" in
8383+ let host_with_port = match port with
8484+ | Some p -> host ^ ":" ^ string_of_int p
8585+ | None -> host
8686+ in
8787+ Uri.of_string (proto ^ "://" ^ host_with_port ^ "/.well-known/jmap")
8888+ in
8989+ ctx.session_url <- Some session_url;
9090+9191+ (* Create a mock session object for this example *)
9292+ let caps = Hashtbl.create 4 in
9393+ Hashtbl.add caps Jmap.capability_core (`Assoc []);
9494+9595+ let accounts = Hashtbl.create 1 in
9696+ let acct = Account.v
9797+ ~name:"user@example.com"
9898+ ~is_personal:true
9999+ ~is_read_only:false
100100+ ()
101101+ in
102102+ Hashtbl.add accounts "u1" acct;
103103+104104+ let primary = Hashtbl.create 1 in
105105+ Hashtbl.add primary Jmap.capability_core "u1";
106106+107107+ let api_url =
108108+ Uri.of_string ("https://" ^ host ^ "/api/jmap")
109109+ in
110110+111111+ let session = Session.v
112112+ ~capabilities:caps
113113+ ~accounts
114114+ ~primary_accounts:primary
115115+ ~username:"user@example.com"
116116+ ~api_url
117117+ ~download_url:(Uri.of_string ("https://" ^ host ^ "/download/{accountId}/{blobId}"))
118118+ ~upload_url:(Uri.of_string ("https://" ^ host ^ "/upload/{accountId}"))
119119+ ~event_source_url:(Uri.of_string ("https://" ^ host ^ "/eventsource"))
120120+ ~state:"1"
121121+ ()
122122+ in
123123+124124+ ctx.session <- Some session;
125125+ Ok (ctx, session)
126126+127127+(* Create a request builder for constructing a JMAP request *)
128128+let build ctx = {
129129+ ctx;
130130+ using = [Jmap.capability_core]; (* Default to core capability *)
131131+ method_calls = [];
132132+}
133133+134134+(* Set the using capabilities for a request *)
135135+let using builder capabilities =
136136+ { builder with using = capabilities }
137137+138138+(* Add a method call to a request builder *)
139139+let add_method_call builder name args id =
140140+ let call = Invocation.v
141141+ ~method_name:name
142142+ ~arguments:args
143143+ ~method_call_id:id
144144+ ()
145145+ in
146146+ { builder with method_calls = builder.method_calls @ [call] }
147147+148148+(* Create a reference to a previous method call result *)
149149+let create_reference result_of name =
150150+ Jmap.Wire.Result_reference.v
151151+ ~result_of
152152+ ~name
153153+ ~path:"" (* In a real implementation, this would include a JSON pointer *)
154154+ ()
155155+156156+(* Execute a request and return the response *)
157157+let execute builder =
158158+ match builder.ctx.session with
159159+ | None -> Error (protocol_error "No active session")
160160+ | Some session ->
161161+ (* In a real implementation, this would create and send an HTTP request *)
162162+163163+ (* Create a mock response for this implementation *)
164164+ let results = List.map (fun call ->
165165+ let method_name = Invocation.method_name call in
166166+ let call_id = Invocation.method_call_id call in
167167+ if method_name = "Core/echo" then
168168+ (* Echo method implementation *)
169169+ Ok call
170170+ else
171171+ (* For other methods, return a method error *)
172172+ Error (
173173+ Method_error.v
174174+ ~description:(Method_error_description.v
175175+ ~description:"Method not implemented in mock"
176176+ ())
177177+ `ServerUnavailable,
178178+ "Mock implementation"
179179+ )
180180+ ) builder.method_calls in
181181+182182+ let resp = Response.v
183183+ ~method_responses:results
184184+ ~session_state:(session |> Session.state)
185185+ ()
186186+ in
187187+ Ok resp
188188+189189+(* Perform a JMAP API request *)
190190+let request ctx req =
191191+ match ctx.session_url, ctx.session with
192192+ | None, _ -> Error (protocol_error "No session URL configured")
193193+ | _, None -> Error (protocol_error "No active session")
194194+ | Some url, Some session ->
195195+ (* In a real implementation, this would serialize the request and send it *)
196196+197197+ (* Mock response implementation *)
198198+ let method_calls = Request.method_calls req in
199199+ let results = List.map (fun call ->
200200+ let method_name = Invocation.method_name call in
201201+ let call_id = Invocation.method_call_id call in
202202+ if method_name = "Core/echo" then
203203+ (* Echo method implementation *)
204204+ Ok call
205205+ else
206206+ (* For other methods, return a method error *)
207207+ Error (
208208+ Method_error.v
209209+ ~description:(Method_error_description.v
210210+ ~description:"Method not implemented in mock"
211211+ ())
212212+ `ServerUnavailable,
213213+ "Mock implementation"
214214+ )
215215+ ) method_calls in
216216+217217+ let resp = Response.v
218218+ ~method_responses:results
219219+ ~session_state:(session |> Session.state)
220220+ ()
221221+ in
222222+ Ok resp
223223+224224+(* Upload binary data *)
225225+let upload ctx ~account_id ~content_type ~data_stream =
226226+ match ctx.session with
227227+ | None -> Error (protocol_error "No active session")
228228+ | Some session ->
229229+ (* In a real implementation, would upload the data stream *)
230230+231231+ (* Mock success response *)
232232+ let response = Jmap.Binary.Upload_response.v
233233+ ~account_id
234234+ ~blob_id:"b123456"
235235+ ~type_:content_type
236236+ ~size:1024 (* Mock size *)
237237+ ()
238238+ in
239239+ Ok response
240240+241241+(* Download binary data *)
242242+let download ctx ~account_id ~blob_id ?content_type ?name =
243243+ match ctx.session with
244244+ | None -> Error (protocol_error "No active session")
245245+ | Some session ->
246246+ (* In a real implementation, would download the data and return a stream *)
247247+248248+ (* Mock data stream - in real code, this would be read from the HTTP response *)
249249+ let mock_data = "This is mock downloaded data for blob " ^ blob_id in
250250+ let seq = Seq.cons mock_data Seq.empty in
251251+ Ok seq
252252+253253+(* Copy blobs between accounts *)
254254+let copy_blobs ctx ~from_account_id ~account_id ~blob_ids =
255255+ match ctx.session with
256256+ | None -> Error (protocol_error "No active session")
257257+ | Some session ->
258258+ (* In a real implementation, would perform server-side copy *)
259259+260260+ (* Mock success response with first blob copied and second failed *)
261261+ let copied = Hashtbl.create 1 in
262262+ Hashtbl.add copied (List.hd blob_ids) "b999999";
263263+264264+ let response = Jmap.Binary.Blob_copy_response.v
265265+ ~from_account_id
266266+ ~account_id
267267+ ~copied
268268+ ()
269269+ in
270270+ Ok response
271271+272272+(* Connect to the EventSource for push notifications *)
273273+let connect_event_source ctx ?types ?close_after ?ping =
274274+ match ctx.session with
275275+ | None -> Error (protocol_error "No active session")
276276+ | Some session ->
277277+ (* In a real implementation, would connect to EventSource URL *)
278278+279279+ (* Create mock connection *)
280280+ let event_url = Session.event_source_url session in
281281+ let conn = { event_url; is_connected = true } in
282282+283283+ (* Create a mock event sequence *)
284284+ let mock_state_change =
285285+ let changed = Hashtbl.create 1 in
286286+ let account_id = "u1" in
287287+ let state_map = Hashtbl.create 2 in
288288+ Hashtbl.add state_map "Email" "s123";
289289+ Hashtbl.add state_map "Mailbox" "s456";
290290+ Hashtbl.add changed account_id state_map;
291291+292292+ Push.State_change.v ~changed ()
293293+ in
294294+295295+ let ping_data =
296296+ Push.Event_source_ping_data.v ~interval:30 ()
297297+ in
298298+299299+ (* Create a sequence with one state event and one ping event *)
300300+ let events = Seq.cons (`State mock_state_change)
301301+ (Seq.cons (`Ping ping_data) Seq.empty) in
302302+303303+ Ok (conn, events)
304304+305305+(* Create a websocket connection for JMAP over WebSocket *)
306306+let connect_websocket ctx =
307307+ match ctx.session with
308308+ | None -> Error (protocol_error "No active session")
309309+ | Some session ->
310310+ (* In a real implementation, would connect via WebSocket *)
311311+312312+ (* Mock connection *)
313313+ let event_url = Session.api_url session in
314314+ let conn = { event_url; is_connected = true } in
315315+ Ok conn
316316+317317+(* Send a message over a websocket connection *)
318318+let websocket_send conn req =
319319+ if not conn.is_connected then
320320+ Error (protocol_error "WebSocket not connected")
321321+ else
322322+ (* In a real implementation, would send over WebSocket *)
323323+324324+ (* Mock response (same as request function) *)
325325+ let method_calls = Request.method_calls req in
326326+ let results = List.map (fun call ->
327327+ let method_name = Invocation.method_name call in
328328+ let call_id = Invocation.method_call_id call in
329329+ if method_name = "Core/echo" then
330330+ Ok call
331331+ else
332332+ Error (
333333+ Method_error.v
334334+ ~description:(Method_error_description.v
335335+ ~description:"Method not implemented in mock"
336336+ ())
337337+ `ServerUnavailable,
338338+ "Mock implementation"
339339+ )
340340+ ) method_calls in
341341+342342+ let resp = Response.v
343343+ ~method_responses:results
344344+ ~session_state:"1"
345345+ ()
346346+ in
347347+ Ok resp
348348+349349+(* Close an EventSource or WebSocket connection *)
350350+let close_connection conn =
351351+ if not conn.is_connected then
352352+ Error (protocol_error "Connection already closed")
353353+ else begin
354354+ conn.is_connected <- false;
355355+ Ok ()
356356+ end
357357+358358+(* Close the JMAP connection context *)
359359+let close ctx =
360360+ ctx.session <- None;
361361+ ctx.session_url <- None;
362362+ Ok ()
363363+364364+(* Helper functions for common tasks *)
365365+366366+(* Helper to get a single object by ID *)
367367+let get_object ctx ~method_name ~account_id ~object_id ?properties =
368368+ let properties_param = match properties with
369369+ | Some props -> `List (List.map (fun p -> `String p) props)
370370+ | None -> `Null
371371+ in
372372+373373+ let args = `Assoc [
374374+ ("accountId", `String account_id);
375375+ ("ids", `List [`String object_id]);
376376+ ("properties", properties_param);
377377+ ] in
378378+379379+ let request_builder = build ctx
380380+ |> add_method_call method_name args "r1"
381381+ in
382382+383383+ match execute request_builder with
384384+ | Error e -> Error e
385385+ | Ok response ->
386386+ (* Find the method response and extract the list with the object *)
387387+ match response |> Response.method_responses with
388388+ | [Ok invocation] when Invocation.method_name invocation = method_name ^ "/get" ->
389389+ let args = Invocation.arguments invocation in
390390+ begin match Yojson.Safe.Util.member "list" args with
391391+ | `List [obj] -> Ok obj
392392+ | _ -> Error (protocol_error "Object not found or invalid response")
393393+ end
394394+ | _ ->
395395+ Error (protocol_error "Method response not found")
396396+397397+(* Helper to set up the connection with minimal options *)
398398+let quick_connect ~host ~username ~password =
399399+ let ctx = create_client () in
400400+ connect ctx ~host ~auth_method:(Basic(username, password)) ()
401401+402402+(* Perform a Core/echo request to test connectivity *)
403403+let echo ctx ?data () =
404404+ let data = match data with
405405+ | Some d -> d
406406+ | None -> `Assoc [("hello", `String "world")]
407407+ in
408408+409409+ let request_builder = build ctx
410410+ |> add_method_call "Core/echo" data "echo1"
411411+ in
412412+413413+ match execute request_builder with
414414+ | Error e -> Error e
415415+ | Ok response ->
416416+ (* Find the Core/echo response and extract the echoed data *)
417417+ match response |> Response.method_responses with
418418+ | [Ok invocation] when Invocation.method_name invocation = "Core/echo" ->
419419+ Ok (Invocation.arguments invocation)
420420+ | _ ->
421421+ Error (protocol_error "Echo response not found")
422422+423423+(* High-level email operations *)
424424+module Email = struct
425425+ open Jmap_email.Types
426426+427427+ (* Get an email by ID *)
428428+ let get_email ctx ~account_id ~email_id ?properties () =
429429+ let props = match properties with
430430+ | Some p -> p
431431+ | None -> List.map email_property_to_string detailed_email_properties
432432+ in
433433+434434+ match get_object ctx ~method_name:"Email/get" ~account_id ~object_id:email_id ~properties:props with
435435+ | Error e -> Error e
436436+ | Ok json ->
437437+ (* In a real implementation, would parse the JSON into an Email.t structure *)
438438+ let mock_email = Email.create
439439+ ~id:email_id
440440+ ~thread_id:"t12345"
441441+ ~mailbox_ids:(let h = Hashtbl.create 1 in Hashtbl.add h "inbox" true; h)
442442+ ~keywords:(Keywords.of_list [Keywords.Seen])
443443+ ~subject:"Mock Email Subject"
444444+ ~preview:"This is a mock email..."
445445+ ~from:[Email_address.v ~name:"Sender Name" ~email:"sender@example.com" ()]
446446+ ~to_:[Email_address.v ~email:"recipient@example.com" ()]
447447+ ()
448448+ in
449449+ Ok mock_email
450450+451451+ (* Search for emails using a filter *)
452452+ let search_emails ctx ~account_id ~filter ?sort ?limit ?position ?properties () =
453453+ (* Create the query args *)
454454+ let args = `Assoc [
455455+ ("accountId", `String account_id);
456456+ ("filter", Jmap.Methods.Filter.to_json filter);
457457+ ("sort", match sort with
458458+ | Some s -> `List [] (* Would convert sort params *)
459459+ | None -> `List [`Assoc [("property", `String "receivedAt"); ("isAscending", `Bool false)]]);
460460+ ("limit", match limit with
461461+ | Some l -> `Int l
462462+ | None -> `Int 20);
463463+ ("position", match position with
464464+ | Some p -> `Int p
465465+ | None -> `Int 0);
466466+ ] in
467467+468468+ let request_builder = build ctx
469469+ |> add_method_call "Email/query" args "q1"
470470+ in
471471+472472+ (* If properties were provided, add a Email/get method call as well *)
473473+ let request_builder = match properties with
474474+ | Some _ ->
475475+ let get_args = `Assoc [
476476+ ("accountId", `String account_id);
477477+ ("#ids", `Assoc [
478478+ ("resultOf", `String "q1");
479479+ ("name", `String "Email/query");
480480+ ("path", `String "/ids")
481481+ ]);
482482+ ("properties", match properties with
483483+ | Some p -> `List (List.map (fun prop -> `String prop) p)
484484+ | None -> `Null);
485485+ ] in
486486+ add_method_call request_builder "Email/get" get_args "g1"
487487+ | None -> request_builder
488488+ in
489489+490490+ match execute request_builder with
491491+ | Error e -> Error e
492492+ | Ok response ->
493493+ (* Find the query response and extract the IDs *)
494494+ match Response.method_responses response with
495495+ | [Ok q_inv; Ok g_inv]
496496+ when Invocation.method_name q_inv = "Email/query"
497497+ && Invocation.method_name g_inv = "Email/get" ->
498498+499499+ (* Extract IDs from query response *)
500500+ let q_args = Invocation.arguments q_inv in
501501+ let ids = match Yojson.Safe.Util.member "ids" q_args with
502502+ | `List l -> List.map Yojson.Safe.Util.to_string l
503503+ | _ -> []
504504+ in
505505+506506+ (* Extract emails from get response *)
507507+ let g_args = Invocation.arguments g_inv in
508508+ (* In a real implementation, would parse each email in the list *)
509509+ let emails = List.map (fun id ->
510510+ Email.create
511511+ ~id
512512+ ~thread_id:("t" ^ id)
513513+ ~subject:(Printf.sprintf "Mock Email %s" id)
514514+ ()
515515+ ) ids in
516516+517517+ Ok (ids, Some emails)
518518+519519+ | [Ok q_inv] when Invocation.method_name q_inv = "Email/query" ->
520520+ (* If only query was performed (no properties requested) *)
521521+ let q_args = Invocation.arguments q_inv in
522522+ let ids = match Yojson.Safe.Util.member "ids" q_args with
523523+ | `List l -> List.map Yojson.Safe.Util.to_string l
524524+ | _ -> []
525525+ in
526526+527527+ Ok (ids, None)
528528+529529+ | _ ->
530530+ Error (protocol_error "Query response not found")
531531+532532+ (* Mark multiple emails with a keyword *)
533533+ let mark_emails ctx ~account_id ~email_ids ~keyword () =
534534+ (* Create the set args with a patch to add the keyword *)
535535+ let keyword_patch = Jmap_email.Keyword_ops.add_keyword_patch keyword in
536536+537537+ (* Create patches map for each email *)
538538+ let update = Hashtbl.create (List.length email_ids) in
539539+ List.iter (fun id ->
540540+ Hashtbl.add update id keyword_patch
541541+ ) email_ids;
542542+543543+ let args = `Assoc [
544544+ ("accountId", `String account_id);
545545+ ("update", `Assoc (
546546+ List.map (fun id ->
547547+ (id, `Assoc (List.map (fun (path, value) ->
548548+ (path, value)
549549+ ) keyword_patch))
550550+ ) email_ids
551551+ ));
552552+ ] in
553553+554554+ let request_builder = build ctx
555555+ |> add_method_call "Email/set" args "s1"
556556+ in
557557+558558+ match execute request_builder with
559559+ | Error e -> Error e
560560+ | Ok response ->
561561+ (* In a real implementation, would check for errors *)
562562+ Ok ()
563563+564564+ (* Mark emails as seen/read *)
565565+ let mark_as_seen ctx ~account_id ~email_ids () =
566566+ mark_emails ctx ~account_id ~email_ids ~keyword:Keywords.Seen ()
567567+568568+ (* Mark emails as unseen/unread *)
569569+ let mark_as_unseen ctx ~account_id ~email_ids () =
570570+ let keyword_patch = Jmap_email.Keyword_ops.mark_unseen_patch () in
571571+572572+ (* Create patches map for each email *)
573573+ let update = Hashtbl.create (List.length email_ids) in
574574+ List.iter (fun id ->
575575+ Hashtbl.add update id keyword_patch
576576+ ) email_ids;
577577+578578+ let args = `Assoc [
579579+ ("accountId", `String account_id);
580580+ ("update", `Assoc (
581581+ List.map (fun id ->
582582+ (id, `Assoc (List.map (fun (path, value) ->
583583+ (path, value)
584584+ ) keyword_patch))
585585+ ) email_ids
586586+ ));
587587+ ] in
588588+589589+ let request_builder = build ctx
590590+ |> add_method_call "Email/set" args "s1"
591591+ in
592592+593593+ match execute request_builder with
594594+ | Error e -> Error e
595595+ | Ok _response -> Ok ()
596596+597597+ (* Move emails to a different mailbox *)
598598+ let move_emails ctx ~account_id ~email_ids ~mailbox_id ?remove_from_mailboxes () =
599599+ (* Create patch to add to destination mailbox *)
600600+ let add_patch = [("mailboxIds/" ^ mailbox_id, `Bool true)] in
601601+602602+ (* If remove_from_mailboxes is specified, add patches to remove *)
603603+ let remove_patch = match remove_from_mailboxes with
604604+ | Some mailboxes ->
605605+ List.map (fun mbx -> ("mailboxIds/" ^ mbx, `Null)) mailboxes
606606+ | None -> []
607607+ in
608608+609609+ (* Combine patches *)
610610+ let patches = add_patch @ remove_patch in
611611+612612+ (* Create patches map for each email *)
613613+ let update = Hashtbl.create (List.length email_ids) in
614614+ List.iter (fun id ->
615615+ Hashtbl.add update id patches
616616+ ) email_ids;
617617+618618+ let args = `Assoc [
619619+ ("accountId", `String account_id);
620620+ ("update", `Assoc (
621621+ List.map (fun id ->
622622+ (id, `Assoc (List.map (fun (path, value) ->
623623+ (path, value)
624624+ ) patches))
625625+ ) email_ids
626626+ ));
627627+ ] in
628628+629629+ let request_builder = build ctx
630630+ |> add_method_call "Email/set" args "s1"
631631+ in
632632+633633+ match execute request_builder with
634634+ | Error e -> Error e
635635+ | Ok _response -> Ok ()
636636+637637+ (* Import an RFC822 message *)
638638+ let import_email ctx ~account_id ~rfc822 ~mailbox_ids ?keywords ?received_at () =
639639+ (* In a real implementation, would first upload the message as a blob *)
640640+ let mock_blob_id = "b9876" in
641641+642642+ (* Create the Email/import call *)
643643+ let args = `Assoc [
644644+ ("accountId", `String account_id);
645645+ ("emails", `Assoc [
646646+ ("msg1", `Assoc [
647647+ ("blobId", `String mock_blob_id);
648648+ ("mailboxIds", `Assoc (
649649+ List.map (fun id -> (id, `Bool true)) mailbox_ids
650650+ ));
651651+ ("keywords", match keywords with
652652+ | Some kws ->
653653+ `Assoc (List.map (fun k ->
654654+ (Types.Keywords.to_string k, `Bool true)) kws)
655655+ | None -> `Null);
656656+ ("receivedAt", match received_at with
657657+ | Some d -> `String (string_of_float d) (* Would format as RFC3339 *)
658658+ | None -> `Null);
659659+ ])
660660+ ]);
661661+ ] in
662662+663663+ let request_builder = build ctx
664664+ |> add_method_call "Email/import" args "i1"
665665+ in
666666+667667+ match execute request_builder with
668668+ | Error e -> Error e
669669+ | Ok response ->
670670+ (* In a real implementation, would extract the created ID *)
671671+ Ok "e12345"
672672+end
+359
jmap-unix/jmap_unix.mli
···11+(** Unix-specific JMAP client implementation interface.
22+33+ This module provides functions to interact with a JMAP server using
44+ Unix sockets for network communication.
55+66+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-4> RFC 8620, Section 4
77+*)
88+99+(** Configuration options for a JMAP client context *)
1010+type client_config = {
1111+ connect_timeout : float option; (** Connection timeout in seconds *)
1212+ request_timeout : float option; (** Request timeout in seconds *)
1313+ max_concurrent_requests : int option; (** Maximum concurrent requests *)
1414+ max_request_size : int option; (** Maximum request size in bytes *)
1515+ user_agent : string option; (** User-Agent header value *)
1616+ authentication_header : string option; (** Custom Authentication header name *)
1717+}
1818+1919+(** Authentication method options *)
2020+type auth_method =
2121+ | Basic of string * string (** Basic auth with username and password *)
2222+ | Bearer of string (** Bearer token auth *)
2323+ | Custom of (string * string) (** Custom header name and value *)
2424+ | Session_cookie of (string * string) (** Session cookie name and value *)
2525+ | No_auth (** No authentication *)
2626+2727+(** Represents an active JMAP connection context. Opaque type. *)
2828+type context
2929+3030+(** Represents an active EventSource connection. Opaque type. *)
3131+type event_source_connection
3232+3333+(** A request builder for constructing and sending JMAP requests *)
3434+type request_builder
3535+3636+(** Create default configuration options *)
3737+val default_config : unit -> client_config
3838+3939+(** Create a client context with the specified configuration
4040+ @return The context object used for JMAP API calls
4141+*)
4242+val create_client :
4343+ ?config:client_config ->
4444+ unit ->
4545+ context
4646+4747+(** Connect to a JMAP server and retrieve the session.
4848+ This handles discovery (if needed) and authentication.
4949+ @param ctx The client context.
5050+ @param ?session_url Optional direct URL to the Session resource.
5151+ @param ?username Optional username (e.g., email address) for discovery.
5252+ @param ?auth_method Authentication method to use (default Basic).
5353+ @param credentials Authentication credentials.
5454+ @return A result with either (context, session) or an error.
5555+*)
5656+val connect :
5757+ context ->
5858+ ?session_url:Uri.t ->
5959+ ?username:string ->
6060+ host:string ->
6161+ ?port:int ->
6262+ ?auth_method:auth_method ->
6363+ unit ->
6464+ (context * Jmap.Session.Session.t) Jmap.Error.result
6565+6666+(** Create a request builder for constructing a JMAP request.
6767+ @param ctx The client context.
6868+ @return A request builder object.
6969+*)
7070+val build : context -> request_builder
7171+7272+(** Set the using capabilities for a request.
7373+ @param builder The request builder.
7474+ @param capabilities List of capability URIs to use.
7575+ @return The updated request builder.
7676+*)
7777+val using : request_builder -> string list -> request_builder
7878+7979+(** Add a method call to a request builder.
8080+ @param builder The request builder.
8181+ @param name Method name (e.g., "Email/get").
8282+ @param args Method arguments.
8383+ @param id Method call ID.
8484+ @return The updated request builder.
8585+*)
8686+val add_method_call :
8787+ request_builder ->
8888+ string ->
8989+ Yojson.Safe.t ->
9090+ string ->
9191+ request_builder
9292+9393+(** Create a reference to a previous method call result.
9494+ @param result_of Method call ID to reference.
9595+ @param name Path in the response.
9696+ @return A ResultReference to use in another method call.
9797+*)
9898+val create_reference : string -> string -> Jmap.Wire.Result_reference.t
9999+100100+(** Execute a request and return the response.
101101+ @param builder The request builder to execute.
102102+ @return The JMAP response from the server.
103103+*)
104104+val execute : request_builder -> Jmap.Wire.Response.t Jmap.Error.result
105105+106106+(** Perform a JMAP API request.
107107+ @param ctx The connection context.
108108+ @param request The JMAP request object.
109109+ @return The JMAP response from the server.
110110+*)
111111+val request : context -> Jmap.Wire.Request.t -> Jmap.Wire.Response.t Jmap.Error.result
112112+113113+(** Upload binary data.
114114+ @param ctx The connection context.
115115+ @param account_id The target account ID.
116116+ @param content_type The MIME type of the data.
117117+ @param data_stream A stream providing the binary data chunks.
118118+ @return A result with either an upload response or an error.
119119+*)
120120+val upload :
121121+ context ->
122122+ account_id:Jmap.Types.id ->
123123+ content_type:string ->
124124+ data_stream:string Seq.t ->
125125+ Jmap.Binary.Upload_response.t Jmap.Error.result
126126+127127+(** Download binary data.
128128+ @param ctx The connection context.
129129+ @param account_id The account ID.
130130+ @param blob_id The blob ID to download.
131131+ @param ?content_type The desired Content-Type for the download response.
132132+ @param ?name The desired filename for the download response.
133133+ @return A result with either a stream of data chunks or an error.
134134+*)
135135+val download :
136136+ context ->
137137+ account_id:Jmap.Types.id ->
138138+ blob_id:Jmap.Types.id ->
139139+ ?content_type:string ->
140140+ ?name:string ->
141141+ (string Seq.t) Jmap.Error.result
142142+143143+(** Copy blobs between accounts.
144144+ @param ctx The connection context.
145145+ @param from_account_id Source account ID.
146146+ @param account_id Destination account ID.
147147+ @param blob_ids List of blob IDs to copy.
148148+ @return A result with either the copy response or an error.
149149+*)
150150+val copy_blobs :
151151+ context ->
152152+ from_account_id:Jmap.Types.id ->
153153+ account_id:Jmap.Types.id ->
154154+ blob_ids:Jmap.Types.id list ->
155155+ Jmap.Binary.Blob_copy_response.t Jmap.Error.result
156156+157157+(** Connect to the EventSource for push notifications.
158158+ @param ctx The connection context.
159159+ @param ?types List of types to subscribe to (default "*").
160160+ @param ?close_after Request server to close after first state event.
161161+ @param ?ping Request ping interval in seconds (default 0).
162162+ @return A result with either a tuple of connection handle and event stream, or an error.
163163+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-7.3> RFC 8620, Section 7.3 *)
164164+val connect_event_source :
165165+ context ->
166166+ ?types:string list ->
167167+ ?close_after:[`State | `No] ->
168168+ ?ping:Jmap.Types.uint ->
169169+ (event_source_connection *
170170+ ([`State of Jmap.Push.State_change.t | `Ping of Jmap.Push.Event_source_ping_data.t ] Seq.t)) Jmap.Error.result
171171+172172+(** Create a websocket connection for JMAP over WebSocket.
173173+ @param ctx The connection context.
174174+ @return A result with either a websocket connection or an error.
175175+ @see <https://www.rfc-editor.org/rfc/rfc8887.html> RFC 8887 *)
176176+val connect_websocket :
177177+ context ->
178178+ event_source_connection Jmap.Error.result
179179+180180+(** Send a message over a websocket connection.
181181+ @param conn The websocket connection.
182182+ @param request The JMAP request to send.
183183+ @return A result with either the response or an error.
184184+*)
185185+val websocket_send :
186186+ event_source_connection ->
187187+ Jmap.Wire.Request.t ->
188188+ Jmap.Wire.Response.t Jmap.Error.result
189189+190190+(** Close an EventSource or WebSocket connection.
191191+ @param conn The connection handle.
192192+ @return A result with either unit or an error.
193193+*)
194194+val close_connection : event_source_connection -> unit Jmap.Error.result
195195+196196+(** Close the JMAP connection context.
197197+ @return A result with either unit or an error.
198198+*)
199199+val close : context -> unit Jmap.Error.result
200200+201201+(** {2 Helper Methods for Common Tasks} *)
202202+203203+(** Helper to get a single object by ID.
204204+ @param ctx The context.
205205+ @param method_name The get method (e.g., "Email/get").
206206+ @param account_id The account ID.
207207+ @param object_id The ID of the object to get.
208208+ @param ?properties Optional list of properties to fetch.
209209+ @return A result with either the object as JSON or an error.
210210+*)
211211+val get_object :
212212+ context ->
213213+ method_name:string ->
214214+ account_id:Jmap.Types.id ->
215215+ object_id:Jmap.Types.id ->
216216+ ?properties:string list ->
217217+ Yojson.Safe.t Jmap.Error.result
218218+219219+(** Helper to set up the connection with minimal options.
220220+ @param host The JMAP server hostname.
221221+ @param username Username for basic auth.
222222+ @param password Password for basic auth.
223223+ @return A result with either (context, session) or an error.
224224+*)
225225+val quick_connect :
226226+ host:string ->
227227+ username:string ->
228228+ password:string ->
229229+ (context * Jmap.Session.Session.t) Jmap.Error.result
230230+231231+(** Perform a Core/echo request to test connectivity.
232232+ @param ctx The JMAP connection context.
233233+ @param ?data Optional data to echo back.
234234+ @return A result with either the response or an error.
235235+*)
236236+val echo :
237237+ context ->
238238+ ?data:Yojson.Safe.t ->
239239+ unit ->
240240+ Yojson.Safe.t Jmap.Error.result
241241+242242+(** {2 Email Operations} *)
243243+244244+(** High-level email operations that map to JMAP email methods *)
245245+module Email : sig
246246+ open Jmap_email.Types
247247+248248+ (** Get an email by ID
249249+ @param ctx The JMAP client context
250250+ @param account_id The account ID
251251+ @param email_id The email ID to fetch
252252+ @param ?properties Optional list of properties to fetch
253253+ @return The email object or an error
254254+ *)
255255+ val get_email :
256256+ context ->
257257+ account_id:Jmap.Types.id ->
258258+ email_id:Jmap.Types.id ->
259259+ ?properties:string list ->
260260+ unit ->
261261+ Email.t Jmap.Error.result
262262+263263+ (** Search for emails using a filter
264264+ @param ctx The JMAP client context
265265+ @param account_id The account ID
266266+ @param filter The search filter
267267+ @param ?sort Optional sort criteria (default received date newest first)
268268+ @param ?limit Optional maximum number of results
269269+ @param ?properties Optional properties to fetch for the matching emails
270270+ @return The list of matching email IDs and optionally the email objects
271271+ *)
272272+ val search_emails :
273273+ context ->
274274+ account_id:Jmap.Types.id ->
275275+ filter:Jmap.Methods.Filter.t ->
276276+ ?sort:Jmap.Methods.Comparator.t list ->
277277+ ?limit:Jmap.Types.uint ->
278278+ ?position:int ->
279279+ ?properties:string list ->
280280+ unit ->
281281+ (Jmap.Types.id list * Email.t list option) Jmap.Error.result
282282+283283+ (** Mark multiple emails with a keyword
284284+ @param ctx The JMAP client context
285285+ @param account_id The account ID
286286+ @param email_ids List of email IDs to update
287287+ @param keyword The keyword to add
288288+ @return The result of the operation
289289+ *)
290290+ val mark_emails :
291291+ context ->
292292+ account_id:Jmap.Types.id ->
293293+ email_ids:Jmap.Types.id list ->
294294+ keyword:Keywords.keyword ->
295295+ unit ->
296296+ unit Jmap.Error.result
297297+298298+ (** Mark emails as seen/read
299299+ @param ctx The JMAP client context
300300+ @param account_id The account ID
301301+ @param email_ids List of email IDs to mark
302302+ @return The result of the operation
303303+ *)
304304+ val mark_as_seen :
305305+ context ->
306306+ account_id:Jmap.Types.id ->
307307+ email_ids:Jmap.Types.id list ->
308308+ unit ->
309309+ unit Jmap.Error.result
310310+311311+ (** Mark emails as unseen/unread
312312+ @param ctx The JMAP client context
313313+ @param account_id The account ID
314314+ @param email_ids List of email IDs to mark
315315+ @return The result of the operation
316316+ *)
317317+ val mark_as_unseen :
318318+ context ->
319319+ account_id:Jmap.Types.id ->
320320+ email_ids:Jmap.Types.id list ->
321321+ unit ->
322322+ unit Jmap.Error.result
323323+324324+ (** Move emails to a different mailbox
325325+ @param ctx The JMAP client context
326326+ @param account_id The account ID
327327+ @param email_ids List of email IDs to move
328328+ @param mailbox_id Destination mailbox ID
329329+ @param ?remove_from_mailboxes Optional list of source mailbox IDs to remove from
330330+ @return The result of the operation
331331+ *)
332332+ val move_emails :
333333+ context ->
334334+ account_id:Jmap.Types.id ->
335335+ email_ids:Jmap.Types.id list ->
336336+ mailbox_id:Jmap.Types.id ->
337337+ ?remove_from_mailboxes:Jmap.Types.id list ->
338338+ unit ->
339339+ unit Jmap.Error.result
340340+341341+ (** Import an RFC822 message
342342+ @param ctx The JMAP client context
343343+ @param account_id The account ID
344344+ @param rfc822 Raw message content
345345+ @param mailbox_ids Mailboxes to add the message to
346346+ @param ?keywords Optional keywords to set
347347+ @param ?received_at Optional received timestamp
348348+ @return The ID of the imported email
349349+ *)
350350+ val import_email :
351351+ context ->
352352+ account_id:Jmap.Types.id ->
353353+ rfc822:string ->
354354+ mailbox_ids:Jmap.Types.id list ->
355355+ ?keywords:Keywords.t ->
356356+ ?received_at:Jmap.Types.date ->
357357+ unit ->
358358+ Jmap.Types.id Jmap.Error.result
359359+end
···11+(* JMAP Core Protocol Library Interface (RFC 8620) *)
22+33+module Types = Jmap_types
44+module Error = Jmap_error
55+module Wire = Jmap_wire
66+module Session = Jmap_session
77+module Methods = Jmap_methods
88+module Binary = Jmap_binary
99+module Push = Jmap_push
1010+1111+(* Capability URI for JMAP Core. *)
1212+let capability_core = "urn:ietf:params:jmap:core"
1313+1414+(* Check if a session supports a given capability. *)
1515+let supports_capability session capability =
1616+ let caps = Session.Session.capabilities session in
1717+ Hashtbl.mem caps capability
1818+1919+(* Get the primary account ID for a given capability. *)
2020+let get_primary_account session capability =
2121+ let primary_accounts = Session.Session.primary_accounts session in
2222+ match Hashtbl.find_opt primary_accounts capability with
2323+ | Some account_id -> Ok account_id
2424+ | None -> Error (Error.protocol_error ("No primary account for capability: " ^ capability))
2525+2626+(* Get the download URL for a blob. *)
2727+let get_download_url session ~account_id ~blob_id ?name ?content_type () =
2828+ let base_url = Session.Session.download_url session in
2929+ let url_str = Uri.to_string base_url in
3030+ let url_str = url_str ^ "/accounts/" ^ account_id ^ "/blobs/" ^ blob_id in
3131+ let url = Uri.of_string url_str in
3232+ let url = match name with
3333+ | Some name -> Uri.add_query_param url ("name", [name])
3434+ | None -> url
3535+ in
3636+ match content_type with
3737+ | Some ct -> Uri.add_query_param url ("type", [ct])
3838+ | None -> url
3939+4040+(* Get the upload URL for a blob. *)
4141+let get_upload_url session ~account_id =
4242+ let base_url = Session.Session.upload_url session in
4343+ let url_str = Uri.to_string base_url in
4444+ let url_str = url_str ^ "/accounts/" ^ account_id in
4545+ Uri.of_string url_str
+136
jmap/jmap.mli
···11+(** JMAP Core Protocol Library Interface (RFC 8620)
22+33+ This library provides OCaml types and function signatures for interacting
44+ with a JMAP server according to the core protocol specification in RFC 8620.
55+66+ Modules:
77+ - {!Jmap.Types}: Basic data types (Id, Date, etc.).
88+ - {!Jmap.Error}: Error types (ProblemDetails, MethodError, SetError).
99+ - {!Jmap.Wire}: Request and Response structures.
1010+ - {!Jmap.Session}: Session object and discovery.
1111+ - {!Jmap.Methods}: Standard method patterns (/get, /set, etc.) and Core/echo.
1212+ - {!Jmap.Binary}: Binary data upload/download types.
1313+ - {!Jmap.Push}: Push notification types (StateChange, PushSubscription).
1414+1515+ For email-specific extensions (RFC 8621), see the Jmap_email library.
1616+ For Unix-specific implementation, see the Jmap_unix library.
1717+1818+ @see <https://www.rfc-editor.org/rfc/rfc8620.html> RFC 8620: Core JMAP
1919+*)
2020+2121+(** {1 Core JMAP Types and Modules} *)
2222+2323+module Types = Jmap_types
2424+module Error = Jmap_error
2525+module Wire = Jmap_wire
2626+module Session = Jmap_session
2727+module Methods = Jmap_methods
2828+module Binary = Jmap_binary
2929+module Push = Jmap_push
3030+3131+(** {1 Example Usage}
3232+3333+ The following example demonstrates using the Core JMAP library with the Unix implementation
3434+ to make a simple echo request.
3535+3636+{[
3737+ (* OCaml 5.1 required for Lwt let operators *)
3838+ open Lwt.Syntax
3939+ open Jmap
4040+ open Jmap.Types
4141+ open Jmap.Wire
4242+ open Jmap.Methods
4343+ open Jmap.Unix
4444+4545+ let simple_echo_request ctx session =
4646+ (* Prepare an echo invocation *)
4747+ let echo_args = Yojson.Safe.to_basic (`Assoc [
4848+ ("hello", `String "world");
4949+ ("array", `List [`Int 1; `Int 2; `Int 3]);
5050+ ]) in
5151+5252+ let echo_invocation = Invocation.v
5353+ ~method_name:"Core/echo"
5454+ ~arguments:echo_args
5555+ ~method_call_id:"echo1"
5656+ ()
5757+ in
5858+5959+ (* Prepare the JMAP request *)
6060+ let request = Request.v
6161+ ~using:[capability_core]
6262+ ~method_calls:[echo_invocation]
6363+ ()
6464+ in
6565+6666+ (* Send the request *)
6767+ let* response = Jmap.Unix.request ctx request in
6868+6969+ (* Process the response *)
7070+ match Wire.find_method_response response "echo1" with
7171+ | Some (method_name, args, _) when method_name = "Core/echo" ->
7272+ (* Echo response should contain the same arguments we sent *)
7373+ let hello_value = match Yojson.Safe.Util.member "hello" args with
7474+ | `String s -> s
7575+ | _ -> "not found"
7676+ in
7777+ Printf.printf "Echo response received: hello=%s\n" hello_value;
7878+ Lwt.return_unit
7979+ | _ ->
8080+ Printf.eprintf "Echo response not found or unexpected format\n";
8181+ Lwt.return_unit
8282+8383+ let main () =
8484+ (* Authentication details are placeholder *)
8585+ let credentials = "my_auth_token" in
8686+ let* (ctx, session) = Jmap.Unix.connect ~host:"jmap.example.com" ~credentials in
8787+ let* () = simple_echo_request ctx session in
8888+ Jmap.Unix.close ctx
8989+9090+ (* Lwt_main.run (main ()) *)
9191+]}
9292+*)
9393+9494+(** Capability URI for JMAP Core.
9595+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-2> RFC 8620, Section 2 *)
9696+val capability_core : string
9797+9898+(** {1 Convenience Functions} *)
9999+100100+(** Check if a session supports a given capability.
101101+ @param session The session object.
102102+ @param capability The capability URI to check.
103103+ @return True if supported, false otherwise.
104104+*)
105105+val supports_capability : Jmap_session.Session.t -> string -> bool
106106+107107+(** Get the primary account ID for a given capability.
108108+ @param session The session object.
109109+ @param capability The capability URI.
110110+ @return The account ID or an error if not found.
111111+*)
112112+val get_primary_account : Jmap_session.Session.t -> string -> (Jmap_types.id, Error.error) result
113113+114114+(** Get the download URL for a blob.
115115+ @param session The session object.
116116+ @param account_id The account ID.
117117+ @param blob_id The blob ID.
118118+ @param ?name Optional filename for the download.
119119+ @param ?content_type Optional content type for the download.
120120+ @return The download URL.
121121+*)
122122+val get_download_url :
123123+ Jmap_session.Session.t ->
124124+ account_id:Jmap_types.id ->
125125+ blob_id:Jmap_types.id ->
126126+ ?name:string ->
127127+ ?content_type:string ->
128128+ unit ->
129129+ Uri.t
130130+131131+(** Get the upload URL for a blob.
132132+ @param session The session object.
133133+ @param account_id The account ID.
134134+ @return The upload URL.
135135+*)
136136+val get_upload_url : Jmap_session.Session.t -> account_id:Jmap_types.id -> Uri.t
+56
jmap/jmap_binary.ml
···11+(* JMAP Binary Data Handling. *)
22+33+open Jmap_types
44+open Jmap_error
55+66+(* Response from uploading binary data. *)
77+module Upload_response = struct
88+ type t = {
99+ account_id: id;
1010+ blob_id: id;
1111+ type_: string;
1212+ size: uint;
1313+ }
1414+1515+ let account_id t = t.account_id
1616+ let blob_id t = t.blob_id
1717+ let type_ t = t.type_
1818+ let size t = t.size
1919+2020+ let v ~account_id ~blob_id ~type_ ~size () =
2121+ { account_id; blob_id; type_; size }
2222+end
2323+2424+(* Arguments for Blob/copy. *)
2525+module Blob_copy_args = struct
2626+ type t = {
2727+ from_account_id: id;
2828+ account_id: id;
2929+ blob_ids: id list;
3030+ }
3131+3232+ let from_account_id t = t.from_account_id
3333+ let account_id t = t.account_id
3434+ let blob_ids t = t.blob_ids
3535+3636+ let v ~from_account_id ~account_id ~blob_ids () =
3737+ { from_account_id; account_id; blob_ids }
3838+end
3939+4040+(* Response for Blob/copy. *)
4141+module Blob_copy_response = struct
4242+ type t = {
4343+ from_account_id: id;
4444+ account_id: id;
4545+ copied: id id_map option;
4646+ not_copied: Set_error.t id_map option;
4747+ }
4848+4949+ let from_account_id t = t.from_account_id
5050+ let account_id t = t.account_id
5151+ let copied t = t.copied
5252+ let not_copied t = t.not_copied
5353+5454+ let v ~from_account_id ~account_id ?copied ?not_copied () =
5555+ { from_account_id; account_id; copied; not_copied }
5656+end
+60
jmap/jmap_binary.mli
···11+(** JMAP Binary Data Handling.
22+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-6> RFC 8620, Section 6 *)
33+44+open Jmap_types
55+open Jmap_error
66+77+(** Response from uploading binary data.
88+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-6.1> RFC 8620, Section 6.1 *)
99+module Upload_response : sig
1010+ type t
1111+1212+ val account_id : t -> id
1313+ val blob_id : t -> id
1414+ val type_ : t -> string
1515+ val size : t -> uint
1616+1717+ val v :
1818+ account_id:id ->
1919+ blob_id:id ->
2020+ type_:string ->
2121+ size:uint ->
2222+ unit ->
2323+ t
2424+end
2525+2626+(** Arguments for Blob/copy.
2727+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-6.3> RFC 8620, Section 6.3 *)
2828+module Blob_copy_args : sig
2929+ type t
3030+3131+ val from_account_id : t -> id
3232+ val account_id : t -> id
3333+ val blob_ids : t -> id list
3434+3535+ val v :
3636+ from_account_id:id ->
3737+ account_id:id ->
3838+ blob_ids:id list ->
3939+ unit ->
4040+ t
4141+end
4242+4343+(** Response for Blob/copy.
4444+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-6.3> RFC 8620, Section 6.3 *)
4545+module Blob_copy_response : sig
4646+ type t
4747+4848+ val from_account_id : t -> id
4949+ val account_id : t -> id
5050+ val copied : t -> id id_map option
5151+ val not_copied : t -> Set_error.t id_map option
5252+5353+ val v :
5454+ from_account_id:id ->
5555+ account_id:id ->
5656+ ?copied:id id_map ->
5757+ ?not_copied:Set_error.t id_map ->
5858+ unit ->
5959+ t
6060+end
+266
jmap/jmap_error.ml
···11+(* JMAP Error Types. *)
22+33+open Jmap_types
44+55+(* Standard Method-level error types. *)
66+type method_error_type = [
77+ | `ServerUnavailable
88+ | `ServerFail
99+ | `ServerPartialFail
1010+ | `UnknownMethod
1111+ | `InvalidArguments
1212+ | `InvalidResultReference
1313+ | `Forbidden
1414+ | `AccountNotFound
1515+ | `AccountNotSupportedByMethod
1616+ | `AccountReadOnly
1717+ | `RequestTooLarge
1818+ | `CannotCalculateChanges
1919+ | `StateMismatch
2020+ | `AnchorNotFound
2121+ | `UnsupportedSort
2222+ | `UnsupportedFilter
2323+ | `TooManyChanges
2424+ | `FromAccountNotFound
2525+ | `FromAccountNotSupportedByMethod
2626+ | `Other_method_error of string
2727+]
2828+2929+(* Standard SetError types. *)
3030+type set_error_type = [
3131+ | `Forbidden
3232+ | `OverQuota
3333+ | `TooLarge
3434+ | `RateLimit
3535+ | `NotFound
3636+ | `InvalidPatch
3737+ | `WillDestroy
3838+ | `InvalidProperties
3939+ | `Singleton
4040+ | `AlreadyExists (* From /copy *)
4141+ | `MailboxHasChild (* RFC 8621 *)
4242+ | `MailboxHasEmail (* RFC 8621 *)
4343+ | `BlobNotFound (* RFC 8621 *)
4444+ | `TooManyKeywords (* RFC 8621 *)
4545+ | `TooManyMailboxes (* RFC 8621 *)
4646+ | `InvalidEmail (* RFC 8621 *)
4747+ | `TooManyRecipients (* RFC 8621 *)
4848+ | `NoRecipients (* RFC 8621 *)
4949+ | `InvalidRecipients (* RFC 8621 *)
5050+ | `ForbiddenMailFrom (* RFC 8621 *)
5151+ | `ForbiddenFrom (* RFC 8621 *)
5252+ | `ForbiddenToSend (* RFC 8621 *)
5353+ | `CannotUnsend (* RFC 8621 *)
5454+ | `Other_set_error of string (* For future or custom errors *)
5555+]
5656+5757+(* Primary error type that can represent all JMAP errors *)
5858+type error =
5959+ | Transport of string (* Network/HTTP-level error *)
6060+ | Parse of string (* JSON parsing error *)
6161+ | Protocol of string (* JMAP protocol error *)
6262+ | Problem of string (* Problem Details object error *)
6363+ | Method of method_error_type * string option (* Method error with optional description *)
6464+ | SetItem of id * set_error_type * string option (* Error for a specific item in a /set operation *)
6565+ | Auth of string (* Authentication error *)
6666+ | ServerError of string (* Server reported an error *)
6767+6868+(* Standard Result type for JMAP operations *)
6969+type 'a result = ('a, error) Result.t
7070+7171+(* Problem details object for HTTP-level errors. *)
7272+module Problem_details = struct
7373+ type t = {
7474+ problem_type: string;
7575+ status: int option;
7676+ detail: string option;
7777+ limit: string option;
7878+ other_fields: Yojson.Safe.t string_map;
7979+ }
8080+8181+ let problem_type t = t.problem_type
8282+ let status t = t.status
8383+ let detail t = t.detail
8484+ let limit t = t.limit
8585+ let other_fields t = t.other_fields
8686+8787+ let v ?status ?detail ?limit ?(other_fields=Hashtbl.create 0) problem_type =
8888+ { problem_type; status; detail; limit; other_fields }
8989+end
9090+9191+(* Description for method errors. May contain additional details. *)
9292+module Method_error_description = struct
9393+ type t = {
9494+ description: string option;
9595+ }
9696+9797+ let description t = t.description
9898+9999+ let v ?description () = { description }
100100+end
101101+102102+(* Represents a method-level error response invocation part. *)
103103+module Method_error = struct
104104+ type t = {
105105+ type_: method_error_type;
106106+ description: Method_error_description.t option;
107107+ }
108108+109109+ let type_ t = t.type_
110110+ let description t = t.description
111111+112112+ let v ?description type_ = { type_; description }
113113+end
114114+115115+(* SetError object. *)
116116+module Set_error = struct
117117+ type t = {
118118+ type_: set_error_type;
119119+ description: string option;
120120+ properties: string list option;
121121+ existing_id: id option;
122122+ max_recipients: uint option;
123123+ invalid_recipients: string list option;
124124+ max_size: uint option;
125125+ not_found_blob_ids: id list option;
126126+ }
127127+128128+ let type_ t = t.type_
129129+ let description t = t.description
130130+ let properties t = t.properties
131131+ let existing_id t = t.existing_id
132132+ let max_recipients t = t.max_recipients
133133+ let invalid_recipients t = t.invalid_recipients
134134+ let max_size t = t.max_size
135135+ let not_found_blob_ids t = t.not_found_blob_ids
136136+137137+ let v ?description ?properties ?existing_id ?max_recipients
138138+ ?invalid_recipients ?max_size ?not_found_blob_ids type_ =
139139+ { type_; description; properties; existing_id; max_recipients;
140140+ invalid_recipients; max_size; not_found_blob_ids }
141141+end
142142+143143+(* Error Handling Functions *)
144144+145145+let transport_error msg = Transport msg
146146+147147+let parse_error msg = Parse msg
148148+149149+let protocol_error msg = Protocol msg
150150+151151+let problem_error problem =
152152+ Problem (Problem_details.problem_type problem)
153153+154154+let method_error ?description type_ =
155155+ Method (type_, description)
156156+157157+let set_item_error id ?description type_ =
158158+ SetItem (id, type_, description)
159159+160160+let auth_error msg = Auth msg
161161+162162+let server_error msg = ServerError msg
163163+164164+let of_method_error method_error =
165165+ let description = match Method_error.description method_error with
166166+ | Some desc -> Method_error_description.description desc
167167+ | None -> None
168168+ in
169169+ Method (Method_error.type_ method_error, description)
170170+171171+let of_set_error id set_error =
172172+ SetItem (id, Set_error.type_ set_error, Set_error.description set_error)
173173+174174+let error_to_string = function
175175+ | Transport msg -> "Transport error: " ^ msg
176176+ | Parse msg -> "Parse error: " ^ msg
177177+ | Protocol msg -> "Protocol error: " ^ msg
178178+ | Problem problem -> "Problem: " ^ problem
179179+ | Method (type_, desc) ->
180180+ let type_str = match type_ with
181181+ | `ServerUnavailable -> "serverUnavailable"
182182+ | `ServerFail -> "serverFail"
183183+ | `ServerPartialFail -> "serverPartialFail"
184184+ | `UnknownMethod -> "unknownMethod"
185185+ | `InvalidArguments -> "invalidArguments"
186186+ | `InvalidResultReference -> "invalidResultReference"
187187+ | `Forbidden -> "forbidden"
188188+ | `AccountNotFound -> "accountNotFound"
189189+ | `AccountNotSupportedByMethod -> "accountNotSupportedByMethod"
190190+ | `AccountReadOnly -> "accountReadOnly"
191191+ | `RequestTooLarge -> "requestTooLarge"
192192+ | `CannotCalculateChanges -> "cannotCalculateChanges"
193193+ | `StateMismatch -> "stateMismatch"
194194+ | `AnchorNotFound -> "anchorNotFound"
195195+ | `UnsupportedSort -> "unsupportedSort"
196196+ | `UnsupportedFilter -> "unsupportedFilter"
197197+ | `TooManyChanges -> "tooManyChanges"
198198+ | `FromAccountNotFound -> "fromAccountNotFound"
199199+ | `FromAccountNotSupportedByMethod -> "fromAccountNotSupportedByMethod"
200200+ | `Other_method_error s -> s
201201+ in
202202+ let desc_str = match desc with
203203+ | Some d -> ": " ^ d
204204+ | None -> ""
205205+ in
206206+ "Method error: " ^ type_str ^ desc_str
207207+ | SetItem (id, type_, desc) ->
208208+ let type_str = match type_ with
209209+ | `Forbidden -> "forbidden"
210210+ | `OverQuota -> "overQuota"
211211+ | `TooLarge -> "tooLarge"
212212+ | `RateLimit -> "rateLimit"
213213+ | `NotFound -> "notFound"
214214+ | `InvalidPatch -> "invalidPatch"
215215+ | `WillDestroy -> "willDestroy"
216216+ | `InvalidProperties -> "invalidProperties"
217217+ | `Singleton -> "singleton"
218218+ | `AlreadyExists -> "alreadyExists"
219219+ | `MailboxHasChild -> "mailboxHasChild"
220220+ | `MailboxHasEmail -> "mailboxHasEmail"
221221+ | `BlobNotFound -> "blobNotFound"
222222+ | `TooManyKeywords -> "tooManyKeywords"
223223+ | `TooManyMailboxes -> "tooManyMailboxes"
224224+ | `InvalidEmail -> "invalidEmail"
225225+ | `TooManyRecipients -> "tooManyRecipients"
226226+ | `NoRecipients -> "noRecipients"
227227+ | `InvalidRecipients -> "invalidRecipients"
228228+ | `ForbiddenMailFrom -> "forbiddenMailFrom"
229229+ | `ForbiddenFrom -> "forbiddenFrom"
230230+ | `ForbiddenToSend -> "forbiddenToSend"
231231+ | `CannotUnsend -> "cannotUnsend"
232232+ | `Other_set_error s -> s
233233+ in
234234+ let desc_str = match desc with
235235+ | Some d -> ": " ^ d
236236+ | None -> ""
237237+ in
238238+ "SetItem error for " ^ id ^ ": " ^ type_str ^ desc_str
239239+ | Auth msg -> "Authentication error: " ^ msg
240240+ | ServerError msg -> "Server error: " ^ msg
241241+242242+(* Result Handling *)
243243+244244+let map_error result f =
245245+ match result with
246246+ | Ok v -> Ok v
247247+ | Error e -> Error (f e)
248248+249249+let with_context result context =
250250+ map_error result (function
251251+ | Transport msg -> Transport (context ^ ": " ^ msg)
252252+ | Parse msg -> Parse (context ^ ": " ^ msg)
253253+ | Protocol msg -> Protocol (context ^ ": " ^ msg)
254254+ | Problem p -> Problem (context ^ ": " ^ p)
255255+ | Method (t, Some d) -> Method (t, Some (context ^ ": " ^ d))
256256+ | Method (t, None) -> Method (t, Some context)
257257+ | SetItem (id, t, Some d) -> SetItem (id, t, Some (context ^ ": " ^ d))
258258+ | SetItem (id, t, None) -> SetItem (id, t, Some context)
259259+ | Auth msg -> Auth (context ^ ": " ^ msg)
260260+ | ServerError msg -> ServerError (context ^ ": " ^ msg)
261261+ )
262262+263263+let of_option opt error =
264264+ match opt with
265265+ | Some v -> Ok v
266266+ | None -> Error error
+189
jmap/jmap_error.mli
···11+(** JMAP Error Types.
22+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-3.6> RFC 8620, Section 3.6 *)
33+44+open Jmap_types
55+66+(** Standard Method-level error types.
77+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-3.6.2> RFC 8620, Section 3.6.2 *)
88+type method_error_type = [
99+ | `ServerUnavailable
1010+ | `ServerFail
1111+ | `ServerPartialFail
1212+ | `UnknownMethod
1313+ | `InvalidArguments
1414+ | `InvalidResultReference
1515+ | `Forbidden
1616+ | `AccountNotFound
1717+ | `AccountNotSupportedByMethod
1818+ | `AccountReadOnly
1919+ | `RequestTooLarge
2020+ | `CannotCalculateChanges
2121+ | `StateMismatch
2222+ | `AnchorNotFound
2323+ | `UnsupportedSort
2424+ | `UnsupportedFilter
2525+ | `TooManyChanges
2626+ | `FromAccountNotFound
2727+ | `FromAccountNotSupportedByMethod
2828+ | `Other_method_error of string
2929+]
3030+3131+(** Standard SetError types.
3232+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-5.3> RFC 8620, Section 5.3 *)
3333+type set_error_type = [
3434+ | `Forbidden
3535+ | `OverQuota
3636+ | `TooLarge
3737+ | `RateLimit
3838+ | `NotFound
3939+ | `InvalidPatch
4040+ | `WillDestroy
4141+ | `InvalidProperties
4242+ | `Singleton
4343+ | `AlreadyExists (* From /copy *)
4444+ | `MailboxHasChild (* RFC 8621 *)
4545+ | `MailboxHasEmail (* RFC 8621 *)
4646+ | `BlobNotFound (* RFC 8621 *)
4747+ | `TooManyKeywords (* RFC 8621 *)
4848+ | `TooManyMailboxes (* RFC 8621 *)
4949+ | `InvalidEmail (* RFC 8621 *)
5050+ | `TooManyRecipients (* RFC 8621 *)
5151+ | `NoRecipients (* RFC 8621 *)
5252+ | `InvalidRecipients (* RFC 8621 *)
5353+ | `ForbiddenMailFrom (* RFC 8621 *)
5454+ | `ForbiddenFrom (* RFC 8621 *)
5555+ | `ForbiddenToSend (* RFC 8621 *)
5656+ | `CannotUnsend (* RFC 8621 *)
5757+ | `Other_set_error of string (* For future or custom errors *)
5858+]
5959+6060+(** Primary error type that can represent all JMAP errors *)
6161+type error =
6262+ | Transport of string (** Network/HTTP-level error *)
6363+ | Parse of string (** JSON parsing error *)
6464+ | Protocol of string (** JMAP protocol error *)
6565+ | Problem of string (** Problem Details object error *)
6666+ | Method of method_error_type * string option (** Method error with optional description *)
6767+ | SetItem of id * set_error_type * string option (** Error for a specific item in a /set operation *)
6868+ | Auth of string (** Authentication error *)
6969+ | ServerError of string (** Server reported an error *)
7070+7171+(** Standard Result type for JMAP operations *)
7272+type 'a result = ('a, error) Result.t
7373+7474+(** Problem details object for HTTP-level errors.
7575+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-3.6.1> RFC 8620, Section 3.6.1
7676+ @see <https://www.rfc-editor.org/rfc/rfc7807.html> RFC 7807 *)
7777+module Problem_details : sig
7878+ type t
7979+8080+ val problem_type : t -> string
8181+ val status : t -> int option
8282+ val detail : t -> string option
8383+ val limit : t -> string option
8484+ val other_fields : t -> Yojson.Safe.t string_map
8585+8686+ val v :
8787+ ?status:int ->
8888+ ?detail:string ->
8989+ ?limit:string ->
9090+ ?other_fields:Yojson.Safe.t string_map ->
9191+ string ->
9292+ t
9393+end
9494+9595+(** Description for method errors. May contain additional details.
9696+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-3.6.2> RFC 8620, Section 3.6.2 *)
9797+module Method_error_description : sig
9898+ type t
9999+100100+ val description : t -> string option
101101+102102+ val v : ?description:string -> unit -> t
103103+end
104104+105105+(** Represents a method-level error response invocation part.
106106+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-3.6.2> RFC 8620, Section 3.6.2 *)
107107+module Method_error : sig
108108+ type t
109109+110110+ val type_ : t -> method_error_type
111111+ val description : t -> Method_error_description.t option
112112+113113+ val v :
114114+ ?description:Method_error_description.t ->
115115+ method_error_type ->
116116+ t
117117+end
118118+119119+(** SetError object.
120120+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-5.3> RFC 8620, Section 5.3 *)
121121+module Set_error : sig
122122+ type t
123123+124124+ val type_ : t -> set_error_type
125125+ val description : t -> string option
126126+ val properties : t -> string list option
127127+ val existing_id : t -> id option
128128+ val max_recipients : t -> uint option
129129+ val invalid_recipients : t -> string list option
130130+ val max_size : t -> uint option
131131+ val not_found_blob_ids : t -> id list option
132132+133133+ val v :
134134+ ?description:string ->
135135+ ?properties:string list ->
136136+ ?existing_id:id ->
137137+ ?max_recipients:uint ->
138138+ ?invalid_recipients:string list ->
139139+ ?max_size:uint ->
140140+ ?not_found_blob_ids:id list ->
141141+ set_error_type ->
142142+ t
143143+end
144144+145145+(** {2 Error Handling Functions} *)
146146+147147+(** Create a transport error *)
148148+val transport_error : string -> error
149149+150150+(** Create a parse error *)
151151+val parse_error : string -> error
152152+153153+(** Create a protocol error *)
154154+val protocol_error : string -> error
155155+156156+(** Create a problem details error *)
157157+val problem_error : Problem_details.t -> error
158158+159159+(** Create a method error *)
160160+val method_error : ?description:string -> method_error_type -> error
161161+162162+(** Create a SetItem error *)
163163+val set_item_error : id -> ?description:string -> set_error_type -> error
164164+165165+(** Create an auth error *)
166166+val auth_error : string -> error
167167+168168+(** Create a server error *)
169169+val server_error : string -> error
170170+171171+(** Convert a Method_error.t to error *)
172172+val of_method_error : Method_error.t -> error
173173+174174+(** Convert a Set_error.t to error for a specific ID *)
175175+val of_set_error : id -> Set_error.t -> error
176176+177177+(** Get a human-readable description of an error *)
178178+val error_to_string : error -> string
179179+180180+(** {2 Result Handling} *)
181181+182182+(** Map an error with additional context *)
183183+val map_error : 'a result -> (error -> error) -> 'a result
184184+185185+(** Add context to an error *)
186186+val with_context : 'a result -> string -> 'a result
187187+188188+(** Convert an option to a result with an error for None *)
189189+val of_option : 'a option -> error -> 'a result
+436
jmap/jmap_methods.ml
···11+(* Standard JMAP Methods and Core/echo. *)
22+33+open Jmap_types
44+open Jmap_error
55+66+(* Generic representation of a record type. Actual types defined elsewhere. *)
77+type generic_record = Yojson.Safe.t
88+99+(* Arguments for /get methods. *)
1010+module Get_args = struct
1111+ type 'record t = {
1212+ account_id: id;
1313+ ids: id list option;
1414+ properties: string list option;
1515+ }
1616+1717+ let account_id t = t.account_id
1818+ let ids t = t.ids
1919+ let properties t = t.properties
2020+2121+ let v ~account_id ?ids ?properties () =
2222+ { account_id; ids; properties }
2323+end
2424+2525+(* Response for /get methods. *)
2626+module Get_response = struct
2727+ type 'record t = {
2828+ account_id: id;
2929+ state: string;
3030+ list: 'record list;
3131+ not_found: id list;
3232+ }
3333+3434+ let account_id t = t.account_id
3535+ let state t = t.state
3636+ let list t = t.list
3737+ let not_found t = t.not_found
3838+3939+ let v ~account_id ~state ~list ~not_found () =
4040+ { account_id; state; list; not_found }
4141+end
4242+4343+(* Arguments for /changes methods. *)
4444+module Changes_args = struct
4545+ type t = {
4646+ account_id: id;
4747+ since_state: string;
4848+ max_changes: uint option;
4949+ }
5050+5151+ let account_id t = t.account_id
5252+ let since_state t = t.since_state
5353+ let max_changes t = t.max_changes
5454+5555+ let v ~account_id ~since_state ?max_changes () =
5656+ { account_id; since_state; max_changes }
5757+end
5858+5959+(* Response for /changes methods. *)
6060+module Changes_response = struct
6161+ type t = {
6262+ account_id: id;
6363+ old_state: string;
6464+ new_state: string;
6565+ has_more_changes: bool;
6666+ created: id list;
6767+ updated: id list;
6868+ destroyed: id list;
6969+ updated_properties: string list option;
7070+ }
7171+7272+ let account_id t = t.account_id
7373+ let old_state t = t.old_state
7474+ let new_state t = t.new_state
7575+ let has_more_changes t = t.has_more_changes
7676+ let created t = t.created
7777+ let updated t = t.updated
7878+ let destroyed t = t.destroyed
7979+ let updated_properties t = t.updated_properties
8080+8181+ let v ~account_id ~old_state ~new_state ~has_more_changes
8282+ ~created ~updated ~destroyed ?updated_properties () =
8383+ { account_id; old_state; new_state; has_more_changes;
8484+ created; updated; destroyed; updated_properties }
8585+end
8686+8787+(* Patch object for /set update.
8888+ A list of (JSON Pointer path, value) pairs. *)
8989+type patch_object = (json_pointer * Yojson.Safe.t) list
9090+9191+(* Arguments for /set methods. *)
9292+module Set_args = struct
9393+ type ('create_record, 'update_record) t = {
9494+ account_id: id;
9595+ if_in_state: string option;
9696+ create: 'create_record id_map option;
9797+ update: 'update_record id_map option;
9898+ destroy: id list option;
9999+ on_success_destroy_original: bool option;
100100+ destroy_from_if_in_state: string option;
101101+ on_destroy_remove_emails: bool option;
102102+ }
103103+104104+ let account_id t = t.account_id
105105+ let if_in_state t = t.if_in_state
106106+ let create t = t.create
107107+ let update t = t.update
108108+ let destroy t = t.destroy
109109+ let on_success_destroy_original t = t.on_success_destroy_original
110110+ let destroy_from_if_in_state t = t.destroy_from_if_in_state
111111+ let on_destroy_remove_emails t = t.on_destroy_remove_emails
112112+113113+ let v ~account_id ?if_in_state ?create ?update ?destroy
114114+ ?on_success_destroy_original ?destroy_from_if_in_state
115115+ ?on_destroy_remove_emails () =
116116+ { account_id; if_in_state; create; update; destroy;
117117+ on_success_destroy_original; destroy_from_if_in_state;
118118+ on_destroy_remove_emails }
119119+end
120120+121121+(* Response for /set methods. *)
122122+module Set_response = struct
123123+ type ('created_record_info, 'updated_record_info) t = {
124124+ account_id: id;
125125+ old_state: string option;
126126+ new_state: string;
127127+ created: 'created_record_info id_map option;
128128+ updated: 'updated_record_info option id_map option;
129129+ destroyed: id list option;
130130+ not_created: Set_error.t id_map option;
131131+ not_updated: Set_error.t id_map option;
132132+ not_destroyed: Set_error.t id_map option;
133133+ }
134134+135135+ let account_id t = t.account_id
136136+ let old_state t = t.old_state
137137+ let new_state t = t.new_state
138138+ let created t = t.created
139139+ let updated t = t.updated
140140+ let destroyed t = t.destroyed
141141+ let not_created t = t.not_created
142142+ let not_updated t = t.not_updated
143143+ let not_destroyed t = t.not_destroyed
144144+145145+ let v ~account_id ?old_state ~new_state ?created ?updated ?destroyed
146146+ ?not_created ?not_updated ?not_destroyed () =
147147+ { account_id; old_state; new_state; created; updated; destroyed;
148148+ not_created; not_updated; not_destroyed }
149149+end
150150+151151+(* Arguments for /copy methods. *)
152152+module Copy_args = struct
153153+ type 'copy_record_override t = {
154154+ from_account_id: id;
155155+ if_from_in_state: string option;
156156+ account_id: id;
157157+ if_in_state: string option;
158158+ create: 'copy_record_override id_map;
159159+ on_success_destroy_original: bool;
160160+ destroy_from_if_in_state: string option;
161161+ }
162162+163163+ let from_account_id t = t.from_account_id
164164+ let if_from_in_state t = t.if_from_in_state
165165+ let account_id t = t.account_id
166166+ let if_in_state t = t.if_in_state
167167+ let create t = t.create
168168+ let on_success_destroy_original t = t.on_success_destroy_original
169169+ let destroy_from_if_in_state t = t.destroy_from_if_in_state
170170+171171+ let v ~from_account_id ?if_from_in_state ~account_id ?if_in_state
172172+ ~create ?(on_success_destroy_original=false) ?destroy_from_if_in_state () =
173173+ { from_account_id; if_from_in_state; account_id; if_in_state;
174174+ create; on_success_destroy_original; destroy_from_if_in_state }
175175+end
176176+177177+(* Response for /copy methods. *)
178178+module Copy_response = struct
179179+ type 'created_record_info t = {
180180+ from_account_id: id;
181181+ account_id: id;
182182+ old_state: string option;
183183+ new_state: string;
184184+ created: 'created_record_info id_map option;
185185+ not_created: Set_error.t id_map option;
186186+ }
187187+188188+ let from_account_id t = t.from_account_id
189189+ let account_id t = t.account_id
190190+ let old_state t = t.old_state
191191+ let new_state t = t.new_state
192192+ let created t = t.created
193193+ let not_created t = t.not_created
194194+195195+ let v ~from_account_id ~account_id ?old_state ~new_state
196196+ ?created ?not_created () =
197197+ { from_account_id; account_id; old_state; new_state;
198198+ created; not_created }
199199+end
200200+201201+(* Module for generic filter representation. *)
202202+module Filter = struct
203203+ type t =
204204+ | Condition of Yojson.Safe.t
205205+ | Operator of [ `AND | `OR | `NOT ] * t list
206206+207207+ let condition json = Condition json
208208+209209+ let operator op filters = Operator (op, filters)
210210+211211+ let and_ filters = operator `AND filters
212212+213213+ let or_ filters = operator `OR filters
214214+215215+ let not_ filter = operator `NOT [filter]
216216+217217+ let rec to_json = function
218218+ | Condition json -> json
219219+ | Operator (op, filters) ->
220220+ let key = match op with
221221+ | `AND -> "AND"
222222+ | `OR -> "OR"
223223+ | `NOT -> "NOT"
224224+ in
225225+ `Assoc [(key, `List (List.map to_json filters))]
226226+227227+ (* Helper functions for common filter conditions *)
228228+229229+ let text_contains property value =
230230+ condition (`Assoc [
231231+ (property, `Assoc [("contains", `String value)])
232232+ ])
233233+234234+ let property_equals property value =
235235+ condition (`Assoc [(property, value)])
236236+237237+ let property_not_equals property value =
238238+ condition (`Assoc [
239239+ (property, `Assoc [("!",value)])
240240+ ])
241241+242242+ let property_gt property value =
243243+ condition (`Assoc [
244244+ (property, `Assoc [("gt", value)])
245245+ ])
246246+247247+ let property_ge property value =
248248+ condition (`Assoc [
249249+ (property, `Assoc [("ge", value)])
250250+ ])
251251+252252+ let property_lt property value =
253253+ condition (`Assoc [
254254+ (property, `Assoc [("lt", value)])
255255+ ])
256256+257257+ let property_le property value =
258258+ condition (`Assoc [
259259+ (property, `Assoc [("le", value)])
260260+ ])
261261+262262+ let property_in property values =
263263+ condition (`Assoc [
264264+ (property, `Assoc [("in", `List values)])
265265+ ])
266266+267267+ let property_not_in property values =
268268+ condition (`Assoc [
269269+ (property, `Assoc [("!in", `List values)])
270270+ ])
271271+272272+ let property_exists property =
273273+ condition (`Assoc [
274274+ (property, `Null) (* Using just the property name means "property exists" *)
275275+ ])
276276+277277+ let string_starts_with property prefix =
278278+ condition (`Assoc [
279279+ (property, `Assoc [("startsWith", `String prefix)])
280280+ ])
281281+282282+ let string_ends_with property suffix =
283283+ condition (`Assoc [
284284+ (property, `Assoc [("endsWith", `String suffix)])
285285+ ])
286286+end
287287+288288+(* Comparator for sorting. *)
289289+module Comparator = struct
290290+ type t = {
291291+ property: string;
292292+ is_ascending: bool option;
293293+ collation: string option;
294294+ keyword: string option;
295295+ other_fields: Yojson.Safe.t string_map;
296296+ }
297297+298298+ let property t = t.property
299299+ let is_ascending t = t.is_ascending
300300+ let collation t = t.collation
301301+ let keyword t = t.keyword
302302+ let other_fields t = t.other_fields
303303+304304+ let v ~property ?is_ascending ?collation ?keyword
305305+ ?(other_fields=Hashtbl.create 0) () =
306306+ { property; is_ascending; collation; keyword; other_fields }
307307+end
308308+309309+(* Arguments for /query methods. *)
310310+module Query_args = struct
311311+ type t = {
312312+ account_id: id;
313313+ filter: Filter.t option;
314314+ sort: Comparator.t list option;
315315+ position: jint option;
316316+ anchor: id option;
317317+ anchor_offset: jint option;
318318+ limit: uint option;
319319+ calculate_total: bool option;
320320+ collapse_threads: bool option;
321321+ sort_as_tree: bool option;
322322+ filter_as_tree: bool option;
323323+ }
324324+325325+ let account_id t = t.account_id
326326+ let filter t = t.filter
327327+ let sort t = t.sort
328328+ let position t = t.position
329329+ let anchor t = t.anchor
330330+ let anchor_offset t = t.anchor_offset
331331+ let limit t = t.limit
332332+ let calculate_total t = t.calculate_total
333333+ let collapse_threads t = t.collapse_threads
334334+ let sort_as_tree t = t.sort_as_tree
335335+ let filter_as_tree t = t.filter_as_tree
336336+337337+ let v ~account_id ?filter ?sort ?position ?anchor ?anchor_offset
338338+ ?limit ?calculate_total ?collapse_threads ?sort_as_tree ?filter_as_tree () =
339339+ { account_id; filter; sort; position; anchor; anchor_offset;
340340+ limit; calculate_total; collapse_threads; sort_as_tree; filter_as_tree }
341341+end
342342+343343+(* Response for /query methods. *)
344344+module Query_response = struct
345345+ type t = {
346346+ account_id: id;
347347+ query_state: string;
348348+ can_calculate_changes: bool;
349349+ position: uint;
350350+ ids: id list;
351351+ total: uint option;
352352+ limit: uint option;
353353+ }
354354+355355+ let account_id t = t.account_id
356356+ let query_state t = t.query_state
357357+ let can_calculate_changes t = t.can_calculate_changes
358358+ let position t = t.position
359359+ let ids t = t.ids
360360+ let total t = t.total
361361+ let limit t = t.limit
362362+363363+ let v ~account_id ~query_state ~can_calculate_changes ~position ~ids
364364+ ?total ?limit () =
365365+ { account_id; query_state; can_calculate_changes; position; ids;
366366+ total; limit }
367367+end
368368+369369+(* Item indicating an added record in /queryChanges. *)
370370+module Added_item = struct
371371+ type t = {
372372+ id: id;
373373+ index: uint;
374374+ }
375375+376376+ let id t = t.id
377377+ let index t = t.index
378378+379379+ let v ~id ~index () = { id; index }
380380+end
381381+382382+(* Arguments for /queryChanges methods. *)
383383+module Query_changes_args = struct
384384+ type t = {
385385+ account_id: id;
386386+ filter: Filter.t option;
387387+ sort: Comparator.t list option;
388388+ since_query_state: string;
389389+ max_changes: uint option;
390390+ up_to_id: id option;
391391+ calculate_total: bool option;
392392+ collapse_threads: bool option;
393393+ }
394394+395395+ let account_id t = t.account_id
396396+ let filter t = t.filter
397397+ let sort t = t.sort
398398+ let since_query_state t = t.since_query_state
399399+ let max_changes t = t.max_changes
400400+ let up_to_id t = t.up_to_id
401401+ let calculate_total t = t.calculate_total
402402+ let collapse_threads t = t.collapse_threads
403403+404404+ let v ~account_id ?filter ?sort ~since_query_state ?max_changes
405405+ ?up_to_id ?calculate_total ?collapse_threads () =
406406+ { account_id; filter; sort; since_query_state; max_changes;
407407+ up_to_id; calculate_total; collapse_threads }
408408+end
409409+410410+(* Response for /queryChanges methods. *)
411411+module Query_changes_response = struct
412412+ type t = {
413413+ account_id: id;
414414+ old_query_state: string;
415415+ new_query_state: string;
416416+ total: uint option;
417417+ removed: id list;
418418+ added: Added_item.t list;
419419+ }
420420+421421+ let account_id t = t.account_id
422422+ let old_query_state t = t.old_query_state
423423+ let new_query_state t = t.new_query_state
424424+ let total t = t.total
425425+ let removed t = t.removed
426426+ let added t = t.added
427427+428428+ let v ~account_id ~old_query_state ~new_query_state ?total
429429+ ~removed ~added () =
430430+ { account_id; old_query_state; new_query_state; total;
431431+ removed; added }
432432+end
433433+434434+(* Core/echo method: Arguments are mirrored in the response. *)
435435+type core_echo_args = Yojson.Safe.t
436436+type core_echo_response = Yojson.Safe.t
+417
jmap/jmap_methods.mli
···11+(** Standard JMAP Methods and Core/echo.
22+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-4> RFC 8620, Section 4
33+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-5> RFC 8620, Section 5 *)
44+55+open Jmap_types
66+open Jmap_error
77+88+(** Generic representation of a record type. Actual types defined elsewhere. *)
99+type generic_record
1010+1111+(** Arguments for /get methods.
1212+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-5.1> RFC 8620, Section 5.1 *)
1313+module Get_args : sig
1414+ type 'record t
1515+1616+ val account_id : 'record t -> id
1717+ val ids : 'record t -> id list option
1818+ val properties : 'record t -> string list option
1919+2020+ val v :
2121+ account_id:id ->
2222+ ?ids:id list ->
2323+ ?properties:string list ->
2424+ unit ->
2525+ 'record t
2626+end
2727+2828+(** Response for /get methods.
2929+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-5.1> RFC 8620, Section 5.1 *)
3030+module Get_response : sig
3131+ type 'record t
3232+3333+ val account_id : 'record t -> id
3434+ val state : 'record t -> string
3535+ val list : 'record t -> 'record list
3636+ val not_found : 'record t -> id list
3737+3838+ val v :
3939+ account_id:id ->
4040+ state:string ->
4141+ list:'record list ->
4242+ not_found:id list ->
4343+ unit ->
4444+ 'record t
4545+end
4646+4747+(** Arguments for /changes methods.
4848+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-5.2> RFC 8620, Section 5.2 *)
4949+module Changes_args : sig
5050+ type t
5151+5252+ val account_id : t -> id
5353+ val since_state : t -> string
5454+ val max_changes : t -> uint option
5555+5656+ val v :
5757+ account_id:id ->
5858+ since_state:string ->
5959+ ?max_changes:uint ->
6060+ unit ->
6161+ t
6262+end
6363+6464+(** Response for /changes methods.
6565+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-5.2> RFC 8620, Section 5.2 *)
6666+module Changes_response : sig
6767+ type t
6868+6969+ val account_id : t -> id
7070+ val old_state : t -> string
7171+ val new_state : t -> string
7272+ val has_more_changes : t -> bool
7373+ val created : t -> id list
7474+ val updated : t -> id list
7575+ val destroyed : t -> id list
7676+ val updated_properties : t -> string list option
7777+7878+ val v :
7979+ account_id:id ->
8080+ old_state:string ->
8181+ new_state:string ->
8282+ has_more_changes:bool ->
8383+ created:id list ->
8484+ updated:id list ->
8585+ destroyed:id list ->
8686+ ?updated_properties:string list ->
8787+ unit ->
8888+ t
8989+end
9090+9191+(** Patch object for /set update.
9292+ A list of (JSON Pointer path, value) pairs.
9393+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-5.3> RFC 8620, Section 5.3 *)
9494+type patch_object = (json_pointer * Yojson.Safe.t) list
9595+9696+(** Arguments for /set methods.
9797+ ['create_record] is the record type without server-set/immutable fields.
9898+ ['update_record] is the patch object type (usually [patch_object]).
9999+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-5.3> RFC 8620, Section 5.3 *)
100100+module Set_args : sig
101101+ type ('create_record, 'update_record) t
102102+103103+ val account_id : ('a, 'b) t -> id
104104+ val if_in_state : ('a, 'b) t -> string option
105105+ val create : ('a, 'b) t -> 'a id_map option
106106+ val update : ('a, 'b) t -> 'b id_map option
107107+ val destroy : ('a, 'b) t -> id list option
108108+ val on_success_destroy_original : ('a, 'b) t -> bool option
109109+ val destroy_from_if_in_state : ('a, 'b) t -> string option
110110+ val on_destroy_remove_emails : ('a, 'b) t -> bool option
111111+112112+ val v :
113113+ account_id:id ->
114114+ ?if_in_state:string ->
115115+ ?create:'a id_map ->
116116+ ?update:'b id_map ->
117117+ ?destroy:id list ->
118118+ ?on_success_destroy_original:bool ->
119119+ ?destroy_from_if_in_state:string ->
120120+ ?on_destroy_remove_emails:bool ->
121121+ unit ->
122122+ ('a, 'b) t
123123+end
124124+125125+(** Response for /set methods.
126126+ ['created_record_info] is the server-set info for created records.
127127+ ['updated_record_info] is the server-set/computed info for updated records.
128128+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-5.3> RFC 8620, Section 5.3 *)
129129+module Set_response : sig
130130+ type ('created_record_info, 'updated_record_info) t
131131+132132+ val account_id : ('a, 'b) t -> id
133133+ val old_state : ('a, 'b) t -> string option
134134+ val new_state : ('a, 'b) t -> string
135135+ val created : ('a, 'b) t -> 'a id_map option
136136+ val updated : ('a, 'b) t -> 'b option id_map option
137137+ val destroyed : ('a, 'b) t -> id list option
138138+ val not_created : ('a, 'b) t -> Set_error.t id_map option
139139+ val not_updated : ('a, 'b) t -> Set_error.t id_map option
140140+ val not_destroyed : ('a, 'b) t -> Set_error.t id_map option
141141+142142+ val v :
143143+ account_id:id ->
144144+ ?old_state:string ->
145145+ new_state:string ->
146146+ ?created:'a id_map ->
147147+ ?updated:'b option id_map ->
148148+ ?destroyed:id list ->
149149+ ?not_created:Set_error.t id_map ->
150150+ ?not_updated:Set_error.t id_map ->
151151+ ?not_destroyed:Set_error.t id_map ->
152152+ unit ->
153153+ ('a, 'b) t
154154+end
155155+156156+(** Arguments for /copy methods.
157157+ ['copy_record_override] contains the record id and override properties.
158158+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-5.4> RFC 8620, Section 5.4 *)
159159+module Copy_args : sig
160160+ type 'copy_record_override t
161161+162162+ val from_account_id : 'a t -> id
163163+ val if_from_in_state : 'a t -> string option
164164+ val account_id : 'a t -> id
165165+ val if_in_state : 'a t -> string option
166166+ val create : 'a t -> 'a id_map
167167+ val on_success_destroy_original : 'a t -> bool
168168+ val destroy_from_if_in_state : 'a t -> string option
169169+170170+ val v :
171171+ from_account_id:id ->
172172+ ?if_from_in_state:string ->
173173+ account_id:id ->
174174+ ?if_in_state:string ->
175175+ create:'a id_map ->
176176+ ?on_success_destroy_original:bool ->
177177+ ?destroy_from_if_in_state:string ->
178178+ unit ->
179179+ 'a t
180180+end
181181+182182+(** Response for /copy methods.
183183+ ['created_record_info] is the server-set info for the created copy.
184184+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-5.4> RFC 8620, Section 5.4 *)
185185+module Copy_response : sig
186186+ type 'created_record_info t
187187+188188+ val from_account_id : 'a t -> id
189189+ val account_id : 'a t -> id
190190+ val old_state : 'a t -> string option
191191+ val new_state : 'a t -> string
192192+ val created : 'a t -> 'a id_map option
193193+ val not_created : 'a t -> Set_error.t id_map option
194194+195195+ val v :
196196+ from_account_id:id ->
197197+ account_id:id ->
198198+ ?old_state:string ->
199199+ new_state:string ->
200200+ ?created:'a id_map ->
201201+ ?not_created:Set_error.t id_map ->
202202+ unit ->
203203+ 'a t
204204+end
205205+206206+(** Module for generic filter representation.
207207+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-5.5> RFC 8620, Section 5.5 *)
208208+module Filter : sig
209209+ type t
210210+211211+ (** Create a filter from a raw JSON condition *)
212212+ val condition : Yojson.Safe.t -> t
213213+214214+ (** Create a filter with a logical operator (AND, OR, NOT) *)
215215+ val operator : [ `AND | `OR | `NOT ] -> t list -> t
216216+217217+ (** Combine filters with AND *)
218218+ val and_ : t list -> t
219219+220220+ (** Combine filters with OR *)
221221+ val or_ : t list -> t
222222+223223+ (** Negate a filter with NOT *)
224224+ val not_ : t -> t
225225+226226+ (** Convert a filter to JSON *)
227227+ val to_json : t -> Yojson.Safe.t
228228+229229+ (** Predefined filter helpers *)
230230+231231+ (** Create a filter for a text property containing a string *)
232232+ val text_contains : string -> string -> t
233233+234234+ (** Create a filter for a property being equal to a value *)
235235+ val property_equals : string -> Yojson.Safe.t -> t
236236+237237+ (** Create a filter for a property being not equal to a value *)
238238+ val property_not_equals : string -> Yojson.Safe.t -> t
239239+240240+ (** Create a filter for a property being greater than a value *)
241241+ val property_gt : string -> Yojson.Safe.t -> t
242242+243243+ (** Create a filter for a property being greater than or equal to a value *)
244244+ val property_ge : string -> Yojson.Safe.t -> t
245245+246246+ (** Create a filter for a property being less than a value *)
247247+ val property_lt : string -> Yojson.Safe.t -> t
248248+249249+ (** Create a filter for a property being less than or equal to a value *)
250250+ val property_le : string -> Yojson.Safe.t -> t
251251+252252+ (** Create a filter for a property value being in a list *)
253253+ val property_in : string -> Yojson.Safe.t list -> t
254254+255255+ (** Create a filter for a property value not being in a list *)
256256+ val property_not_in : string -> Yojson.Safe.t list -> t
257257+258258+ (** Create a filter for a property being present (not null) *)
259259+ val property_exists : string -> t
260260+261261+ (** Create a filter for a string property starting with a prefix *)
262262+ val string_starts_with : string -> string -> t
263263+264264+ (** Create a filter for a string property ending with a suffix *)
265265+ val string_ends_with : string -> string -> t
266266+end
267267+268268+269269+270270+(** Comparator for sorting.
271271+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-5.5> RFC 8620, Section 5.5 *)
272272+module Comparator : sig
273273+ type t
274274+275275+ val property : t -> string
276276+ val is_ascending : t -> bool option
277277+ val collation : t -> string option
278278+ val keyword : t -> string option
279279+ val other_fields : t -> Yojson.Safe.t string_map
280280+281281+ val v :
282282+ property:string ->
283283+ ?is_ascending:bool ->
284284+ ?collation:string ->
285285+ ?keyword:string ->
286286+ ?other_fields:Yojson.Safe.t string_map ->
287287+ unit ->
288288+ t
289289+end
290290+291291+(** Arguments for /query methods.
292292+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-5.5> RFC 8620, Section 5.5 *)
293293+module Query_args : sig
294294+ type t
295295+296296+ val account_id : t -> id
297297+ val filter : t -> Filter.t option
298298+ val sort : t -> Comparator.t list option
299299+ val position : t -> jint option
300300+ val anchor : t -> id option
301301+ val anchor_offset : t -> jint option
302302+ val limit : t -> uint option
303303+ val calculate_total : t -> bool option
304304+ val collapse_threads : t -> bool option
305305+ val sort_as_tree : t -> bool option
306306+ val filter_as_tree : t -> bool option
307307+308308+ val v :
309309+ account_id:id ->
310310+ ?filter:Filter.t ->
311311+ ?sort:Comparator.t list ->
312312+ ?position:jint ->
313313+ ?anchor:id ->
314314+ ?anchor_offset:jint ->
315315+ ?limit:uint ->
316316+ ?calculate_total:bool ->
317317+ ?collapse_threads:bool ->
318318+ ?sort_as_tree:bool ->
319319+ ?filter_as_tree:bool ->
320320+ unit ->
321321+ t
322322+end
323323+324324+(** Response for /query methods.
325325+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-5.5> RFC 8620, Section 5.5 *)
326326+module Query_response : sig
327327+ type t
328328+329329+ val account_id : t -> id
330330+ val query_state : t -> string
331331+ val can_calculate_changes : t -> bool
332332+ val position : t -> uint
333333+ val ids : t -> id list
334334+ val total : t -> uint option
335335+ val limit : t -> uint option
336336+337337+ val v :
338338+ account_id:id ->
339339+ query_state:string ->
340340+ can_calculate_changes:bool ->
341341+ position:uint ->
342342+ ids:id list ->
343343+ ?total:uint ->
344344+ ?limit:uint ->
345345+ unit ->
346346+ t
347347+end
348348+349349+(** Item indicating an added record in /queryChanges.
350350+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-5.6> RFC 8620, Section 5.6 *)
351351+module Added_item : sig
352352+ type t
353353+354354+ val id : t -> id
355355+ val index : t -> uint
356356+357357+ val v :
358358+ id:id ->
359359+ index:uint ->
360360+ unit ->
361361+ t
362362+end
363363+364364+(** Arguments for /queryChanges methods.
365365+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-5.6> RFC 8620, Section 5.6 *)
366366+module Query_changes_args : sig
367367+ type t
368368+369369+ val account_id : t -> id
370370+ val filter : t -> Filter.t option
371371+ val sort : t -> Comparator.t list option
372372+ val since_query_state : t -> string
373373+ val max_changes : t -> uint option
374374+ val up_to_id : t -> id option
375375+ val calculate_total : t -> bool option
376376+ val collapse_threads : t -> bool option
377377+378378+ val v :
379379+ account_id:id ->
380380+ ?filter:Filter.t ->
381381+ ?sort:Comparator.t list ->
382382+ since_query_state:string ->
383383+ ?max_changes:uint ->
384384+ ?up_to_id:id ->
385385+ ?calculate_total:bool ->
386386+ ?collapse_threads:bool ->
387387+ unit ->
388388+ t
389389+end
390390+391391+(** Response for /queryChanges methods.
392392+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-5.6> RFC 8620, Section 5.6 *)
393393+module Query_changes_response : sig
394394+ type t
395395+396396+ val account_id : t -> id
397397+ val old_query_state : t -> string
398398+ val new_query_state : t -> string
399399+ val total : t -> uint option
400400+ val removed : t -> id list
401401+ val added : t -> Added_item.t list
402402+403403+ val v :
404404+ account_id:id ->
405405+ old_query_state:string ->
406406+ new_query_state:string ->
407407+ ?total:uint ->
408408+ removed:id list ->
409409+ added:Added_item.t list ->
410410+ unit ->
411411+ t
412412+end
413413+414414+(** Core/echo method: Arguments are mirrored in the response.
415415+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-4> RFC 8620, Section 4 *)
416416+type core_echo_args = Yojson.Safe.t
417417+type core_echo_response = Yojson.Safe.t
+192
jmap/jmap_push.ml
···11+(* JMAP Push Notifications. *)
22+33+open Jmap_types
44+open Jmap_methods
55+open Jmap_error
66+77+(* TypeState object map (TypeName -> StateString). *)
88+type type_state = string string_map
99+1010+(* StateChange object. *)
1111+module State_change = struct
1212+ type t = {
1313+ changed: type_state id_map;
1414+ }
1515+1616+ let changed t = t.changed
1717+1818+ let v ~changed () = { changed }
1919+end
2020+2121+(* PushSubscription encryption keys. *)
2222+module Push_encryption_keys = struct
2323+ type t = {
2424+ p256dh: string;
2525+ auth: string;
2626+ }
2727+2828+ let p256dh t = t.p256dh
2929+ let auth t = t.auth
3030+3131+ let v ~p256dh ~auth () = { p256dh; auth }
3232+end
3333+3434+(* PushSubscription object. *)
3535+module Push_subscription = struct
3636+ type t = {
3737+ id: id;
3838+ device_client_id: string;
3939+ url: Uri.t;
4040+ keys: Push_encryption_keys.t option;
4141+ verification_code: string option;
4242+ expires: utc_date option;
4343+ types: string list option;
4444+ }
4545+4646+ let id t = t.id
4747+ let device_client_id t = t.device_client_id
4848+ let url t = t.url
4949+ let keys t = t.keys
5050+ let verification_code t = t.verification_code
5151+ let expires t = t.expires
5252+ let types t = t.types
5353+5454+ let v ~id ~device_client_id ~url ?keys ?verification_code ?expires ?types () =
5555+ { id; device_client_id; url; keys; verification_code; expires; types }
5656+end
5757+5858+(* PushSubscription object for creation (omits server-set fields). *)
5959+module Push_subscription_create = struct
6060+ type t = {
6161+ device_client_id: string;
6262+ url: Uri.t;
6363+ keys: Push_encryption_keys.t option;
6464+ expires: utc_date option;
6565+ types: string list option;
6666+ }
6767+6868+ let device_client_id t = t.device_client_id
6969+ let url t = t.url
7070+ let keys t = t.keys
7171+ let expires t = t.expires
7272+ let types t = t.types
7373+7474+ let v ~device_client_id ~url ?keys ?expires ?types () =
7575+ { device_client_id; url; keys; expires; types }
7676+end
7777+7878+(* PushSubscription object for update patch.
7979+ Only verification_code and expires can be updated. *)
8080+type push_subscription_update = patch_object
8181+8282+(* Arguments for PushSubscription/get. *)
8383+module Push_subscription_get_args = struct
8484+ type t = {
8585+ ids: id list option;
8686+ properties: string list option;
8787+ }
8888+8989+ let ids t = t.ids
9090+ let properties t = t.properties
9191+9292+ let v ?ids ?properties () = { ids; properties }
9393+end
9494+9595+(* Response for PushSubscription/get. *)
9696+module Push_subscription_get_response = struct
9797+ type t = {
9898+ list: Push_subscription.t list;
9999+ not_found: id list;
100100+ }
101101+102102+ let list t = t.list
103103+ let not_found t = t.not_found
104104+105105+ let v ~list ~not_found () = { list; not_found }
106106+end
107107+108108+(* Arguments for PushSubscription/set. *)
109109+module Push_subscription_set_args = struct
110110+ type t = {
111111+ create: Push_subscription_create.t id_map option;
112112+ update: push_subscription_update id_map option;
113113+ destroy: id list option;
114114+ }
115115+116116+ let create t = t.create
117117+ let update t = t.update
118118+ let destroy t = t.destroy
119119+120120+ let v ?create ?update ?destroy () = { create; update; destroy }
121121+end
122122+123123+(* Server-set information for created PushSubscription. *)
124124+module Push_subscription_created_info = struct
125125+ type t = {
126126+ id: id;
127127+ expires: utc_date option;
128128+ }
129129+130130+ let id t = t.id
131131+ let expires t = t.expires
132132+133133+ let v ~id ?expires () = { id; expires }
134134+end
135135+136136+(* Server-set information for updated PushSubscription. *)
137137+module Push_subscription_updated_info = struct
138138+ type t = {
139139+ expires: utc_date option;
140140+ }
141141+142142+ let expires t = t.expires
143143+144144+ let v ?expires () = { expires }
145145+end
146146+147147+(* Response for PushSubscription/set. *)
148148+module Push_subscription_set_response = struct
149149+ type t = {
150150+ created: Push_subscription_created_info.t id_map option;
151151+ updated: Push_subscription_updated_info.t option id_map option;
152152+ destroyed: id list option;
153153+ not_created: Set_error.t id_map option;
154154+ not_updated: Set_error.t id_map option;
155155+ not_destroyed: Set_error.t id_map option;
156156+ }
157157+158158+ let created t = t.created
159159+ let updated t = t.updated
160160+ let destroyed t = t.destroyed
161161+ let not_created t = t.not_created
162162+ let not_updated t = t.not_updated
163163+ let not_destroyed t = t.not_destroyed
164164+165165+ let v ?created ?updated ?destroyed ?not_created ?not_updated ?not_destroyed () =
166166+ { created; updated; destroyed; not_created; not_updated; not_destroyed }
167167+end
168168+169169+(* PushVerification object. *)
170170+module Push_verification = struct
171171+ type t = {
172172+ push_subscription_id: id;
173173+ verification_code: string;
174174+ }
175175+176176+ let push_subscription_id t = t.push_subscription_id
177177+ let verification_code t = t.verification_code
178178+179179+ let v ~push_subscription_id ~verification_code () =
180180+ { push_subscription_id; verification_code }
181181+end
182182+183183+(* Data for EventSource ping event. *)
184184+module Event_source_ping_data = struct
185185+ type t = {
186186+ interval: uint;
187187+ }
188188+189189+ let interval t = t.interval
190190+191191+ let v ~interval () = { interval }
192192+end
+230
jmap/jmap_push.mli
···11+(** JMAP Push Notifications.
22+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-7> RFC 8620, Section 7 *)
33+44+open Jmap_types
55+open Jmap_methods
66+open Jmap_error
77+88+(** TypeState object map (TypeName -> StateString).
99+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-7.1> RFC 8620, Section 7.1 *)
1010+type type_state = string string_map
1111+1212+(** StateChange object.
1313+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-7.1> RFC 8620, Section 7.1 *)
1414+module State_change : sig
1515+ type t
1616+1717+ val changed : t -> type_state id_map
1818+1919+ val v :
2020+ changed:type_state id_map ->
2121+ unit ->
2222+ t
2323+end
2424+2525+(** PushSubscription encryption keys.
2626+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-7.2> RFC 8620, Section 7.2 *)
2727+module Push_encryption_keys : sig
2828+ type t
2929+3030+ (** P-256 ECDH public key (URL-safe base64) *)
3131+ val p256dh : t -> string
3232+3333+ (** Authentication secret (URL-safe base64) *)
3434+ val auth : t -> string
3535+3636+ val v :
3737+ p256dh:string ->
3838+ auth:string ->
3939+ unit ->
4040+ t
4141+end
4242+4343+(** PushSubscription object.
4444+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-7.2> RFC 8620, Section 7.2 *)
4545+module Push_subscription : sig
4646+ type t
4747+4848+ (** Id of the subscription (server-set, immutable) *)
4949+ val id : t -> id
5050+5151+ (** Device client id (immutable) *)
5252+ val device_client_id : t -> string
5353+5454+ (** Notification URL (immutable) *)
5555+ val url : t -> Uri.t
5656+5757+ (** Encryption keys (immutable) *)
5858+ val keys : t -> Push_encryption_keys.t option
5959+ val verification_code : t -> string option
6060+ val expires : t -> utc_date option
6161+ val types : t -> string list option
6262+6363+ val v :
6464+ id:id ->
6565+ device_client_id:string ->
6666+ url:Uri.t ->
6767+ ?keys:Push_encryption_keys.t ->
6868+ ?verification_code:string ->
6969+ ?expires:utc_date ->
7070+ ?types:string list ->
7171+ unit ->
7272+ t
7373+end
7474+7575+(** PushSubscription object for creation (omits server-set fields).
7676+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-7.2> RFC 8620, Section 7.2 *)
7777+module Push_subscription_create : sig
7878+ type t
7979+8080+ val device_client_id : t -> string
8181+ val url : t -> Uri.t
8282+ val keys : t -> Push_encryption_keys.t option
8383+ val expires : t -> utc_date option
8484+ val types : t -> string list option
8585+8686+ val v :
8787+ device_client_id:string ->
8888+ url:Uri.t ->
8989+ ?keys:Push_encryption_keys.t ->
9090+ ?expires:utc_date ->
9191+ ?types:string list ->
9292+ unit ->
9393+ t
9494+end
9595+9696+(** PushSubscription object for update patch.
9797+ Only verification_code and expires can be updated.
9898+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-7.2> RFC 8620, Section 7.2
9999+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-7.2.2> RFC 8620, Section 7.2.2 *)
100100+type push_subscription_update = patch_object
101101+102102+(** Arguments for PushSubscription/get.
103103+ Extends standard /get args.
104104+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-7.2.1> RFC 8620, Section 7.2.1 *)
105105+module Push_subscription_get_args : sig
106106+ type t
107107+108108+ val ids : t -> id list option
109109+ val properties : t -> string list option
110110+111111+ val v :
112112+ ?ids:id list ->
113113+ ?properties:string list ->
114114+ unit ->
115115+ t
116116+end
117117+118118+(** Response for PushSubscription/get.
119119+ Extends standard /get response.
120120+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-7.2.1> RFC 8620, Section 7.2.1 *)
121121+module Push_subscription_get_response : sig
122122+ type t
123123+124124+ val list : t -> Push_subscription.t list
125125+ val not_found : t -> id list
126126+127127+ val v :
128128+ list:Push_subscription.t list ->
129129+ not_found:id list ->
130130+ unit ->
131131+ t
132132+end
133133+134134+(** Arguments for PushSubscription/set.
135135+ Extends standard /set args.
136136+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-7.2.2> RFC 8620, Section 7.2.2 *)
137137+module Push_subscription_set_args : sig
138138+ type t
139139+140140+ val create : t -> Push_subscription_create.t id_map option
141141+ val update : t -> push_subscription_update id_map option
142142+ val destroy : t -> id list option
143143+144144+ val v :
145145+ ?create:Push_subscription_create.t id_map ->
146146+ ?update:push_subscription_update id_map ->
147147+ ?destroy:id list ->
148148+ unit ->
149149+ t
150150+end
151151+152152+(** Server-set information for created PushSubscription.
153153+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-7.2.2> RFC 8620, Section 7.2.2 *)
154154+module Push_subscription_created_info : sig
155155+ type t
156156+157157+ val id : t -> id
158158+ val expires : t -> utc_date option
159159+160160+ val v :
161161+ id:id ->
162162+ ?expires:utc_date ->
163163+ unit ->
164164+ t
165165+end
166166+167167+(** Server-set information for updated PushSubscription.
168168+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-7.2.2> RFC 8620, Section 7.2.2 *)
169169+module Push_subscription_updated_info : sig
170170+ type t
171171+172172+ val expires : t -> utc_date option
173173+174174+ val v :
175175+ ?expires:utc_date ->
176176+ unit ->
177177+ t
178178+end
179179+180180+(** Response for PushSubscription/set.
181181+ Extends standard /set response.
182182+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-7.2.2> RFC 8620, Section 7.2.2 *)
183183+module Push_subscription_set_response : sig
184184+ type t
185185+186186+ val created : t -> Push_subscription_created_info.t id_map option
187187+ val updated : t -> Push_subscription_updated_info.t option id_map option
188188+ val destroyed : t -> id list option
189189+ val not_created : t -> Set_error.t id_map option
190190+ val not_updated : t -> Set_error.t id_map option
191191+ val not_destroyed : t -> Set_error.t id_map option
192192+193193+ val v :
194194+ ?created:Push_subscription_created_info.t id_map ->
195195+ ?updated:Push_subscription_updated_info.t option id_map ->
196196+ ?destroyed:id list ->
197197+ ?not_created:Set_error.t id_map ->
198198+ ?not_updated:Set_error.t id_map ->
199199+ ?not_destroyed:Set_error.t id_map ->
200200+ unit ->
201201+ t
202202+end
203203+204204+(** PushVerification object.
205205+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-7.2.2> RFC 8620, Section 7.2.2 *)
206206+module Push_verification : sig
207207+ type t
208208+209209+ val push_subscription_id : t -> id
210210+ val verification_code : t -> string
211211+212212+ val v :
213213+ push_subscription_id:id ->
214214+ verification_code:string ->
215215+ unit ->
216216+ t
217217+end
218218+219219+(** Data for EventSource ping event.
220220+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-7.3> RFC 8620, Section 7.3 *)
221221+module Event_source_ping_data : sig
222222+ type t
223223+224224+ val interval : t -> uint
225225+226226+ val v :
227227+ interval:uint ->
228228+ unit ->
229229+ t
230230+end
+114
jmap/jmap_session.ml
···11+(* JMAP Session Resource. *)
22+33+open Jmap_types
44+55+(* Account capability information.
66+ The value is capability-specific. *)
77+type account_capability_value = Yojson.Safe.t
88+99+(* Server capability information.
1010+ The value is capability-specific. *)
1111+type server_capability_value = Yojson.Safe.t
1212+1313+(* Core capability information. *)
1414+module Core_capability = struct
1515+ type t = {
1616+ max_size_upload: uint;
1717+ max_concurrent_upload: uint;
1818+ max_size_request: uint;
1919+ max_concurrent_requests: uint;
2020+ max_calls_in_request: uint;
2121+ max_objects_in_get: uint;
2222+ max_objects_in_set: uint;
2323+ collation_algorithms: string list;
2424+ }
2525+2626+ let max_size_upload t = t.max_size_upload
2727+ let max_concurrent_upload t = t.max_concurrent_upload
2828+ let max_size_request t = t.max_size_request
2929+ let max_concurrent_requests t = t.max_concurrent_requests
3030+ let max_calls_in_request t = t.max_calls_in_request
3131+ let max_objects_in_get t = t.max_objects_in_get
3232+ let max_objects_in_set t = t.max_objects_in_set
3333+ let collation_algorithms t = t.collation_algorithms
3434+3535+ let v ~max_size_upload ~max_concurrent_upload ~max_size_request
3636+ ~max_concurrent_requests ~max_calls_in_request ~max_objects_in_get
3737+ ~max_objects_in_set ~collation_algorithms () =
3838+ { max_size_upload; max_concurrent_upload; max_size_request;
3939+ max_concurrent_requests; max_calls_in_request; max_objects_in_get;
4040+ max_objects_in_set; collation_algorithms }
4141+end
4242+4343+(* An Account object. *)
4444+module Account = struct
4545+ type t = {
4646+ name: string;
4747+ is_personal: bool;
4848+ is_read_only: bool;
4949+ account_capabilities: account_capability_value string_map;
5050+ }
5151+5252+ let name t = t.name
5353+ let is_personal t = t.is_personal
5454+ let is_read_only t = t.is_read_only
5555+ let account_capabilities t = t.account_capabilities
5656+5757+ let v ~name ?(is_personal=true) ?(is_read_only=false)
5858+ ?(account_capabilities=Hashtbl.create 0) () =
5959+ { name; is_personal; is_read_only; account_capabilities }
6060+end
6161+6262+(* The Session object. *)
6363+module Session = struct
6464+ type t = {
6565+ capabilities: server_capability_value string_map;
6666+ accounts: Account.t id_map;
6767+ primary_accounts: id string_map;
6868+ username: string;
6969+ api_url: Uri.t;
7070+ download_url: Uri.t;
7171+ upload_url: Uri.t;
7272+ event_source_url: Uri.t;
7373+ state: string;
7474+ }
7575+7676+ let capabilities t = t.capabilities
7777+ let accounts t = t.accounts
7878+ let primary_accounts t = t.primary_accounts
7979+ let username t = t.username
8080+ let api_url t = t.api_url
8181+ let download_url t = t.download_url
8282+ let upload_url t = t.upload_url
8383+ let event_source_url t = t.event_source_url
8484+ let state t = t.state
8585+8686+ let v ~capabilities ~accounts ~primary_accounts ~username
8787+ ~api_url ~download_url ~upload_url ~event_source_url ~state () =
8888+ { capabilities; accounts; primary_accounts; username;
8989+ api_url; download_url; upload_url; event_source_url; state }
9090+end
9191+9292+(* Function to perform service autodiscovery.
9393+ Returns the session URL if found. *)
9494+let discover ~domain =
9595+ (* This is a placeholder implementation - would need to be completed in Unix implementation *)
9696+ let well_known_url = Uri.of_string ("https://" ^ domain ^ "/.well-known/jmap") in
9797+ Some well_known_url
9898+9999+(* Function to fetch the session object from a given URL.
100100+ Requires authentication handling (details TBD/outside this signature). *)
101101+let get_session ~url =
102102+ (* This is a placeholder implementation - would need to be completed in Unix implementation *)
103103+ let empty_map () = Hashtbl.create 0 in
104104+ Session.v
105105+ ~capabilities:(empty_map ())
106106+ ~accounts:(empty_map ())
107107+ ~primary_accounts:(empty_map ())
108108+ ~username:"placeholder"
109109+ ~api_url:url
110110+ ~download_url:url
111111+ ~upload_url:url
112112+ ~event_source_url:url
113113+ ~state:"placeholder"
114114+ ()
+98
jmap/jmap_session.mli
···11+(** JMAP Session Resource.
22+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-2> RFC 8620, Section 2 *)
33+44+open Jmap_types
55+66+(** Account capability information.
77+ The value is capability-specific.
88+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-2> RFC 8620, Section 2 *)
99+type account_capability_value = Yojson.Safe.t
1010+1111+(** Server capability information.
1212+ The value is capability-specific.
1313+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-2> RFC 8620, Section 2 *)
1414+type server_capability_value = Yojson.Safe.t
1515+1616+(** Core capability information.
1717+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-2> RFC 8620, Section 2 *)
1818+module Core_capability : sig
1919+ type t
2020+2121+ val max_size_upload : t -> uint
2222+ val max_concurrent_upload : t -> uint
2323+ val max_size_request : t -> uint
2424+ val max_concurrent_requests : t -> uint
2525+ val max_calls_in_request : t -> uint
2626+ val max_objects_in_get : t -> uint
2727+ val max_objects_in_set : t -> uint
2828+ val collation_algorithms : t -> string list
2929+3030+ val v :
3131+ max_size_upload:uint ->
3232+ max_concurrent_upload:uint ->
3333+ max_size_request:uint ->
3434+ max_concurrent_requests:uint ->
3535+ max_calls_in_request:uint ->
3636+ max_objects_in_get:uint ->
3737+ max_objects_in_set:uint ->
3838+ collation_algorithms:string list ->
3939+ unit ->
4040+ t
4141+end
4242+4343+(** An Account object.
4444+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-2> RFC 8620, Section 2 *)
4545+module Account : sig
4646+ type t
4747+4848+ val name : t -> string
4949+ val is_personal : t -> bool
5050+ val is_read_only : t -> bool
5151+ val account_capabilities : t -> account_capability_value string_map
5252+5353+ val v :
5454+ name:string ->
5555+ ?is_personal:bool ->
5656+ ?is_read_only:bool ->
5757+ ?account_capabilities:account_capability_value string_map ->
5858+ unit ->
5959+ t
6060+end
6161+6262+(** The Session object.
6363+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-2> RFC 8620, Section 2 *)
6464+module Session : sig
6565+ type t
6666+6767+ val capabilities : t -> server_capability_value string_map
6868+ val accounts : t -> Account.t id_map
6969+ val primary_accounts : t -> id string_map
7070+ val username : t -> string
7171+ val api_url : t -> Uri.t
7272+ val download_url : t -> Uri.t
7373+ val upload_url : t -> Uri.t
7474+ val event_source_url : t -> Uri.t
7575+ val state : t -> string
7676+7777+ val v :
7878+ capabilities:server_capability_value string_map ->
7979+ accounts:Account.t id_map ->
8080+ primary_accounts:id string_map ->
8181+ username:string ->
8282+ api_url:Uri.t ->
8383+ download_url:Uri.t ->
8484+ upload_url:Uri.t ->
8585+ event_source_url:Uri.t ->
8686+ state:string ->
8787+ unit ->
8888+ t
8989+end
9090+9191+(** Function to perform service autodiscovery.
9292+ Returns the session URL if found.
9393+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-2.2> RFC 8620, Section 2.2 *)
9494+val discover : domain:string -> Uri.t option
9595+9696+(** Function to fetch the session object from a given URL.
9797+ Requires authentication handling (details TBD/outside this signature). *)
9898+val get_session : url:Uri.t -> Session.t
+32
jmap/jmap_types.ml
···11+(* Basic JMAP types as defined in RFC 8620. *)
22+33+(* The Id data type.
44+ A string of 1 to 255 octets, using URL-safe base64 characters. *)
55+type id = string
66+77+(* The Int data type.
88+ An integer in the range [-2^53+1, 2^53-1]. Represented as OCaml's standard [int]. *)
99+type jint = int
1010+1111+(* The UnsignedInt data type.
1212+ An integer in the range [0, 2^53-1]. Represented as OCaml's standard [int]. *)
1313+type uint = int
1414+1515+(* The Date data type.
1616+ A string in RFC 3339 "date-time" format.
1717+ Represented as a float using Unix time. *)
1818+type date = float
1919+2020+(* The UTCDate data type.
2121+ A string in RFC 3339 "date-time" format, restricted to UTC (Z timezone).
2222+ Represented as a float using Unix time. *)
2323+type utc_date = float
2424+2525+(* Represents a JSON object used as a map String -> V. *)
2626+type 'v string_map = (string, 'v) Hashtbl.t
2727+2828+(* Represents a JSON object used as a map Id -> V. *)
2929+type 'v id_map = (id, 'v) Hashtbl.t
3030+3131+(* Represents a JSON Pointer path with JMAP extensions. *)
3232+type json_pointer = string
+38
jmap/jmap_types.mli
···11+(** Basic JMAP types as defined in RFC 8620. *)
22+33+(** The Id data type.
44+ A string of 1 to 255 octets, using URL-safe base64 characters.
55+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-1.2> RFC 8620, Section 1.2 *)
66+type id = string
77+88+(** The Int data type.
99+ An integer in the range [-2^53+1, 2^53-1]. Represented as OCaml's standard [int].
1010+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-1.3> RFC 8620, Section 1.3 *)
1111+type jint = int
1212+1313+(** The UnsignedInt data type.
1414+ An integer in the range [0, 2^53-1]. Represented as OCaml's standard [int].
1515+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-1.3> RFC 8620, Section 1.3 *)
1616+type uint = int
1717+1818+(** The Date data type.
1919+ A string in RFC 3339 "date-time" format.
2020+ Represented as a float using Unix time.
2121+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-1.4> RFC 8620, Section 1.4 *)
2222+type date = float
2323+2424+(** The UTCDate data type.
2525+ A string in RFC 3339 "date-time" format, restricted to UTC (Z timezone).
2626+ Represented as a float using Unix time.
2727+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-1.4> RFC 8620, Section 1.4 *)
2828+type utc_date = float
2929+3030+(** Represents a JSON object used as a map String -> V. *)
3131+type 'v string_map = (string, 'v) Hashtbl.t
3232+3333+(** Represents a JSON object used as a map Id -> V. *)
3434+type 'v id_map = (id, 'v) Hashtbl.t
3535+3636+(** Represents a JSON Pointer path with JMAP extensions.
3737+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-3.7> RFC 8620, Section 3.7 *)
3838+type json_pointer = string
+73
jmap/jmap_wire.ml
···11+(* JMAP Wire Protocol Structures (Request/Response). *)
22+33+open Jmap_types
44+55+(* An invocation tuple within a request or response. *)
66+module Invocation = struct
77+ type t = {
88+ method_name: string;
99+ arguments: Yojson.Safe.t;
1010+ method_call_id: string;
1111+ }
1212+1313+ let method_name t = t.method_name
1414+ let arguments t = t.arguments
1515+ let method_call_id t = t.method_call_id
1616+1717+ let v ?(arguments=`Assoc []) ~method_name ~method_call_id () =
1818+ { method_name; arguments; method_call_id }
1919+end
2020+2121+(* Method error type with context. *)
2222+type method_error = Jmap_error.Method_error.t * string
2323+2424+(* A response invocation part, which can be a standard response or an error. *)
2525+type response_invocation = (Invocation.t, method_error) result
2626+2727+(* A reference to a previous method call's result. *)
2828+module Result_reference = struct
2929+ type t = {
3030+ result_of: string;
3131+ name: string;
3232+ path: json_pointer;
3333+ }
3434+3535+ let result_of t = t.result_of
3636+ let name t = t.name
3737+ let path t = t.path
3838+3939+ let v ~result_of ~name ~path () =
4040+ { result_of; name; path }
4141+end
4242+4343+(* The Request object. *)
4444+module Request = struct
4545+ type t = {
4646+ using: string list;
4747+ method_calls: Invocation.t list;
4848+ created_ids: id id_map option;
4949+ }
5050+5151+ let using t = t.using
5252+ let method_calls t = t.method_calls
5353+ let created_ids t = t.created_ids
5454+5555+ let v ~using ~method_calls ?created_ids () =
5656+ { using; method_calls; created_ids }
5757+end
5858+5959+(* The Response object. *)
6060+module Response = struct
6161+ type t = {
6262+ method_responses: response_invocation list;
6363+ created_ids: id id_map option;
6464+ session_state: string;
6565+ }
6666+6767+ let method_responses t = t.method_responses
6868+ let created_ids t = t.created_ids
6969+ let session_state t = t.session_state
7070+7171+ let v ~method_responses ?created_ids ~session_state () =
7272+ { method_responses; created_ids; session_state }
7373+end
+80
jmap/jmap_wire.mli
···11+(** JMAP Wire Protocol Structures (Request/Response). *)
22+33+open Jmap_types
44+55+(** An invocation tuple within a request or response.
66+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-3.2> RFC 8620, Section 3.2 *)
77+module Invocation : sig
88+ type t
99+1010+ val method_name : t -> string
1111+ val arguments : t -> Yojson.Safe.t
1212+ val method_call_id : t -> string
1313+1414+ val v :
1515+ ?arguments:Yojson.Safe.t ->
1616+ method_name:string ->
1717+ method_call_id:string ->
1818+ unit ->
1919+ t
2020+end
2121+2222+(** Method error type with context.
2323+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-3.6.2> RFC 8620, Section 3.6.2 *)
2424+type method_error = Jmap_error.Method_error.t * string
2525+2626+(** A response invocation part, which can be a standard response or an error.
2727+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-3.4> RFC 8620, Section 3.4
2828+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-3.6.2> RFC 8620, Section 3.6.2 *)
2929+type response_invocation = (Invocation.t, method_error) result
3030+3131+(** A reference to a previous method call's result.
3232+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-3.7> RFC 8620, Section 3.7 *)
3333+module Result_reference : sig
3434+ type t
3535+3636+ val result_of : t -> string
3737+ val name : t -> string
3838+ val path : t -> json_pointer
3939+4040+ val v :
4141+ result_of:string ->
4242+ name:string ->
4343+ path:json_pointer ->
4444+ unit ->
4545+ t
4646+end
4747+4848+(** The Request object.
4949+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-3.3> RFC 8620, Section 3.3 *)
5050+module Request : sig
5151+ type t
5252+5353+ val using : t -> string list
5454+ val method_calls : t -> Invocation.t list
5555+ val created_ids : t -> id id_map option
5656+5757+ val v :
5858+ using:string list ->
5959+ method_calls:Invocation.t list ->
6060+ ?created_ids:id id_map ->
6161+ unit ->
6262+ t
6363+end
6464+6565+(** The Response object.
6666+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-3.4> RFC 8620, Section 3.4 *)
6767+module Response : sig
6868+ type t
6969+7070+ val method_responses : t -> response_invocation list
7171+ val created_ids : t -> id id_map option
7272+ val session_state : t -> string
7373+7474+ val v :
7575+ method_responses:response_invocation list ->
7676+ ?created_ids:id id_map ->
7777+ session_state:string ->
7878+ unit ->
7979+ t
8080+end