this repo has no description

trim

-300
jmap-email/jmap_email.ml
···
-
(* JMAP Mail Extension Library (RFC 8621). *)
-
-
(* Core Types *)
-
module Types = Jmap_email_types
-
-
(* Mailbox *)
-
module Mailbox = Jmap_mailbox
-
-
(* Thread *)
-
module Thread = Jmap_thread
-
-
(* Search Snippet *)
-
module SearchSnippet = Jmap_search_snippet
-
-
(* Identity *)
-
module Identity = Jmap_identity
-
-
(* Email Submission *)
-
module Submission = Jmap_submission
-
-
(* Vacation Response *)
-
module Vacation = Jmap_vacation
-
-
(* Capability URI for JMAP Mail. *)
-
let capability_mail = "urn:ietf:params:jmap:mail"
-
-
(* Capability URI for JMAP Submission. *)
-
let capability_submission = "urn:ietf:params:jmap:submission"
-
-
(* Capability URI for JMAP Vacation Response. *)
-
let capability_vacationresponse = "urn:ietf:params:jmap:vacationresponse"
-
-
(* Type name for EmailDelivery push notifications. *)
-
let push_event_type_email_delivery = "EmailDelivery"
-
-
(* JMAP keywords corresponding to IMAP system flags. *)
-
let keyword_draft = "$draft"
-
let keyword_seen = "$seen"
-
let keyword_flagged = "$flagged"
-
let keyword_answered = "$answered"
-
-
(* Common JMAP keywords from RFC 5788. *)
-
let keyword_forwarded = "$forwarded"
-
let keyword_phishing = "$phishing"
-
let keyword_junk = "$junk"
-
let keyword_notjunk = "$notjunk"
-
-
(* Functions to manipulate email flags/keywords *)
-
module Keyword_ops = struct
-
let add email keyword =
-
match Types.Email.keywords email with
-
| None ->
-
Types.Email.create
-
?id:(Types.Email.id email)
-
?blob_id:(Types.Email.blob_id email)
-
?thread_id:(Types.Email.thread_id email)
-
?mailbox_ids:(Types.Email.mailbox_ids email)
-
~keywords:(Types.Keywords.of_list [keyword])
-
?size:(Types.Email.size email)
-
?received_at:(Types.Email.received_at email)
-
?subject:(Types.Email.subject email)
-
?preview:(Types.Email.preview email)
-
?from:(Types.Email.from email)
-
?to_:(Types.Email.to_ email)
-
?cc:(Types.Email.cc email)
-
?message_id:(Types.Email.message_id email)
-
?has_attachment:(Types.Email.has_attachment email)
-
?text_body:(Types.Email.text_body email)
-
?html_body:(Types.Email.html_body email)
-
?attachments:(Types.Email.attachments email)
-
()
-
| Some kws ->
-
Types.Email.create
-
?id:(Types.Email.id email)
-
?blob_id:(Types.Email.blob_id email)
-
?thread_id:(Types.Email.thread_id email)
-
?mailbox_ids:(Types.Email.mailbox_ids email)
-
~keywords:(Types.Keywords.add kws keyword)
-
?size:(Types.Email.size email)
-
?received_at:(Types.Email.received_at email)
-
?subject:(Types.Email.subject email)
-
?preview:(Types.Email.preview email)
-
?from:(Types.Email.from email)
-
?to_:(Types.Email.to_ email)
-
?cc:(Types.Email.cc email)
-
?message_id:(Types.Email.message_id email)
-
?has_attachment:(Types.Email.has_attachment email)
-
?text_body:(Types.Email.text_body email)
-
?html_body:(Types.Email.html_body email)
-
?attachments:(Types.Email.attachments email)
-
()
-
-
let remove email keyword =
-
match Types.Email.keywords email with
-
| None -> email
-
| Some kws ->
-
Types.Email.create
-
?id:(Types.Email.id email)
-
?blob_id:(Types.Email.blob_id email)
-
?thread_id:(Types.Email.thread_id email)
-
?mailbox_ids:(Types.Email.mailbox_ids email)
-
~keywords:(Types.Keywords.remove kws keyword)
-
?size:(Types.Email.size email)
-
?received_at:(Types.Email.received_at email)
-
?subject:(Types.Email.subject email)
-
?preview:(Types.Email.preview email)
-
?from:(Types.Email.from email)
-
?to_:(Types.Email.to_ email)
-
?cc:(Types.Email.cc email)
-
?message_id:(Types.Email.message_id email)
-
?has_attachment:(Types.Email.has_attachment email)
-
?text_body:(Types.Email.text_body email)
-
?html_body:(Types.Email.html_body email)
-
?attachments:(Types.Email.attachments email)
-
()
-
-
let mark_as_seen email = add email Types.Keywords.Seen
-
-
let mark_as_unseen email = remove email Types.Keywords.Seen
-
-
let mark_as_flagged email = add email Types.Keywords.Flagged
-
-
let unmark_flagged email = remove email Types.Keywords.Flagged
-
-
let mark_as_draft email = add email Types.Keywords.Draft
-
-
let unmark_draft email = remove email Types.Keywords.Draft
-
-
let mark_as_answered email = add email Types.Keywords.Answered
-
-
let unmark_answered email = remove email Types.Keywords.Answered
-
-
let mark_as_forwarded email = add email Types.Keywords.Forwarded
-
-
let mark_as_junk email = add email Types.Keywords.Junk
-
-
let mark_as_not_junk email = add email Types.Keywords.NotJunk
-
-
let mark_as_phishing email = add email Types.Keywords.Phishing
-
-
let add_custom email custom_kw =
-
add email (Types.Keywords.Custom custom_kw)
-
-
let remove_custom email custom_kw =
-
remove email (Types.Keywords.Custom custom_kw)
-
-
let add_keyword_patch keyword =
-
[("keywords/" ^ Types.Keywords.to_string keyword, `Bool true)]
-
-
let remove_keyword_patch keyword =
-
[("keywords/" ^ Types.Keywords.to_string keyword, `Null)]
-
-
let mark_seen_patch () =
-
add_keyword_patch Types.Keywords.Seen
-
-
let mark_unseen_patch () =
-
remove_keyword_patch Types.Keywords.Seen
-
end
-
-
(* Conversion functions for JMAP/IMAP compatibility *)
-
module Conversion = struct
-
let keyword_to_imap_flag = function
-
| Types.Keywords.Draft -> "\\Draft"
-
| Types.Keywords.Seen -> "\\Seen"
-
| Types.Keywords.Flagged -> "\\Flagged"
-
| Types.Keywords.Answered -> "\\Answered"
-
| Types.Keywords.Forwarded -> "$Forwarded"
-
| Types.Keywords.Phishing -> "$Phishing"
-
| Types.Keywords.Junk -> "$Junk"
-
| Types.Keywords.NotJunk -> "$NotJunk"
-
| Types.Keywords.Custom c -> c
-
-
let imap_flag_to_keyword = function
-
| "\\Draft" -> Types.Keywords.Draft
-
| "\\Seen" -> Types.Keywords.Seen
-
| "\\Flagged" -> Types.Keywords.Flagged
-
| "\\Answered" -> Types.Keywords.Answered
-
| "$Forwarded" -> Types.Keywords.Forwarded
-
| "$Phishing" -> Types.Keywords.Phishing
-
| "$Junk" -> Types.Keywords.Junk
-
| "$NotJunk" -> Types.Keywords.NotJunk
-
| c -> Types.Keywords.Custom c
-
-
let is_valid_custom_keyword s =
-
String.length s > 0 && s.[0] <> '$' &&
-
String.for_all (fun c ->
-
(c >= 'a' && c <= 'z') ||
-
(c >= 'A' && c <= 'Z') ||
-
(c >= '0' && c <= '9') ||
-
c = '-' || c = '_') s
-
-
let keyword_to_string = Types.Keywords.to_string
-
-
let string_to_keyword = Types.Keywords.of_string
-
end
-
-
(* Email query filter helpers *)
-
module Email_filter = struct
-
let in_mailbox mailbox_id =
-
let prop_name = "mailboxIds/" ^ mailbox_id in
-
Jmap.Methods.Filter.property_equals prop_name (`Bool true)
-
-
let has_keyword keyword =
-
let prop_name = "keywords/" ^ Types.Keywords.to_string keyword in
-
Jmap.Methods.Filter.property_equals prop_name (`Bool true)
-
-
let not_has_keyword keyword =
-
let prop_name = "keywords/" ^ Types.Keywords.to_string keyword in
-
Jmap.Methods.Filter.property_equals prop_name (`Bool false)
-
-
let unread () =
-
not_has_keyword Types.Keywords.Seen
-
-
let subject subject_text =
-
Jmap.Methods.Filter.text_contains "subject" subject_text
-
-
let from email =
-
Jmap.Methods.Filter.text_contains "from" email
-
-
let to_ email =
-
Jmap.Methods.Filter.text_contains "to" email
-
-
let has_attachment () =
-
Jmap.Methods.Filter.property_equals "hasAttachment" (`Bool true)
-
-
let before date =
-
Jmap.Methods.Filter.property_lt "receivedAt" (`Float date)
-
-
let after date =
-
Jmap.Methods.Filter.property_gt "receivedAt" (`Float date)
-
-
let larger_than size =
-
Jmap.Methods.Filter.property_gt "size" (`Int size)
-
-
let smaller_than size =
-
Jmap.Methods.Filter.property_lt "size" (`Int size)
-
end
-
-
(* Common email sorting comparators *)
-
module Email_sort = struct
-
let received_newest_first () =
-
Jmap.Methods.Comparator.v
-
~property:"receivedAt"
-
~is_ascending:false
-
()
-
-
let received_oldest_first () =
-
Jmap.Methods.Comparator.v
-
~property:"receivedAt"
-
~is_ascending:true
-
()
-
-
let sent_newest_first () =
-
Jmap.Methods.Comparator.v
-
~property:"sentAt"
-
~is_ascending:false
-
()
-
-
let sent_oldest_first () =
-
Jmap.Methods.Comparator.v
-
~property:"sentAt"
-
~is_ascending:true
-
()
-
-
let subject_asc () =
-
Jmap.Methods.Comparator.v
-
~property:"subject"
-
~is_ascending:true
-
()
-
-
let subject_desc () =
-
Jmap.Methods.Comparator.v
-
~property:"subject"
-
~is_ascending:false
-
()
-
-
let size_largest_first () =
-
Jmap.Methods.Comparator.v
-
~property:"size"
-
~is_ascending:false
-
()
-
-
let size_smallest_first () =
-
Jmap.Methods.Comparator.v
-
~property:"size"
-
~is_ascending:true
-
()
-
-
let from_asc () =
-
Jmap.Methods.Comparator.v
-
~property:"from"
-
~is_ascending:true
-
()
-
-
let from_desc () =
-
Jmap.Methods.Comparator.v
-
~property:"from"
-
~is_ascending:false
-
()
-
end
-405
jmap-email/jmap_email_types.ml
···
-
(* Common types for JMAP Mail (RFC 8621). *)
-
-
open Jmap.Types
-
-
(* Represents an email address with an optional name. *)
-
module Email_address = struct
-
type t = {
-
name: string option;
-
email: string;
-
}
-
-
let name t = t.name
-
let email t = t.email
-
-
let v ?name ~email () = { name; email }
-
end
-
-
(* Represents a group of email addresses. *)
-
module Email_address_group = struct
-
type t = {
-
name: string option;
-
addresses: Email_address.t list;
-
}
-
-
let name t = t.name
-
let addresses t = t.addresses
-
-
let v ?name ~addresses () = { name; addresses }
-
end
-
-
(* Represents a header field (name and raw value). *)
-
module Email_header = struct
-
type t = {
-
name: string;
-
value: string;
-
}
-
-
let name t = t.name
-
let value t = t.value
-
-
let v ~name ~value () = { name; value }
-
end
-
-
(* Represents a body part within an Email's MIME structure. *)
-
module Email_body_part = struct
-
type t = {
-
id: string option;
-
blob_id: id option;
-
size: uint;
-
headers: Email_header.t list;
-
name: string option;
-
mime_type: string;
-
charset: string option;
-
disposition: string option;
-
cid: string option;
-
language: string list option;
-
location: string option;
-
sub_parts: t list option;
-
other_headers: Yojson.Safe.t string_map;
-
}
-
-
let id t = t.id
-
let blob_id t = t.blob_id
-
let size t = t.size
-
let headers t = t.headers
-
let name t = t.name
-
let mime_type t = t.mime_type
-
let charset t = t.charset
-
let disposition t = t.disposition
-
let cid t = t.cid
-
let language t = t.language
-
let location t = t.location
-
let sub_parts t = t.sub_parts
-
let other_headers t = t.other_headers
-
-
let v ?id ?blob_id ~size ~headers ?name ~mime_type ?charset
-
?disposition ?cid ?language ?location ?sub_parts
-
?(other_headers=Hashtbl.create 0) () =
-
{ id; blob_id; size; headers; name; mime_type; charset;
-
disposition; cid; language; location; sub_parts; other_headers }
-
end
-
-
(* Represents the decoded value of a text body part. *)
-
module Email_body_value = struct
-
type t = {
-
value: string;
-
has_encoding_problem: bool;
-
is_truncated: bool;
-
}
-
-
let value t = t.value
-
let has_encoding_problem t = t.has_encoding_problem
-
let is_truncated t = t.is_truncated
-
-
let v ~value ?(encoding_problem=false) ?(truncated=false) () =
-
{ value; has_encoding_problem = encoding_problem; is_truncated = truncated }
-
end
-
-
(* Type to represent email message flags/keywords. *)
-
module Keywords = struct
-
type keyword =
-
| Draft (* "$draft": The Email is a draft the user is composing *)
-
| Seen (* "$seen": The Email has been read *)
-
| Flagged (* "$flagged": The Email has been flagged for urgent/special attention *)
-
| Answered (* "$answered": The Email has been replied to *)
-
-
(* Common extension keywords from RFC 5788 *)
-
| Forwarded (* "$forwarded": The Email has been forwarded *)
-
| Phishing (* "$phishing": The Email is likely to be phishing *)
-
| Junk (* "$junk": The Email is spam/junk *)
-
| NotJunk (* "$notjunk": The Email is explicitly marked as not spam/junk *)
-
| Custom of string (* Arbitrary user-defined keyword *)
-
-
type t = keyword list
-
-
let is_draft keywords =
-
List.exists (function Draft -> true | _ -> false) keywords
-
-
let is_seen keywords =
-
List.exists (function Seen -> true | _ -> false) keywords
-
-
let is_unread keywords =
-
not (is_seen keywords || is_draft keywords)
-
-
let is_flagged keywords =
-
List.exists (function Flagged -> true | _ -> false) keywords
-
-
let is_answered keywords =
-
List.exists (function Answered -> true | _ -> false) keywords
-
-
let is_forwarded keywords =
-
List.exists (function Forwarded -> true | _ -> false) keywords
-
-
let is_phishing keywords =
-
List.exists (function Phishing -> true | _ -> false) keywords
-
-
let is_junk keywords =
-
List.exists (function Junk -> true | _ -> false) keywords
-
-
let is_not_junk keywords =
-
List.exists (function NotJunk -> true | _ -> false) keywords
-
-
let has_keyword keywords custom_keyword =
-
List.exists (function Custom k when k = custom_keyword -> true | _ -> false) keywords
-
-
let custom_keywords keywords =
-
List.fold_left (fun acc kw ->
-
match kw with
-
| Custom k -> k :: acc
-
| _ -> acc
-
) [] keywords
-
-
let add keywords keyword =
-
if List.exists (fun k -> k = keyword) keywords then
-
keywords
-
else
-
keyword :: keywords
-
-
let remove keywords keyword =
-
List.filter (fun k -> k <> keyword) keywords
-
-
let empty () = []
-
-
let of_list keywords = keywords
-
-
let to_string = function
-
| Draft -> "$draft"
-
| Seen -> "$seen"
-
| Flagged -> "$flagged"
-
| Answered -> "$answered"
-
| Forwarded -> "$forwarded"
-
| Phishing -> "$phishing"
-
| Junk -> "$junk"
-
| NotJunk -> "$notjunk"
-
| Custom k -> k
-
-
let of_string s =
-
match s with
-
| "$draft" -> Draft
-
| "$seen" -> Seen
-
| "$flagged" -> Flagged
-
| "$answered" -> Answered
-
| "$forwarded" -> Forwarded
-
| "$phishing" -> Phishing
-
| "$junk" -> Junk
-
| "$notjunk" -> NotJunk
-
| k -> Custom k
-
-
let to_map keywords =
-
let map = Hashtbl.create (List.length keywords) in
-
List.iter (fun kw ->
-
Hashtbl.add map (to_string kw) true
-
) keywords;
-
map
-
end
-
-
(* Email properties enum. *)
-
type email_property =
-
| Id (* The id of the email *)
-
| BlobId (* The id of the blob containing the raw message *)
-
| ThreadId (* The id of the thread this email belongs to *)
-
| MailboxIds (* The mailboxes this email belongs to *)
-
| Keywords (* The keywords/flags for this email *)
-
| Size (* Size of the message in bytes *)
-
| ReceivedAt (* When the message was received by the server *)
-
| MessageId (* Value of the Message-ID header *)
-
| InReplyTo (* Value of the In-Reply-To header *)
-
| References (* Value of the References header *)
-
| Sender (* Value of the Sender header *)
-
| From (* Value of the From header *)
-
| To (* Value of the To header *)
-
| Cc (* Value of the Cc header *)
-
| Bcc (* Value of the Bcc header *)
-
| ReplyTo (* Value of the Reply-To header *)
-
| Subject (* Value of the Subject header *)
-
| SentAt (* Value of the Date header *)
-
| HasAttachment (* Whether the email has attachments *)
-
| Preview (* Preview text of the email *)
-
| BodyStructure (* MIME structure of the email *)
-
| BodyValues (* Decoded body part values *)
-
| TextBody (* Text body parts *)
-
| HtmlBody (* HTML body parts *)
-
| Attachments (* Attachments *)
-
| Header of string (* Specific header *)
-
| Other of string (* Extension property *)
-
-
(* Represents an Email object. *)
-
module Email = struct
-
type t = {
-
id: id option;
-
blob_id: id option;
-
thread_id: id option;
-
mailbox_ids: bool id_map option;
-
keywords: Keywords.t option;
-
size: uint option;
-
received_at: date option;
-
subject: string option;
-
preview: string option;
-
from: Email_address.t list option;
-
to_: Email_address.t list option;
-
cc: Email_address.t list option;
-
message_id: string list option;
-
has_attachment: bool option;
-
text_body: Email_body_part.t list option;
-
html_body: Email_body_part.t list option;
-
attachments: Email_body_part.t list option;
-
}
-
-
let id t = t.id
-
let blob_id t = t.blob_id
-
let thread_id t = t.thread_id
-
let mailbox_ids t = t.mailbox_ids
-
let keywords t = t.keywords
-
let size t = t.size
-
let received_at t = t.received_at
-
let subject t = t.subject
-
let preview t = t.preview
-
let from t = t.from
-
let to_ t = t.to_
-
let cc t = t.cc
-
let message_id t = t.message_id
-
let has_attachment t = t.has_attachment
-
let text_body t = t.text_body
-
let html_body t = t.html_body
-
let attachments t = t.attachments
-
-
let create ?id ?blob_id ?thread_id ?mailbox_ids ?keywords ?size
-
?received_at ?subject ?preview ?from ?to_ ?cc ?message_id
-
?has_attachment ?text_body ?html_body ?attachments () =
-
{ id; blob_id; thread_id; mailbox_ids; keywords; size;
-
received_at; subject; preview; from; to_; cc; message_id;
-
has_attachment; text_body; html_body; attachments }
-
-
let make_patch ?add_keywords ?remove_keywords ?add_mailboxes ?remove_mailboxes () =
-
let patch = [] in
-
let patch = match add_keywords with
-
| Some kw ->
-
("keywords/", `Assoc (List.map (fun k ->
-
(Keywords.to_string k, `Bool true)
-
) kw)) :: patch
-
| None -> patch
-
in
-
let patch = match remove_keywords with
-
| Some kw ->
-
List.fold_left (fun p k ->
-
("keywords/" ^ Keywords.to_string k, `Null) :: p
-
) patch kw
-
| None -> patch
-
in
-
let patch = match add_mailboxes with
-
| Some mboxes ->
-
List.fold_left (fun p mbx ->
-
("mailboxIds/" ^ mbx, `Bool true) :: p
-
) patch mboxes
-
| None -> patch
-
in
-
let patch = match remove_mailboxes with
-
| Some mboxes ->
-
List.fold_left (fun p mbx ->
-
("mailboxIds/" ^ mbx, `Null) :: p
-
) patch mboxes
-
| None -> patch
-
in
-
patch
-
-
let get_id t =
-
match t.id with
-
| Some id -> Ok id
-
| None -> Error "Email missing ID"
-
-
let take_id t =
-
match t.id with
-
| Some id -> id
-
| None -> failwith "Email missing ID"
-
end
-
-
(* Email import options. *)
-
type email_import_options = {
-
import_to_mailboxes : id list;
-
import_keywords : Keywords.t option;
-
import_received_at : date option;
-
}
-
-
(* Email copy options. *)
-
type email_copy_options = {
-
copy_to_account_id : id;
-
copy_to_mailboxes : id list;
-
copy_on_success_destroy_original : bool option;
-
}
-
-
(* Convert a property variant to its string representation *)
-
let email_property_to_string = function
-
| Id -> "id"
-
| BlobId -> "blobId"
-
| ThreadId -> "threadId"
-
| MailboxIds -> "mailboxIds"
-
| Keywords -> "keywords"
-
| Size -> "size"
-
| ReceivedAt -> "receivedAt"
-
| MessageId -> "messageId"
-
| InReplyTo -> "inReplyTo"
-
| References -> "references"
-
| Sender -> "sender"
-
| From -> "from"
-
| To -> "to"
-
| Cc -> "cc"
-
| Bcc -> "bcc"
-
| ReplyTo -> "replyTo"
-
| Subject -> "subject"
-
| SentAt -> "sentAt"
-
| HasAttachment -> "hasAttachment"
-
| Preview -> "preview"
-
| BodyStructure -> "bodyStructure"
-
| BodyValues -> "bodyValues"
-
| TextBody -> "textBody"
-
| HtmlBody -> "htmlBody"
-
| Attachments -> "attachments"
-
| Header h -> "header:" ^ h
-
| Other s -> s
-
-
(* Parse a string into a property variant *)
-
let string_to_email_property s =
-
match s with
-
| "id" -> Id
-
| "blobId" -> BlobId
-
| "threadId" -> ThreadId
-
| "mailboxIds" -> MailboxIds
-
| "keywords" -> Keywords
-
| "size" -> Size
-
| "receivedAt" -> ReceivedAt
-
| "messageId" -> MessageId
-
| "inReplyTo" -> InReplyTo
-
| "references" -> References
-
| "sender" -> Sender
-
| "from" -> From
-
| "to" -> To
-
| "cc" -> Cc
-
| "bcc" -> Bcc
-
| "replyTo" -> ReplyTo
-
| "subject" -> Subject
-
| "sentAt" -> SentAt
-
| "hasAttachment" -> HasAttachment
-
| "preview" -> Preview
-
| "bodyStructure" -> BodyStructure
-
| "bodyValues" -> BodyValues
-
| "textBody" -> TextBody
-
| "htmlBody" -> HtmlBody
-
| "attachments" -> Attachments
-
| s when String.length s > 7 && String.sub s 0 7 = "header:" ->
-
Header (String.sub s 7 (String.length s - 7))
-
| s -> Other s
-
-
(* Get a list of common properties useful for displaying email lists *)
-
let common_email_properties = [
-
Id; ThreadId; MailboxIds; Keywords; Size; ReceivedAt;
-
From; Subject; Preview; HasAttachment; SentAt;
-
]
-
-
(* Get a list of common properties for detailed email view *)
-
let detailed_email_properties = [
-
Id; ThreadId; MailboxIds; Keywords; Size; ReceivedAt;
-
MessageId; InReplyTo; References; Sender; From; To; Cc;
-
ReplyTo; Subject; SentAt; HasAttachment; Preview;
-
TextBody; HtmlBody; Attachments;
-
]
-130
jmap-email/jmap_identity.ml
···
-
(* JMAP Identity. *)
-
-
open Jmap.Types
-
open Jmap.Methods
-
-
(* Identity object. *)
-
type t = {
-
id_value: id;
-
name_value: string;
-
email_value: string;
-
reply_to_value: Jmap_email_types.Email_address.t list option;
-
bcc_value: Jmap_email_types.Email_address.t list option;
-
text_signature_value: string;
-
html_signature_value: string;
-
may_delete_value: bool;
-
}
-
-
(* Get the identity ID (immutable, server-set) *)
-
let id t = t.id_value
-
-
(* Get the display name (defaults to "") *)
-
let name t = t.name_value
-
-
(* Get the email address (immutable) *)
-
let email t = t.email_value
-
-
(* Get the reply-to addresses (if any) *)
-
let reply_to t = t.reply_to_value
-
-
(* Get the bcc addresses (if any) *)
-
let bcc t = t.bcc_value
-
-
(* Get the plain text signature (defaults to "") *)
-
let text_signature t = t.text_signature_value
-
-
(* Get the HTML signature (defaults to "") *)
-
let html_signature t = t.html_signature_value
-
-
(* Check if this identity may be deleted (server-set) *)
-
let may_delete t = t.may_delete_value
-
-
(* Create a new identity object *)
-
let v ~id ?(name="") ~email ?reply_to ?bcc ?(text_signature="") ?(html_signature="") ~may_delete () = {
-
id_value = id;
-
name_value = name;
-
email_value = email;
-
reply_to_value = reply_to;
-
bcc_value = bcc;
-
text_signature_value = text_signature;
-
html_signature_value = html_signature;
-
may_delete_value = may_delete;
-
}
-
-
(* Types and functions for identity creation and updates *)
-
module Create = struct
-
type t = {
-
name_value: string option;
-
email_value: string;
-
reply_to_value: Jmap_email_types.Email_address.t list option;
-
bcc_value: Jmap_email_types.Email_address.t list option;
-
text_signature_value: string option;
-
html_signature_value: string option;
-
}
-
-
(* Get the name (if specified) *)
-
let name t = t.name_value
-
-
(* Get the email address *)
-
let email t = t.email_value
-
-
(* Get the reply-to addresses (if any) *)
-
let reply_to t = t.reply_to_value
-
-
(* Get the bcc addresses (if any) *)
-
let bcc t = t.bcc_value
-
-
(* Get the plain text signature (if specified) *)
-
let text_signature t = t.text_signature_value
-
-
(* Get the HTML signature (if specified) *)
-
let html_signature t = t.html_signature_value
-
-
(* Create a new identity creation object *)
-
let v ?name ~email ?reply_to ?bcc ?text_signature ?html_signature () = {
-
name_value = name;
-
email_value = email;
-
reply_to_value = reply_to;
-
bcc_value = bcc;
-
text_signature_value = text_signature;
-
html_signature_value = html_signature;
-
}
-
-
(* Server response with info about the created identity *)
-
module Response = struct
-
type t = {
-
id_value: id;
-
may_delete_value: bool;
-
}
-
-
(* Get the server-assigned ID for the created identity *)
-
let id t = t.id_value
-
-
(* Check if this identity may be deleted *)
-
let may_delete t = t.may_delete_value
-
-
(* Create a new response object *)
-
let v ~id ~may_delete () = {
-
id_value = id;
-
may_delete_value = may_delete;
-
}
-
end
-
end
-
-
(* Identity object for update.
-
Patch object, specific structure not enforced here. *)
-
type update = patch_object
-
-
(* Server-set/computed info for updated identity.
-
Contains only changed server-set props. *)
-
module Update_response = struct
-
(* We use the same type as main identity *)
-
type identity_update = t
-
type t = identity_update
-
-
(* Convert to a full Identity object (contains only changed server-set props) *)
-
let to_identity t = (t : t :> t)
-
-
(* Create from a full Identity object *)
-
let of_identity t = (t : t :> t)
-
end
-282
jmap-email/jmap_mailbox.ml
···
-
(* JMAP Mailbox. *)
-
-
open Jmap.Types
-
open Jmap.Methods
-
-
(* Standard mailbox roles as defined in RFC 8621. *)
-
type role =
-
| Inbox (* Messages in the primary inbox *)
-
| Archive (* Archived messages *)
-
| Drafts (* Draft messages being composed *)
-
| Sent (* Messages that have been sent *)
-
| Trash (* Messages that have been deleted *)
-
| Junk (* Messages determined to be spam *)
-
| Important (* Messages deemed important *)
-
| Other of string (* Custom or non-standard role *)
-
| None (* No specific role assigned *)
-
-
(* Mailbox property identifiers. *)
-
type property =
-
| Id (* The id of the mailbox *)
-
| Name (* The name of the mailbox *)
-
| ParentId (* The id of the parent mailbox *)
-
| Role (* The role of the mailbox *)
-
| SortOrder (* The sort order of the mailbox *)
-
| TotalEmails (* The total number of emails in the mailbox *)
-
| UnreadEmails (* The number of unread emails in the mailbox *)
-
| TotalThreads (* The total number of threads in the mailbox *)
-
| UnreadThreads (* The number of unread threads in the mailbox *)
-
| MyRights (* The rights the user has for the mailbox *)
-
| IsSubscribed (* Whether the mailbox is subscribed to *)
-
| Other of string (* Any server-specific extension properties *)
-
-
(* Mailbox access rights. *)
-
type mailbox_rights = {
-
may_read_items : bool;
-
may_add_items : bool;
-
may_remove_items : bool;
-
may_set_seen : bool;
-
may_set_keywords : bool;
-
may_create_child : bool;
-
may_rename : bool;
-
may_delete : bool;
-
may_submit : bool;
-
}
-
-
(* Mailbox object. *)
-
type mailbox = {
-
mailbox_id : id; (* immutable, server-set *)
-
name : string;
-
parent_id : id option;
-
role : role option;
-
sort_order : uint; (* default: 0 *)
-
total_emails : uint; (* server-set *)
-
unread_emails : uint; (* server-set *)
-
total_threads : uint; (* server-set *)
-
unread_threads : uint; (* server-set *)
-
my_rights : mailbox_rights; (* server-set *)
-
is_subscribed : bool;
-
}
-
-
(* Mailbox object for creation.
-
Excludes server-set fields. *)
-
type mailbox_create = {
-
mailbox_create_name : string;
-
mailbox_create_parent_id : id option;
-
mailbox_create_role : role option;
-
mailbox_create_sort_order : uint option;
-
mailbox_create_is_subscribed : bool option;
-
}
-
-
(* Mailbox object for update.
-
Patch object, specific structure not enforced here. *)
-
type mailbox_update = patch_object
-
-
(* Server-set info for created mailbox. *)
-
type mailbox_created_info = {
-
mailbox_created_id : id;
-
mailbox_created_role : role option; (* If default used *)
-
mailbox_created_sort_order : uint; (* If default used *)
-
mailbox_created_total_emails : uint;
-
mailbox_created_unread_emails : uint;
-
mailbox_created_total_threads : uint;
-
mailbox_created_unread_threads : uint;
-
mailbox_created_my_rights : mailbox_rights;
-
mailbox_created_is_subscribed : bool; (* If default used *)
-
}
-
-
(* Server-set/computed info for updated mailbox. *)
-
type mailbox_updated_info = mailbox (* Contains only changed server-set props *)
-
-
(* FilterCondition for Mailbox/query. *)
-
type mailbox_filter_condition = {
-
filter_parent_id : id option option; (* Use option option for explicit null *)
-
filter_name : string option;
-
filter_role : role option option; (* Use option option for explicit null *)
-
filter_has_any_role : bool option;
-
filter_is_subscribed : bool option;
-
}
-
-
(* Role and Property Conversion Functions *)
-
-
(* Role conversion utilities *)
-
let role_to_string = function
-
| Inbox -> "inbox"
-
| Archive -> "archive"
-
| Drafts -> "drafts"
-
| Sent -> "sent"
-
| Trash -> "trash"
-
| Junk -> "junk"
-
| Important -> "important"
-
| Other s -> s
-
| None -> ""
-
-
let string_to_role = function
-
| "inbox" -> Inbox
-
| "archive" -> Archive
-
| "drafts" -> Drafts
-
| "sent" -> Sent
-
| "trash" -> Trash
-
| "junk" -> Junk
-
| "important" -> Important
-
| "" -> None
-
| s -> Other s
-
-
(* Property conversion utilities *)
-
let property_to_string = function
-
| Id -> "id"
-
| Name -> "name"
-
| ParentId -> "parentId"
-
| Role -> "role"
-
| SortOrder -> "sortOrder"
-
| TotalEmails -> "totalEmails"
-
| UnreadEmails -> "unreadEmails"
-
| TotalThreads -> "totalThreads"
-
| UnreadThreads -> "unreadThreads"
-
| MyRights -> "myRights"
-
| IsSubscribed -> "isSubscribed"
-
| Other s -> s
-
-
let string_to_property = function
-
| "id" -> Id
-
| "name" -> Name
-
| "parentId" -> ParentId
-
| "role" -> Role
-
| "sortOrder" -> SortOrder
-
| "totalEmails" -> TotalEmails
-
| "unreadEmails" -> UnreadEmails
-
| "totalThreads" -> TotalThreads
-
| "unreadThreads" -> UnreadThreads
-
| "myRights" -> MyRights
-
| "isSubscribed" -> IsSubscribed
-
| s -> Other s
-
-
(* Get a list of common properties useful for displaying mailboxes *)
-
let common_properties = [
-
Id; Name; ParentId; Role;
-
TotalEmails; UnreadEmails;
-
IsSubscribed
-
]
-
-
(* Get a list of all standard properties *)
-
let all_properties = [
-
Id; Name; ParentId; Role; SortOrder;
-
TotalEmails; UnreadEmails; TotalThreads; UnreadThreads;
-
MyRights; IsSubscribed
-
]
-
-
(* Check if a property is a count property (TotalEmails, UnreadEmails, etc.) *)
-
let is_count_property = function
-
| TotalEmails | UnreadEmails | TotalThreads | UnreadThreads -> true
-
| _ -> false
-
-
(* Mailbox Creation and Manipulation *)
-
-
(* Create a set of default rights with all permissions *)
-
let default_rights () = {
-
may_read_items = true;
-
may_add_items = true;
-
may_remove_items = true;
-
may_set_seen = true;
-
may_set_keywords = true;
-
may_create_child = true;
-
may_rename = true;
-
may_delete = true;
-
may_submit = true;
-
}
-
-
(* Create a set of read-only rights *)
-
let readonly_rights () = {
-
may_read_items = true;
-
may_add_items = false;
-
may_remove_items = false;
-
may_set_seen = false;
-
may_set_keywords = false;
-
may_create_child = false;
-
may_rename = false;
-
may_delete = false;
-
may_submit = false;
-
}
-
-
(* Create a new mailbox object with minimal required fields *)
-
let create ~name ?parent_id ?role ?sort_order ?is_subscribed () = {
-
mailbox_create_name = name;
-
mailbox_create_parent_id = parent_id;
-
mailbox_create_role = role;
-
mailbox_create_sort_order = sort_order;
-
mailbox_create_is_subscribed = is_subscribed;
-
}
-
-
(* Build a patch object for updating mailbox properties *)
-
let update ?name ?parent_id ?role ?sort_order ?is_subscribed () =
-
let patches = [] in
-
let patches =
-
match name with
-
| Some new_name -> ("name", `String new_name) :: patches
-
| None -> patches
-
in
-
let patches =
-
match parent_id with
-
| Some (Some pid) -> ("parentId", `String pid) :: patches
-
| Some None -> ("parentId", `Null) :: patches
-
| None -> patches
-
in
-
let patches =
-
match role with
-
| Some (Some r) -> ("role", `String (role_to_string r)) :: patches
-
| Some None -> ("role", `Null) :: patches
-
| None -> patches
-
in
-
let patches =
-
match sort_order with
-
| Some order -> ("sortOrder", `Int order) :: patches
-
| None -> patches
-
in
-
let patches =
-
match is_subscribed with
-
| Some subscribed -> ("isSubscribed", `Bool subscribed) :: patches
-
| None -> patches
-
in
-
patches
-
-
(* Get the list of standard role names and their string representations *)
-
let standard_role_names = [
-
(Inbox, "inbox");
-
(Archive, "archive");
-
(Drafts, "drafts");
-
(Sent, "sent");
-
(Trash, "trash");
-
(Junk, "junk");
-
(Important, "important");
-
(None, "");
-
]
-
-
(* Filter Construction *)
-
-
(* Create a filter to match mailboxes with a specific role *)
-
let filter_has_role role =
-
Filter.property_equals "role" (`String (role_to_string role))
-
-
(* Create a filter to match mailboxes with no role *)
-
let filter_has_no_role () =
-
Filter.property_equals "role" `Null
-
-
(* Create a filter to match mailboxes that are child of a given parent *)
-
let filter_has_parent parent_id =
-
Filter.property_equals "parentId" (`String parent_id)
-
-
(* Create a filter to match mailboxes at the root level (no parent) *)
-
let filter_is_root () =
-
Filter.property_equals "parentId" `Null
-
-
(* Create a filter to match subscribed mailboxes *)
-
let filter_is_subscribed () =
-
Filter.property_equals "isSubscribed" (`Bool true)
-
-
(* Create a filter to match unsubscribed mailboxes *)
-
let filter_is_not_subscribed () =
-
Filter.property_equals "isSubscribed" (`Bool false)
-
-
(* Create a filter to match mailboxes by name (using case-insensitive substring matching) *)
-
let filter_name_contains name =
-
Filter.text_contains "name" name
-9
jmap-email/jmap_search_snippet.ml
···
-
(* JMAP Search Snippet. *)
-
-
(* SearchSnippet object.
-
Note: Does not have an 'id' property. *)
-
type t = {
-
email_id : Jmap.Types.id;
-
subject : string option;
-
preview : string option;
-
}
-125
jmap-email/jmap_submission.ml
···
-
(* JMAP Email Submission. *)
-
-
open Jmap.Types
-
open Jmap.Methods
-
-
(* Address object for Envelope. *)
-
type envelope_address = {
-
env_addr_email : string;
-
env_addr_parameters : Yojson.Safe.t string_map option;
-
}
-
-
(* Envelope object. *)
-
type envelope = {
-
env_mail_from : envelope_address;
-
env_rcpt_to : envelope_address list;
-
}
-
-
(* Delivery status for a recipient. *)
-
type delivery_status = {
-
delivery_smtp_reply : string;
-
delivery_delivered : [ `Queued | `Yes | `No | `Unknown ];
-
delivery_displayed : [ `Yes | `Unknown ];
-
}
-
-
(* EmailSubmission object. *)
-
type email_submission = {
-
email_sub_id : id; (* immutable, server-set *)
-
identity_id : id; (* immutable *)
-
email_id : id; (* immutable *)
-
thread_id : id; (* immutable, server-set *)
-
envelope : envelope option; (* immutable *)
-
send_at : utc_date; (* immutable, server-set *)
-
undo_status : [ `Pending | `Final | `Canceled ];
-
delivery_status : delivery_status string_map option; (* server-set *)
-
dsn_blob_ids : id list; (* server-set *)
-
mdn_blob_ids : id list; (* server-set *)
-
}
-
-
(* EmailSubmission object for creation.
-
Excludes server-set fields. *)
-
type email_submission_create = {
-
email_sub_create_identity_id : id;
-
email_sub_create_email_id : id;
-
email_sub_create_envelope : envelope option;
-
}
-
-
(* EmailSubmission object for update.
-
Only undoStatus can be updated (to 'canceled'). *)
-
type email_submission_update = patch_object
-
-
(* Server-set info for created email submission. *)
-
type email_submission_created_info = {
-
email_sub_created_id : id;
-
email_sub_created_thread_id : id;
-
email_sub_created_send_at : utc_date;
-
}
-
-
(* Server-set/computed info for updated email submission. *)
-
type email_submission_updated_info = email_submission (* Contains only changed server-set props *)
-
-
(* FilterCondition for EmailSubmission/query. *)
-
type email_submission_filter_condition = {
-
filter_identity_ids : id list option;
-
filter_email_ids : id list option;
-
filter_thread_ids : id list option;
-
filter_undo_status : [ `Pending | `Final | `Canceled ] option;
-
filter_before : utc_date option;
-
filter_after : utc_date option;
-
}
-
-
(* EmailSubmission/get: Args type (specialized from ['record Get_args.t]). *)
-
module Email_submission_get_args = struct
-
type t = email_submission Get_args.t
-
end
-
-
(* EmailSubmission/get: Response type (specialized from ['record Get_response.t]). *)
-
module Email_submission_get_response = struct
-
type t = email_submission Get_response.t
-
end
-
-
(* EmailSubmission/changes: Args type (specialized from [Changes_args.t]). *)
-
module Email_submission_changes_args = struct
-
type t = Changes_args.t
-
end
-
-
(* EmailSubmission/changes: Response type (specialized from [Changes_response.t]). *)
-
module Email_submission_changes_response = struct
-
type t = Changes_response.t
-
end
-
-
(* EmailSubmission/query: Args type (specialized from [Query_args.t]). *)
-
module Email_submission_query_args = struct
-
type t = Query_args.t
-
end
-
-
(* EmailSubmission/query: Response type (specialized from [Query_response.t]). *)
-
module Email_submission_query_response = struct
-
type t = Query_response.t
-
end
-
-
(* EmailSubmission/queryChanges: Args type (specialized from [Query_changes_args.t]). *)
-
module Email_submission_query_changes_args = struct
-
type t = Query_changes_args.t
-
end
-
-
(* EmailSubmission/queryChanges: Response type (specialized from [Query_changes_response.t]). *)
-
module Email_submission_query_changes_response = struct
-
type t = Query_changes_response.t
-
end
-
-
(* EmailSubmission/set: Args type (specialized from [('c, 'u) set_args]).
-
Includes onSuccess arguments. *)
-
type email_submission_set_args = {
-
set_account_id : id;
-
set_if_in_state : string option;
-
set_create : email_submission_create id_map option;
-
set_update : email_submission_update id_map option;
-
set_destroy : id list option;
-
set_on_success_destroy_email : id list option;
-
}
-
-
(* EmailSubmission/set: Response type (specialized from [('c, 'u) Set_response.t]). *)
-
module Email_submission_set_response = struct
-
type t = (email_submission_created_info, email_submission_updated_info) Set_response.t
-
end
-19
jmap-email/jmap_thread.ml
···
-
(* JMAP Thread. *)
-
-
open Jmap.Types
-
-
(* Thread object. *)
-
module Thread = struct
-
type t = {
-
id_value: id;
-
email_ids_value: id list;
-
}
-
-
let id t = t.id_value
-
let email_ids t = t.email_ids_value
-
-
let v ~id ~email_ids = {
-
id_value = id;
-
email_ids_value = email_ids;
-
}
-
end
-103
jmap-email/jmap_vacation.ml
···
-
(* JMAP Vacation Response. *)
-
-
open Jmap.Types
-
open Jmap.Methods
-
open Jmap.Error
-
-
(* VacationResponse object.
-
Note: id is always "singleton". *)
-
module Vacation_response = struct
-
type t = {
-
id_value: id;
-
is_enabled_value: bool;
-
from_date_value: utc_date option;
-
to_date_value: utc_date option;
-
subject_value: string option;
-
text_body_value: string option;
-
html_body_value: string option;
-
}
-
-
(* Id of the vacation response (immutable, server-set, MUST be "singleton") *)
-
let id t = t.id_value
-
let is_enabled t = t.is_enabled_value
-
let from_date t = t.from_date_value
-
let to_date t = t.to_date_value
-
let subject t = t.subject_value
-
let text_body t = t.text_body_value
-
let html_body t = t.html_body_value
-
-
let v ~id ~is_enabled ?from_date ?to_date ?subject ?text_body ?html_body () = {
-
id_value = id;
-
is_enabled_value = is_enabled;
-
from_date_value = from_date;
-
to_date_value = to_date;
-
subject_value = subject;
-
text_body_value = text_body;
-
html_body_value = html_body;
-
}
-
end
-
-
(* VacationResponse object for update.
-
Patch object, specific structure not enforced here. *)
-
type vacation_response_update = patch_object
-
-
(* VacationResponse/get: Args type (specialized from ['record get_args]). *)
-
module Vacation_response_get_args = struct
-
type t = Vacation_response.t Get_args.t
-
-
let v ~account_id ?ids ?properties () =
-
Get_args.v ~account_id ?ids ?properties ()
-
end
-
-
(* VacationResponse/get: Response type (specialized from ['record get_response]). *)
-
module Vacation_response_get_response = struct
-
type t = Vacation_response.t Get_response.t
-
-
let v ~account_id ~state ~list ~not_found () =
-
Get_response.v ~account_id ~state ~list ~not_found ()
-
end
-
-
(* VacationResponse/set: Args type.
-
Only allows update, id must be "singleton". *)
-
module Vacation_response_set_args = struct
-
type t = {
-
account_id_value: id;
-
if_in_state_value: string option;
-
update_value: vacation_response_update id_map option;
-
}
-
-
let account_id t = t.account_id_value
-
let if_in_state t = t.if_in_state_value
-
let update t = t.update_value
-
-
let v ~account_id ?if_in_state ?update () = {
-
account_id_value = account_id;
-
if_in_state_value = if_in_state;
-
update_value = update;
-
}
-
end
-
-
(* VacationResponse/set: Response type. *)
-
module Vacation_response_set_response = struct
-
type t = {
-
account_id_value: id;
-
old_state_value: string option;
-
new_state_value: string;
-
updated_value: Vacation_response.t option id_map option;
-
not_updated_value: Set_error.t id_map option;
-
}
-
-
let account_id t = t.account_id_value
-
let old_state t = t.old_state_value
-
let new_state t = t.new_state_value
-
let updated t = t.updated_value
-
let not_updated t = t.not_updated_value
-
-
let v ~account_id ?old_state ~new_state ?updated ?not_updated () = {
-
account_id_value = account_id;
-
old_state_value = old_state;
-
new_state_value = new_state;
-
updated_value = updated;
-
not_updated_value = not_updated;
-
}
-
end
-672
jmap-unix/jmap_unix.ml
···
-
(* Unix-specific JMAP client implementation interface. *)
-
-
open Jmap
-
open Jmap.Types
-
open Jmap.Error
-
open Jmap.Session
-
open Jmap.Wire
-
-
(* Configuration options for a JMAP client context *)
-
type client_config = {
-
connect_timeout : float option; (* Connection timeout in seconds *)
-
request_timeout : float option; (* Request timeout in seconds *)
-
max_concurrent_requests : int option; (* Maximum concurrent requests *)
-
max_request_size : int option; (* Maximum request size in bytes *)
-
user_agent : string option; (* User-Agent header value *)
-
authentication_header : string option; (* Custom Authentication header name *)
-
}
-
-
(* Authentication method options *)
-
type auth_method =
-
| Basic of string * string (* Basic auth with username and password *)
-
| Bearer of string (* Bearer token auth *)
-
| Custom of (string * string) (* Custom header name and value *)
-
| Session_cookie of (string * string) (* Session cookie name and value *)
-
| No_auth (* No authentication *)
-
-
(* The internal state of a JMAP client connection *)
-
type context = {
-
config: client_config;
-
mutable session_url: Uri.t option;
-
mutable session: Session.t option;
-
mutable auth: auth_method;
-
}
-
-
(* Represents an active EventSource connection *)
-
type event_source_connection = {
-
event_url: Uri.t;
-
mutable is_connected: bool;
-
}
-
-
(* A request builder for constructing and sending JMAP requests *)
-
type request_builder = {
-
ctx: context;
-
mutable using: string list;
-
mutable method_calls: Invocation.t list;
-
}
-
-
(* Create default configuration options *)
-
let default_config () = {
-
connect_timeout = Some 30.0;
-
request_timeout = Some 300.0;
-
max_concurrent_requests = Some 4;
-
max_request_size = Some (1024 * 1024 * 10); (* 10 MB *)
-
user_agent = Some "OCaml JMAP Unix Client/1.0";
-
authentication_header = None;
-
}
-
-
(* Create a client context with the specified configuration *)
-
let create_client ?(config = default_config ()) () = {
-
config;
-
session_url = None;
-
session = None;
-
auth = No_auth;
-
}
-
-
(* Mock implementation for the Unix connection *)
-
let connect ctx ?session_url ?username ~host ?port ?auth_method () =
-
(* In a real implementation, this would use Unix HTTP functions *)
-
let auth = match auth_method with
-
| Some auth -> auth
-
| None -> No_auth
-
in
-
-
(* Store the auth method for future requests *)
-
ctx.auth <- auth;
-
-
(* Set session URL, either directly or after discovery *)
-
let session_url = match session_url with
-
| Some url -> url
-
| None ->
-
(* In a real implementation, this would perform RFC 8620 discovery *)
-
let proto = "https" in
-
let host_with_port = match port with
-
| Some p -> host ^ ":" ^ string_of_int p
-
| None -> host
-
in
-
Uri.of_string (proto ^ "://" ^ host_with_port ^ "/.well-known/jmap")
-
in
-
ctx.session_url <- Some session_url;
-
-
(* Create a mock session object for this example *)
-
let caps = Hashtbl.create 4 in
-
Hashtbl.add caps Jmap.capability_core (`Assoc []);
-
-
let accounts = Hashtbl.create 1 in
-
let acct = Account.v
-
~name:"user@example.com"
-
~is_personal:true
-
~is_read_only:false
-
()
-
in
-
Hashtbl.add accounts "u1" acct;
-
-
let primary = Hashtbl.create 1 in
-
Hashtbl.add primary Jmap.capability_core "u1";
-
-
let api_url =
-
Uri.of_string ("https://" ^ host ^ "/api/jmap")
-
in
-
-
let session = Session.v
-
~capabilities:caps
-
~accounts
-
~primary_accounts:primary
-
~username:"user@example.com"
-
~api_url
-
~download_url:(Uri.of_string ("https://" ^ host ^ "/download/{accountId}/{blobId}"))
-
~upload_url:(Uri.of_string ("https://" ^ host ^ "/upload/{accountId}"))
-
~event_source_url:(Uri.of_string ("https://" ^ host ^ "/eventsource"))
-
~state:"1"
-
()
-
in
-
-
ctx.session <- Some session;
-
Ok (ctx, session)
-
-
(* Create a request builder for constructing a JMAP request *)
-
let build ctx = {
-
ctx;
-
using = [Jmap.capability_core]; (* Default to core capability *)
-
method_calls = [];
-
}
-
-
(* Set the using capabilities for a request *)
-
let using builder capabilities =
-
{ builder with using = capabilities }
-
-
(* Add a method call to a request builder *)
-
let add_method_call builder name args id =
-
let call = Invocation.v
-
~method_name:name
-
~arguments:args
-
~method_call_id:id
-
()
-
in
-
{ builder with method_calls = builder.method_calls @ [call] }
-
-
(* Create a reference to a previous method call result *)
-
let create_reference result_of name =
-
Jmap.Wire.Result_reference.v
-
~result_of
-
~name
-
~path:"" (* In a real implementation, this would include a JSON pointer *)
-
()
-
-
(* Execute a request and return the response *)
-
let execute builder =
-
match builder.ctx.session with
-
| None -> Error (protocol_error "No active session")
-
| Some session ->
-
(* In a real implementation, this would create and send an HTTP request *)
-
-
(* Create a mock response for this implementation *)
-
let results = List.map (fun call ->
-
let method_name = Invocation.method_name call in
-
let call_id = Invocation.method_call_id call in
-
if method_name = "Core/echo" then
-
(* Echo method implementation *)
-
Ok call
-
else
-
(* For other methods, return a method error *)
-
Error (
-
Method_error.v
-
~description:(Method_error_description.v
-
~description:"Method not implemented in mock"
-
())
-
`ServerUnavailable,
-
"Mock implementation"
-
)
-
) builder.method_calls in
-
-
let resp = Response.v
-
~method_responses:results
-
~session_state:(session |> Session.state)
-
()
-
in
-
Ok resp
-
-
(* Perform a JMAP API request *)
-
let request ctx req =
-
match ctx.session_url, ctx.session with
-
| None, _ -> Error (protocol_error "No session URL configured")
-
| _, None -> Error (protocol_error "No active session")
-
| Some url, Some session ->
-
(* In a real implementation, this would serialize the request and send it *)
-
-
(* Mock response implementation *)
-
let method_calls = Request.method_calls req in
-
let results = List.map (fun call ->
-
let method_name = Invocation.method_name call in
-
let call_id = Invocation.method_call_id call in
-
if method_name = "Core/echo" then
-
(* Echo method implementation *)
-
Ok call
-
else
-
(* For other methods, return a method error *)
-
Error (
-
Method_error.v
-
~description:(Method_error_description.v
-
~description:"Method not implemented in mock"
-
())
-
`ServerUnavailable,
-
"Mock implementation"
-
)
-
) method_calls in
-
-
let resp = Response.v
-
~method_responses:results
-
~session_state:(session |> Session.state)
-
()
-
in
-
Ok resp
-
-
(* Upload binary data *)
-
let upload ctx ~account_id ~content_type ~data_stream =
-
match ctx.session with
-
| None -> Error (protocol_error "No active session")
-
| Some session ->
-
(* In a real implementation, would upload the data stream *)
-
-
(* Mock success response *)
-
let response = Jmap.Binary.Upload_response.v
-
~account_id
-
~blob_id:"b123456"
-
~type_:content_type
-
~size:1024 (* Mock size *)
-
()
-
in
-
Ok response
-
-
(* Download binary data *)
-
let download ctx ~account_id ~blob_id ?content_type ?name =
-
match ctx.session with
-
| None -> Error (protocol_error "No active session")
-
| Some session ->
-
(* In a real implementation, would download the data and return a stream *)
-
-
(* Mock data stream - in real code, this would be read from the HTTP response *)
-
let mock_data = "This is mock downloaded data for blob " ^ blob_id in
-
let seq = Seq.cons mock_data Seq.empty in
-
Ok seq
-
-
(* Copy blobs between accounts *)
-
let copy_blobs ctx ~from_account_id ~account_id ~blob_ids =
-
match ctx.session with
-
| None -> Error (protocol_error "No active session")
-
| Some session ->
-
(* In a real implementation, would perform server-side copy *)
-
-
(* Mock success response with first blob copied and second failed *)
-
let copied = Hashtbl.create 1 in
-
Hashtbl.add copied (List.hd blob_ids) "b999999";
-
-
let response = Jmap.Binary.Blob_copy_response.v
-
~from_account_id
-
~account_id
-
~copied
-
()
-
in
-
Ok response
-
-
(* Connect to the EventSource for push notifications *)
-
let connect_event_source ctx ?types ?close_after ?ping =
-
match ctx.session with
-
| None -> Error (protocol_error "No active session")
-
| Some session ->
-
(* In a real implementation, would connect to EventSource URL *)
-
-
(* Create mock connection *)
-
let event_url = Session.event_source_url session in
-
let conn = { event_url; is_connected = true } in
-
-
(* Create a mock event sequence *)
-
let mock_state_change =
-
let changed = Hashtbl.create 1 in
-
let account_id = "u1" in
-
let state_map = Hashtbl.create 2 in
-
Hashtbl.add state_map "Email" "s123";
-
Hashtbl.add state_map "Mailbox" "s456";
-
Hashtbl.add changed account_id state_map;
-
-
Push.State_change.v ~changed ()
-
in
-
-
let ping_data =
-
Push.Event_source_ping_data.v ~interval:30 ()
-
in
-
-
(* Create a sequence with one state event and one ping event *)
-
let events = Seq.cons (`State mock_state_change)
-
(Seq.cons (`Ping ping_data) Seq.empty) in
-
-
Ok (conn, events)
-
-
(* Create a websocket connection for JMAP over WebSocket *)
-
let connect_websocket ctx =
-
match ctx.session with
-
| None -> Error (protocol_error "No active session")
-
| Some session ->
-
(* In a real implementation, would connect via WebSocket *)
-
-
(* Mock connection *)
-
let event_url = Session.api_url session in
-
let conn = { event_url; is_connected = true } in
-
Ok conn
-
-
(* Send a message over a websocket connection *)
-
let websocket_send conn req =
-
if not conn.is_connected then
-
Error (protocol_error "WebSocket not connected")
-
else
-
(* In a real implementation, would send over WebSocket *)
-
-
(* Mock response (same as request function) *)
-
let method_calls = Request.method_calls req in
-
let results = List.map (fun call ->
-
let method_name = Invocation.method_name call in
-
let call_id = Invocation.method_call_id call in
-
if method_name = "Core/echo" then
-
Ok call
-
else
-
Error (
-
Method_error.v
-
~description:(Method_error_description.v
-
~description:"Method not implemented in mock"
-
())
-
`ServerUnavailable,
-
"Mock implementation"
-
)
-
) method_calls in
-
-
let resp = Response.v
-
~method_responses:results
-
~session_state:"1"
-
()
-
in
-
Ok resp
-
-
(* Close an EventSource or WebSocket connection *)
-
let close_connection conn =
-
if not conn.is_connected then
-
Error (protocol_error "Connection already closed")
-
else begin
-
conn.is_connected <- false;
-
Ok ()
-
end
-
-
(* Close the JMAP connection context *)
-
let close ctx =
-
ctx.session <- None;
-
ctx.session_url <- None;
-
Ok ()
-
-
(* Helper functions for common tasks *)
-
-
(* Helper to get a single object by ID *)
-
let get_object ctx ~method_name ~account_id ~object_id ?properties =
-
let properties_param = match properties with
-
| Some props -> `List (List.map (fun p -> `String p) props)
-
| None -> `Null
-
in
-
-
let args = `Assoc [
-
("accountId", `String account_id);
-
("ids", `List [`String object_id]);
-
("properties", properties_param);
-
] in
-
-
let request_builder = build ctx
-
|> add_method_call method_name args "r1"
-
in
-
-
match execute request_builder with
-
| Error e -> Error e
-
| Ok response ->
-
(* Find the method response and extract the list with the object *)
-
match response |> Response.method_responses with
-
| [Ok invocation] when Invocation.method_name invocation = method_name ^ "/get" ->
-
let args = Invocation.arguments invocation in
-
begin match Yojson.Safe.Util.member "list" args with
-
| `List [obj] -> Ok obj
-
| _ -> Error (protocol_error "Object not found or invalid response")
-
end
-
| _ ->
-
Error (protocol_error "Method response not found")
-
-
(* Helper to set up the connection with minimal options *)
-
let quick_connect ~host ~username ~password =
-
let ctx = create_client () in
-
connect ctx ~host ~auth_method:(Basic(username, password)) ()
-
-
(* Perform a Core/echo request to test connectivity *)
-
let echo ctx ?data () =
-
let data = match data with
-
| Some d -> d
-
| None -> `Assoc [("hello", `String "world")]
-
in
-
-
let request_builder = build ctx
-
|> add_method_call "Core/echo" data "echo1"
-
in
-
-
match execute request_builder with
-
| Error e -> Error e
-
| Ok response ->
-
(* Find the Core/echo response and extract the echoed data *)
-
match response |> Response.method_responses with
-
| [Ok invocation] when Invocation.method_name invocation = "Core/echo" ->
-
Ok (Invocation.arguments invocation)
-
| _ ->
-
Error (protocol_error "Echo response not found")
-
-
(* High-level email operations *)
-
module Email = struct
-
open Jmap_email.Types
-
-
(* Get an email by ID *)
-
let get_email ctx ~account_id ~email_id ?properties () =
-
let props = match properties with
-
| Some p -> p
-
| None -> List.map email_property_to_string detailed_email_properties
-
in
-
-
match get_object ctx ~method_name:"Email/get" ~account_id ~object_id:email_id ~properties:props with
-
| Error e -> Error e
-
| Ok json ->
-
(* In a real implementation, would parse the JSON into an Email.t structure *)
-
let mock_email = Email.create
-
~id:email_id
-
~thread_id:"t12345"
-
~mailbox_ids:(let h = Hashtbl.create 1 in Hashtbl.add h "inbox" true; h)
-
~keywords:(Keywords.of_list [Keywords.Seen])
-
~subject:"Mock Email Subject"
-
~preview:"This is a mock email..."
-
~from:[Email_address.v ~name:"Sender Name" ~email:"sender@example.com" ()]
-
~to_:[Email_address.v ~email:"recipient@example.com" ()]
-
()
-
in
-
Ok mock_email
-
-
(* Search for emails using a filter *)
-
let search_emails ctx ~account_id ~filter ?sort ?limit ?position ?properties () =
-
(* Create the query args *)
-
let args = `Assoc [
-
("accountId", `String account_id);
-
("filter", Jmap.Methods.Filter.to_json filter);
-
("sort", match sort with
-
| Some s -> `List [] (* Would convert sort params *)
-
| None -> `List [`Assoc [("property", `String "receivedAt"); ("isAscending", `Bool false)]]);
-
("limit", match limit with
-
| Some l -> `Int l
-
| None -> `Int 20);
-
("position", match position with
-
| Some p -> `Int p
-
| None -> `Int 0);
-
] in
-
-
let request_builder = build ctx
-
|> add_method_call "Email/query" args "q1"
-
in
-
-
(* If properties were provided, add a Email/get method call as well *)
-
let request_builder = match properties with
-
| Some _ ->
-
let get_args = `Assoc [
-
("accountId", `String account_id);
-
("#ids", `Assoc [
-
("resultOf", `String "q1");
-
("name", `String "Email/query");
-
("path", `String "/ids")
-
]);
-
("properties", match properties with
-
| Some p -> `List (List.map (fun prop -> `String prop) p)
-
| None -> `Null);
-
] in
-
add_method_call request_builder "Email/get" get_args "g1"
-
| None -> request_builder
-
in
-
-
match execute request_builder with
-
| Error e -> Error e
-
| Ok response ->
-
(* Find the query response and extract the IDs *)
-
match Response.method_responses response with
-
| [Ok q_inv; Ok g_inv]
-
when Invocation.method_name q_inv = "Email/query"
-
&& Invocation.method_name g_inv = "Email/get" ->
-
-
(* Extract IDs from query response *)
-
let q_args = Invocation.arguments q_inv in
-
let ids = match Yojson.Safe.Util.member "ids" q_args with
-
| `List l -> List.map Yojson.Safe.Util.to_string l
-
| _ -> []
-
in
-
-
(* Extract emails from get response *)
-
let g_args = Invocation.arguments g_inv in
-
(* In a real implementation, would parse each email in the list *)
-
let emails = List.map (fun id ->
-
Email.create
-
~id
-
~thread_id:("t" ^ id)
-
~subject:(Printf.sprintf "Mock Email %s" id)
-
()
-
) ids in
-
-
Ok (ids, Some emails)
-
-
| [Ok q_inv] when Invocation.method_name q_inv = "Email/query" ->
-
(* If only query was performed (no properties requested) *)
-
let q_args = Invocation.arguments q_inv in
-
let ids = match Yojson.Safe.Util.member "ids" q_args with
-
| `List l -> List.map Yojson.Safe.Util.to_string l
-
| _ -> []
-
in
-
-
Ok (ids, None)
-
-
| _ ->
-
Error (protocol_error "Query response not found")
-
-
(* Mark multiple emails with a keyword *)
-
let mark_emails ctx ~account_id ~email_ids ~keyword () =
-
(* Create the set args with a patch to add the keyword *)
-
let keyword_patch = Jmap_email.Keyword_ops.add_keyword_patch keyword in
-
-
(* Create patches map for each email *)
-
let update = Hashtbl.create (List.length email_ids) in
-
List.iter (fun id ->
-
Hashtbl.add update id keyword_patch
-
) email_ids;
-
-
let args = `Assoc [
-
("accountId", `String account_id);
-
("update", `Assoc (
-
List.map (fun id ->
-
(id, `Assoc (List.map (fun (path, value) ->
-
(path, value)
-
) keyword_patch))
-
) email_ids
-
));
-
] in
-
-
let request_builder = build ctx
-
|> add_method_call "Email/set" args "s1"
-
in
-
-
match execute request_builder with
-
| Error e -> Error e
-
| Ok response ->
-
(* In a real implementation, would check for errors *)
-
Ok ()
-
-
(* Mark emails as seen/read *)
-
let mark_as_seen ctx ~account_id ~email_ids () =
-
mark_emails ctx ~account_id ~email_ids ~keyword:Keywords.Seen ()
-
-
(* Mark emails as unseen/unread *)
-
let mark_as_unseen ctx ~account_id ~email_ids () =
-
let keyword_patch = Jmap_email.Keyword_ops.mark_unseen_patch () in
-
-
(* Create patches map for each email *)
-
let update = Hashtbl.create (List.length email_ids) in
-
List.iter (fun id ->
-
Hashtbl.add update id keyword_patch
-
) email_ids;
-
-
let args = `Assoc [
-
("accountId", `String account_id);
-
("update", `Assoc (
-
List.map (fun id ->
-
(id, `Assoc (List.map (fun (path, value) ->
-
(path, value)
-
) keyword_patch))
-
) email_ids
-
));
-
] in
-
-
let request_builder = build ctx
-
|> add_method_call "Email/set" args "s1"
-
in
-
-
match execute request_builder with
-
| Error e -> Error e
-
| Ok _response -> Ok ()
-
-
(* Move emails to a different mailbox *)
-
let move_emails ctx ~account_id ~email_ids ~mailbox_id ?remove_from_mailboxes () =
-
(* Create patch to add to destination mailbox *)
-
let add_patch = [("mailboxIds/" ^ mailbox_id, `Bool true)] in
-
-
(* If remove_from_mailboxes is specified, add patches to remove *)
-
let remove_patch = match remove_from_mailboxes with
-
| Some mailboxes ->
-
List.map (fun mbx -> ("mailboxIds/" ^ mbx, `Null)) mailboxes
-
| None -> []
-
in
-
-
(* Combine patches *)
-
let patches = add_patch @ remove_patch in
-
-
(* Create patches map for each email *)
-
let update = Hashtbl.create (List.length email_ids) in
-
List.iter (fun id ->
-
Hashtbl.add update id patches
-
) email_ids;
-
-
let args = `Assoc [
-
("accountId", `String account_id);
-
("update", `Assoc (
-
List.map (fun id ->
-
(id, `Assoc (List.map (fun (path, value) ->
-
(path, value)
-
) patches))
-
) email_ids
-
));
-
] in
-
-
let request_builder = build ctx
-
|> add_method_call "Email/set" args "s1"
-
in
-
-
match execute request_builder with
-
| Error e -> Error e
-
| Ok _response -> Ok ()
-
-
(* Import an RFC822 message *)
-
let import_email ctx ~account_id ~rfc822 ~mailbox_ids ?keywords ?received_at () =
-
(* In a real implementation, would first upload the message as a blob *)
-
let mock_blob_id = "b9876" in
-
-
(* Create the Email/import call *)
-
let args = `Assoc [
-
("accountId", `String account_id);
-
("emails", `Assoc [
-
("msg1", `Assoc [
-
("blobId", `String mock_blob_id);
-
("mailboxIds", `Assoc (
-
List.map (fun id -> (id, `Bool true)) mailbox_ids
-
));
-
("keywords", match keywords with
-
| Some kws ->
-
`Assoc (List.map (fun k ->
-
(Types.Keywords.to_string k, `Bool true)) kws)
-
| None -> `Null);
-
("receivedAt", match received_at with
-
| Some d -> `String (string_of_float d) (* Would format as RFC3339 *)
-
| None -> `Null);
-
])
-
]);
-
] in
-
-
let request_builder = build ctx
-
|> add_method_call "Email/import" args "i1"
-
in
-
-
match execute request_builder with
-
| Error e -> Error e
-
| Ok response ->
-
(* In a real implementation, would extract the created ID *)
-
Ok "e12345"
-
end
-45
jmap/jmap.ml
···
-
(* JMAP Core Protocol Library Interface (RFC 8620) *)
-
-
module Types = Jmap_types
-
module Error = Jmap_error
-
module Wire = Jmap_wire
-
module Session = Jmap_session
-
module Methods = Jmap_methods
-
module Binary = Jmap_binary
-
module Push = Jmap_push
-
-
(* Capability URI for JMAP Core. *)
-
let capability_core = "urn:ietf:params:jmap:core"
-
-
(* Check if a session supports a given capability. *)
-
let supports_capability session capability =
-
let caps = Session.Session.capabilities session in
-
Hashtbl.mem caps capability
-
-
(* Get the primary account ID for a given capability. *)
-
let get_primary_account session capability =
-
let primary_accounts = Session.Session.primary_accounts session in
-
match Hashtbl.find_opt primary_accounts capability with
-
| Some account_id -> Ok account_id
-
| None -> Error (Error.protocol_error ("No primary account for capability: " ^ capability))
-
-
(* Get the download URL for a blob. *)
-
let get_download_url session ~account_id ~blob_id ?name ?content_type () =
-
let base_url = Session.Session.download_url session in
-
let url_str = Uri.to_string base_url in
-
let url_str = url_str ^ "/accounts/" ^ account_id ^ "/blobs/" ^ blob_id in
-
let url = Uri.of_string url_str in
-
let url = match name with
-
| Some name -> Uri.add_query_param url ("name", [name])
-
| None -> url
-
in
-
match content_type with
-
| Some ct -> Uri.add_query_param url ("type", [ct])
-
| None -> url
-
-
(* Get the upload URL for a blob. *)
-
let get_upload_url session ~account_id =
-
let base_url = Session.Session.upload_url session in
-
let url_str = Uri.to_string base_url in
-
let url_str = url_str ^ "/accounts/" ^ account_id in
-
Uri.of_string url_str
-56
jmap/jmap_binary.ml
···
-
(* JMAP Binary Data Handling. *)
-
-
open Jmap_types
-
open Jmap_error
-
-
(* Response from uploading binary data. *)
-
module Upload_response = struct
-
type t = {
-
account_id: id;
-
blob_id: id;
-
type_: string;
-
size: uint;
-
}
-
-
let account_id t = t.account_id
-
let blob_id t = t.blob_id
-
let type_ t = t.type_
-
let size t = t.size
-
-
let v ~account_id ~blob_id ~type_ ~size () =
-
{ account_id; blob_id; type_; size }
-
end
-
-
(* Arguments for Blob/copy. *)
-
module Blob_copy_args = struct
-
type t = {
-
from_account_id: id;
-
account_id: id;
-
blob_ids: id list;
-
}
-
-
let from_account_id t = t.from_account_id
-
let account_id t = t.account_id
-
let blob_ids t = t.blob_ids
-
-
let v ~from_account_id ~account_id ~blob_ids () =
-
{ from_account_id; account_id; blob_ids }
-
end
-
-
(* Response for Blob/copy. *)
-
module Blob_copy_response = struct
-
type t = {
-
from_account_id: id;
-
account_id: id;
-
copied: id id_map option;
-
not_copied: Set_error.t id_map option;
-
}
-
-
let from_account_id t = t.from_account_id
-
let account_id t = t.account_id
-
let copied t = t.copied
-
let not_copied t = t.not_copied
-
-
let v ~from_account_id ~account_id ?copied ?not_copied () =
-
{ from_account_id; account_id; copied; not_copied }
-
end
-266
jmap/jmap_error.ml
···
-
(* JMAP Error Types. *)
-
-
open Jmap_types
-
-
(* Standard Method-level error types. *)
-
type method_error_type = [
-
| `ServerUnavailable
-
| `ServerFail
-
| `ServerPartialFail
-
| `UnknownMethod
-
| `InvalidArguments
-
| `InvalidResultReference
-
| `Forbidden
-
| `AccountNotFound
-
| `AccountNotSupportedByMethod
-
| `AccountReadOnly
-
| `RequestTooLarge
-
| `CannotCalculateChanges
-
| `StateMismatch
-
| `AnchorNotFound
-
| `UnsupportedSort
-
| `UnsupportedFilter
-
| `TooManyChanges
-
| `FromAccountNotFound
-
| `FromAccountNotSupportedByMethod
-
| `Other_method_error of string
-
]
-
-
(* Standard SetError types. *)
-
type set_error_type = [
-
| `Forbidden
-
| `OverQuota
-
| `TooLarge
-
| `RateLimit
-
| `NotFound
-
| `InvalidPatch
-
| `WillDestroy
-
| `InvalidProperties
-
| `Singleton
-
| `AlreadyExists (* From /copy *)
-
| `MailboxHasChild (* RFC 8621 *)
-
| `MailboxHasEmail (* RFC 8621 *)
-
| `BlobNotFound (* RFC 8621 *)
-
| `TooManyKeywords (* RFC 8621 *)
-
| `TooManyMailboxes (* RFC 8621 *)
-
| `InvalidEmail (* RFC 8621 *)
-
| `TooManyRecipients (* RFC 8621 *)
-
| `NoRecipients (* RFC 8621 *)
-
| `InvalidRecipients (* RFC 8621 *)
-
| `ForbiddenMailFrom (* RFC 8621 *)
-
| `ForbiddenFrom (* RFC 8621 *)
-
| `ForbiddenToSend (* RFC 8621 *)
-
| `CannotUnsend (* RFC 8621 *)
-
| `Other_set_error of string (* For future or custom errors *)
-
]
-
-
(* Primary error type that can represent all JMAP errors *)
-
type error =
-
| Transport of string (* Network/HTTP-level error *)
-
| Parse of string (* JSON parsing error *)
-
| Protocol of string (* JMAP protocol error *)
-
| Problem of string (* Problem Details object error *)
-
| Method of method_error_type * string option (* Method error with optional description *)
-
| SetItem of id * set_error_type * string option (* Error for a specific item in a /set operation *)
-
| Auth of string (* Authentication error *)
-
| ServerError of string (* Server reported an error *)
-
-
(* Standard Result type for JMAP operations *)
-
type 'a result = ('a, error) Result.t
-
-
(* Problem details object for HTTP-level errors. *)
-
module Problem_details = struct
-
type t = {
-
problem_type: string;
-
status: int option;
-
detail: string option;
-
limit: string option;
-
other_fields: Yojson.Safe.t string_map;
-
}
-
-
let problem_type t = t.problem_type
-
let status t = t.status
-
let detail t = t.detail
-
let limit t = t.limit
-
let other_fields t = t.other_fields
-
-
let v ?status ?detail ?limit ?(other_fields=Hashtbl.create 0) problem_type =
-
{ problem_type; status; detail; limit; other_fields }
-
end
-
-
(* Description for method errors. May contain additional details. *)
-
module Method_error_description = struct
-
type t = {
-
description: string option;
-
}
-
-
let description t = t.description
-
-
let v ?description () = { description }
-
end
-
-
(* Represents a method-level error response invocation part. *)
-
module Method_error = struct
-
type t = {
-
type_: method_error_type;
-
description: Method_error_description.t option;
-
}
-
-
let type_ t = t.type_
-
let description t = t.description
-
-
let v ?description type_ = { type_; description }
-
end
-
-
(* SetError object. *)
-
module Set_error = struct
-
type t = {
-
type_: set_error_type;
-
description: string option;
-
properties: string list option;
-
existing_id: id option;
-
max_recipients: uint option;
-
invalid_recipients: string list option;
-
max_size: uint option;
-
not_found_blob_ids: id list option;
-
}
-
-
let type_ t = t.type_
-
let description t = t.description
-
let properties t = t.properties
-
let existing_id t = t.existing_id
-
let max_recipients t = t.max_recipients
-
let invalid_recipients t = t.invalid_recipients
-
let max_size t = t.max_size
-
let not_found_blob_ids t = t.not_found_blob_ids
-
-
let v ?description ?properties ?existing_id ?max_recipients
-
?invalid_recipients ?max_size ?not_found_blob_ids type_ =
-
{ type_; description; properties; existing_id; max_recipients;
-
invalid_recipients; max_size; not_found_blob_ids }
-
end
-
-
(* Error Handling Functions *)
-
-
let transport_error msg = Transport msg
-
-
let parse_error msg = Parse msg
-
-
let protocol_error msg = Protocol msg
-
-
let problem_error problem =
-
Problem (Problem_details.problem_type problem)
-
-
let method_error ?description type_ =
-
Method (type_, description)
-
-
let set_item_error id ?description type_ =
-
SetItem (id, type_, description)
-
-
let auth_error msg = Auth msg
-
-
let server_error msg = ServerError msg
-
-
let of_method_error method_error =
-
let description = match Method_error.description method_error with
-
| Some desc -> Method_error_description.description desc
-
| None -> None
-
in
-
Method (Method_error.type_ method_error, description)
-
-
let of_set_error id set_error =
-
SetItem (id, Set_error.type_ set_error, Set_error.description set_error)
-
-
let error_to_string = function
-
| Transport msg -> "Transport error: " ^ msg
-
| Parse msg -> "Parse error: " ^ msg
-
| Protocol msg -> "Protocol error: " ^ msg
-
| Problem problem -> "Problem: " ^ problem
-
| Method (type_, desc) ->
-
let type_str = match type_ with
-
| `ServerUnavailable -> "serverUnavailable"
-
| `ServerFail -> "serverFail"
-
| `ServerPartialFail -> "serverPartialFail"
-
| `UnknownMethod -> "unknownMethod"
-
| `InvalidArguments -> "invalidArguments"
-
| `InvalidResultReference -> "invalidResultReference"
-
| `Forbidden -> "forbidden"
-
| `AccountNotFound -> "accountNotFound"
-
| `AccountNotSupportedByMethod -> "accountNotSupportedByMethod"
-
| `AccountReadOnly -> "accountReadOnly"
-
| `RequestTooLarge -> "requestTooLarge"
-
| `CannotCalculateChanges -> "cannotCalculateChanges"
-
| `StateMismatch -> "stateMismatch"
-
| `AnchorNotFound -> "anchorNotFound"
-
| `UnsupportedSort -> "unsupportedSort"
-
| `UnsupportedFilter -> "unsupportedFilter"
-
| `TooManyChanges -> "tooManyChanges"
-
| `FromAccountNotFound -> "fromAccountNotFound"
-
| `FromAccountNotSupportedByMethod -> "fromAccountNotSupportedByMethod"
-
| `Other_method_error s -> s
-
in
-
let desc_str = match desc with
-
| Some d -> ": " ^ d
-
| None -> ""
-
in
-
"Method error: " ^ type_str ^ desc_str
-
| SetItem (id, type_, desc) ->
-
let type_str = match type_ with
-
| `Forbidden -> "forbidden"
-
| `OverQuota -> "overQuota"
-
| `TooLarge -> "tooLarge"
-
| `RateLimit -> "rateLimit"
-
| `NotFound -> "notFound"
-
| `InvalidPatch -> "invalidPatch"
-
| `WillDestroy -> "willDestroy"
-
| `InvalidProperties -> "invalidProperties"
-
| `Singleton -> "singleton"
-
| `AlreadyExists -> "alreadyExists"
-
| `MailboxHasChild -> "mailboxHasChild"
-
| `MailboxHasEmail -> "mailboxHasEmail"
-
| `BlobNotFound -> "blobNotFound"
-
| `TooManyKeywords -> "tooManyKeywords"
-
| `TooManyMailboxes -> "tooManyMailboxes"
-
| `InvalidEmail -> "invalidEmail"
-
| `TooManyRecipients -> "tooManyRecipients"
-
| `NoRecipients -> "noRecipients"
-
| `InvalidRecipients -> "invalidRecipients"
-
| `ForbiddenMailFrom -> "forbiddenMailFrom"
-
| `ForbiddenFrom -> "forbiddenFrom"
-
| `ForbiddenToSend -> "forbiddenToSend"
-
| `CannotUnsend -> "cannotUnsend"
-
| `Other_set_error s -> s
-
in
-
let desc_str = match desc with
-
| Some d -> ": " ^ d
-
| None -> ""
-
in
-
"SetItem error for " ^ id ^ ": " ^ type_str ^ desc_str
-
| Auth msg -> "Authentication error: " ^ msg
-
| ServerError msg -> "Server error: " ^ msg
-
-
(* Result Handling *)
-
-
let map_error result f =
-
match result with
-
| Ok v -> Ok v
-
| Error e -> Error (f e)
-
-
let with_context result context =
-
map_error result (function
-
| Transport msg -> Transport (context ^ ": " ^ msg)
-
| Parse msg -> Parse (context ^ ": " ^ msg)
-
| Protocol msg -> Protocol (context ^ ": " ^ msg)
-
| Problem p -> Problem (context ^ ": " ^ p)
-
| Method (t, Some d) -> Method (t, Some (context ^ ": " ^ d))
-
| Method (t, None) -> Method (t, Some context)
-
| SetItem (id, t, Some d) -> SetItem (id, t, Some (context ^ ": " ^ d))
-
| SetItem (id, t, None) -> SetItem (id, t, Some context)
-
| Auth msg -> Auth (context ^ ": " ^ msg)
-
| ServerError msg -> ServerError (context ^ ": " ^ msg)
-
)
-
-
let of_option opt error =
-
match opt with
-
| Some v -> Ok v
-
| None -> Error error
-436
jmap/jmap_methods.ml
···
-
(* Standard JMAP Methods and Core/echo. *)
-
-
open Jmap_types
-
open Jmap_error
-
-
(* Generic representation of a record type. Actual types defined elsewhere. *)
-
type generic_record = Yojson.Safe.t
-
-
(* Arguments for /get methods. *)
-
module Get_args = struct
-
type 'record t = {
-
account_id: id;
-
ids: id list option;
-
properties: string list option;
-
}
-
-
let account_id t = t.account_id
-
let ids t = t.ids
-
let properties t = t.properties
-
-
let v ~account_id ?ids ?properties () =
-
{ account_id; ids; properties }
-
end
-
-
(* Response for /get methods. *)
-
module Get_response = struct
-
type 'record t = {
-
account_id: id;
-
state: string;
-
list: 'record list;
-
not_found: id list;
-
}
-
-
let account_id t = t.account_id
-
let state t = t.state
-
let list t = t.list
-
let not_found t = t.not_found
-
-
let v ~account_id ~state ~list ~not_found () =
-
{ account_id; state; list; not_found }
-
end
-
-
(* Arguments for /changes methods. *)
-
module Changes_args = struct
-
type t = {
-
account_id: id;
-
since_state: string;
-
max_changes: uint option;
-
}
-
-
let account_id t = t.account_id
-
let since_state t = t.since_state
-
let max_changes t = t.max_changes
-
-
let v ~account_id ~since_state ?max_changes () =
-
{ account_id; since_state; max_changes }
-
end
-
-
(* Response for /changes methods. *)
-
module Changes_response = struct
-
type t = {
-
account_id: id;
-
old_state: string;
-
new_state: string;
-
has_more_changes: bool;
-
created: id list;
-
updated: id list;
-
destroyed: id list;
-
updated_properties: string list option;
-
}
-
-
let account_id t = t.account_id
-
let old_state t = t.old_state
-
let new_state t = t.new_state
-
let has_more_changes t = t.has_more_changes
-
let created t = t.created
-
let updated t = t.updated
-
let destroyed t = t.destroyed
-
let updated_properties t = t.updated_properties
-
-
let v ~account_id ~old_state ~new_state ~has_more_changes
-
~created ~updated ~destroyed ?updated_properties () =
-
{ account_id; old_state; new_state; has_more_changes;
-
created; updated; destroyed; updated_properties }
-
end
-
-
(* Patch object for /set update.
-
A list of (JSON Pointer path, value) pairs. *)
-
type patch_object = (json_pointer * Yojson.Safe.t) list
-
-
(* Arguments for /set methods. *)
-
module Set_args = struct
-
type ('create_record, 'update_record) t = {
-
account_id: id;
-
if_in_state: string option;
-
create: 'create_record id_map option;
-
update: 'update_record id_map option;
-
destroy: id list option;
-
on_success_destroy_original: bool option;
-
destroy_from_if_in_state: string option;
-
on_destroy_remove_emails: bool option;
-
}
-
-
let account_id t = t.account_id
-
let if_in_state t = t.if_in_state
-
let create t = t.create
-
let update t = t.update
-
let destroy t = t.destroy
-
let on_success_destroy_original t = t.on_success_destroy_original
-
let destroy_from_if_in_state t = t.destroy_from_if_in_state
-
let on_destroy_remove_emails t = t.on_destroy_remove_emails
-
-
let v ~account_id ?if_in_state ?create ?update ?destroy
-
?on_success_destroy_original ?destroy_from_if_in_state
-
?on_destroy_remove_emails () =
-
{ account_id; if_in_state; create; update; destroy;
-
on_success_destroy_original; destroy_from_if_in_state;
-
on_destroy_remove_emails }
-
end
-
-
(* Response for /set methods. *)
-
module Set_response = struct
-
type ('created_record_info, 'updated_record_info) t = {
-
account_id: id;
-
old_state: string option;
-
new_state: string;
-
created: 'created_record_info id_map option;
-
updated: 'updated_record_info option id_map option;
-
destroyed: id list option;
-
not_created: Set_error.t id_map option;
-
not_updated: Set_error.t id_map option;
-
not_destroyed: Set_error.t id_map option;
-
}
-
-
let account_id t = t.account_id
-
let old_state t = t.old_state
-
let new_state t = t.new_state
-
let created t = t.created
-
let updated t = t.updated
-
let destroyed t = t.destroyed
-
let not_created t = t.not_created
-
let not_updated t = t.not_updated
-
let not_destroyed t = t.not_destroyed
-
-
let v ~account_id ?old_state ~new_state ?created ?updated ?destroyed
-
?not_created ?not_updated ?not_destroyed () =
-
{ account_id; old_state; new_state; created; updated; destroyed;
-
not_created; not_updated; not_destroyed }
-
end
-
-
(* Arguments for /copy methods. *)
-
module Copy_args = struct
-
type 'copy_record_override t = {
-
from_account_id: id;
-
if_from_in_state: string option;
-
account_id: id;
-
if_in_state: string option;
-
create: 'copy_record_override id_map;
-
on_success_destroy_original: bool;
-
destroy_from_if_in_state: string option;
-
}
-
-
let from_account_id t = t.from_account_id
-
let if_from_in_state t = t.if_from_in_state
-
let account_id t = t.account_id
-
let if_in_state t = t.if_in_state
-
let create t = t.create
-
let on_success_destroy_original t = t.on_success_destroy_original
-
let destroy_from_if_in_state t = t.destroy_from_if_in_state
-
-
let v ~from_account_id ?if_from_in_state ~account_id ?if_in_state
-
~create ?(on_success_destroy_original=false) ?destroy_from_if_in_state () =
-
{ from_account_id; if_from_in_state; account_id; if_in_state;
-
create; on_success_destroy_original; destroy_from_if_in_state }
-
end
-
-
(* Response for /copy methods. *)
-
module Copy_response = struct
-
type 'created_record_info t = {
-
from_account_id: id;
-
account_id: id;
-
old_state: string option;
-
new_state: string;
-
created: 'created_record_info id_map option;
-
not_created: Set_error.t id_map option;
-
}
-
-
let from_account_id t = t.from_account_id
-
let account_id t = t.account_id
-
let old_state t = t.old_state
-
let new_state t = t.new_state
-
let created t = t.created
-
let not_created t = t.not_created
-
-
let v ~from_account_id ~account_id ?old_state ~new_state
-
?created ?not_created () =
-
{ from_account_id; account_id; old_state; new_state;
-
created; not_created }
-
end
-
-
(* Module for generic filter representation. *)
-
module Filter = struct
-
type t =
-
| Condition of Yojson.Safe.t
-
| Operator of [ `AND | `OR | `NOT ] * t list
-
-
let condition json = Condition json
-
-
let operator op filters = Operator (op, filters)
-
-
let and_ filters = operator `AND filters
-
-
let or_ filters = operator `OR filters
-
-
let not_ filter = operator `NOT [filter]
-
-
let rec to_json = function
-
| Condition json -> json
-
| Operator (op, filters) ->
-
let key = match op with
-
| `AND -> "AND"
-
| `OR -> "OR"
-
| `NOT -> "NOT"
-
in
-
`Assoc [(key, `List (List.map to_json filters))]
-
-
(* Helper functions for common filter conditions *)
-
-
let text_contains property value =
-
condition (`Assoc [
-
(property, `Assoc [("contains", `String value)])
-
])
-
-
let property_equals property value =
-
condition (`Assoc [(property, value)])
-
-
let property_not_equals property value =
-
condition (`Assoc [
-
(property, `Assoc [("!",value)])
-
])
-
-
let property_gt property value =
-
condition (`Assoc [
-
(property, `Assoc [("gt", value)])
-
])
-
-
let property_ge property value =
-
condition (`Assoc [
-
(property, `Assoc [("ge", value)])
-
])
-
-
let property_lt property value =
-
condition (`Assoc [
-
(property, `Assoc [("lt", value)])
-
])
-
-
let property_le property value =
-
condition (`Assoc [
-
(property, `Assoc [("le", value)])
-
])
-
-
let property_in property values =
-
condition (`Assoc [
-
(property, `Assoc [("in", `List values)])
-
])
-
-
let property_not_in property values =
-
condition (`Assoc [
-
(property, `Assoc [("!in", `List values)])
-
])
-
-
let property_exists property =
-
condition (`Assoc [
-
(property, `Null) (* Using just the property name means "property exists" *)
-
])
-
-
let string_starts_with property prefix =
-
condition (`Assoc [
-
(property, `Assoc [("startsWith", `String prefix)])
-
])
-
-
let string_ends_with property suffix =
-
condition (`Assoc [
-
(property, `Assoc [("endsWith", `String suffix)])
-
])
-
end
-
-
(* Comparator for sorting. *)
-
module Comparator = struct
-
type t = {
-
property: string;
-
is_ascending: bool option;
-
collation: string option;
-
keyword: string option;
-
other_fields: Yojson.Safe.t string_map;
-
}
-
-
let property t = t.property
-
let is_ascending t = t.is_ascending
-
let collation t = t.collation
-
let keyword t = t.keyword
-
let other_fields t = t.other_fields
-
-
let v ~property ?is_ascending ?collation ?keyword
-
?(other_fields=Hashtbl.create 0) () =
-
{ property; is_ascending; collation; keyword; other_fields }
-
end
-
-
(* Arguments for /query methods. *)
-
module Query_args = struct
-
type t = {
-
account_id: id;
-
filter: Filter.t option;
-
sort: Comparator.t list option;
-
position: jint option;
-
anchor: id option;
-
anchor_offset: jint option;
-
limit: uint option;
-
calculate_total: bool option;
-
collapse_threads: bool option;
-
sort_as_tree: bool option;
-
filter_as_tree: bool option;
-
}
-
-
let account_id t = t.account_id
-
let filter t = t.filter
-
let sort t = t.sort
-
let position t = t.position
-
let anchor t = t.anchor
-
let anchor_offset t = t.anchor_offset
-
let limit t = t.limit
-
let calculate_total t = t.calculate_total
-
let collapse_threads t = t.collapse_threads
-
let sort_as_tree t = t.sort_as_tree
-
let filter_as_tree t = t.filter_as_tree
-
-
let v ~account_id ?filter ?sort ?position ?anchor ?anchor_offset
-
?limit ?calculate_total ?collapse_threads ?sort_as_tree ?filter_as_tree () =
-
{ account_id; filter; sort; position; anchor; anchor_offset;
-
limit; calculate_total; collapse_threads; sort_as_tree; filter_as_tree }
-
end
-
-
(* Response for /query methods. *)
-
module Query_response = struct
-
type t = {
-
account_id: id;
-
query_state: string;
-
can_calculate_changes: bool;
-
position: uint;
-
ids: id list;
-
total: uint option;
-
limit: uint option;
-
}
-
-
let account_id t = t.account_id
-
let query_state t = t.query_state
-
let can_calculate_changes t = t.can_calculate_changes
-
let position t = t.position
-
let ids t = t.ids
-
let total t = t.total
-
let limit t = t.limit
-
-
let v ~account_id ~query_state ~can_calculate_changes ~position ~ids
-
?total ?limit () =
-
{ account_id; query_state; can_calculate_changes; position; ids;
-
total; limit }
-
end
-
-
(* Item indicating an added record in /queryChanges. *)
-
module Added_item = struct
-
type t = {
-
id: id;
-
index: uint;
-
}
-
-
let id t = t.id
-
let index t = t.index
-
-
let v ~id ~index () = { id; index }
-
end
-
-
(* Arguments for /queryChanges methods. *)
-
module Query_changes_args = struct
-
type t = {
-
account_id: id;
-
filter: Filter.t option;
-
sort: Comparator.t list option;
-
since_query_state: string;
-
max_changes: uint option;
-
up_to_id: id option;
-
calculate_total: bool option;
-
collapse_threads: bool option;
-
}
-
-
let account_id t = t.account_id
-
let filter t = t.filter
-
let sort t = t.sort
-
let since_query_state t = t.since_query_state
-
let max_changes t = t.max_changes
-
let up_to_id t = t.up_to_id
-
let calculate_total t = t.calculate_total
-
let collapse_threads t = t.collapse_threads
-
-
let v ~account_id ?filter ?sort ~since_query_state ?max_changes
-
?up_to_id ?calculate_total ?collapse_threads () =
-
{ account_id; filter; sort; since_query_state; max_changes;
-
up_to_id; calculate_total; collapse_threads }
-
end
-
-
(* Response for /queryChanges methods. *)
-
module Query_changes_response = struct
-
type t = {
-
account_id: id;
-
old_query_state: string;
-
new_query_state: string;
-
total: uint option;
-
removed: id list;
-
added: Added_item.t list;
-
}
-
-
let account_id t = t.account_id
-
let old_query_state t = t.old_query_state
-
let new_query_state t = t.new_query_state
-
let total t = t.total
-
let removed t = t.removed
-
let added t = t.added
-
-
let v ~account_id ~old_query_state ~new_query_state ?total
-
~removed ~added () =
-
{ account_id; old_query_state; new_query_state; total;
-
removed; added }
-
end
-
-
(* Core/echo method: Arguments are mirrored in the response. *)
-
type core_echo_args = Yojson.Safe.t
-
type core_echo_response = Yojson.Safe.t
-192
jmap/jmap_push.ml
···
-
(* JMAP Push Notifications. *)
-
-
open Jmap_types
-
open Jmap_methods
-
open Jmap_error
-
-
(* TypeState object map (TypeName -> StateString). *)
-
type type_state = string string_map
-
-
(* StateChange object. *)
-
module State_change = struct
-
type t = {
-
changed: type_state id_map;
-
}
-
-
let changed t = t.changed
-
-
let v ~changed () = { changed }
-
end
-
-
(* PushSubscription encryption keys. *)
-
module Push_encryption_keys = struct
-
type t = {
-
p256dh: string;
-
auth: string;
-
}
-
-
let p256dh t = t.p256dh
-
let auth t = t.auth
-
-
let v ~p256dh ~auth () = { p256dh; auth }
-
end
-
-
(* PushSubscription object. *)
-
module Push_subscription = struct
-
type t = {
-
id: id;
-
device_client_id: string;
-
url: Uri.t;
-
keys: Push_encryption_keys.t option;
-
verification_code: string option;
-
expires: utc_date option;
-
types: string list option;
-
}
-
-
let id t = t.id
-
let device_client_id t = t.device_client_id
-
let url t = t.url
-
let keys t = t.keys
-
let verification_code t = t.verification_code
-
let expires t = t.expires
-
let types t = t.types
-
-
let v ~id ~device_client_id ~url ?keys ?verification_code ?expires ?types () =
-
{ id; device_client_id; url; keys; verification_code; expires; types }
-
end
-
-
(* PushSubscription object for creation (omits server-set fields). *)
-
module Push_subscription_create = struct
-
type t = {
-
device_client_id: string;
-
url: Uri.t;
-
keys: Push_encryption_keys.t option;
-
expires: utc_date option;
-
types: string list option;
-
}
-
-
let device_client_id t = t.device_client_id
-
let url t = t.url
-
let keys t = t.keys
-
let expires t = t.expires
-
let types t = t.types
-
-
let v ~device_client_id ~url ?keys ?expires ?types () =
-
{ device_client_id; url; keys; expires; types }
-
end
-
-
(* PushSubscription object for update patch.
-
Only verification_code and expires can be updated. *)
-
type push_subscription_update = patch_object
-
-
(* Arguments for PushSubscription/get. *)
-
module Push_subscription_get_args = struct
-
type t = {
-
ids: id list option;
-
properties: string list option;
-
}
-
-
let ids t = t.ids
-
let properties t = t.properties
-
-
let v ?ids ?properties () = { ids; properties }
-
end
-
-
(* Response for PushSubscription/get. *)
-
module Push_subscription_get_response = struct
-
type t = {
-
list: Push_subscription.t list;
-
not_found: id list;
-
}
-
-
let list t = t.list
-
let not_found t = t.not_found
-
-
let v ~list ~not_found () = { list; not_found }
-
end
-
-
(* Arguments for PushSubscription/set. *)
-
module Push_subscription_set_args = struct
-
type t = {
-
create: Push_subscription_create.t id_map option;
-
update: push_subscription_update id_map option;
-
destroy: id list option;
-
}
-
-
let create t = t.create
-
let update t = t.update
-
let destroy t = t.destroy
-
-
let v ?create ?update ?destroy () = { create; update; destroy }
-
end
-
-
(* Server-set information for created PushSubscription. *)
-
module Push_subscription_created_info = struct
-
type t = {
-
id: id;
-
expires: utc_date option;
-
}
-
-
let id t = t.id
-
let expires t = t.expires
-
-
let v ~id ?expires () = { id; expires }
-
end
-
-
(* Server-set information for updated PushSubscription. *)
-
module Push_subscription_updated_info = struct
-
type t = {
-
expires: utc_date option;
-
}
-
-
let expires t = t.expires
-
-
let v ?expires () = { expires }
-
end
-
-
(* Response for PushSubscription/set. *)
-
module Push_subscription_set_response = struct
-
type t = {
-
created: Push_subscription_created_info.t id_map option;
-
updated: Push_subscription_updated_info.t option id_map option;
-
destroyed: id list option;
-
not_created: Set_error.t id_map option;
-
not_updated: Set_error.t id_map option;
-
not_destroyed: Set_error.t id_map option;
-
}
-
-
let created t = t.created
-
let updated t = t.updated
-
let destroyed t = t.destroyed
-
let not_created t = t.not_created
-
let not_updated t = t.not_updated
-
let not_destroyed t = t.not_destroyed
-
-
let v ?created ?updated ?destroyed ?not_created ?not_updated ?not_destroyed () =
-
{ created; updated; destroyed; not_created; not_updated; not_destroyed }
-
end
-
-
(* PushVerification object. *)
-
module Push_verification = struct
-
type t = {
-
push_subscription_id: id;
-
verification_code: string;
-
}
-
-
let push_subscription_id t = t.push_subscription_id
-
let verification_code t = t.verification_code
-
-
let v ~push_subscription_id ~verification_code () =
-
{ push_subscription_id; verification_code }
-
end
-
-
(* Data for EventSource ping event. *)
-
module Event_source_ping_data = struct
-
type t = {
-
interval: uint;
-
}
-
-
let interval t = t.interval
-
-
let v ~interval () = { interval }
-
end
-114
jmap/jmap_session.ml
···
-
(* JMAP Session Resource. *)
-
-
open Jmap_types
-
-
(* Account capability information.
-
The value is capability-specific. *)
-
type account_capability_value = Yojson.Safe.t
-
-
(* Server capability information.
-
The value is capability-specific. *)
-
type server_capability_value = Yojson.Safe.t
-
-
(* Core capability information. *)
-
module Core_capability = struct
-
type t = {
-
max_size_upload: uint;
-
max_concurrent_upload: uint;
-
max_size_request: uint;
-
max_concurrent_requests: uint;
-
max_calls_in_request: uint;
-
max_objects_in_get: uint;
-
max_objects_in_set: uint;
-
collation_algorithms: string list;
-
}
-
-
let max_size_upload t = t.max_size_upload
-
let max_concurrent_upload t = t.max_concurrent_upload
-
let max_size_request t = t.max_size_request
-
let max_concurrent_requests t = t.max_concurrent_requests
-
let max_calls_in_request t = t.max_calls_in_request
-
let max_objects_in_get t = t.max_objects_in_get
-
let max_objects_in_set t = t.max_objects_in_set
-
let collation_algorithms t = t.collation_algorithms
-
-
let v ~max_size_upload ~max_concurrent_upload ~max_size_request
-
~max_concurrent_requests ~max_calls_in_request ~max_objects_in_get
-
~max_objects_in_set ~collation_algorithms () =
-
{ max_size_upload; max_concurrent_upload; max_size_request;
-
max_concurrent_requests; max_calls_in_request; max_objects_in_get;
-
max_objects_in_set; collation_algorithms }
-
end
-
-
(* An Account object. *)
-
module Account = struct
-
type t = {
-
name: string;
-
is_personal: bool;
-
is_read_only: bool;
-
account_capabilities: account_capability_value string_map;
-
}
-
-
let name t = t.name
-
let is_personal t = t.is_personal
-
let is_read_only t = t.is_read_only
-
let account_capabilities t = t.account_capabilities
-
-
let v ~name ?(is_personal=true) ?(is_read_only=false)
-
?(account_capabilities=Hashtbl.create 0) () =
-
{ name; is_personal; is_read_only; account_capabilities }
-
end
-
-
(* The Session object. *)
-
module Session = struct
-
type t = {
-
capabilities: server_capability_value string_map;
-
accounts: Account.t id_map;
-
primary_accounts: id string_map;
-
username: string;
-
api_url: Uri.t;
-
download_url: Uri.t;
-
upload_url: Uri.t;
-
event_source_url: Uri.t;
-
state: string;
-
}
-
-
let capabilities t = t.capabilities
-
let accounts t = t.accounts
-
let primary_accounts t = t.primary_accounts
-
let username t = t.username
-
let api_url t = t.api_url
-
let download_url t = t.download_url
-
let upload_url t = t.upload_url
-
let event_source_url t = t.event_source_url
-
let state t = t.state
-
-
let v ~capabilities ~accounts ~primary_accounts ~username
-
~api_url ~download_url ~upload_url ~event_source_url ~state () =
-
{ capabilities; accounts; primary_accounts; username;
-
api_url; download_url; upload_url; event_source_url; state }
-
end
-
-
(* Function to perform service autodiscovery.
-
Returns the session URL if found. *)
-
let discover ~domain =
-
(* This is a placeholder implementation - would need to be completed in Unix implementation *)
-
let well_known_url = Uri.of_string ("https://" ^ domain ^ "/.well-known/jmap") in
-
Some well_known_url
-
-
(* Function to fetch the session object from a given URL.
-
Requires authentication handling (details TBD/outside this signature). *)
-
let get_session ~url =
-
(* This is a placeholder implementation - would need to be completed in Unix implementation *)
-
let empty_map () = Hashtbl.create 0 in
-
Session.v
-
~capabilities:(empty_map ())
-
~accounts:(empty_map ())
-
~primary_accounts:(empty_map ())
-
~username:"placeholder"
-
~api_url:url
-
~download_url:url
-
~upload_url:url
-
~event_source_url:url
-
~state:"placeholder"
-
()
-32
jmap/jmap_types.ml
···
-
(* Basic JMAP types as defined in RFC 8620. *)
-
-
(* The Id data type.
-
A string of 1 to 255 octets, using URL-safe base64 characters. *)
-
type id = string
-
-
(* The Int data type.
-
An integer in the range [-2^53+1, 2^53-1]. Represented as OCaml's standard [int]. *)
-
type jint = int
-
-
(* The UnsignedInt data type.
-
An integer in the range [0, 2^53-1]. Represented as OCaml's standard [int]. *)
-
type uint = int
-
-
(* The Date data type.
-
A string in RFC 3339 "date-time" format.
-
Represented as a float using Unix time. *)
-
type date = float
-
-
(* The UTCDate data type.
-
A string in RFC 3339 "date-time" format, restricted to UTC (Z timezone).
-
Represented as a float using Unix time. *)
-
type utc_date = float
-
-
(* Represents a JSON object used as a map String -> V. *)
-
type 'v string_map = (string, 'v) Hashtbl.t
-
-
(* Represents a JSON object used as a map Id -> V. *)
-
type 'v id_map = (id, 'v) Hashtbl.t
-
-
(* Represents a JSON Pointer path with JMAP extensions. *)
-
type json_pointer = string
-73
jmap/jmap_wire.ml
···
-
(* JMAP Wire Protocol Structures (Request/Response). *)
-
-
open Jmap_types
-
-
(* An invocation tuple within a request or response. *)
-
module Invocation = struct
-
type t = {
-
method_name: string;
-
arguments: Yojson.Safe.t;
-
method_call_id: string;
-
}
-
-
let method_name t = t.method_name
-
let arguments t = t.arguments
-
let method_call_id t = t.method_call_id
-
-
let v ?(arguments=`Assoc []) ~method_name ~method_call_id () =
-
{ method_name; arguments; method_call_id }
-
end
-
-
(* Method error type with context. *)
-
type method_error = Jmap_error.Method_error.t * string
-
-
(* A response invocation part, which can be a standard response or an error. *)
-
type response_invocation = (Invocation.t, method_error) result
-
-
(* A reference to a previous method call's result. *)
-
module Result_reference = struct
-
type t = {
-
result_of: string;
-
name: string;
-
path: json_pointer;
-
}
-
-
let result_of t = t.result_of
-
let name t = t.name
-
let path t = t.path
-
-
let v ~result_of ~name ~path () =
-
{ result_of; name; path }
-
end
-
-
(* The Request object. *)
-
module Request = struct
-
type t = {
-
using: string list;
-
method_calls: Invocation.t list;
-
created_ids: id id_map option;
-
}
-
-
let using t = t.using
-
let method_calls t = t.method_calls
-
let created_ids t = t.created_ids
-
-
let v ~using ~method_calls ?created_ids () =
-
{ using; method_calls; created_ids }
-
end
-
-
(* The Response object. *)
-
module Response = struct
-
type t = {
-
method_responses: response_invocation list;
-
created_ids: id id_map option;
-
session_state: string;
-
}
-
-
let method_responses t = t.method_responses
-
let created_ids t = t.created_ids
-
let session_state t = t.session_state
-
-
let v ~method_responses ?created_ids ~session_state () =
-
{ method_responses; created_ids; session_state }
-
end