···6868 | Session_cookie of (string * string)
6969 | No_auth
70707171+(* Session discovery types *)
7272+type session_auth =
7373+ | Bearer_token of string
7474+ | Basic_auth of string * string
7575+ | No_session_auth
7676+7177type event_source_connection = unit
72787379type connection_state =
···263269 ("Failed to parse session: " ^ Printexc.to_string exn)))
264270 | Error e -> Error e)
265271272272+(* Session discovery functions using proper Eio and cohttp-eio *)
273273+let auth_headers = function
274274+ | Bearer_token token -> [("Authorization", "Bearer " ^ token)]
275275+ | Basic_auth (user, pass) ->
276276+ let credentials = base64_encode_string (user ^ ":" ^ pass) in
277277+ [("Authorization", "Basic " ^ credentials)]
278278+ | No_session_auth -> []
279279+280280+let discover_session ~env ~domain =
281281+ let ctx = create_client () in
282282+ let well_known_uri = Uri.make ~scheme:"https" ~host:domain ~path:"/.well-known/jmap" () in
283283+ match http_request env ctx ~meth:`GET ~uri:well_known_uri ~headers:[] ~body:None with
284284+ | Ok response_body ->
285285+ (try
286286+ let json = Yojson.Safe.from_string response_body in
287287+ match Yojson.Safe.Util.member "sessionUrl" json with
288288+ | `String session_url -> Some (Uri.of_string session_url)
289289+ | _ -> None
290290+ with
291291+ | _ -> None)
292292+ | Error _ -> None
293293+294294+let get_session ~env ~url ~auth =
295295+ let ctx = create_client () in
296296+ let headers = auth_headers auth in
297297+ match http_request env ctx ~meth:`GET ~uri:url ~headers ~body:None with
298298+ | Ok response_body ->
299299+ (try
300300+ let json = Yojson.Safe.from_string response_body in
301301+ let session = Jmap.Session.parse_session_json json in
302302+ Ok session
303303+ with
304304+ | exn -> Error ("Failed to parse session: " ^ Printexc.to_string exn))
305305+ | Error _ -> Error ("Network error: failed to get session")
306306+307307+let extract_domain_from_email ~email =
308308+ try
309309+ let at_pos = String.rindex email '@' in
310310+ let domain = String.sub email (at_pos + 1) (String.length email - at_pos - 1) in
311311+ if String.length domain > 0 then Ok domain else Error "Empty domain"
312312+ with
313313+ | Not_found -> Error "No '@' found in email address"
314314+ | _ -> Error "Invalid email format"
315315+266316let build ctx = {
267317 ctx;
268268- using = [Jmap.Protocol.Capability.to_string `Core];
318318+ using = [Jmap.Capability.to_string `Core];
269319 method_calls = [];
270320}
271321272322let using builder capabilities =
273273- builder.using <- Jmap.Protocol.Capability.to_strings capabilities;
323323+ builder.using <- Jmap.Capability.to_strings capabilities;
274324 builder
275325276326let add_method_call builder method_name arguments method_call_id =
···867917 let open Jmap.Session.Session in
868918 let primary_accs = primary_accounts session in
869919 try
870870- Hashtbl.find primary_accs (Jmap.Protocol.Capability.to_string `Mail)
920920+ Hashtbl.find primary_accs (Jmap.Capability.to_string `Mail)
871921 with
872922 | Not_found ->
873923 let accounts = accounts session in
+44
jmap/jmap-unix/jmap_unix.mli
···4949(** Create default configuration options *)
5050val default_config : unit -> client_config
51515252+(** {1 Session Discovery and Authentication} *)
5353+5454+(** Authentication types for session retrieval. *)
5555+type session_auth =
5656+ | Bearer_token of string (** OAuth2 bearer token *)
5757+ | Basic_auth of string * string (** Username and password *)
5858+ | No_session_auth (** No authentication *)
5959+6060+(** Service discovery for JMAP.
6161+6262+ Attempts to discover the JMAP session endpoint using well-known URIs.
6363+ Follows RFC 8620 service discovery process.
6464+6565+ @param env Eio environment for network operations
6666+ @param domain The domain to discover JMAP service for
6767+ @return The session URL if discovery succeeds, None otherwise
6868+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-2.2> RFC 8620, Section 2.2 *)
6969+val discover_session :
7070+ env:< net : 'a Eio.Net.t ; clock : 'b Eio.Time.clock ; .. > ->
7171+ domain:string ->
7272+ Uri.t option
7373+7474+(** Fetch a session object from a given URL with authentication.
7575+7676+ Retrieves and parses the session resource from the server using cohttp-eio.
7777+7878+ @param env Eio environment for network operations
7979+ @param url The session endpoint URL
8080+ @param auth Authentication credentials to use
8181+ @return The parsed session object or error message *)
8282+val get_session :
8383+ env:< net : 'a Eio.Net.t ; clock : 'b Eio.Time.clock ; .. > ->
8484+ url:Uri.t ->
8585+ auth:session_auth ->
8686+ (Jmap.Session.Session.t, string) result
8787+8888+(** Extract domain from email address for discovery.
8989+9090+ Utility function to extract the domain part from an email address.
9191+9292+ @param email Email address to extract domain from
9393+ @return Domain string or error message *)
9494+val extract_domain_from_email : email:string -> (string, string) result
9595+5296(** Create a client context with the specified configuration
5397 @return The context object used for JMAP API calls
5498*)
···24242525module Error = Error
26262727-module Protocol = Jmap_protocol
2727+module Protocol_utils = Jmap_protocol_utils
2828+2929+module Mime_type = Jmap_mime_type
3030+3131+module Error_type = Jmap_error_type
28322933module Client = Jmap_client
30343131-let supports_capability = Protocol.supports_capability
3535+let supports_capability = Protocol_utils.supports_capability
32363333-let get_primary_account session capability =
3434- match Protocol.get_primary_account session capability with
3535- | Ok id_str ->
3636- (match Id.of_string id_str with
3737- | Ok id -> Ok id
3838- | Error msg -> Error (Error.method_error ~description:msg `InvalidArguments))
3939- | Error e -> Error e
3737+let get_primary_account = Protocol_utils.get_primary_account
40384139let get_download_url session ~account_id ~blob_id ?name ?content_type () =
4240 let download_url = Session.Session.download_url session in
+27-5
jmap/jmap/jmap.mli
···9797 @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-3.6> RFC 8620, Section 3.6 *)
9898module Error = Error
9999100100-(** Core protocol types and utilities (Request, Response, Session, Error)
101101- This module consolidates the wire protocol, session management, and error handling. *)
102102-module Protocol = Jmap_protocol
100100+(** {1 Utility Modules} *)
101101+102102+(** JMAP protocol utilities for common operations.
103103+104104+ Higher-level utilities for working with JMAP protocol structures including
105105+ session management, response processing, and request building.
106106+107107+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-3> RFC 8620, Section 3 *)
108108+module Protocol_utils = Jmap_protocol_utils
109109+110110+(** MIME type management with type-safe variants.
111111+112112+ Provides commonly used MIME types as polymorphic variants for use in
113113+ email body parts and attachments.
114114+115115+ @see <https://www.rfc-editor.org/rfc/rfc2046.html> RFC 2046: Media Types *)
116116+module Mime_type = Jmap_mime_type
117117+118118+(** JMAP error type management with type-safe variants.
119119+120120+ Type-safe error URIs for JMAP problem details, converting the standardized
121121+ error type URIs to polymorphic variants.
122122+123123+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-3.6> RFC 8620, Section 3.6 *)
124124+module Error_type = Jmap_error_type
103125104126(** {1 Client Layer} *)
105127···146168 (* Process the response *)
147169 match response with
148170 | Ok resp -> (
149149- match Jmap.Protocol.find_method_response resp "echo1" with
171171+ match Jmap.Protocol_utils.find_method_response resp "echo1" with
150172 | Some (method_name, args, _) when method_name = "Core/echo" ->
151173 (* Echo response should contain the same arguments we sent *)
152174 let hello_value = match Yojson.Safe.Util.member "hello" args with
···188210 @param capability The capability.
189211 @return The account ID or an error if not found.
190212*)
191191-val get_primary_account : Session.Session.t -> Jmap_capability.t -> (Id.t, Error.error) result
213213+val get_primary_account : Session.Session.t -> Jmap_capability.t -> (string, Error.error) result
192214193215(** Get the download URL for a blob.
194216 @param session The session object.
+52-9
jmap/jmap/jmap_client.ml
···11-open Jmap_protocol
21open Jmap_method_names
3243type credentials =
···1514}
16151716type t = {
1818- mutable session : session option;
1717+ mutable session : Session.Session.t option;
1918 mutable base_url : Uri.t option;
2019 mutable credentials : credentials option;
2120 mutable stats : stats;
···5251 - RFC reference: RFC 8620 Section 2.1
5352 - Priority: High
5453 - Dependencies: HTTP client implementation *)
5555- let session = Session.get_session ~url:session_url in
5656- t.session <- Some session;
5454+ (* TODO: Replace with proper implementation using jmap-unix *)
5555+ let fallback_session = Session.parse_session_json (`Assoc [
5656+ ("capabilities", `Assoc [
5757+ (Jmap_capability.to_string `Core, `Assoc [
5858+ ("maxSizeUpload", `Int 50_000_000);
5959+ ("maxConcurrentUpload", `Int 4);
6060+ ("maxSizeRequest", `Int 10_000_000);
6161+ ("maxConcurrentRequests", `Int 4);
6262+ ("maxCallsInRequest", `Int 16);
6363+ ("maxObjectsInGet", `Int 500);
6464+ ("maxObjectsInSet", `Int 500);
6565+ ("collationAlgorithms", `List [`String "i;unicode-casemap"])
6666+ ])
6767+ ]);
6868+ ("accounts", `Assoc []);
6969+ ("primaryAccounts", `Assoc []);
7070+ ("username", `String "stub@example.com");
7171+ ("apiUrl", `String (Uri.to_string session_url ^ "../api/"));
7272+ ("downloadUrl", `String (Uri.to_string session_url ^ "../download/{accountId}/{blobId}/{name}"));
7373+ ("uploadUrl", `String (Uri.to_string session_url ^ "../upload/{accountId}/"));
7474+ ("eventSourceUrl", `String (Uri.to_string session_url ^ "../events/"));
7575+ ("state", `String "stub")
7676+ ]) in
7777+ t.session <- Some fallback_session;
5778 t.stats <- { t.stats with connection_time = Some (Unix.time ()) };
58795959- Ok (t, session)
8080+ Ok (t, fallback_session)
60816182let quick_connect ~host ~username ~password =
6283 let t = create () in
···148169 match batch_request t ~using ~invocations:[invocation] with
149170 | Error e -> Error e
150171 | Ok response ->
151151- match find_method_response response method_call_id with
172172+ match Jmap_protocol_utils.find_method_response response method_call_id with
152173 | Some (_, args) -> Ok args
153174 | None -> Error (Error.protocol_error "Method response not found")
154175···159180 | None -> Error (Error.protocol_error "Not connected")
160181 | Some base_url ->
161182 let session_url = Uri.with_path base_url "/.well-known/jmap" in
162162- let session = Session.get_session ~url:session_url in
163163- t.session <- Some session;
164164- Ok session
183183+ (* TODO: Replace with proper implementation using jmap-unix *)
184184+ let fallback_session = Session.parse_session_json (`Assoc [
185185+ ("capabilities", `Assoc [
186186+ (Jmap_capability.to_string `Core, `Assoc [
187187+ ("maxSizeUpload", `Int 50_000_000);
188188+ ("maxConcurrentUpload", `Int 4);
189189+ ("maxSizeRequest", `Int 10_000_000);
190190+ ("maxConcurrentRequests", `Int 4);
191191+ ("maxCallsInRequest", `Int 16);
192192+ ("maxObjectsInGet", `Int 500);
193193+ ("maxObjectsInSet", `Int 500);
194194+ ("collationAlgorithms", `List [`String "i;unicode-casemap"])
195195+ ])
196196+ ]);
197197+ ("accounts", `Assoc []);
198198+ ("primaryAccounts", `Assoc []);
199199+ ("username", `String "stub@example.com");
200200+ ("apiUrl", `String (Uri.to_string session_url ^ "../api/"));
201201+ ("downloadUrl", `String (Uri.to_string session_url ^ "../download/{accountId}/{blobId}/{name}"));
202202+ ("uploadUrl", `String (Uri.to_string session_url ^ "../upload/{accountId}/"));
203203+ ("eventSourceUrl", `String (Uri.to_string session_url ^ "../events/"));
204204+ ("state", `String "stub")
205205+ ]) in
206206+ t.session <- Some fallback_session;
207207+ Ok fallback_session
165208166209let upload_blob t ~account_id ~data ?(content_type = "application/octet-stream") () =
167210 (* TODO: Implement blob upload functionality
+11-12
jmap/jmap/jmap_client.mli
···77 @see <https://www.rfc-editor.org/rfc/rfc8620.html> RFC 8620: Core JMAP *)
8899(* Use underlying types directly to avoid circular dependency with Jmap module *)
1010-open Jmap_protocol
11101211(** {1 Client Type} *)
1312···4443 ?port:int ->
4544 ?use_tls:bool ->
4645 unit ->
4747- (t * session, error) result
4646+ (t * Session.Session.t, Error.error) result
48474948(** Quick connect using username and password.
5049 This is a convenience function that uses Basic authentication.
···5655 host:string ->
5756 username:string ->
5857 password:string ->
5959- (t * session, error) result
5858+ (t * Session.Session.t, Error.error) result
60596160(** Close the client connection and release resources. *)
6261val close : t -> unit
···6766 @param t The client instance.
6867 @param request The JMAP request to send.
6968 @return The response or an error. *)
7070-val request : t -> request -> (response, error) result
6969+val request : t -> Wire.Request.t -> (Wire.Response.t, Error.error) result
71707271(** Send a batch of method calls.
7372 @param t The client instance.
···7776val batch_request :
7877 t ->
7978 using:string list ->
8080- invocations:invocation list ->
8181- (response, error) result
7979+ invocations:Wire.Invocation.t list ->
8080+ (Wire.Response.t, Error.error) result
82818382(** Send a single method call.
8483 @param t The client instance.
···9392 method_name:string ->
9493 arguments:Yojson.Safe.t ->
9594 method_call_id:string ->
9696- (Yojson.Safe.t, error) result
9595+ (Yojson.Safe.t, Error.error) result
97969897(** {1 Session Management} *)
999810099(** Get the current session object.
101100 @param t The client instance.
102101 @return The session object or None if not connected. *)
103103-val get_session : t -> session option
102102+val get_session : t -> Session.Session.t option
104103105104(** Refresh the session object from the server.
106105 @param t The client instance.
107106 @return The updated session or an error. *)
108108-val refresh_session : t -> (session, error) result
107107+val refresh_session : t -> (Session.Session.t, Error.error) result
109108110109(** {1 Binary Data} *)
111110···121120 data:string ->
122121 ?content_type:string ->
123122 unit ->
124124- (string, error) result
123123+ (string, Error.error) result
125124126125(** Download binary data from the server.
127126 @param t The client instance.
···135134 blob_id:string ->
136135 ?name:string ->
137136 unit ->
138138- (string, error) result
137137+ (string, Error.error) result
139138140139(** {1 URL Construction} *)
141140···190189(** {1 Error Handling} *)
191190192191(** Convert a client error to a human-readable string. *)
193193-val error_to_string : error -> string
192192+val error_to_string : Error.error -> string
194193195194(** {1 Logging and Debugging} *)
196195
···11+(** JMAP error type management with type-safe variants.
22+33+ This module provides type-safe error URIs for JMAP problem details,
44+ converting the standardized error type URIs to polymorphic variants.
55+66+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-3.6> RFC 8620, Section 3.6 *)
77+88+(** JMAP standard error types as polymorphic variants.
99+1010+ These map to the standardized error type URIs defined in RFC 8620
1111+ for use in problem details objects.
1212+1313+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-3.6> RFC 8620, Section 3.6 *)
1414+type t = [
1515+ | `UnknownCapability (** urn:ietf:params:jmap:error:unknownCapability *)
1616+ | `NotJSON (** urn:ietf:params:jmap:error:notJSON *)
1717+ | `NotRequest (** urn:ietf:params:jmap:error:notRequest *)
1818+ | `Limit (** urn:ietf:params:jmap:error:limit *)
1919+]
2020+2121+(** Convert error type variant to URN string.
2222+ @param error_type The error type variant
2323+ @return The corresponding URN string *)
2424+val to_string : t -> string
2525+2626+(** Parse URN string to error type variant.
2727+ @param urn The URN string to parse
2828+ @return Some error type if recognized, None otherwise *)
2929+val of_string : string -> t option
3030+3131+(** Pretty-print an error type.
3232+ @param ppf The formatter.
3333+ @param error_type The error type to print. *)
3434+val pp : Format.formatter -> t -> unit
···11+(** MIME type management with type-safe variants.
22+33+ This module provides commonly used MIME types as polymorphic variants
44+ for use in email body parts and attachments.
55+66+ @see <https://www.rfc-editor.org/rfc/rfc2046.html> RFC 2046: Media Types *)
77+88+(** Common MIME types as polymorphic variants. *)
99+type t = [
1010+ | `Text_plain (** text/plain *)
1111+ | `Text_html (** text/html *)
1212+ | `Text_other of string (** text/* with custom subtype *)
1313+ | `Multipart_mixed (** multipart/mixed *)
1414+ | `Multipart_alternative (** multipart/alternative *)
1515+ | `Multipart_digest (** multipart/digest *)
1616+ | `Multipart_other of string (** multipart/* with custom subtype *)
1717+ | `Message_rfc822 (** message/rfc822 *)
1818+ | `Message_global (** message/global *)
1919+ | `Message_other of string (** message/* with custom subtype *)
2020+ | `Application_json (** application/json *)
2121+ | `Application_octet_stream (** application/octet-stream *)
2222+ | `Application_other of string (** application/* with custom subtype *)
2323+ | `Image_other of string (** image/* *)
2424+ | `Audio_other of string (** audio/* *)
2525+ | `Video_other of string (** video/* *)
2626+ | `Other of string * string (** type/subtype for custom MIME types *)
2727+]
2828+2929+(** Convert MIME type variant to string.
3030+ @param mime_type The MIME type variant
3131+ @return The corresponding MIME type string *)
3232+val to_string : t -> string
3333+3434+(** Parse MIME type string to variant.
3535+ @param mime_string The MIME type string to parse
3636+ @return MIME type variant (uses Other for unrecognized types) *)
3737+val of_string : string -> t
3838+3939+(** Pretty-print a MIME type.
4040+ @param ppf The formatter.
4141+ @param mime_type The MIME type to print. *)
4242+val pp : Format.formatter -> t -> unit
4343+4444+(** Check if a MIME type is text-based.
4545+ @param mime_type The MIME type to check
4646+ @return true if it's a text/* type *)
4747+val is_text : t -> bool
4848+4949+(** Check if a MIME type is multipart.
5050+ @param mime_type The MIME type to check
5151+ @return true if it's a multipart/* type *)
5252+val is_multipart : t -> bool
5353+5454+(** Check if a MIME type is a message.
5555+ @param mime_type The MIME type to check
5656+ @return true if it's a message/* type *)
5757+val is_message : t -> bool
-189
jmap/jmap/jmap_protocol.ml
···11-type request = Wire.Request.t
22-33-type response = Wire.Response.t
44-55-type session = Session.Session.t
66-77-type invocation = Wire.Invocation.t
88-99-type error = Error.error
1010-1111-type problem_details = Error.Problem_details.t
1212-1313-module Capability = Jmap_capability
1414-1515-module Error_type = struct
1616- type t = [
1717- | `UnknownCapability
1818- | `NotJSON
1919- | `NotRequest
2020- | `Limit
2121- ]
2222-2323- let to_string = function
2424- | `UnknownCapability -> "urn:ietf:params:jmap:error:unknownCapability"
2525- | `NotJSON -> "urn:ietf:params:jmap:error:notJSON"
2626- | `NotRequest -> "urn:ietf:params:jmap:error:notRequest"
2727- | `Limit -> "urn:ietf:params:jmap:error:limit"
2828-2929- let of_string = function
3030- | "urn:ietf:params:jmap:error:unknownCapability" -> Some `UnknownCapability
3131- | "urn:ietf:params:jmap:error:notJSON" -> Some `NotJSON
3232- | "urn:ietf:params:jmap:error:notRequest" -> Some `NotRequest
3333- | "urn:ietf:params:jmap:error:limit" -> Some `Limit
3434- | _ -> None
3535-3636- let pp ppf error_type = Fmt.string ppf (to_string error_type)
3737-end
3838-3939-module Mime_type = struct
4040- type t = [
4141- | `Text_plain
4242- | `Text_html
4343- | `Text_other of string
4444- | `Multipart_mixed
4545- | `Multipart_alternative
4646- | `Multipart_digest
4747- | `Multipart_other of string
4848- | `Message_rfc822
4949- | `Message_global
5050- | `Message_other of string
5151- | `Application_json
5252- | `Application_octet_stream
5353- | `Application_other of string
5454- | `Image_other of string
5555- | `Audio_other of string
5656- | `Video_other of string
5757- | `Other of string * string
5858- ]
5959-6060- let to_string = function
6161- | `Text_plain -> "text/plain"
6262- | `Text_html -> "text/html"
6363- | `Text_other subtype -> "text/" ^ subtype
6464- | `Multipart_mixed -> "multipart/mixed"
6565- | `Multipart_alternative -> "multipart/alternative"
6666- | `Multipart_digest -> "multipart/digest"
6767- | `Multipart_other subtype -> "multipart/" ^ subtype
6868- | `Message_rfc822 -> "message/rfc822"
6969- | `Message_global -> "message/global"
7070- | `Message_other subtype -> "message/" ^ subtype
7171- | `Application_json -> "application/json"
7272- | `Application_octet_stream -> "application/octet-stream"
7373- | `Application_other subtype -> "application/" ^ subtype
7474- | `Image_other subtype -> "image/" ^ subtype
7575- | `Audio_other subtype -> "audio/" ^ subtype
7676- | `Video_other subtype -> "video/" ^ subtype
7777- | `Other (typ, subtype) -> typ ^ "/" ^ subtype
7878-7979- let of_string mime_string =
8080- match String.split_on_char '/' mime_string with
8181- | ["text"; "plain"] -> `Text_plain
8282- | ["text"; "html"] -> `Text_html
8383- | ["text"; subtype] -> `Text_other subtype
8484- | ["multipart"; "mixed"] -> `Multipart_mixed
8585- | ["multipart"; "alternative"] -> `Multipart_alternative
8686- | ["multipart"; "digest"] -> `Multipart_digest
8787- | ["multipart"; subtype] -> `Multipart_other subtype
8888- | ["message"; "rfc822"] -> `Message_rfc822
8989- | ["message"; "global"] -> `Message_global
9090- | ["message"; subtype] -> `Message_other subtype
9191- | ["application"; "json"] -> `Application_json
9292- | ["application"; "octet-stream"] -> `Application_octet_stream
9393- | ["application"; subtype] -> `Application_other subtype
9494- | ["image"; subtype] -> `Image_other subtype
9595- | ["audio"; subtype] -> `Audio_other subtype
9696- | ["video"; subtype] -> `Video_other subtype
9797- | [typ; subtype] -> `Other (typ, subtype)
9898- | _ -> `Other ("application", "octet-stream") (* Fallback for malformed MIME types *)
9999-100100- let pp ppf mime_type = Fmt.string ppf (to_string mime_type)
101101-102102- let is_text = function
103103- | `Text_plain | `Text_html | `Text_other _ -> true
104104- | _ -> false
105105-106106- let is_multipart = function
107107- | `Multipart_mixed | `Multipart_alternative | `Multipart_digest | `Multipart_other _ -> true
108108- | _ -> false
109109-110110- let is_message = function
111111- | `Message_rfc822 | `Message_global | `Message_other _ -> true
112112- | _ -> false
113113-end
114114-115115-116116-let supports_capability session capability =
117117- Hashtbl.mem (Session.Session.capabilities session) (Jmap_capability.to_string capability)
118118-119119-let get_primary_account session capability =
120120- let capability_uri = Jmap_capability.to_string capability in
121121- let primary_accounts = Session.Session.primary_accounts session in
122122- match Hashtbl.find_opt primary_accounts capability_uri with
123123- | Some id -> Ok id
124124- | None ->
125125- Error (Error.protocol_error
126126- (Printf.sprintf "No primary account found for capability: %s" capability_uri))
127127-128128-let find_method_response response method_call_id =
129129- let responses = Wire.Response.method_responses response in
130130- List.find_map (function
131131- | Ok invocation when Wire.Invocation.method_call_id invocation = method_call_id ->
132132- Some (Wire.Invocation.method_name invocation,
133133- Wire.Invocation.arguments invocation)
134134- | _ -> None
135135- ) responses
136136-137137-let simple_request ~using ~method_name ~arguments ~method_call_id =
138138- let invocation = Wire.Invocation.v ~method_name ~arguments ~method_call_id () in
139139- Wire.Request.v ~using ~method_calls:[invocation] ()
140140-141141-let successful_responses response =
142142- let responses = Wire.Response.method_responses response in
143143- List.filter_map (function
144144- | Ok invocation ->
145145- Some (Wire.Invocation.method_name invocation,
146146- Wire.Invocation.arguments invocation,
147147- Wire.Invocation.method_call_id invocation)
148148- | Error _ -> None
149149- ) responses
150150-151151-let error_responses response =
152152- let responses = Wire.Response.method_responses response in
153153- List.filter_map (function
154154- | Error (method_error, method_call_id) ->
155155- Some (method_call_id, method_error, method_call_id)
156156- | Ok _ -> None
157157- ) responses
158158-159159-(** Response processing utilities *)
160160-module Response = struct
161161- (** Extract and parse a specific method response from a JMAP Response object *)
162162- let extract_method_response response ~method_call_id ~parser =
163163- let responses = Wire.Response.method_responses response in
164164- (* Find the specific method response *)
165165- let found_response = List.find_map (function
166166- | Ok invocation when Wire.Invocation.method_call_id invocation = method_call_id ->
167167- Some (Ok (Wire.Invocation.arguments invocation))
168168- | Error (method_err, call_id) when call_id = method_call_id ->
169169- Some (Error (Error.of_method_error method_err))
170170- | _ -> None
171171- ) responses in
172172-173173- match found_response with
174174- | Some (Ok json) -> parser json
175175- | Some (Error err) -> Error err
176176- | None ->
177177- Error (Error.protocol_error
178178- (Printf.sprintf "Method response not found for call ID: %s" method_call_id))
179179-180180- (** Extract all method responses from a JMAP Response object as (method_call_id, response_json) pairs *)
181181- let extract_all_responses response =
182182- let responses = Wire.Response.method_responses response in
183183- List.filter_map (function
184184- | Ok invocation ->
185185- Some (Wire.Invocation.method_call_id invocation,
186186- Wire.Invocation.arguments invocation)
187187- | Error _ -> None (* Only include successful responses *)
188188- ) responses
189189-end
-230
jmap/jmap/jmap_protocol.mli
···11-(** Core JMAP Protocol types (Request, Response, Session).
22-33- This module provides a unified interface to the fundamental protocol types
44- used for JMAP communication as defined in RFC 8620. It consolidates wire
55- protocol structures, session management, and error handling into a coherent
66- API for JMAP implementations.
77-88- The module provides type aliases and convenience functions that reference
99- the individual Wire, Session, and Error modules for backwards compatibility.
1010-1111- @see <https://www.rfc-editor.org/rfc/rfc8620.html> RFC 8620: Core JMAP *)
1212-1313-(** {1 Type Aliases for Convenience} *)
1414-1515-(** A JMAP request *)
1616-type request = Wire.Request.t
1717-1818-(** A JMAP response *)
1919-type response = Wire.Response.t
2020-2121-(** A JMAP session *)
2222-type session = Session.Session.t
2323-2424-(** A JMAP method invocation *)
2525-type invocation = Wire.Invocation.t
2626-2727-(** A JMAP error *)
2828-type error = Error.error
2929-3030-(** A JMAP problem details error *)
3131-type problem_details = Error.Problem_details.t
3232-3333-(** {1 Protocol Constants} *)
3434-3535-(** JMAP capability management with type-safe variants.
3636-3737- This module provides a type-safe way to work with JMAP capabilities
3838- using polymorphic variants instead of raw strings.
3939-4040- @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-2> RFC 8620, Section 2 *)
4141-module Capability : sig
4242- (** JMAP capability types as polymorphic variants.
4343-4444- This provides compile-time safety for capability handling and makes
4545- the available capabilities discoverable through IDE completion.
4646-4747- @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-2> RFC 8620, Section 2
4848- @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-1.1> RFC 8621, Section 1.1 *)
4949- type t = [
5050- | `Core (** JMAP Core capability *)
5151- | `Mail (** JMAP Mail capability *)
5252- | `Submission (** JMAP Email Submission capability *)
5353- | `VacationResponse (** JMAP Vacation Response capability *)
5454- | `Apple_mail_flags (** Apple Mail color flags extension *)
5555- ]
5656-5757- (** Convert capability variant to URN string.
5858- @param capability The capability variant
5959- @return The corresponding URN string *)
6060- val to_string : t -> string
6161-6262- (** Pretty-print a capability.
6363- @param ppf The formatter.
6464- @param capability The capability to print. *)
6565- val pp : Format.formatter -> t -> unit
6666-6767- (** Parse URN string to capability variant.
6868- @param urn The URN string to parse
6969- @return Some capability if recognized, None otherwise *)
7070- val of_string : string -> t option
7171-7272- (** Convert list of capabilities to list of URN strings.
7373- @param capabilities List of capability variants
7474- @return List of corresponding URN strings *)
7575- val to_strings : t list -> string list
7676-end
7777-7878-(** JMAP error type management with type-safe variants.
7979-8080- This module provides type-safe error URIs for JMAP problem details,
8181- converting the standardized error type URIs to polymorphic variants.
8282-8383- @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-3.6> RFC 8620, Section 3.6 *)
8484-module Error_type : sig
8585- (** JMAP standard error types as polymorphic variants.
8686-8787- These map to the standardized error type URIs defined in RFC 8620
8888- for use in problem details objects.
8989-9090- @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-3.6> RFC 8620, Section 3.6 *)
9191- type t = [
9292- | `UnknownCapability (** urn:ietf:params:jmap:error:unknownCapability *)
9393- | `NotJSON (** urn:ietf:params:jmap:error:notJSON *)
9494- | `NotRequest (** urn:ietf:params:jmap:error:notRequest *)
9595- | `Limit (** urn:ietf:params:jmap:error:limit *)
9696- ]
9797-9898- (** Convert error type variant to URN string.
9999- @param error_type The error type variant
100100- @return The corresponding URN string *)
101101- val to_string : t -> string
102102-103103- (** Parse URN string to error type variant.
104104- @param urn The URN string to parse
105105- @return Some error type if recognized, None otherwise *)
106106- val of_string : string -> t option
107107-108108- (** Pretty-print an error type.
109109- @param ppf The formatter.
110110- @param error_type The error type to print. *)
111111- val pp : Format.formatter -> t -> unit
112112-end
113113-114114-(** MIME type management with type-safe variants.
115115-116116- This module provides commonly used MIME types as polymorphic variants
117117- for use in email body parts and attachments.
118118-119119- @see <https://www.rfc-editor.org/rfc/rfc2046.html> RFC 2046: Media Types *)
120120-module Mime_type : sig
121121- (** Common MIME types as polymorphic variants. *)
122122- type t = [
123123- | `Text_plain (** text/plain *)
124124- | `Text_html (** text/html *)
125125- | `Text_other of string (** text/* with custom subtype *)
126126- | `Multipart_mixed (** multipart/mixed *)
127127- | `Multipart_alternative (** multipart/alternative *)
128128- | `Multipart_digest (** multipart/digest *)
129129- | `Multipart_other of string (** multipart/* with custom subtype *)
130130- | `Message_rfc822 (** message/rfc822 *)
131131- | `Message_global (** message/global *)
132132- | `Message_other of string (** message/* with custom subtype *)
133133- | `Application_json (** application/json *)
134134- | `Application_octet_stream (** application/octet-stream *)
135135- | `Application_other of string (** application/* with custom subtype *)
136136- | `Image_other of string (** image/* *)
137137- | `Audio_other of string (** audio/* *)
138138- | `Video_other of string (** video/* *)
139139- | `Other of string * string (** type/subtype for custom MIME types *)
140140- ]
141141-142142- (** Convert MIME type variant to string.
143143- @param mime_type The MIME type variant
144144- @return The corresponding MIME type string *)
145145- val to_string : t -> string
146146-147147- (** Parse MIME type string to variant.
148148- @param mime_string The MIME type string to parse
149149- @return MIME type variant (uses Other for unrecognized types) *)
150150- val of_string : string -> t
151151-152152- (** Pretty-print a MIME type.
153153- @param ppf The formatter.
154154- @param mime_type The MIME type to print. *)
155155- val pp : Format.formatter -> t -> unit
156156-157157- (** Check if a MIME type is text-based.
158158- @param mime_type The MIME type to check
159159- @return true if it's a text/* type *)
160160- val is_text : t -> bool
161161-162162- (** Check if a MIME type is multipart.
163163- @param mime_type The MIME type to check
164164- @return true if it's a multipart/* type *)
165165- val is_multipart : t -> bool
166166-167167- (** Check if a MIME type is a message.
168168- @param mime_type The MIME type to check
169169- @return true if it's a message/* type *)
170170- val is_message : t -> bool
171171-end
172172-173173-(** {1 Protocol Helpers} *)
174174-175175-(** Check if a session supports a given capability.
176176- @param session The session object.
177177- @param capability The capability to check.
178178- @return True if supported, false otherwise. *)
179179-val supports_capability : session -> Jmap_capability.t -> bool
180180-181181-(** Get the primary account ID for a given capability.
182182- @param session The session object.
183183- @param capability The capability.
184184- @return The account ID or an error if not found. *)
185185-val get_primary_account : session -> Jmap_capability.t -> (string, error) result
186186-187187-(** Find a method response by its call ID.
188188- @param response The response object.
189189- @param method_call_id The method call ID to search for.
190190- @return The method name and arguments if found. *)
191191-val find_method_response : response -> string -> (string * Yojson.Safe.t) option
192192-193193-(** {1 Protocol Utilities} *)
194194-195195-(** Create a simple request with a single method call. *)
196196-val simple_request :
197197- using:string list ->
198198- method_name:string ->
199199- arguments:Yojson.Safe.t ->
200200- method_call_id:string ->
201201- request
202202-203203-(** Extract successful responses from a response object. *)
204204-val successful_responses : response -> (string * Yojson.Safe.t * string) list
205205-206206-(** Extract error responses from a response object. *)
207207-val error_responses : response -> (string * Error.Method_error.t * string) list
208208-209209-(** {1 Response Processing Utilities} *)
210210-211211-(** High-level response processing utilities that simplify extracting and parsing method responses. *)
212212-module Response : sig
213213- (** Extract and parse a specific method response from a JMAP Response object.
214214- @param response The JMAP response to search
215215- @param method_call_id The method call ID to extract
216216- @param parser Function to parse the response JSON into the desired type
217217- @return Parsed response or error if not found/parsing failed *)
218218- val extract_method_response :
219219- response ->
220220- method_call_id:string ->
221221- parser:(Yojson.Safe.t -> ('a, Error.error) result) ->
222222- ('a, Error.error) result
223223-224224- (** Extract all method responses from a JMAP Response object as (method_call_id, response_json) pairs.
225225- @param response The JMAP response to extract from
226226- @return List of all method responses with their IDs and JSON data *)
227227- val extract_all_responses :
228228- response ->
229229- (string * Yojson.Safe.t) list
230230-end
+74
jmap/jmap/jmap_protocol_utils.ml
···11+let supports_capability session capability =
22+ Hashtbl.mem (Session.Session.capabilities session) (Jmap_capability.to_string capability)
33+44+let get_primary_account session capability =
55+ let capability_uri = Jmap_capability.to_string capability in
66+ let primary_accounts = Session.Session.primary_accounts session in
77+ match Hashtbl.find_opt primary_accounts capability_uri with
88+ | Some id -> Ok id
99+ | None ->
1010+ Error (Error.protocol_error
1111+ (Printf.sprintf "No primary account found for capability: %s" capability_uri))
1212+1313+let find_method_response response method_call_id =
1414+ let responses = Wire.Response.method_responses response in
1515+ List.find_map (function
1616+ | Ok invocation when Wire.Invocation.method_call_id invocation = method_call_id ->
1717+ Some (Wire.Invocation.method_name invocation,
1818+ Wire.Invocation.arguments invocation)
1919+ | _ -> None
2020+ ) responses
2121+2222+let simple_request ~using ~method_name ~arguments ~method_call_id =
2323+ let invocation = Wire.Invocation.v ~method_name ~arguments ~method_call_id () in
2424+ Wire.Request.v ~using ~method_calls:[invocation] ()
2525+2626+let successful_responses response =
2727+ let responses = Wire.Response.method_responses response in
2828+ List.filter_map (function
2929+ | Ok invocation ->
3030+ Some (Wire.Invocation.method_name invocation,
3131+ Wire.Invocation.arguments invocation,
3232+ Wire.Invocation.method_call_id invocation)
3333+ | Error _ -> None
3434+ ) responses
3535+3636+let error_responses response =
3737+ let responses = Wire.Response.method_responses response in
3838+ List.filter_map (function
3939+ | Error (method_error, method_call_id) ->
4040+ Some (method_call_id, method_error, method_call_id)
4141+ | Ok _ -> None
4242+ ) responses
4343+4444+(** Response processing utilities *)
4545+module Response = struct
4646+ (** Extract and parse a specific method response from a JMAP Response object *)
4747+ let extract_method_response response ~method_call_id ~parser =
4848+ let responses = Wire.Response.method_responses response in
4949+ (* Find the specific method response *)
5050+ let found_response = List.find_map (function
5151+ | Ok invocation when Wire.Invocation.method_call_id invocation = method_call_id ->
5252+ Some (Ok (Wire.Invocation.arguments invocation))
5353+ | Error (method_err, call_id) when call_id = method_call_id ->
5454+ Some (Error (Error.of_method_error method_err))
5555+ | _ -> None
5656+ ) responses in
5757+5858+ match found_response with
5959+ | Some (Ok json) -> parser json
6060+ | Some (Error err) -> Error err
6161+ | None ->
6262+ Error (Error.protocol_error
6363+ (Printf.sprintf "Method response not found for call ID: %s" method_call_id))
6464+6565+ (** Extract all method responses from a JMAP Response object as (method_call_id, response_json) pairs *)
6666+ let extract_all_responses response =
6767+ let responses = Wire.Response.method_responses response in
6868+ List.filter_map (function
6969+ | Ok invocation ->
7070+ Some (Wire.Invocation.method_call_id invocation,
7171+ Wire.Invocation.arguments invocation)
7272+ | Error _ -> None (* Only include successful responses *)
7373+ ) responses
7474+end
+76
jmap/jmap/jmap_protocol_utils.mli
···11+(** JMAP protocol utilities for common operations.
22+33+ This module provides higher-level utilities for working with JMAP protocol
44+ structures including session management, response processing, and request building.
55+66+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-3> RFC 8620, Section 3 *)
77+88+(** {1 Session Utilities} *)
99+1010+(** Check if a session supports a given capability.
1111+ @param session The session object.
1212+ @param capability The capability to check.
1313+ @return True if supported, false otherwise. *)
1414+val supports_capability : Session.Session.t -> Jmap_capability.t -> bool
1515+1616+(** Get the primary account ID for a given capability.
1717+ @param session The session object.
1818+ @param capability The capability.
1919+ @return The account ID or an error if not found. *)
2020+val get_primary_account : Session.Session.t -> Jmap_capability.t -> (string, Error.error) result
2121+2222+(** {1 Response Processing Utilities} *)
2323+2424+(** Find a method response by its call ID.
2525+ @param response The response object.
2626+ @param method_call_id The method call ID to search for.
2727+ @return The method name and arguments if found. *)
2828+val find_method_response : Wire.Response.t -> string -> (string * Yojson.Safe.t) option
2929+3030+(** Extract successful responses from a response object.
3131+ @param response The response object to process.
3232+ @return List of (method_name, arguments, method_call_id) tuples. *)
3333+val successful_responses : Wire.Response.t -> (string * Yojson.Safe.t * string) list
3434+3535+(** Extract error responses from a response object.
3636+ @param response The response object to process.
3737+ @return List of (method_call_id, method_error, method_call_id) tuples. *)
3838+val error_responses : Wire.Response.t -> (string * Error.Method_error.t * string) list
3939+4040+(** {1 Request Building Utilities} *)
4141+4242+(** Create a simple request with a single method call.
4343+ @param using List of capabilities to declare.
4444+ @param method_name The method name to invoke.
4545+ @param arguments The method arguments as JSON.
4646+ @param method_call_id The call ID for this method.
4747+ @return A complete JMAP request. *)
4848+val simple_request :
4949+ using:string list ->
5050+ method_name:string ->
5151+ arguments:Yojson.Safe.t ->
5252+ method_call_id:string ->
5353+ Wire.Request.t
5454+5555+(** {1 Advanced Response Processing} *)
5656+5757+(** High-level response processing utilities that simplify extracting and parsing method responses. *)
5858+module Response : sig
5959+ (** Extract and parse a specific method response from a JMAP Response object.
6060+ @param response The JMAP response to search
6161+ @param method_call_id The method call ID to extract
6262+ @param parser Function to parse the response JSON into the desired type
6363+ @return Parsed response or error if not found/parsing failed *)
6464+ val extract_method_response :
6565+ Wire.Response.t ->
6666+ method_call_id:string ->
6767+ parser:(Yojson.Safe.t -> ('a, Error.error) result) ->
6868+ ('a, Error.error) result
6969+7070+ (** Extract all method responses from a JMAP Response object as (method_call_id, response_json) pairs.
7171+ @param response The JMAP response to extract from
7272+ @return List of all method responses with their IDs and JSON data *)
7373+ val extract_all_responses :
7474+ Wire.Response.t ->
7575+ (string * Yojson.Safe.t) list
7676+end
+1-186
jmap/jmap/session.ml
···4455type server_capability_value = Yojson.Safe.t
6677-type auth =
88- | Bearer_token of string
99- | Basic_auth of string * string
1010- | No_auth
1111-127module Core_capability = struct
138 type t = {
149 max_size_upload : int;
···347342 ) t.primary_accounts
348343end
349344350350-module Discovery = struct
351351- type discovery_error =
352352- | Network_error of string
353353- | Invalid_domain of string
354354- | Dns_lookup_failed of string
355355- | No_service_found
356356-357357- let discovery_error_to_string = function
358358- | Network_error msg -> "Network error: " ^ msg
359359- | Invalid_domain domain -> "Invalid domain: " ^ domain
360360- | Dns_lookup_failed domain -> "DNS lookup failed for: " ^ domain
361361- | No_service_found -> "No JMAP service found"
362362-363363- let validate_domain domain =
364364- if String.length domain = 0 then false
365365- else if String.contains domain ' ' then false
366366- else if String.contains domain '\t' then false
367367- else if String.contains domain '\n' then false
368368- else true
369369-370370- let discover_well_known ~domain =
371371- if not (validate_domain domain) then
372372- Error (Invalid_domain domain)
373373- else
374374- try
375375- let well_known_url = Uri.make ~scheme:"https" ~host:domain
376376- ~path:"/.well-known/jmap" () in
377377- Ok well_known_url
378378- with
379379- | _ -> Error (Network_error ("Failed to construct well-known URL for " ^ domain))
380380-381381- let discover_srv ~domain =
382382- if not (validate_domain domain) then
383383- Error (Invalid_domain domain)
384384- else
385385- try
386386- let hostname = "jmap." ^ domain in
387387- let port = 443 in
388388- let session_url = Uri.make ~scheme:"https" ~host:hostname ~port
389389- ~path:"/.well-known/jmap" () in
390390- Ok session_url
391391- with
392392- | _ -> Error (Dns_lookup_failed domain)
393393-394394- let discover_any ~domain =
395395- match discover_well_known ~domain with
396396- | Ok url -> Ok url
397397- | Error _ ->
398398- match discover_srv ~domain with
399399- | Ok url -> Ok url
400400- | Error _ -> Error No_service_found
401401-402402- let discover_from_email ~email =
403403- try
404404- let at_pos = String.rindex email '@' in
405405- let domain = String.sub email (at_pos + 1) (String.length email - at_pos - 1) in
406406- discover_any ~domain
407407- with
408408- | Not_found -> Error (Invalid_domain email)
409409- | _ -> Error (Invalid_domain email)
410410-end
411411-412412-let discover ~domain =
413413- match Discovery.discover_any ~domain with
414414- | Ok url -> Some url
415415- | Error _ -> None
416416-417417-module HTTP_Client = struct
418418- type http_error =
419419- | Connection_failed of string
420420-421421- let http_error_to_string = function
422422- | Connection_failed msg -> "Connection failed: " ^ msg
423423-424424- let auth_headers = function
425425- | Bearer_token token -> [("Authorization", "Bearer " ^ token)]
426426- | Basic_auth (user, pass) ->
427427- let credentials = Base64.encode_string (user ^ ":" ^ pass) in
428428- [("Authorization", "Basic " ^ credentials)]
429429- | No_auth -> []
430430-431431- let make_request ~url ~auth =
432432- let headers = ("Accept", "application/json") :: ("User-Agent", "ocaml-jmap/1.0") :: (auth_headers auth) in
433433- try
434434- let response_json = `Assoc [
435435- ("capabilities", `Assoc [
436436- (Jmap_capability.to_string `Core, `Assoc [
437437- ("maxSizeUpload", `Int 50_000_000);
438438- ("maxConcurrentUpload", `Int 8);
439439- ("maxSizeRequest", `Int 10_000_000);
440440- ("maxConcurrentRequests", `Int 8);
441441- ("maxCallsInRequest", `Int 32);
442442- ("maxObjectsInGet", `Int 500);
443443- ("maxObjectsInSet", `Int 500);
444444- ("collationAlgorithms", `List [
445445- `String "i;ascii-numeric";
446446- `String "i;ascii-casemap";
447447- `String "i;unicode-casemap"
448448- ])
449449- ]);
450450- (Jmap_capability.to_string `Mail, `Assoc []);
451451- ("urn:ietf:params:jmap:contacts", `Assoc [])
452452- ]);
453453- ("accounts", `Assoc [
454454- ("A13824", `Assoc [
455455- ("name", `String "john@example.com");
456456- ("isPersonal", `Bool true);
457457- ("isReadOnly", `Bool false);
458458- ("accountCapabilities", `Assoc [
459459- (Jmap_capability.to_string `Mail, `Assoc [
460460- ("maxMailboxesPerEmail", `Null);
461461- ("maxMailboxDepth", `Int 10)
462462- ]);
463463- ("urn:ietf:params:jmap:contacts", `Assoc [])
464464- ])
465465- ])
466466- ]);
467467- ("primaryAccounts", `Assoc [
468468- (Jmap_capability.to_string `Mail, `String "A13824");
469469- ("urn:ietf:params:jmap:contacts", `String "A13824")
470470- ]);
471471- ("username", `String (match auth with
472472- | Basic_auth (user, _) -> user
473473- | Bearer_token _ -> "authenticated@example.com"
474474- | No_auth -> "anonymous@example.com"));
475475- ("apiUrl", `String (Uri.to_string url ^ "../api/"));
476476- ("downloadUrl", `String (Uri.to_string url ^ "../download/{accountId}/{blobId}/{name}?accept={type}"));
477477- ("uploadUrl", `String (Uri.to_string url ^ "../upload/{accountId}/"));
478478- ("eventSourceUrl", `String (Uri.to_string url ^ "../eventsource/?types={types}&closeafter={closeafter}&ping={ping}"));
479479- ("state", `String "75128aab4b1b")
480480- ] in
481481- let _ = headers in
482482- Ok response_json
483483- with
484484- | _ -> Error (Connection_failed ("Failed to connect to " ^ Uri.to_string url))
485485-end
486486-487345let parse_session_json json =
488346 try
489347 let open Yojson.Safe.Util in
···558416 ~upload_url:(Uri.of_string "https://example.com/upload/{accountId}/")
559417 ~event_source_url:(Uri.of_string "https://example.com/events/")
560418 ~state:"fallback"
561561- ()
562562-563563-let get_session ~url =
564564- match HTTP_Client.make_request ~url ~auth:No_auth with
565565- | Ok json -> parse_session_json json
566566- | Error _err ->
567567- let fallback_json = `Assoc [
568568- ("capabilities", `Assoc [
569569- (Jmap_capability.to_string `Core, `Assoc [
570570- ("maxSizeUpload", `Int 50_000_000);
571571- ("maxConcurrentUpload", `Int 4);
572572- ("maxSizeRequest", `Int 10_000_000);
573573- ("maxConcurrentRequests", `Int 4);
574574- ("maxCallsInRequest", `Int 16);
575575- ("maxObjectsInGet", `Int 500);
576576- ("maxObjectsInSet", `Int 500);
577577- ("collationAlgorithms", `List [`String "i;unicode-casemap"])
578578- ])
579579- ]);
580580- ("accounts", `Assoc []);
581581- ("primaryAccounts", `Assoc []);
582582- ("username", `String "fallback@example.com");
583583- ("apiUrl", `String "https://example.com/api/");
584584- ("downloadUrl", `String "https://example.com/download/{accountId}/{blobId}/{name}");
585585- ("uploadUrl", `String "https://example.com/upload/{accountId}/");
586586- ("eventSourceUrl", `String "https://example.com/events/");
587587- ("state", `String "fallback")
588588- ] in
589589- parse_session_json fallback_json
590590-591591-let get_session_with_auth ~url ~auth =
592592- match HTTP_Client.make_request ~url ~auth with
593593- | Ok json -> Ok (parse_session_json json)
594594- | Error err -> Error (HTTP_Client.http_error_to_string err)
595595-596596-let discover_and_connect ~domain =
597597- match discover ~domain with
598598- | Some url -> Ok (get_session ~url)
599599- | None -> Error ("Could not discover JMAP service for domain: " ^ domain)
600600-601601-let discover_and_connect_with_email ~email =
602602- match Discovery.discover_from_email ~email with
603603- | Ok url -> Ok (get_session ~url)
604604- | Error err -> Error (Discovery.discovery_error_to_string err)419419+ ()
+2-70
jmap/jmap/session.mli
···289289 val get_capability_accounts : t -> Jmap_capability.t -> (string * Account.t) list
290290end
291291292292-(** {1 Session Discovery and Retrieval} *)
293293-294294-(** Function to perform service autodiscovery.
295295-296296- JMAP supports automatic discovery of the session endpoint using well-known
297297- URIs. This function attempts to discover the JMAP session URL for a given
298298- domain by checking the well-known location.
299299-300300- The discovery process involves:
301301- 1. Checking /.well-known/jmap for the domain
302302- 2. Following any redirects
303303- 3. Parsing the response to extract the session URL
304304-305305- {b Example usage}:
306306- {[
307307- match discover ~domain:"mail.example.com" with
308308- | Some session_url -> (* Use session_url to get session *)
309309- | None -> (* Fall back to manual configuration *)
310310- ]}
311311-312312- @param domain The domain to discover JMAP service for (e.g., "mail.example.com")
313313- @return The session URL if discovery succeeds, None otherwise
314314- @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-2.2> RFC 8620, Section 2.2 *)
315315-val discover : domain:string -> Uri.t option
316316-317317-(** Function to fetch the session object from a given URL.
318318-319319- This function retrieves and parses the session resource from the server.
320320- The session URL is typically obtained either through service discovery
321321- or from manual configuration.
322322-323323- {b Note}: This function signature assumes authentication is handled
324324- externally (e.g., through HTTP headers or URL parameters). In a real
325325- implementation, authentication credentials would need to be provided.
326326-327327- {b Example usage}:
328328- {[
329329- let session_url = Uri.of_string "https://mail.example.com/jmap/session" in
330330- let session = get_session ~url:session_url in
331331- (* Use session for subsequent JMAP requests *)
332332- ]}
333333-334334- @param url The session endpoint URL (typically ends with /session)
335335- @return The parsed session object
336336-337337- May raise network or parsing exceptions on failure. *)
338338-val get_session : url:Uri.t -> Session.t
292292+(** {1 JSON Parsing} *)
339293340294(** Parse a session object from JSON.
341295 @param json The JSON representation of the session
342296 @return The parsed session object *)
343343-val parse_session_json : Yojson.Safe.t -> Session.t
344344-345345-(** Authentication types for session retrieval. *)
346346-type auth =
347347- | Bearer_token of string (** OAuth2 bearer token *)
348348- | Basic_auth of string * string (** Username and password *)
349349- | No_auth (** No authentication *)
350350-351351-(** Get session with authentication credentials.
352352- @param url The session endpoint URL
353353- @param auth Authentication credentials to use
354354- @return The parsed session object or error message *)
355355-val get_session_with_auth : url:Uri.t -> auth:auth -> (Session.t, string) result
356356-357357-(** Discover JMAP service and connect in one step.
358358- @param domain Domain to discover and connect to
359359- @return Connected session or error message *)
360360-val discover_and_connect : domain:string -> (Session.t, string) result
361361-362362-(** Discover JMAP service from email address and connect.
363363- @param email Email address to extract domain from
364364- @return Connected session or error message *)
365365-val discover_and_connect_with_email : email:string -> (Session.t, string) result297297+val parse_session_json : Yojson.Safe.t -> Session.t