···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-]
-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
-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
-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-}
-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
-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
-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
-672
jmap-unix/jmap_unix.ml
···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
-45
jmap/jmap.ml
···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
-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
-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
-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
-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
-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- ()
-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
-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