···131131let args = `Assoc [("accountId", `String id); ...]
132132133133(* CORRECT in jmap-unix *)
134134-let query = Jmap_email_query.query () |> with_account id |> ...
134134+let query = Jmap_email.Query.query () |> with_account id |> ...
135135```
136136137137## 3. **Layer Responsibilities and Dependencies** ⬆️
···155155- **Exports**: Email-specific JSON builders, property utilities, email parsers
156156- **NO I/O**: Pure JSON processing, no transport or execution
157157- **Key Functions**:
158158- - `Jmap_email_query.to_json : query_builder -> Yojson.Safe.t`
159159- - `Jmap_email_query.property_preset_to_strings : preset -> string list`
160160- - `Jmap_email.of_json : Yojson.Safe.t -> (t, error) result`
158158+ - `Jmap_email.Query.to_json : query_builder -> Yojson.Safe.t`
159159+ - `Jmap_email.Query.property_preset_to_strings : preset -> string list`
160160+ - `Jmap_email.Email.of_json : Yojson.Safe.t -> (t, error) result`
161161162162### **jmap-unix** (Network Transport Layer)
163163- **Purpose**: Network I/O, connection management, request execution
···165165- **Exports**: High-level email operations with network execution
166166- **Has Context**: Manages connection state, sessions, authentication
167167- **Key Functions**:
168168- - `query_emails : context -> Jmap_email_query.query_builder -> (email list, error) result`
169169- - `get_emails : context -> Jmap_email_query.get_args -> (email list, error) result`
168168+ - `query_emails : context -> Jmap_email.Query.query_builder -> (email list, error) result`
169169+ - `get_emails : context -> Jmap_email.Query.get_args -> (email list, error) result`
170170171171### **CRITICAL ARCHITECTURAL INSIGHT**:
172172**jmap-email produces JSON, jmap-unix consumes it for transport**
···177177178178(* jmap-unix: uses jmap-email builders + adds transport *)
179179let query_emails env ctx query_builder =
180180- let query_json = Jmap_email_query.build_email_query query_builder in
180180+ let query_json = Jmap_email.Query.build_email_query query_builder in
181181 (* ... execute via network transport ... *)
182182```
183183···187187188188```ocaml
189189(* jmap-unix: ONLY these imports allowed *)
190190-open Jmap_email_query
191191-open Jmap_email_batch
192192-open Jmap_email_methods
190190+open Jmap_email.Query
191191+open Jmap_email.Email
192192+open Jmap_email.Response
193193194194(* jmap-email: ONLY these imports allowed *)
195195open Jmap.Methods
196196open Jmap.Types
197197-open Jmap.Protocol
197197+open Jmap.Wire
198198199199(* jmap: ONLY these imports allowed *)
200200open Jmap_sigs
···202202203203**FORBIDDEN imports:**
204204- jmap-unix importing Jmap directly
205205-- jmap-email importing Jmap_sigs directly
205205+- jmap-email importing Jmap_sigs directly
206206- Any cross-layer violations
207207+- Using old nested module paths like `Jmap.Protocol.Wire`
207208208209# CODE QUALITY PRINCIPLES FOR UNRELEASED LIBRARY
209210···429430 @param id The unique identifier for this email
430431 @param blob_id The identifier for the raw RFC5322 message content *)
431432type t = {
432432- id : Id.t;
433433- blob_id : Id.t option;
433433+ id : Jmap.Types.Id.t;
434434+ blob_id : Jmap.Types.Id.t option;
434435 (* ... *)
435436}
436437```
···446447JMAP's error model maps well to OCaml's result types:
447448448449```ocaml
449449-type 'a result = ('a, Error.t) Result.t
450450+type 'a result = ('a, Jmap.Error.error) Result.t
450451451452module Error : sig
452453 type t =
···685686val query_emails :
686687 env:< net : 'a Eio.Net.t ; .. > ->
687688 context ->
688688- Jmap_email_query.query_builder ->
689689- (Jmap_email.t list, error) result
689689+ Jmap_email.Query.query_builder ->
690690+ (Jmap_email.Email.t list, error) result
690691691692(* Implementation example *)
692693let query_emails env ctx query_builder =
693694 (* Use jmap-email to build JSON *)
694694- let query_json = Jmap_email_query.build_email_query query_builder in
695695+ let query_json = Jmap_email.Query.build_email_query query_builder in
695696 let builder = build ctx |> add_method_call "Email/query" query_json "q1" in
696697 (* Execute via network transport *)
697698 execute env builder >>= fun response ->
698699 (* Use jmap-email to parse response *)
699699- Jmap_email.parse_email_list response
700700+ Jmap_email.Email.parse_email_list response
700701```
701702702703## 🔧 **Current fastmail_connect Issues**
···718719**Line 66-71**: Manual email parsing
719720```ocaml
720721(* WRONG: Direct JSON parsing bypassing jmap-email *)
721721-let email_from_json json = match Jmap_email.of_json json with ...
722722+let email_from_json json = match Jmap_email.Email.of_json json with ...
722723```
723724724725### **Needed Implementation (Priority Order)**
7257267267271. **jmap-email JSON builders** (eliminates lines 23-52):
727727- - `Jmap_email_query.build_query : query_builder -> Yojson.Safe.t`
728728- - `Jmap_email_query.build_get_args : property list -> Yojson.Safe.t`
728728+ - `Jmap_email.Query.build_query : query_builder -> Yojson.Safe.t`
729729+ - `Jmap_email.Query.build_get_args : property list -> Yojson.Safe.t`
729730 - Property preset utilities
7307317317322. **jmap-unix high-level operations** (eliminates manual execution):
+8-5
jmap/README.md
···47474848(* Using the JMAP Email extension library *)
4949open Jmap_email
5050-open Jmap_email.Types
51505251(* Example: Connecting to a JMAP server *)
5353-let connect_to_server () =
5454- let credentials = Jmap_unix.Basic("username", "password") in
5555- let (ctx, session) = Jmap_unix.quick_connect ~host:"jmap.example.com" ~username:"user" ~password:"pass" in
5656- ...
5252+let connect_to_server env =
5353+ let ctx = Jmap_unix.create_client () in
5454+ match Jmap_unix.quick_connect env ~host:"jmap.example.com" ~username:"user" ~password:"pass" () with
5555+ | Ok (ctx, session) ->
5656+ (* Use ctx and session for JMAP requests *)
5757+ Printf.printf "Connected successfully!\n"
5858+ | Error err ->
5959+ Printf.eprintf "Connection failed: %s\n" (Jmap.Error.to_string err)
5760```
58615962## Building
+20-20
jmap/bin/fastmail_connect.ml
···99 printf "Using account: %s\nBuilding JMAP request using type-safe capabilities...\n" account_id;
10101111 let query_json =
1212- Jmap_email.Jmap_email_query.(query () |> with_account account_id |> order_by Sort.by_date_desc |> limit 5 |> build_email_query) in
1212+ Jmap_email.Query.(query () |> with_account account_id |> order_by Sort.by_date_desc |> limit 5 |> build_email_query) in
13131414 let get_json =
1515- Jmap_email.Jmap_email_query.(build_email_get_with_ref ~account_id
1515+ Jmap_email.Query.(build_email_get_with_ref ~account_id
1616 ~properties:[`Id; `ThreadId; `From; `Subject; `ReceivedAt; `Preview; `Keywords; `HasAttachment]
1717 ~result_of:"q1") in
1818···2525 printf "✓ Got JMAP response\n";
26262727 let+ query_response_json = Jmap_unix.Response.extract_method ~method_name:`Email_query ~method_call_id:"q1" response in
2828- let+ query_response = Jmap_email.Email_response.parse_query_response query_response_json in
2929- printf "✓ Found %d emails\n\n" (Jmap_email.Email_response.ids_from_query_response query_response |> List.length);
2828+ let+ query_response = Jmap_email.Response.parse_query_response query_response_json in
2929+ printf "✓ Found %d emails\n\n" (Jmap_email.Response.ids_from_query_response query_response |> List.length);
30303131 let+ get_response_json = Jmap_unix.Response.extract_method ~method_name:`Email_get ~method_call_id:"g1" response in
3232- let+ get_response = Jmap_email.Email_response.parse_get_response
3333- ~from_json:(fun json -> match Jmap_email.of_json json with
3232+ let+ get_response = Jmap_email.Response.parse_get_response
3333+ ~from_json:(fun json -> match Jmap_email.Email.of_json json with
3434 | Ok email -> email
3535 | Error err -> failwith ("Email parse error: " ^ err))
3636 get_response_json in
37373838- let emails = Jmap_email.Email_response.emails_from_get_response get_response in
3838+ let emails = Jmap_email.Response.emails_from_get_response get_response in
39394040 let print_sender email =
4141- Jmap_email.(match from email with
4141+ Jmap_email.Email.(match from email with
4242 | Some (sender :: _) ->
4343- Jmap_email.Email_address.(printf " From: %s\n"
4343+ Jmap_email.Address.(printf " From: %s\n"
4444 (match name sender with | Some n -> n ^ " <" ^ email sender ^ ">" | None -> email sender))
4545 | _ -> printf " From: (Unknown)\n") in
46464747 let print_preview email =
4848- Jmap_email.(match preview email with
4848+ Jmap_email.Email.(match preview email with
4949 | Some p when String.length p > 0 ->
5050 let preview = if String.length p > 100 then String.sub p 0 97 ^ "..." else p in
5151 printf " Preview: %s\n" preview
···53535454 List.iteri (fun i email ->
5555 printf "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nEmail #%d:\n" (i + 1);
5656- printf " Subject: %s\n" Jmap_email.(subject email |> Option.value ~default:"(No Subject)");
5656+ printf " Subject: %s\n" (Jmap_email.Email.subject email |> Option.value ~default:"(No Subject)");
5757 print_sender email;
5858- Jmap_email.(received_at email |> Option.iter (fun t ->
5959- printf " Date: %s\n" Jmap.Date.(of_timestamp t |> to_rfc3339)));
5858+ Jmap_email.Email.(received_at email |> Option.iter (fun t ->
5959+ printf " Date: %s\n" Jmap.Types.Date.(of_timestamp t |> to_rfc3339)));
6060 print_preview email
6161 ) emails;
6262 printf "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n";
6363 Ok ()
6464 with
6565- | exn -> Error (Jmap.Protocol.Error.protocol_error ("Exception: " ^ Printexc.to_string exn))
6565+ | exn -> Error (Jmap.Error.protocol_error ("Exception: " ^ Printexc.to_string exn))
66666767let main () =
6868 (* Initialize the random number generator for TLS *)
···7777 printf "Testing core JMAP modules...\n";
78787979 let test_modules = [
8080- ("Jmap.Id", Jmap.Id.(of_string "test-id-123" |> Result.map (Format.asprintf "%a" pp)));
8181- ("Jmap.Date", Ok (Jmap.Date.(Unix.time () |> of_timestamp |> to_timestamp |> Printf.sprintf "%.0f")));
8282- ("Jmap.UInt", Jmap.UInt.(of_int 42 |> Result.map (Format.asprintf "%a" pp)));
8080+ ("Jmap.Types.Id", Jmap.Types.Id.(of_string "test-id-123" |> Result.map (Format.asprintf "%a" pp)));
8181+ ("Jmap.Types.Date", Ok (Jmap.Types.Date.(Unix.time () |> of_timestamp |> to_timestamp |> Printf.sprintf "%.0f")));
8282+ ("Jmap.Types.UInt", Jmap.Types.UInt.(of_int 42 |> Result.map (Format.asprintf "%a" pp)));
8383 ] in
84848585 let test_results = List.map (fun (name, result) -> match result with
···125125 printf "\n📧 Fetching recent emails...\n";
126126 (match fetch_recent_emails env ctx session with
127127 | Ok () -> printf "✓ Email fetch completed successfully\n"
128128- | Error error -> Format.printf "⚠ Email fetch failed: %a\n" Jmap.Protocol.Error.pp error);
128128+ | Error error -> Format.printf "⚠ Email fetch failed: %a\n" Jmap.Error.pp error);
129129130130 printf "\nClosing connection...\n";
131131 (match Jmap_unix.close ctx with
132132 | Ok () -> printf "✓ Connection closed successfully\n"
133133- | Error error -> Format.printf "⚠ Error closing connection: %a\n" Jmap.Protocol.Error.pp error);
133133+ | Error error -> Format.printf "⚠ Error closing connection: %a\n" Jmap.Error.pp error);
134134135135 printf "\nOverall: ALL TESTS PASSED\n"
136136137137 | Error error ->
138138 Format.eprintf "✗ Connection failed: %a\n"
139139- Jmap.Protocol.Error.pp error;
139139+ Jmap.Error.pp error;
140140 eprintf "\nThis could be due to:\n";
141141 eprintf " - Invalid API key\n";
142142 eprintf " - Network connectivity issues\n";
+9-9
jmap/bin/test_session_wire.ml
···44let test_session_wire_type () =
55 printf "Testing Session WIRE_TYPE implementation...\n";
6677- (* Use the Protocol.Session.Session module *)
88- let open Protocol.Session.Session in
77+ (* Use the Session module *)
88+ let open Session in
991010 (* Create a basic session *)
1111 let capabilities = Hashtbl.create 1 in
···2424 let accounts = Hashtbl.create 0 in
2525 let primary_accounts = Hashtbl.create 0 in
26262727- let session = v
2727+ let session = Session.v
2828 ~capabilities
2929 ~accounts
3030 ~primary_accounts
···38383939 (* Test validation *)
4040 printf "Testing validation...\n";
4141- (match validate session with
4141+ (match Session.validate session with
4242 | Ok () -> printf "✓ Session validation passed\n"
4343 | Error msg -> printf "✗ Session validation failed: %s\n" msg);
44444545 (* Test pretty printing *)
4646 printf "Testing pretty printing...\n";
4747- Format.printf "Session (pp): %a\n" pp session;
4848- Format.printf "Session (pp_hum):\n%a\n" pp_hum session;
4747+ Format.printf "Session (pp): %a\n" Session.pp session;
4848+ Format.printf "Session (pp_hum):\n%a\n" Session.pp_hum session;
49495050 (* Test JSON roundtrip *)
5151 printf "Testing JSON serialization...\n";
5252- let json = to_json session in
5353- (match of_json json with
5252+ let json = Session.to_json session in
5353+ (match Session.of_json json with
5454 | Ok session2 ->
5555 printf "✓ JSON roundtrip successful\n";
5656- (match validate session2 with
5656+ (match Session.validate session2 with
5757 | Ok () -> printf "✓ Deserialized session is valid\n"
5858 | Error msg -> printf "✗ Deserialized session validation failed: %s\n" msg)
5959 | Error msg -> printf "✗ JSON roundtrip failed: %s\n" msg);
+1-1
jmap/docs/queries/README.md
···163163- [RFC 8620: The JSON Meta Application Protocol (JMAP)](https://www.rfc-editor.org/rfc/rfc8620.html)
164164- [RFC 8621: The JSON Meta Application Protocol (JMAP) for Mail](https://www.rfc-editor.org/rfc/rfc8621.html)
165165- [JMAP Method Documentation](../jmap/jmap_methods.mli)
166166-- [Email Type Definitions](../jmap-email/jmap_email_types.mli)166166+- [Email Type Definitions](../jmap-email/email.mli)
···5353(** Get the keywords/flags applied to this email.
5454 @param t The email object
5555 @return Set of keywords if included in the retrieved properties *)
5656-val keywords : t -> Jmap_email_keywords.t option
5656+val keywords : t -> Keywords.t option
57575858(** Get the total size of the raw message.
5959 @param t The email object
···8383(** Get the Sender header address.
8484 @param t The email object
8585 @return Single sender address if the Sender property was requested *)
8686-val sender : t -> Jmap_email_address.t option
8686+val sender : t -> Address.t option
87878888(** Get the From header addresses.
8989 @param t The email object
9090 @return List of sender addresses if the From property was requested *)
9191-val from : t -> Jmap_email_address.t list option
9191+val from : t -> Address.t list option
92929393(** Get the To header addresses.
9494 @param t The email object
9595 @return List of primary recipient addresses if the To property was requested *)
9696-val to_ : t -> Jmap_email_address.t list option
9696+val to_ : t -> Address.t list option
97979898(** Get the Cc header addresses.
9999 @param t The email object
100100 @return List of carbon copy addresses if the Cc property was requested *)
101101-val cc : t -> Jmap_email_address.t list option
101101+val cc : t -> Address.t list option
102102103103(** Get the Bcc header addresses.
104104 @param t The email object
105105 @return List of blind carbon copy addresses if the Bcc property was requested *)
106106-val bcc : t -> Jmap_email_address.t list option
106106+val bcc : t -> Address.t list option
107107108108(** Get the Reply-To header addresses.
109109 @param t The email object
110110 @return List of reply-to addresses if the ReplyTo property was requested *)
111111-val reply_to : t -> Jmap_email_address.t list option
111111+val reply_to : t -> Address.t list option
112112113113(** Get the email subject line.
114114 @param t The email object
···133133(** Get the complete MIME structure tree of the message.
134134 @param t The email object
135135 @return Body structure if the BodyStructure property was requested *)
136136-val body_structure : t -> Jmap_email_body.t option
136136+val body_structure : t -> Body.t option
137137138138(** Get decoded content of requested text body parts.
139139 @param t The email object
140140 @return Map of part IDs to decoded content if BodyValues was requested *)
141141-val body_values : t -> Jmap_email_body.Value.t string_map option
141141+val body_values : t -> Body.Value.t string_map option
142142143143(** Get text/plain body parts suitable for display.
144144 @param t The email object
145145 @return List of text body parts if the TextBody property was requested *)
146146-val text_body : t -> Jmap_email_body.t list option
146146+val text_body : t -> Body.t list option
147147148148(** Get text/html body parts suitable for display.
149149 @param t The email object
150150 @return List of HTML body parts if the HtmlBody property was requested *)
151151-val html_body : t -> Jmap_email_body.t list option
151151+val html_body : t -> Body.t list option
152152153153(** Get attachment body parts.
154154 @param t The email object
155155 @return List of attachment parts if the Attachments property was requested *)
156156-val attachments : t -> Jmap_email_body.t list option
156156+val attachments : t -> Body.t list option
157157158158(** Get the value of a specific header field.
159159···213213 ?blob_id:id ->
214214 ?thread_id:id ->
215215 ?mailbox_ids:bool id_map ->
216216- ?keywords:Jmap_email_keywords.t ->
216216+ ?keywords:Keywords.t ->
217217 ?size:uint ->
218218 ?received_at:date ->
219219 ?message_id:string list ->
220220 ?in_reply_to:string list ->
221221 ?references:string list ->
222222- ?sender:Jmap_email_address.t ->
223223- ?from:Jmap_email_address.t list ->
224224- ?to_:Jmap_email_address.t list ->
225225- ?cc:Jmap_email_address.t list ->
226226- ?bcc:Jmap_email_address.t list ->
227227- ?reply_to:Jmap_email_address.t list ->
222222+ ?sender:Address.t ->
223223+ ?from:Address.t list ->
224224+ ?to_:Address.t list ->
225225+ ?cc:Address.t list ->
226226+ ?bcc:Address.t list ->
227227+ ?reply_to:Address.t list ->
228228 ?subject:string ->
229229 ?sent_at:date ->
230230 ?has_attachment:bool ->
231231 ?preview:string ->
232232- ?body_structure:Jmap_email_body.t ->
233233- ?body_values:Jmap_email_body.Value.t string_map ->
234234- ?text_body:Jmap_email_body.t list ->
235235- ?html_body:Jmap_email_body.t list ->
236236- ?attachments:Jmap_email_body.t list ->
232232+ ?body_structure:Body.t ->
233233+ ?body_values:Body.Value.t string_map ->
234234+ ?text_body:Body.t list ->
235235+ ?html_body:Body.t list ->
236236+ ?attachments:Body.t list ->
237237 ?headers:string string_map ->
238238 ?other_properties:Yojson.Safe.t string_map ->
239239 unit -> t
···276276277277 @param t The email object
278278 @return Primary sender address if available *)
279279-val primary_sender : t -> Jmap_email_address.t option
279279+val primary_sender : t -> Address.t option
280280281281(** Get all recipient addresses (To, Cc, Bcc combined).
282282283283 @param t The email object
284284 @return List of all recipient addresses from To, Cc, and Bcc fields *)
285285-val all_recipients : t -> Jmap_email_address.t list
285285+val all_recipients : t -> Address.t list
286286287287(** Get a short display summary of the email.
288288···313313 @param remove_mailboxes Mailboxes to remove the email from
314314 @return JSON Patch operations for Email/set *)
315315 val create :
316316- ?add_keywords:Jmap_email_keywords.t ->
317317- ?remove_keywords:Jmap_email_keywords.t ->
316316+ ?add_keywords:Keywords.t ->
317317+ ?remove_keywords:Keywords.t ->
318318 ?add_mailboxes:id list ->
319319 ?remove_mailboxes:id list ->
320320 unit -> Yojson.Safe.t
···345345(** Module aliases for external access *)
346346347347(** Email address types and operations *)
348348-module Email_address = Jmap_email_address
348348+module Email_address = Address
349349350350(** Email keywords and flags *)
351351-module Email_keywords = Jmap_email_keywords
351351+module Email_keywords = Keywords
352352353353(** Email header fields *)
354354-module Email_header = Jmap_email_header
354354+module Email_header = Header
355355356356(** Email body parts and content *)
357357-module Email_body = Jmap_email_body
357357+module Email_body = Body
358358359359(** Apple Mail extensions *)
360360-module Apple_mail = Jmap_email_apple
360360+module Apple_mail = Apple
361361362362(** Thread operations and data types *)
363363-module Thread = Jmap_thread
363363+module Thread = Thread
364364365365(** Identity operations and data types *)
366366-module Identity = Jmap_identity
366366+module Identity = Identity
367367368368(** Email query builder and operations *)
369369-module Jmap_email_query = Jmap_email_query
369369+module Query = Query
370370371371(** Email response parsing using core JMAP parsers *)
372372-module Email_response = Jmap_email_response
372372+module Email_response = Response
373373374374(** Email set operations using core JMAP Set_args *)
375375-module Email_set = Jmap_email_set
375375+module Email_set = Set
376376377377(** Email changes operations using core JMAP Changes_args *)
378378-module Email_changes = Jmap_email_changes
378378+module Email_changes = Changes
379379380380(** Legacy aliases for backward compatibility *)
381381module Types : sig
382382- module Keywords = Jmap_email_keywords
383383- module Email_address = Jmap_email_address
382382+ module Keywords = Keywords
383383+ module Email_address = Address
384384 module Email : sig
385385 type nonrec t = t
386386 val id : t -> id option
387387 val received_at : t -> date option
388388 val subject : t -> string option
389389- val from : t -> Jmap_email_address.t list option
390390- val keywords : t -> Jmap_email_keywords.t option
389389+ val from : t -> Address.t list option
390390+ val keywords : t -> Keywords.t option
391391 end
392392end
···44 flag encoding defined in draft-ietf-mailmaint-messageflag.
55*)
6677-open Jmap_email_types
77+open Types
8899(** Apple Mail color flag enumeration *)
1010type color =
···1212 @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-2.6> RFC 8621 Keywords
1313*)
14141515-open Jmap_email_types
1515+open Types
16161717(** Apple Mail color flag enumeration.
1818
···4545(** Get the list of MIME headers for this part.
4646 @param t The body part
4747 @return List of header fields specific to this body part *)
4848-val headers : t -> Jmap_email_header.t list
4848+val headers : t -> Header.t list
49495050(** Get the filename parameter from Content-Disposition or Content-Type.
5151 @param t The body part
···116116 ?id:string ->
117117 ?blob_id:id ->
118118 size:uint ->
119119- headers:Jmap_email_header.t list ->
119119+ headers:Header.t list ->
120120 ?name:string ->
121121 mime_type:string ->
122122 ?charset:string ->
···151151 ?id:string ->
152152 ?blob_id:id ->
153153 size:uint ->
154154- headers:Jmap_email_header.t list ->
154154+ headers:Header.t list ->
155155 ?name:string ->
156156 mime_type:string ->
157157 ?charset:string ->
···991010(** Type-safe email property selectors.
11111212- Uses the canonical polymorphic variant property system from [Jmap_email_property].
1212+ Uses the canonical polymorphic variant property system from [property].
1313 This provides full compatibility with all JMAP Email properties including
1414 header and custom extension properties.
1515*)
1616-type property = Jmap_email_property.t
1616+type property = Property.t
171718181919
···1313*)
14141515open Jmap.Types
1616-open Jmap.Protocol.Error
1616+open Jmap.Error
17171818(** Complete identity object representation.
1919···47474848(** Get the default Reply-To addresses for this identity.
4949 @return List of reply-to addresses, or None if not specified *)
5050-val reply_to : t -> Jmap_email_types.Email_address.t list option
5050+val reply_to : t -> Types.Email_address.t list option
51515252(** Get the default Bcc addresses for this identity.
5353 @return List of addresses to always Bcc, or None if not specified *)
5454-val bcc : t -> Jmap_email_types.Email_address.t list option
5454+val bcc : t -> Types.Email_address.t list option
55555656(** Get the plain text email signature.
5757 @return Text signature to append to plain text messages *)
···7979 id:id ->
8080 ?name:string ->
8181 email:string ->
8282- ?reply_to:Jmap_email_types.Email_address.t list ->
8383- ?bcc:Jmap_email_types.Email_address.t list ->
8282+ ?reply_to:Types.Email_address.t list ->
8383+ ?bcc:Types.Email_address.t list ->
8484 ?text_signature:string ->
8585 ?html_signature:string ->
8686 may_delete:bool ->
···110110111111 (** Get the Reply-To addresses for creation.
112112 @return Optional list of reply-to addresses *)
113113- val reply_to : t -> Jmap_email_types.Email_address.t list option
113113+ val reply_to : t -> Types.Email_address.t list option
114114115115 (** Get the Bcc addresses for creation.
116116 @return Optional list of default Bcc addresses *)
117117- val bcc : t -> Jmap_email_types.Email_address.t list option
117117+ val bcc : t -> Types.Email_address.t list option
118118119119 (** Get the plain text signature for creation.
120120 @return Optional text signature *)
···135135 val v :
136136 ?name:string ->
137137 email:string ->
138138- ?reply_to:Jmap_email_types.Email_address.t list ->
139139- ?bcc:Jmap_email_types.Email_address.t list ->
138138+ ?reply_to:Types.Email_address.t list ->
139139+ ?bcc:Types.Email_address.t list ->
140140 ?text_signature:string ->
141141 ?html_signature:string ->
142142 unit -> t
···201201 (** Create an update that sets the Reply-To addresses.
202202 @param reply_to New Reply-To addresses (None to clear)
203203 @return Update patch object *)
204204- val set_reply_to : Jmap_email_types.Email_address.t list option -> t
204204+ val set_reply_to : Types.Email_address.t list option -> t
205205206206 (** Create an update that sets the Bcc addresses.
207207 @param bcc New default Bcc addresses (None to clear)
208208 @return Update patch object *)
209209- val set_bcc : Jmap_email_types.Email_address.t list option -> t
209209+ val set_bcc : Types.Email_address.t list option -> t
210210211211 (** Create an update that sets the plain text signature.
212212 @param text_signature New text signature (empty string to clear)
···320320 id : id;
321321 name : string;
322322 email : string;
323323- reply_to : Jmap_email_types.Email_address.t list option;
324324- bcc : Jmap_email_types.Email_address.t list option;
323323+ reply_to : Types.Email_address.t list option;
324324+ bcc : Types.Email_address.t list option;
325325 text_signature : string;
326326 html_signature : string;
327327 may_delete : bool;
···887887 (** Get the creation failures.
888888 @param response Set response
889889 @return Map of creation IDs to error objects *)
890890- val not_created : t -> (string * Jmap.Protocol.Error.error) list
890890+ val not_created : t -> (string * Jmap.Error.error) list
891891892892 (** Get the update failures.
893893 @param response Set response
894894 @return Map of mailbox IDs to error objects *)
895895- val not_updated : t -> (id * Jmap.Protocol.Error.error) list
895895+ val not_updated : t -> (id * Jmap.Error.error) list
896896897897 (** Get the destruction failures.
898898 @param response Set response
899899 @return Map of mailbox IDs to error objects *)
900900- val not_destroyed : t -> (id * Jmap.Protocol.Error.error) list
900900+ val not_destroyed : t -> (id * Jmap.Error.error) list
901901end
902902903903module Changes_args : sig
···662662 (** Get the submission IDs that could not be created.
663663 @param response The set response object
664664 @return Submission IDs that could not be created *)
665665- val not_created : t -> Jmap.Protocol.Error.Set_error.t id_map option
665665+ val not_created : t -> Jmap.Error.Set_error.t id_map option
666666667667 (** Get the submission IDs that could not be updated.
668668 @param response The set response object
669669 @return Submission IDs that could not be updated *)
670670- val not_updated : t -> Jmap.Protocol.Error.Set_error.t id_map option
670670+ val not_updated : t -> Jmap.Error.Set_error.t id_map option
671671672672 (** Get the submission IDs that could not be destroyed.
673673 @param response The set response object
674674 @return Submission IDs that could not be destroyed *)
675675- val not_destroyed : t -> Jmap.Protocol.Error.Set_error.t id_map option
675675+ val not_destroyed : t -> Jmap.Error.Set_error.t id_map option
676676end
677677678678(** {1 Filter Helper Functions} *)
···11(** Expect tests for Apple Mail color flag support *)
2233-open Jmap_email_apple
44-open Jmap_email_keywords
33+open Apple
44+open Keywords
5566let%expect_test "Apple Mail color keyword mapping" =
77 (* Test individual color keyword mappings *)
+3-3
jmap/jmap-email/test_email_json.ml
···11-open Jmap_email_address
22-open Jmap_email_keywords
11+open Address
22+open Keywords
3344let%expect_test "email_address_json_roundtrip" =
55 let addr = match create ~name:"John Doe" ~email:"john@example.com" () with
···203203204204(* EmailSubmission tests *)
205205(* Access submission module through the main Jmap_email module *)
206206-module Submission = Jmap_email.Submission
206206+module Submission = Submission
207207open Jmap.Methods
208208209209let%expect_test "email_submission_filter_identity_ids" =
+32-19
jmap/jmap-unix/README.md
···2121open Jmap_unix
22222323(* Create a connection to a JMAP server *)
2424-let credentials = Basic("username", "password") in
2525-let (ctx, session) = Jmap_unix.connect ~host:"jmap.example.com" ~credentials in
2626-2727-(* Use the connection for JMAP requests *)
2828-let response = Jmap_unix.request ctx request in
2929-3030-(* Close the connection when done *)
3131-Jmap_unix.close ctx
2424+let connect_example env =
2525+ let ctx = Jmap_unix.create_client () in
2626+ match Jmap_unix.quick_connect env ~host:"jmap.example.com" ~username:"user" ~password:"pass" () with
2727+ | Ok (ctx, session) ->
2828+ (* Use the connection for JMAP requests *)
2929+ let builder = Jmap_unix.build ctx in
3030+ let builder = Jmap_unix.using builder [`Core] in
3131+ (* ... add method calls ... *)
3232+ let response = Jmap_unix.execute env builder in
3333+ ignore (Jmap_unix.close ctx)
3434+ | Error err ->
3535+ Printf.eprintf "Connection failed: %s\n" (Jmap.Error.to_string err)
3236```
33373438## Email Operations
···37413842```ocaml
3943open Jmap
4040-open Jmap.Unix
4444+open Jmap_unix
41454246(* Get an email *)
4343-let email = Email.get_email ctx ~account_id ~email_id ()
4747+let get_email_example env ctx account_id email_id =
4848+ match Email.get_email env ctx ~account_id ~email_id () with
4949+ | Ok email -> Printf.printf "Got email: %s\n" (Jmap_email.Email.subject email)
5050+ | Error err -> Printf.eprintf "Error: %s\n" (Jmap.Error.to_string err)
44514552(* Search for unread emails *)
4646-let filter = Jmap_email.Email_filter.unread ()
4747-let (ids, emails) = Email.search_emails ctx ~account_id ~filter ()
5353+let search_unread env ctx account_id =
5454+ let filter = Jmap.Methods.Filter.(["hasKeyword", `String "$unseen"]) in
5555+ match Email.search_emails env ctx ~account_id ~filter () with
5656+ | Ok (ids, Some emails) -> Printf.printf "Found %d unread emails\n" (List.length emails)
5757+ | Ok (ids, None) -> Printf.printf "Found %d unread email IDs\n" (List.length ids)
5858+ | Error err -> Printf.eprintf "Search error: %s\n" (Jmap.Error.to_string err)
48594960(* Mark emails as read *)
5050-Email.mark_as_seen ctx ~account_id ~email_ids:["email1"; "email2"] ()
5151-5252-(* Move emails to another mailbox *)
5353-Email.move_emails ctx ~account_id ~email_ids ~mailbox_id ()
6161+let mark_read env ctx account_id email_ids =
6262+ match Email.mark_as_seen env ctx ~account_id ~email_ids () with
6363+ | Ok () -> Printf.printf "Marked %d emails as read\n" (List.length email_ids)
6464+ | Error err -> Printf.eprintf "Mark error: %s\n" (Jmap.Error.to_string err)
5465```
55665667## Dependencies
57685869- jmap (core library)
5970- jmap-email (email types and helpers)
6060-- yojson
6161-- uri
6262-- unix
7171+- eio (structured concurrency)
7272+- tls-eio (TLS support)
7373+- cohttp-eio (HTTP client)
7474+- yojson (JSON handling)
7575+- uri (URL parsing)
+56-57
jmap/jmap-unix/jmap_unix.ml
···1616*)
17171818(* Core JMAP protocol for transport layer *)
1919-open Jmap.Protocol
20192120(* Email-layer imports - using proper jmap-email abstractions *)
2221module JmapEmail = Jmap_email
2323-(* module JmapEmailQuery = Jmap_email_query (* Module interface issue - will implement later *) *)
2222+(* module JmapEmailQuery = Jmap_email.Query (* Module interface issue - will implement later *) *)
242325242625(* Simple Base64 encoding function *)
···7675 | Connected of Uri.t (* Base URL for API calls *)
77767877type context = {
7979- mutable session : Session.Session.t option;
7878+ mutable session : Jmap.Session.Session.t option;
8079 mutable base_url : Uri.t option;
8180 mutable auth : auth_method;
8281 config : client_config;
···8685type request_builder = {
8786 ctx : context;
8887 mutable using : string list;
8989- mutable method_calls : Wire.Invocation.t list;
8888+ mutable method_calls : Jmap.Wire.Invocation.t list;
9089}
91909291let default_tls_config () = {
···211210 if status_code >= 200 && status_code < 300 then
212211 Ok body_content
213212 else
214214- Error (Jmap.Protocol.Error.Transport
213213+ Error (Jmap.Error.Transport
215214 (Printf.sprintf "HTTP error %d: %s" status_code body_content))
216215 with
217216 | exn ->
218218- Error (Jmap.Protocol.Error.Transport
217217+ Error (Jmap.Error.Transport
219218 (Printf.sprintf "Network error: %s" (Printexc.to_string exn)))
220219221220(* Discover JMAP session endpoint *)
···227226 let json = Yojson.Safe.from_string response_body in
228227 match Yojson.Safe.Util.member "apiUrl" json with
229228 | `String api_url -> Ok (Uri.of_string api_url)
230230- | _ -> Error (Jmap.Protocol.Error.Protocol "Invalid session discovery response")
229229+ | _ -> Error (Jmap.Error.Protocol "Invalid session discovery response")
231230 with
232231 | Yojson.Json_error msg ->
233233- Error (Jmap.Protocol.Error.Protocol ("JSON parse error: " ^ msg)))
232232+ Error (Jmap.Error.Protocol ("JSON parse error: " ^ msg)))
234233 | Error e -> Error e
235234236235let connect env ctx ?session_url ?username ~host ?(port = 443) ?(use_tls = true) ?(auth_method = No_auth) () =
···256255 | Ok response_body ->
257256 (try
258257 let json = Yojson.Safe.from_string response_body in
259259- let session = Session.parse_session_json json in
258258+ let session = Jmap.Session.parse_session_json json in
260259 ctx.session <- Some session;
261260 Ok (ctx, session)
262261 with
263263- | exn -> Error (Jmap.Protocol.Error.Protocol
262262+ | exn -> Error (Jmap.Error.Protocol
264263 ("Failed to parse session: " ^ Printexc.to_string exn)))
265264 | Error e -> Error e)
266265···276275277276let add_method_call builder method_name arguments method_call_id =
278277 let method_name_str = Jmap.Method_names.method_to_string method_name in
279279- let invocation = Wire.Invocation.v ~method_name:method_name_str ~arguments ~method_call_id () in
278278+ let invocation = Jmap.Wire.Invocation.v ~method_name:method_name_str ~arguments ~method_call_id () in
280279 builder.method_calls <- builder.method_calls @ [invocation];
281280 builder
282281283282let create_reference result_of path =
284284- Wire.Result_reference.v ~result_of ~name:path ~path ()
283283+ Jmap.Wire.Result_reference.v ~result_of ~name:path ~path ()
285284286285let execute env builder =
287286 match builder.ctx.session with
288288- | None -> Error (Jmap.Protocol.Error.Transport "Not connected")
287287+ | None -> Error (Jmap.Error.Transport "Not connected")
289288 | Some session ->
290290- let api_uri = Session.Session.api_url session in
289289+ let api_uri = Jmap.Session.Session.api_url session in
291290 (* Manual JSON construction since to_json is not exposed *)
292291 let method_calls_json = List.map (fun inv ->
293292 `List [
294294- `String (Wire.Invocation.method_name inv);
295295- Wire.Invocation.arguments inv;
296296- `String (Wire.Invocation.method_call_id inv)
293293+ `String (Jmap.Wire.Invocation.method_name inv);
294294+ Jmap.Wire.Invocation.arguments inv;
295295+ `String (Jmap.Wire.Invocation.method_call_id inv)
297296 ]
298297 ) builder.method_calls in
299298 let request_json = `Assoc [
···320319 let method_name = method_name_json |> to_string in
321320 let call_id = call_id_json |> to_string in
322321 Printf.eprintf "DEBUG: Parsed method response: %s (call_id: %s)\n" method_name call_id;
323323- let invocation = Wire.Invocation.v ~method_name ~arguments:args_json ~method_call_id:call_id () in
322322+ let invocation = Jmap.Wire.Invocation.v ~method_name ~arguments:args_json ~method_call_id:call_id () in
324323 Ok invocation
325324 | _ ->
326325 (* If parsing fails, create an error response invocation *)
327326 let error_msg = "Invalid method response format" in
328328- let method_error_obj = Jmap.Protocol.Error.Method_error.v `UnknownMethod in
327327+ let method_error_obj = Jmap.Error.Method_error.v `UnknownMethod in
329328 let method_error = (method_error_obj, error_msg) in
330329 Error method_error
331330 ) method_responses_json in
···333332 (* Get session state *)
334333 let session_state = json |> member "sessionState" |> to_string_option |> Option.value ~default:"unknown" in
335334336336- let response = Wire.Response.v
335335+ let response = Jmap.Wire.Response.v
337336 ~method_responses
338337 ~session_state
339338 ()
340339 in
341340 Ok response
342341 with
343343- | exn -> Error (Jmap.Protocol.Error.Protocol
342342+ | exn -> Error (Jmap.Error.Protocol
344343 ("Failed to parse response: " ^ Printexc.to_string exn)))
345344 | Error e -> Error e)
346345347346let request env ctx req =
348348- let builder = { ctx; using = Wire.Request.using req; method_calls = Wire.Request.method_calls req } in
347347+ let builder = { ctx; using = Jmap.Wire.Request.using req; method_calls = Jmap.Wire.Request.method_calls req } in
349348 execute env builder
350349351350let upload env ctx ~account_id ~content_type ~data_stream =
352351 match ctx.base_url, ctx.session with
353353- | None, _ -> Error (Jmap.Protocol.Error.Transport "Not connected")
354354- | _, None -> Error (Jmap.Protocol.Error.Transport "No session")
352352+ | None, _ -> Error (Jmap.Error.Transport "Not connected")
353353+ | _, None -> Error (Jmap.Error.Transport "No session")
355354 | Some _base_uri, Some session ->
356356- let upload_template = Session.Session.upload_url session in
355355+ let upload_template = Jmap.Session.Session.upload_url session in
357356 let upload_url = Uri.to_string upload_template ^ "?accountId=" ^ account_id in
358357 let upload_uri = Uri.of_string upload_url in
359358 let data_string = Seq.fold_left (fun acc chunk -> acc ^ chunk) "" data_stream in
···374373375374let download env ctx ~account_id ~blob_id ?(content_type="application/octet-stream") ?(name="download") () =
376375 match ctx.base_url, ctx.session with
377377- | None, _ -> Error (Jmap.Protocol.Error.Transport "Not connected")
378378- | _, None -> Error (Jmap.Protocol.Error.Transport "No session")
376376+ | None, _ -> Error (Jmap.Error.Transport "Not connected")
377377+ | _, None -> Error (Jmap.Error.Transport "No session")
379378 | Some _, Some session ->
380380- let download_template = Session.Session.download_url session in
379379+ let download_template = Jmap.Session.Session.download_url session in
381380 let params = [
382381 ("accountId", account_id);
383382 ("blobId", blob_id);
···396395397396let copy_blobs env ctx ~from_account_id ~account_id ~blob_ids =
398397 match ctx.base_url with
399399- | None -> Error (Jmap.Protocol.Error.Transport "Not connected")
398398+ | None -> Error (Jmap.Error.Transport "Not connected")
400399 | Some _base_uri ->
401400 let args = `Assoc [
402401 ("fromAccountId", `String from_account_id);
···453452 let _ = ignore req in
454453 (* WebSocket send implementation would go here *)
455454 (* For now, return a placeholder response *)
456456- let response = Wire.Response.v
455455+ let response = Jmap.Wire.Response.v
457456 ~method_responses:[]
458457 ~session_state:"state"
459458 ()
···521520 let base_args = [
522521 ("accountId", `String account_id);
523522 ("ids", `Assoc [("#", `Assoc [
524524- ("resultOf", `String (Wire.Result_reference.result_of result_reference));
525525- ("name", `String (Wire.Result_reference.name result_reference));
526526- ("path", `String (Wire.Result_reference.path result_reference));
523523+ ("resultOf", `String (Jmap.Wire.Result_reference.result_of result_reference));
524524+ ("name", `String (Jmap.Wire.Result_reference.name result_reference));
525525+ ("path", `String (Jmap.Wire.Result_reference.path result_reference));
527526 ])]);
528527 ] in
529528 let args_with_props = match properties with
···536535537536 (** Convert the request builder to a JMAP Request object *)
538537 let to_request builder =
539539- Wire.Request.v ~using:builder.using ~method_calls:builder.method_calls ()
538538+ Jmap.Wire.Request.v ~using:builder.using ~method_calls:builder.method_calls ()
540539end
541540542541module Email = struct
···652651 - RFC reference: RFC 8621 Section 4.2
653652 - Priority: High
654653 - Dependencies: Jmap_email.of_json implementation *)
655655- | Ok _ -> Error (Jmap.Protocol.Error.Method (`InvalidArguments, Some "Email parsing not implemented"))
654654+ | Ok _ -> Error (Jmap.Error.Method (`InvalidArguments, Some "Email parsing not implemented"))
656655 | Error e -> Error e
657656658657 let search_emails env ctx ~account_id ~filter ?sort ?limit ?position ?properties () =
···703702 - RFC reference: RFC 8621 Section 4.3
704703 - Priority: High
705704 - Dependencies: Email patch operations *)
706706- Error (Jmap.Protocol.Error.Method (`InvalidArguments, Some "mark_seen not implemented"))
705705+ Error (Jmap.Error.Method (`InvalidArguments, Some "mark_seen not implemented"))
707706708707 let mark_as_unseen _env _ctx ~account_id ~email_ids:_ () =
709708 let _ = ignore account_id in
···713712 - RFC reference: RFC 8621 Section 4.3
714713 - Priority: High
715714 - Dependencies: Email patch operations *)
716716- Error (Jmap.Protocol.Error.Method (`InvalidArguments, Some "mark_unseen not implemented"))
715715+ Error (Jmap.Error.Method (`InvalidArguments, Some "mark_unseen not implemented"))
717716718717 let move_emails _env _ctx ~account_id:_ ~email_ids:_ ~mailbox_id:_ ?remove_from_mailboxes:_ () =
719718 (* TODO: Implement email move functionality
···722721 - RFC reference: RFC 8621 Section 4.3
723722 - Priority: High
724723 - Dependencies: Mailbox management, Email patches *)
725725- Error (Jmap.Protocol.Error.Method (`InvalidArguments, Some "move_emails not implemented"))
724724+ Error (Jmap.Error.Method (`InvalidArguments, Some "move_emails not implemented"))
726725727726 let import_email env ctx ~account_id ~rfc822 ~mailbox_ids ?keywords ?received_at () =
728727 let _ = ignore rfc822 in
···754753 Jmap_email.of_json json
755754756755 let from_json_address json =
757757- Jmap_email_address.of_json json
756756+ Jmap_email.Address.of_json json
758757759758 let from_json_keywords json =
760760- Jmap_email_keywords.of_json json *)
759759+ Jmap_email.Keywords.of_json json *)
761760end
762761763762module Auth = struct
···776775777776module Session_utils = struct
778777 let print_session_info session =
779779- let open Jmap.Protocol.Session.Session in
778778+ let open Jmap.Session.Session in
780779 Printf.printf "JMAP Session Information:\n";
781780 Printf.printf " Username: %s\n" (username session);
782781 Printf.printf " API URL: %s\n" (Uri.to_string (api_url session));
···795794 Printf.printf " Accounts:\n";
796795 let accounts = accounts session in
797796 Hashtbl.iter (fun account_id account ->
798798- let open Jmap.Protocol.Session.Account in
797797+ let open Jmap.Session.Account in
799798 Printf.printf " - %s: %s (%b)\n"
800799 account_id
801800 (name account)
···804803 print_endline ""
805804806805 let get_primary_mail_account session =
807807- let open Jmap.Protocol.Session.Session in
806806+ let open Jmap.Session.Session in
808807 let primary_accs = primary_accounts session in
809808 try
810809 Hashtbl.find primary_accs (Jmap.Protocol.Capability.to_string `Mail)
···819818module Response = struct
820819 let extract_method ~method_name ~method_call_id response =
821820 let method_name_str = Jmap.Method_names.method_to_string method_name in
822822- let method_responses = Jmap.Protocol.Wire.Response.method_responses response in
821821+ let method_responses = Jmap.Wire.Response.method_responses response in
823822 let find_response = List.find_map (function
824823 | Ok invocation ->
825825- if Jmap.Protocol.Wire.Invocation.method_call_id invocation = method_call_id &&
826826- Jmap.Protocol.Wire.Invocation.method_name invocation = method_name_str then
827827- Some (Jmap.Protocol.Wire.Invocation.arguments invocation)
824824+ if Jmap.Wire.Invocation.method_call_id invocation = method_call_id &&
825825+ Jmap.Wire.Invocation.method_name invocation = method_name_str then
826826+ Some (Jmap.Wire.Invocation.arguments invocation)
828827 else None
829828 | Error _ -> None
830829 ) method_responses in
831830 match find_response with
832831 | Some response_args -> Ok response_args
833833- | None -> Error (Jmap.Protocol.Error.protocol_error
832832+ | None -> Error (Jmap.Error.protocol_error
834833 (Printf.sprintf "%s response (call_id: %s) not found" method_name_str method_call_id))
835834836835 let extract_method_by_name ~method_name response =
837836 let method_name_str = Jmap.Method_names.method_to_string method_name in
838838- let method_responses = Jmap.Protocol.Wire.Response.method_responses response in
837837+ let method_responses = Jmap.Wire.Response.method_responses response in
839838 let find_response = List.find_map (function
840839 | Ok invocation ->
841841- if Jmap.Protocol.Wire.Invocation.method_name invocation = method_name_str then
842842- Some (Jmap.Protocol.Wire.Invocation.arguments invocation)
840840+ if Jmap.Wire.Invocation.method_name invocation = method_name_str then
841841+ Some (Jmap.Wire.Invocation.arguments invocation)
843842 else None
844843 | Error _ -> None
845844 ) method_responses in
846845 match find_response with
847846 | Some response_args -> Ok response_args
848848- | None -> Error (Jmap.Protocol.Error.protocol_error
847847+ | None -> Error (Jmap.Error.protocol_error
849848 (Printf.sprintf "%s response not found" method_name_str))
850849end
851850···10521051 let list_json = json |> member "list" |> to_list in
10531052 Ok list_json
10541053 with
10551055- | exn -> Error (Jmap.Protocol.Error.protocol_error
10541054+ | exn -> Error (Jmap.Error.protocol_error
10561055 ("Failed to parse Email/get list: " ^ Printexc.to_string exn)))
10571056 | Error e -> Error e
10581057 end
···10711070 let list_json = json |> member "list" |> to_list in
10721071 Ok list_json
10731072 with
10741074- | exn -> Error (Jmap.Protocol.Error.protocol_error
10731073+ | exn -> Error (Jmap.Error.protocol_error
10751074 ("Failed to parse Thread/get list: " ^ Printexc.to_string exn)))
10761075 | Error e -> Error e
10771076 end
···10901089 let list_json = json |> member "list" |> to_list in
10911090 Ok list_json
10921091 with
10931093- | exn -> Error (Jmap.Protocol.Error.protocol_error
10921092+ | exn -> Error (Jmap.Error.protocol_error
10941093 ("Failed to parse Mailbox/get list: " ^ Printexc.to_string exn)))
10951094 | Error e -> Error e
10961095 end
···13251324 | Error e -> Error e)
13261325 | Error e -> Error e
13271326 with
13281328- | exn -> Error (Jmap.Protocol.Error.protocol_error
13271327+ | exn -> Error (Jmap.Error.protocol_error
13291328 ("Failed to parse mailbox: " ^ Printexc.to_string exn)))
13301330- | Ok None -> Error (Jmap.Protocol.Error.protocol_error
13291329+ | Ok None -> Error (Jmap.Error.protocol_error
13311330 ("Mailbox with role '" ^ mailbox_role ^ "' not found"))
13321331 | Error e -> Error e
13331332
+77-78
jmap/jmap-unix/jmap_unix.mli
···7878 ?use_tls:bool ->
7979 ?auth_method:auth_method ->
8080 unit ->
8181- (context * Jmap.Protocol.Session.Session.t) Jmap.Protocol.Error.result
8181+ (context * Jmap.Session.Session.t) Jmap.Error.result
82828383(** Create a request builder for constructing a JMAP request.
8484 @param ctx The client context.
···9191 @param capabilities List of capability variants to use.
9292 @return The updated request builder.
9393*)
9494-val using : request_builder -> Jmap.Protocol.Capability.t list -> request_builder
9494+val using : request_builder -> Jmap.Capability.t list -> request_builder
95959696(** Add a method call to a request builder.
9797 @param builder The request builder.
···112112 @param name Path in the response.
113113 @return A ResultReference to use in another method call.
114114*)
115115-val create_reference : string -> string -> Jmap.Protocol.Wire.Result_reference.t
115115+val create_reference : string -> string -> Jmap.Wire.Result_reference.t
116116117117(** Execute a request and return the response.
118118 @param env The Eio environment for network operations.
119119 @param builder The request builder to execute.
120120 @return The JMAP response from the server.
121121*)
122122-val execute : < net : 'a Eio.Net.t ; .. > -> request_builder -> Jmap.Protocol.Wire.Response.t Jmap.Protocol.Error.result
122122+val execute : < net : 'a Eio.Net.t ; .. > -> request_builder -> Jmap.Wire.Response.t Jmap.Error.result
123123124124(** Perform a JMAP API request.
125125 @param env The Eio environment for network operations.
···127127 @param request The JMAP request object.
128128 @return The JMAP response from the server.
129129*)
130130-val request : < net : 'a Eio.Net.t ; .. > -> context -> Jmap.Protocol.Wire.Request.t -> Jmap.Protocol.Wire.Response.t Jmap.Protocol.Error.result
130130+val request : < net : 'a Eio.Net.t ; .. > -> context -> Jmap.Wire.Request.t -> Jmap.Wire.Response.t Jmap.Error.result
131131132132(** Upload binary data.
133133 @param env The Eio environment for network operations.
···143143 account_id:Jmap.Types.id ->
144144 content_type:string ->
145145 data_stream:string Seq.t ->
146146- Jmap.Binary.Upload_response.t Jmap.Protocol.Error.result
146146+ Jmap.Binary.Upload_response.t Jmap.Error.result
147147148148(** Download binary data.
149149 @param env The Eio environment for network operations.
···162162 ?content_type:string ->
163163 ?name:string ->
164164 unit ->
165165- (string Seq.t) Jmap.Protocol.Error.result
165165+ (string Seq.t) Jmap.Error.result
166166167167(** Copy blobs between accounts.
168168 @param env The Eio environment for network operations.
···178178 from_account_id:Jmap.Types.id ->
179179 account_id:Jmap.Types.id ->
180180 blob_ids:Jmap.Types.id list ->
181181- Jmap.Binary.Blob_copy_response.t Jmap.Protocol.Error.result
181181+ Jmap.Binary.Blob_copy_response.t Jmap.Error.result
182182183183(** Connect to the EventSource for push notifications.
184184 @param env The Eio environment for network operations.
···196196 ?ping:Jmap.Types.uint ->
197197 unit ->
198198 (event_source_connection *
199199- ([`State of Jmap.Push.State_change.t | `Ping of Jmap.Push.Event_source_ping_data.t ] Seq.t)) Jmap.Protocol.Error.result
199199+ ([`State of Jmap.Push.State_change.t | `Ping of Jmap.Push.Event_source_ping_data.t ] Seq.t)) Jmap.Error.result
200200201201(** Create a websocket connection for JMAP over WebSocket.
202202 @param env The Eio environment for network operations.
···206206val connect_websocket :
207207 < net : 'a Eio.Net.t ; .. > ->
208208 context ->
209209- event_source_connection Jmap.Protocol.Error.result
209209+ event_source_connection Jmap.Error.result
210210211211(** Send a message over a websocket connection.
212212 @param env The Eio environment for network operations.
···217217val websocket_send :
218218 < net : 'a Eio.Net.t ; .. > ->
219219 event_source_connection ->
220220- Jmap.Protocol.Wire.Request.t ->
221221- Jmap.Protocol.Wire.Response.t Jmap.Protocol.Error.result
220220+ Jmap.Wire.Request.t ->
221221+ Jmap.Wire.Response.t Jmap.Error.result
222222223223(** Close an EventSource or WebSocket connection.
224224 @param conn The connection handle.
225225 @return A result with either unit or an error.
226226*)
227227-val close_connection : event_source_connection -> unit Jmap.Protocol.Error.result
227227+val close_connection : event_source_connection -> unit Jmap.Error.result
228228229229(** Close the JMAP connection context.
230230 @return A result with either unit or an error.
231231*)
232232-val close : context -> unit Jmap.Protocol.Error.result
232232+val close : context -> unit Jmap.Error.result
233233234234(** {2 Helper Methods for Common Tasks} *)
235235···250250 object_id:Jmap.Types.id ->
251251 ?properties:string list ->
252252 unit ->
253253- Yojson.Safe.t Jmap.Protocol.Error.result
253253+ Yojson.Safe.t Jmap.Error.result
254254255255(** Helper to set up the connection with minimal options.
256256 @param env The Eio environment for network operations.
···269269 ?use_tls:bool ->
270270 ?port:int ->
271271 unit ->
272272- (context * Jmap.Protocol.Session.Session.t) Jmap.Protocol.Error.result
272272+ (context * Jmap.Session.Session.t) Jmap.Error.result
273273274274(** Perform a Core/echo request to test connectivity.
275275 @param env The Eio environment for network operations.
···282282 context ->
283283 ?data:Yojson.Safe.t ->
284284 unit ->
285285- Yojson.Safe.t Jmap.Protocol.Error.result
285285+ Yojson.Safe.t Jmap.Error.result
286286287287(** {2 Request Builder Pattern} *)
288288···294294 (** Create a new request builder with specified capabilities.
295295 @param using List of capability variants to use in the request
296296 @return A new request builder with the specified capabilities *)
297297- val create : using:Jmap.Protocol.Capability.t list -> context -> t
297297+ val create : using:Jmap.Capability.t list -> context -> t
298298299299 (** Add a query method call to the request builder.
300300 @param t The request builder
···334334 t ->
335335 method_name:Jmap.Method_names.jmap_method ->
336336 account_id:Jmap.Types.id ->
337337- result_reference:Jmap.Protocol.Wire.Result_reference.t ->
337337+ result_reference:Jmap.Wire.Result_reference.t ->
338338 ?properties:string list ->
339339 method_call_id:string ->
340340 unit ->
···343343 (** Convert the request builder to a JMAP Request object.
344344 @param t The request builder
345345 @return A JMAP Request ready to be sent *)
346346- val to_request : t -> Jmap.Protocol.Wire.Request.t
346346+ val to_request : t -> Jmap.Wire.Request.t
347347end
348348349349(** {2 Email Operations} *)
350350351351(** High-level email operations that map to JMAP email methods *)
352352module Email : sig
353353- open Jmap_email
354353355354 (** Arguments for Email/query method calls.
356355···446445 email_id:Jmap.Types.id ->
447446 ?properties:string list ->
448447 unit ->
449449- t Jmap.Protocol.Error.result
448448+ Jmap_email.Email.t Jmap.Error.result
450449451450 (** Search for emails using a filter
452451 @param env The Eio environment for network operations
···468467 ?position:int ->
469468 ?properties:string list ->
470469 unit ->
471471- (Jmap.Types.id list * t list option) Jmap.Protocol.Error.result
470470+ (Jmap.Types.id list * Jmap_email.Email.t list option) Jmap.Error.result
472471473472 (** Mark multiple emails with a keyword
474473 @param env The Eio environment for network operations
···483482 context ->
484483 account_id:Jmap.Types.id ->
485484 email_ids:Jmap.Types.id list ->
486486- keyword:Jmap_email.Email_keywords.keyword ->
485485+ keyword:Jmap_email.Keywords.keyword ->
487486 unit ->
488488- unit Jmap.Protocol.Error.result
487487+ unit Jmap.Error.result
489488490489 (** Mark emails as seen/read
491490 @param env The Eio environment for network operations
···500499 account_id:Jmap.Types.id ->
501500 email_ids:Jmap.Types.id list ->
502501 unit ->
503503- unit Jmap.Protocol.Error.result
502502+ unit Jmap.Error.result
504503505504 (** Mark emails as unseen/unread
506505 @param env The Eio environment for network operations
···515514 account_id:Jmap.Types.id ->
516515 email_ids:Jmap.Types.id list ->
517516 unit ->
518518- unit Jmap.Protocol.Error.result
517517+ unit Jmap.Error.result
519518520519 (** Move emails to a different mailbox
521520 @param env The Eio environment for network operations
···534533 mailbox_id:Jmap.Types.id ->
535534 ?remove_from_mailboxes:Jmap.Types.id list ->
536535 unit ->
537537- unit Jmap.Protocol.Error.result
536536+ unit Jmap.Error.result
538537539538 (** Import an RFC822 message
540539 @param env The Eio environment for network operations
···552551 account_id:Jmap.Types.id ->
553552 rfc822:string ->
554553 mailbox_ids:Jmap.Types.id list ->
555555- ?keywords:Jmap_email.Email_keywords.t ->
554554+ ?keywords:Jmap_email.Keywords.t ->
556555 ?received_at:Jmap.Types.date ->
557556 unit ->
558558- Jmap.Types.id Jmap.Protocol.Error.result
557557+ Jmap.Types.id Jmap.Error.result
559558560559 (** {2 JSON Parsing Functions} *)
561560···585584 @return Parsed Keywords set
586585587586 @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-4.1.1> RFC 8621, Section 4.1.1 *)
588588- val from_json_keywords : Yojson.Safe.t -> Jmap_email.Email_keywords.t *)
587587+ val from_json_keywords : Yojson.Safe.t -> Jmap_email.Keywords.t *)
589588end
590589591590(** {2 Utility Functions} *)
···606605module Session_utils : sig
607606 (** Print detailed session information to stdout for debugging.
608607 @param session The JMAP session to display *)
609609- val print_session_info : Jmap.Protocol.Session.Session.t -> unit
608608+ val print_session_info : Jmap.Session.Session.t -> unit
610609611610 (** Get the primary mail account ID from a session.
612611 Falls back to the first available account if no primary mail account is found.
613612 @param session The JMAP session
614613 @return The account ID to use for mail operations *)
615615- val get_primary_mail_account : Jmap.Protocol.Session.Session.t -> Jmap.Types.id
614614+ val get_primary_mail_account : Jmap.Session.Session.t -> Jmap.Types.id
616615end
617616618617(** Response utilities for extracting data from JMAP responses *)
···625624 val extract_method :
626625 method_name:Jmap.Method_names.jmap_method ->
627626 method_call_id:string ->
628628- Jmap.Protocol.Wire.Response.t ->
629629- Yojson.Safe.t Jmap.Protocol.Error.result
627627+ Jmap.Wire.Response.t ->
628628+ Yojson.Safe.t Jmap.Error.result
630629631630 (** Extract the first method response with a given name, ignoring call ID.
632631 @param method_name Typed method name to search for
···634633 @return The method response arguments or an error *)
635634 val extract_method_by_name :
636635 method_name:Jmap.Method_names.jmap_method ->
637637- Jmap.Protocol.Wire.Response.t ->
638638- Yojson.Safe.t Jmap.Protocol.Error.result
636636+ Jmap.Wire.Response.t ->
637637+ Yojson.Safe.t Jmap.Error.result
639638end
640639641640(** {2 Email High-Level Operations} *)
···662661 (** Add Email/get method with automatic result reference *)
663662 val email_get :
664663 ?account_id:Jmap.Types.id ->
665665- ?ids:Jmap.Id.t list ->
664664+ ?ids:Jmap.Types.Id.t list ->
666665 ?properties:string list ->
667666 ?reference_from:string -> (* Call ID to reference *)
668667 t -> t
···671670 val email_set :
672671 ?account_id:Jmap.Types.id ->
673672 ?create:(string * Yojson.Safe.t) list ->
674674- ?update:(Jmap.Id.t * Jmap.Patch.t) list ->
675675- ?destroy:Jmap.Id.t list ->
673673+ ?update:(Jmap.Types.Id.t * Jmap.Types.Patch.t) list ->
674674+ ?destroy:Jmap.Types.Id.t list ->
676675 t -> t
677676678677 (** Add Thread/get method *)
679678 val thread_get :
680679 ?account_id:Jmap.Types.id ->
681681- ?ids:Jmap.Id.t list ->
680680+ ?ids:Jmap.Types.Id.t list ->
682681 t -> t
683682684683 (** Add Mailbox/query method *)
···691690 (** Add Mailbox/get method *)
692691 val mailbox_get :
693692 ?account_id:Jmap.Types.id ->
694694- ?ids:Jmap.Id.t list ->
693693+ ?ids:Jmap.Types.Id.t list ->
695694 t -> t
696695697696 (** Execute the built request *)
698697 val execute :
699698 < net : 'a Eio.Net.t ; .. > ->
700700- session:Jmap.Protocol.Session.Session.t ->
699699+ session:Jmap.Session.Session.t ->
701700 t ->
702702- (Jmap.Protocol.Wire.Response.t, Jmap.Protocol.Error.error) result
701701+ (Jmap.Wire.Response.t, Jmap.Error.error) result
703702704703 (** Get specific method response by type *)
705704 val get_response :
706705 method_:Jmap.Method_names.jmap_method ->
707706 ?call_id:string ->
708708- Jmap.Protocol.Wire.Response.t ->
709709- (Yojson.Safe.t, Jmap.Protocol.Error.error) result
707707+ Jmap.Wire.Response.t ->
708708+ (Yojson.Safe.t, Jmap.Error.error) result
710709 end
711710712711 (** Response parsing functions *)
···714713 (** Extract and parse Email/query response *)
715714 val parse_email_query :
716715 ?call_id:string ->
717717- Jmap.Protocol.Wire.Response.t ->
718718- (Yojson.Safe.t, Jmap.Protocol.Error.error) result
716716+ Jmap.Wire.Response.t ->
717717+ (Yojson.Safe.t, Jmap.Error.error) result
719718720719 (** Extract and parse Email/get response *)
721720 val parse_email_get :
722721 ?call_id:string ->
723723- Jmap.Protocol.Wire.Response.t ->
724724- (Yojson.Safe.t list, Jmap.Protocol.Error.error) result
722722+ Jmap.Wire.Response.t ->
723723+ (Yojson.Safe.t list, Jmap.Error.error) result
725724726725 (** Extract and parse Thread/get response *)
727726 val parse_thread_get :
728727 ?call_id:string ->
729729- Jmap.Protocol.Wire.Response.t ->
730730- (Yojson.Safe.t list, Jmap.Protocol.Error.error) result
728728+ Jmap.Wire.Response.t ->
729729+ (Yojson.Safe.t list, Jmap.Error.error) result
731730732731 (** Extract and parse Mailbox/get response *)
733732 val parse_mailbox_get :
734733 ?call_id:string ->
735735- Jmap.Protocol.Wire.Response.t ->
736736- (Yojson.Safe.t list, Jmap.Protocol.Error.error) result
734734+ Jmap.Wire.Response.t ->
735735+ (Yojson.Safe.t list, Jmap.Error.error) result
737736 end
738737739738 (** Common email operation patterns *)
···742741 val query_and_fetch :
743742 < net : 'a Eio.Net.t ; .. > ->
744743 ctx:context ->
745745- session:Jmap.Protocol.Session.Session.t ->
744744+ session:Jmap.Session.Session.t ->
746745 ?account_id:Jmap.Types.id ->
747746 ?filter:Yojson.Safe.t ->
748747 ?sort:Jmap.Methods.Comparator.t list ->
749748 ?limit:int ->
750749 ?properties:string list ->
751750 unit ->
752752- (Yojson.Safe.t list, Jmap.Protocol.Error.error) result
751751+ (Yojson.Safe.t list, Jmap.Error.error) result
753752754753 (** Get emails by IDs *)
755754 val get_emails_by_ids :
756755 < net : 'a Eio.Net.t ; .. > ->
757756 ctx:context ->
758758- session:Jmap.Protocol.Session.Session.t ->
757757+ session:Jmap.Session.Session.t ->
759758 ?account_id:Jmap.Types.id ->
760759 ?properties:string list ->
761761- Jmap.Id.t list ->
762762- (Yojson.Safe.t list, Jmap.Protocol.Error.error) result
760760+ Jmap.Types.Id.t list ->
761761+ (Yojson.Safe.t list, Jmap.Error.error) result
763762764763 (** Get all mailboxes *)
765764 val get_mailboxes :
766765 < net : 'a Eio.Net.t ; .. > ->
767766 ctx:context ->
768768- session:Jmap.Protocol.Session.Session.t ->
767767+ session:Jmap.Session.Session.t ->
769768 ?account_id:Jmap.Types.id ->
770769 unit ->
771771- (Yojson.Safe.t list, Jmap.Protocol.Error.error) result
770770+ (Yojson.Safe.t list, Jmap.Error.error) result
772771773772 (** Find mailbox by role (e.g., "inbox", "sent", "drafts") *)
774773 val find_mailbox_by_role :
775774 < net : 'a Eio.Net.t ; .. > ->
776775 ctx:context ->
777777- session:Jmap.Protocol.Session.Session.t ->
776776+ session:Jmap.Session.Session.t ->
778777 ?account_id:Jmap.Types.id ->
779778 string ->
780780- (Yojson.Safe.t option, Jmap.Protocol.Error.error) result
779779+ (Yojson.Safe.t option, Jmap.Error.error) result
781780end
782781783782(** {2 Email Query Operations} *)
···789788 val execute_query :
790789 < net : 'a Eio.Net.t ; .. > ->
791790 ctx:context ->
792792- session:Jmap.Protocol.Session.Session.t ->
791791+ session:Jmap.Session.Session.t ->
793792 Yojson.Safe.t ->
794794- (Yojson.Safe.t, Jmap.Protocol.Error.error) result
793793+ (Yojson.Safe.t, Jmap.Error.error) result
795794796795 (** Execute query and automatically fetch email data *)
797796 val execute_with_fetch :
798797 < net : 'a Eio.Net.t ; .. > ->
799798 ctx:context ->
800800- session:Jmap.Protocol.Session.Session.t ->
799799+ session:Jmap.Session.Session.t ->
801800 Yojson.Safe.t ->
802802- (Yojson.Safe.t, Jmap.Protocol.Error.error) result
801801+ (Yojson.Safe.t, Jmap.Error.error) result
803802804803end
805804···812811 val execute :
813812 < net : 'a Eio.Net.t ; .. > ->
814813 ctx:context ->
815815- session:Jmap.Protocol.Session.Session.t ->
814814+ session:Jmap.Session.Session.t ->
816815 ?account_id:Jmap.Types.id ->
817816 Yojson.Safe.t ->
818818- (Yojson.Safe.t, Jmap.Protocol.Error.error) result
817817+ (Yojson.Safe.t, Jmap.Error.error) result
819818820819 (** Common batch workflow operations *)
821820···823822 val process_inbox :
824823 < net : 'a Eio.Net.t ; .. > ->
825824 ctx:context ->
826826- session:Jmap.Protocol.Session.Session.t ->
827827- email_ids:Jmap.Id.t list ->
828828- (Yojson.Safe.t, Jmap.Protocol.Error.error) result
825825+ session:Jmap.Session.Session.t ->
826826+ email_ids:Jmap.Types.Id.t list ->
827827+ (Yojson.Safe.t, Jmap.Error.error) result
829828830829 (** Bulk delete spam/trash emails older than N days *)
831830 val cleanup_old_emails :
832831 < net : 'a Eio.Net.t ; .. > ->
833832 ctx:context ->
834834- session:Jmap.Protocol.Session.Session.t ->
833833+ session:Jmap.Session.Session.t ->
835834 mailbox_role:string -> (* "spam" or "trash" *)
836835 older_than_days:int ->
837837- (Yojson.Safe.t, Jmap.Protocol.Error.error) result
836836+ (Yojson.Safe.t, Jmap.Error.error) result
838837839838 (** Organize emails by sender into mailboxes *)
840839 val organize_by_sender :
841840 < net : 'a Eio.Net.t ; .. > ->
842841 ctx:context ->
843843- session:Jmap.Protocol.Session.Session.t ->
842842+ session:Jmap.Session.Session.t ->
844843 rules:(string * string) list -> (* sender email -> mailbox name *)
845845- (Yojson.Safe.t, Jmap.Protocol.Error.error) result
844844+ (Yojson.Safe.t, Jmap.Error.error) result
846845847846 (** Progress callback for long operations *)
848847 type progress = {
···855854 val execute_with_progress :
856855 < net : 'a Eio.Net.t ; .. > ->
857856 ctx:context ->
858858- session:Jmap.Protocol.Session.Session.t ->
857857+ session:Jmap.Session.Session.t ->
859858 ?account_id:Jmap.Types.id ->
860859 progress_fn:(progress -> unit) ->
861860 Yojson.Safe.t ->
862862- (Yojson.Safe.t, Jmap.Protocol.Error.error) result
861861+ (Yojson.Safe.t, Jmap.Error.error) result
863862end
···11-(** JMAP Date data type implementation *)
22-33-type t = float (* Unix timestamp *)
44-55-(* Basic RFC 3339 parsing - simplified for JMAP usage *)
66-let parse_rfc3339 str =
77- try
88- (* Use Unix.strptime if available, otherwise simplified parsing *)
99- let len = String.length str in
1010- if len < 19 then failwith "Too short for RFC 3339";
1111-1212- (* Extract year, month, day, hour, minute, second *)
1313- let year = int_of_string (String.sub str 0 4) in
1414- let month = int_of_string (String.sub str 5 2) in
1515- let day = int_of_string (String.sub str 8 2) in
1616- let hour = int_of_string (String.sub str 11 2) in
1717- let minute = int_of_string (String.sub str 14 2) in
1818- let second = int_of_string (String.sub str 17 2) in
1919-2020- (* Basic validation *)
2121- if year < 1970 || year > 9999 then failwith "Invalid year";
2222- if month < 1 || month > 12 then failwith "Invalid month";
2323- if day < 1 || day > 31 then failwith "Invalid day";
2424- if hour < 0 || hour > 23 then failwith "Invalid hour";
2525- if minute < 0 || minute > 59 then failwith "Invalid minute";
2626- if second < 0 || second > 59 then failwith "Invalid second";
2727-2828- (* Convert to Unix timestamp using built-in functions *)
2929- let tm = {
3030- Unix.tm_year = year - 1900;
3131- tm_mon = month - 1;
3232- tm_mday = day;
3333- tm_hour = hour;
3434- tm_min = minute;
3535- tm_sec = second;
3636- tm_wday = 0;
3737- tm_yday = 0;
3838- tm_isdst = false;
3939- } in
4040-4141- (* Handle timezone - simplified to assume UTC for 'Z' suffix *)
4242- let timestamp =
4343- if len >= 20 && str.[len-1] = 'Z' then
4444- (* UTC time - convert to UTC timestamp *)
4545- let local_time = fst (Unix.mktime tm) in
4646- let gm_tm = Unix.gmtime local_time in
4747- let utc_time = fst (Unix.mktime gm_tm) in
4848- utc_time
4949- else if len >= 25 && (str.[len-6] = '+' || str.[len-6] = '-') then
5050- (* Timezone offset specified *)
5151- let sign = if str.[len-6] = '+' then -1.0 else 1.0 in
5252- let tz_hours = int_of_string (String.sub str (len-5) 2) in
5353- let tz_minutes = int_of_string (String.sub str (len-2) 2) in
5454- let offset = sign *. (float_of_int tz_hours *. 3600.0 +. float_of_int tz_minutes *. 60.0) in
5555- fst (Unix.mktime tm) +. offset
5656- else
5757- (* No timezone - assume local time *)
5858- fst (Unix.mktime tm)
5959- in
6060- Ok timestamp
6161- with
6262- | Failure msg -> Error ("Invalid RFC 3339 format: " ^ msg)
6363- | Invalid_argument _ -> Error "Invalid RFC 3339 format: parsing error"
6464- | _ -> Error "Invalid RFC 3339 format"
6565-6666-let format_rfc3339 timestamp =
6767- let tm = Unix.gmtime timestamp in
6868- Printf.sprintf "%04d-%02d-%02dT%02d:%02d:%02dZ"
6969- (tm.tm_year + 1900)
7070- (tm.tm_mon + 1)
7171- tm.tm_mday
7272- tm.tm_hour
7373- tm.tm_min
7474- tm.tm_sec
7575-7676-let of_timestamp timestamp = timestamp
7777-7878-let to_timestamp date = date
7979-8080-let of_rfc3339 str = parse_rfc3339 str
8181-8282-let to_rfc3339 date = format_rfc3339 date
8383-8484-let now () = Unix.time ()
8585-8686-let validate date =
8787- if date >= 0.0 && date <= 253402300799.0 (* 9999-12-31T23:59:59Z *) then
8888- Ok ()
8989- else
9090- Error "Date timestamp out of valid range"
9191-9292-let equal date1 date2 =
9393- (* Equal within 1 second precision *)
9494- abs_float (date1 -. date2) < 1.0
9595-9696-let compare date1 date2 =
9797- if date1 < date2 then -1
9898- else if date1 > date2 then 1
9999- else 0
100100-101101-let is_before date1 date2 = date1 < date2
102102-103103-let is_after date1 date2 = date1 > date2
104104-105105-let pp ppf date = Fmt.string ppf (to_rfc3339 date)
106106-107107-let pp_hum ppf date = Fmt.pf ppf "Date(%s)" (to_rfc3339 date)
108108-109109-let pp_debug ppf date =
110110- Fmt.pf ppf "Date(%s)" (to_rfc3339 date)
111111-112112-let to_string_debug date =
113113- Printf.sprintf "Date(%s)" (to_rfc3339 date)
114114-115115-(* JSON serialization *)
116116-let to_json date = `String (to_rfc3339 date)
117117-118118-let of_json = function
119119- | `String str -> of_rfc3339 str
120120- | json ->
121121- let json_str = Yojson.Safe.to_string json in
122122- Error (Printf.sprintf "Expected JSON string for Date, got: %s" json_str)
-98
jmap/jmap/jmap_date.mli
···11-(** JMAP Date data type (RFC 8620).
22-33- The Date data type is a string in RFC 3339 "date-time" format, optionally
44- with timezone information. For example: "2014-10-30T14:12:00+08:00" or
55- "2014-10-30T06:12:00Z".
66-77- In this OCaml implementation, dates are internally represented as Unix
88- timestamps (float) for efficient computation, with conversion to/from
99- RFC 3339 string format handled by the serialization functions.
1010-1111- {b Note}: When represented as a float, precision may be lost for sub-second
1212- values. The implementation preserves second-level precision.
1313-1414- @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-1.4> RFC 8620, Section 1.4
1515- @see <https://www.rfc-editor.org/rfc/rfc3339.html> RFC 3339 *)
1616-1717-(** Abstract type representing a JMAP Date. *)
1818-type t
1919-2020-(** JSON serialization interface *)
2121-include Jmap_sigs.JSONABLE with type t := t
2222-2323-(** Pretty-printing interface *)
2424-include Jmap_sigs.PRINTABLE with type t := t
2525-2626-(** {1 Construction and Access} *)
2727-2828-(** Create a Date from a Unix timestamp.
2929- @param timestamp The Unix timestamp (seconds since epoch).
3030- @return A Date representing the timestamp. *)
3131-val of_timestamp : float -> t
3232-3333-(** Convert a Date to a Unix timestamp.
3434- @param date The Date to convert.
3535- @return The Unix timestamp (seconds since epoch). *)
3636-val to_timestamp : t -> float
3737-3838-(** Create a Date from an RFC 3339 string.
3939- @param str The RFC 3339 formatted string.
4040- @return Ok with the parsed Date, or Error if the string is not valid RFC 3339. *)
4141-val of_rfc3339 : string -> (t, string) result
4242-4343-(** Convert a Date to an RFC 3339 string.
4444- @param date The Date to convert.
4545- @return The RFC 3339 formatted string. *)
4646-val to_rfc3339 : t -> string
4747-4848-(** Create a Date representing the current time.
4949- @return A Date set to the current time. *)
5050-val now : unit -> t
5151-5252-(** {1 Validation} *)
5353-5454-(** Validate a Date according to JMAP constraints.
5555- @param date The Date to validate.
5656- @return Ok () if valid, Error with description if invalid. *)
5757-val validate : t -> (unit, string) result
5858-5959-(** {1 Comparison and Utilities} *)
6060-6161-(** Compare two Dates for equality.
6262- @param date1 First Date.
6363- @param date2 Second Date.
6464- @return True if equal (within 1 second precision), false otherwise. *)
6565-val equal : t -> t -> bool
6666-6767-(** Compare two Dates chronologically.
6868- @param date1 First Date.
6969- @param date2 Second Date.
7070- @return Negative if date1 < date2, zero if equal, positive if date1 > date2. *)
7171-val compare : t -> t -> int
7272-7373-(** Check if first Date is before second Date.
7474- @param date1 First Date.
7575- @param date2 Second Date.
7676- @return True if date1 is before date2. *)
7777-val is_before : t -> t -> bool
7878-7979-(** Check if first Date is after second Date.
8080- @param date1 First Date.
8181- @param date2 Second Date.
8282- @return True if date1 is after date2. *)
8383-val is_after : t -> t -> bool
8484-8585-(** Pretty-print a Date in RFC3339 format.
8686- @param ppf The formatter.
8787- @param date The Date to print. *)
8888-val pp : Format.formatter -> t -> unit
8989-9090-(** Pretty-print a Date for debugging.
9191- @param ppf The formatter.
9292- @param date The Date to format. *)
9393-val pp_debug : Format.formatter -> t -> unit
9494-9595-(** Convert a Date to a human-readable string for debugging.
9696- @param date The Date to format.
9797- @return A debug string representation. *)
9898-val to_string_debug : t -> string
···11-(** JMAP Id data type implementation *)
22-33-type t = string
44-55-let is_base64url_char c =
66- (c >= 'A' && c <= 'Z') ||
77- (c >= 'a' && c <= 'z') ||
88- (c >= '0' && c <= '9') ||
99- c = '-' || c = '_'
1010-1111-let is_valid_string str =
1212- let len = String.length str in
1313- len > 0 && len <= 255 &&
1414- let rec check i =
1515- if i >= len then true
1616- else if is_base64url_char str.[i] then check (i + 1)
1717- else false
1818- in
1919- check 0
2020-2121-let of_string str =
2222- if is_valid_string str then Ok str
2323- else
2424- let len = String.length str in
2525- if len = 0 then Error "Id cannot be empty"
2626- else if len > 255 then Error "Id cannot be longer than 255 octets"
2727- else Error "Id contains invalid characters (must be base64url alphabet only)"
2828-2929-let to_string id = id
3030-3131-let pp ppf id = Fmt.string ppf id
3232-3333-let pp_hum ppf id = Fmt.pf ppf "Id(%s)" id
3434-3535-let validate id =
3636- if is_valid_string id then Ok ()
3737- else Error "Invalid Id format"
3838-3939-let equal = String.equal
4040-4141-let compare = String.compare
4242-4343-let pp_debug ppf id = Fmt.pf ppf "Id(%s)" id
4444-4545-let to_string_debug id = Printf.sprintf "Id(%s)" id
4646-4747-(* JSON serialization *)
4848-let to_json id = `String id
4949-5050-let of_json = function
5151- | `String str -> of_string str
5252- | json ->
5353- let json_str = Yojson.Safe.to_string json in
5454- Error (Printf.sprintf "Expected JSON string for Id, got: %s" json_str)
-74
jmap/jmap/jmap_id.mli
···11-(** JMAP Id data type (RFC 8620).
22-33- The Id data type is a string of 1 to 255 octets in length and MUST consist
44- only of characters from the base64url alphabet, as defined in Section 5 of
55- RFC 4648. This includes ASCII alphanumeric characters, plus the characters
66- '-' and '_'.
77-88- Ids are used to identify JMAP objects within an account. They are assigned
99- by the server and are immutable once assigned. The same id MUST refer to
1010- the same object throughout the lifetime of the object.
1111-1212- @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-1.2> RFC 8620, Section 1.2 *)
1313-1414-(** Abstract type representing a JMAP Id. *)
1515-type t
1616-1717-(** JSON serialization interface *)
1818-include Jmap_sigs.JSONABLE with type t := t
1919-2020-(** Pretty-printing interface *)
2121-include Jmap_sigs.PRINTABLE with type t := t
2222-2323-(** {1 Construction and Access} *)
2424-2525-(** Create a new Id from a string.
2626- @param str The string representation.
2727- @return Ok with the created Id, or Error if the string violates Id constraints. *)
2828-val of_string : string -> (t, string) result
2929-3030-(** Convert an Id to its string representation.
3131- @param id The Id to convert.
3232- @return The string representation. *)
3333-val to_string : t -> string
3434-3535-(** Pretty-print an Id.
3636- @param ppf The formatter.
3737- @param id The Id to print. *)
3838-val pp : Format.formatter -> t -> unit
3939-4040-(** {1 Validation} *)
4141-4242-(** Check if a string is a valid JMAP Id.
4343- @param str The string to validate.
4444- @return True if the string meets Id requirements, false otherwise. *)
4545-val is_valid_string : string -> bool
4646-4747-(** Validate an Id according to JMAP constraints.
4848- @param id The Id to validate.
4949- @return Ok () if valid, Error with description if invalid. *)
5050-val validate : t -> (unit, string) result
5151-5252-(** {1 Comparison and Utilities} *)
5353-5454-(** Compare two Ids for equality.
5555- @param id1 First Id.
5656- @param id2 Second Id.
5757- @return True if equal, false otherwise. *)
5858-val equal : t -> t -> bool
5959-6060-(** Compare two Ids lexicographically.
6161- @param id1 First Id.
6262- @param id2 Second Id.
6363- @return Negative if id1 < id2, zero if equal, positive if id1 > id2. *)
6464-val compare : t -> t -> int
6565-6666-(** Pretty-print an Id for debugging.
6767- @param ppf The formatter.
6868- @param id The Id to format. *)
6969-val pp_debug : Format.formatter -> t -> unit
7070-7171-(** Convert an Id to a human-readable string for debugging.
7272- @param id The Id to format.
7373- @return A debug string representation. *)
7474-val to_string_debug : t -> string
···1919 @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-4> RFC 8620, Section 4 (Core/echo)
2020 @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-5> RFC 8620, Section 5 (Standard Methods) *)
21212222-open Jmap_types
2222+open Types
23232424(** {1 Generic Types} *)
2525···163163 val of_json :
164164 from_json:(Yojson.Safe.t -> 'record) ->
165165 Yojson.Safe.t ->
166166- ('record t, Jmap_error.error) result
166166+ ('record t, Error.error) result
167167end
168168169169(** Arguments for /changes methods.
···225225 @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-5.2> RFC 8620, Section 5.2 *)
226226 val of_json :
227227 Yojson.Safe.t ->
228228- (t, Jmap_error.error) result
228228+ (t, Error.error) result
229229end
230230231231(** Patch object for /set update.
···287287 val created : ('a, 'b) t -> 'a id_map option
288288 val updated : ('a, 'b) t -> 'b option id_map option
289289 val destroyed : ('a, 'b) t -> id list option
290290- val not_created : ('a, 'b) t -> Jmap_error.Set_error.t id_map option
291291- val not_updated : ('a, 'b) t -> Jmap_error.Set_error.t id_map option
292292- val not_destroyed : ('a, 'b) t -> Jmap_error.Set_error.t id_map option
290290+ val not_created : ('a, 'b) t -> Error.Set_error.t id_map option
291291+ val not_updated : ('a, 'b) t -> Error.Set_error.t id_map option
292292+ val not_destroyed : ('a, 'b) t -> Error.Set_error.t id_map option
293293294294 val v :
295295 account_id:id ->
···298298 ?created:'a id_map ->
299299 ?updated:'b option id_map ->
300300 ?destroyed:id list ->
301301- ?not_created:Jmap_error.Set_error.t id_map ->
302302- ?not_updated:Jmap_error.Set_error.t id_map ->
303303- ?not_destroyed:Jmap_error.Set_error.t id_map ->
301301+ ?not_created:Error.Set_error.t id_map ->
302302+ ?not_updated:Error.Set_error.t id_map ->
303303+ ?not_destroyed:Error.Set_error.t id_map ->
304304 unit ->
305305 ('a, 'b) t
306306···319319 from_created_json:(Yojson.Safe.t -> 'a) ->
320320 from_updated_json:(Yojson.Safe.t -> 'b) ->
321321 Yojson.Safe.t ->
322322- (('a, 'b) t, Jmap_error.error) result
322322+ (('a, 'b) t, Error.error) result
323323end
324324325325(** Arguments for /copy methods.
···359359 val old_state : 'a t -> string option
360360 val new_state : 'a t -> string
361361 val created : 'a t -> 'a id_map option
362362- val not_created : 'a t -> Jmap_error.Set_error.t id_map option
362362+ val not_created : 'a t -> Error.Set_error.t id_map option
363363364364 val v :
365365 from_account_id:id ->
···367367 ?old_state:string ->
368368 new_state:string ->
369369 ?created:'a id_map ->
370370- ?not_created:Jmap_error.Set_error.t id_map ->
370370+ ?not_created:Error.Set_error.t id_map ->
371371 unit ->
372372 'a t
373373end
···573573 @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-5.5> RFC 8620, Section 5.5 *)
574574 val of_json :
575575 Yojson.Safe.t ->
576576- (t, Jmap_error.error) result
576576+ (t, Error.error) result
577577end
578578579579(** Item indicating an added record in /queryChanges.
-134
jmap/jmap/jmap_patch.ml
···11-(** JMAP Patch Object data type implementation *)
22-33-(* Internal representation as a hash table for efficient operations *)
44-type t = (string, Yojson.Safe.t) Hashtbl.t
55-66-(* JSON Pointer validation - simplified but covers common cases *)
77-let is_valid_property_path path =
88- let len = String.length path in
99- if len = 0 then true (* empty path is valid root *)
1010- else if path.[0] <> '/' then true (* simple property names are valid *)
1111- else
1212- (* Check for valid JSON Pointer format *)
1313- let rec check_escaping i =
1414- if i >= len then true
1515- else match path.[i] with
1616- | '~' when i + 1 < len ->
1717- (match path.[i + 1] with
1818- | '0' | '1' -> check_escaping (i + 2)
1919- | _ -> false)
2020- | '/' -> check_escaping (i + 1)
2121- | _ -> check_escaping (i + 1)
2222- in
2323- check_escaping 0
2424-2525-let empty = Hashtbl.create 8
2626-2727-let of_operations operations =
2828- let patch = Hashtbl.create (List.length operations) in
2929- let rec process = function
3030- | [] -> Ok patch
3131- | (property, value) :: rest ->
3232- if is_valid_property_path property then (
3333- Hashtbl.replace patch property value;
3434- process rest
3535- ) else
3636- Error ("Invalid property path: " ^ property)
3737- in
3838- process operations
3939-4040-let to_operations patch =
4141- Hashtbl.fold (fun property value acc ->
4242- (property, value) :: acc
4343- ) patch []
4444-4545-let of_json_object = function
4646- | `Assoc pairs -> of_operations pairs
4747- | json ->
4848- let json_str = Yojson.Safe.to_string json in
4949- Error (Printf.sprintf "Expected JSON object for Patch, got: %s" json_str)
5050-5151-let to_json_object patch =
5252- let pairs = to_operations patch in
5353- `Assoc pairs
5454-5555-let set_property patch property value =
5656- if is_valid_property_path property then (
5757- let new_patch = Hashtbl.copy patch in
5858- Hashtbl.replace new_patch property value;
5959- Ok new_patch
6060- ) else
6161- Error ("Invalid property path: " ^ property)
6262-6363-let remove_property patch property =
6464- set_property patch property `Null
6565-6666-let has_property patch property =
6767- Hashtbl.mem patch property
6868-6969-let get_property patch property =
7070- try Some (Hashtbl.find patch property)
7171- with Not_found -> None
7272-7373-let merge patch1 patch2 =
7474- let result = Hashtbl.copy patch1 in
7575- Hashtbl.iter (fun property value ->
7676- Hashtbl.replace result property value
7777- ) patch2;
7878- result
7979-8080-let is_empty patch =
8181- Hashtbl.length patch = 0
8282-8383-let size patch =
8484- Hashtbl.length patch
8585-8686-let validate patch =
8787- (* Validate all property paths *)
8888- try
8989- Hashtbl.iter (fun property _value ->
9090- if not (is_valid_property_path property) then
9191- failwith ("Invalid property path: " ^ property)
9292- ) patch;
9393- Ok ()
9494- with
9595- | Failure msg -> Error msg
9696-9797-let equal patch1 patch2 =
9898- if Hashtbl.length patch1 <> Hashtbl.length patch2 then false
9999- else
100100- try
101101- Hashtbl.iter (fun property value1 ->
102102- match get_property patch2 property with
103103- | None -> failwith "Property not found"
104104- | Some value2 when Yojson.Safe.equal value1 value2 -> ()
105105- | Some _ -> failwith "Property values differ"
106106- ) patch1;
107107- true
108108- with
109109- | Failure _ -> false
110110-111111-let pp ppf patch =
112112- Fmt.pf ppf "%s" (Yojson.Safe.to_string (to_json_object patch))
113113-114114-let pp_hum ppf patch =
115115- let operations = to_operations patch in
116116- let op_count = List.length operations in
117117- let key_list = List.map fst operations in
118118- let key_str = match key_list with
119119- | [] -> "none"
120120- | keys -> String.concat ", " keys
121121- in
122122- Fmt.pf ppf "Patch{operations=%d; keys=[%s]}" op_count key_str
123123-124124-let to_string_debug patch =
125125- let operations = to_operations patch in
126126- let op_strings = List.map (fun (prop, value) ->
127127- Printf.sprintf "%s: %s" prop (Yojson.Safe.to_string value)
128128- ) operations in
129129- Printf.sprintf "Patch({%s})" (String.concat "; " op_strings)
130130-131131-(* JSON serialization *)
132132-let to_json patch = to_json_object patch
133133-134134-let of_json json = of_json_object json
-122
jmap/jmap/jmap_patch.mli
···11-(** JMAP Patch Object data type (RFC 8620).
22-33- A patch object is used to update properties of JMAP objects. It represents
44- a JSON object where each key is a property path (using JSON Pointer syntax)
55- and each value is the new value to set for that property, or null to remove
66- the property.
77-88- Patch objects are commonly used in /set method calls to update existing
99- objects without having to send the complete object representation.
1010-1111- Examples of patch operations:
1212- - Setting a property: [{"name": "New Name"}]
1313- - Removing a property: [{"oldProperty": null}]
1414- - Setting nested properties: [{"address/street": "123 Main St"}]
1515-1616- @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-5.3> RFC 8620, Section 5.3
1717- @see <https://www.rfc-editor.org/rfc/rfc6901.html> RFC 6901 (JSON Pointer) *)
1818-1919-(** Abstract type representing a JMAP Patch Object. *)
2020-type t
2121-2222-(** JSON serialization interface *)
2323-include Jmap_sigs.JSONABLE with type t := t
2424-2525-(** Pretty-printing interface *)
2626-include Jmap_sigs.PRINTABLE with type t := t
2727-2828-(** {1 Construction and Access} *)
2929-3030-(** Create an empty patch object.
3131- @return An empty patch with no operations. *)
3232-val empty : t
3333-3434-(** Create a patch from a list of property-value pairs.
3535- @param operations List of (property_path, value) pairs.
3636- @return Ok with the patch, or Error if any property path is invalid. *)
3737-val of_operations : (string * Yojson.Safe.t) list -> (t, string) result
3838-3939-(** Convert a patch to a list of property-value pairs.
4040- @param patch The patch to convert.
4141- @return List of (property_path, value) pairs. *)
4242-val to_operations : t -> (string * Yojson.Safe.t) list
4343-4444-(** Create a patch from a Yojson.Safe.t object directly.
4545- @param json The JSON object.
4646- @return Ok with the patch, or Error if the JSON is not a valid object. *)
4747-val of_json_object : Yojson.Safe.t -> (t, string) result
4848-4949-(** Convert a patch to a Yojson.Safe.t object directly.
5050- @param patch The patch to convert.
5151- @return The JSON object representation. *)
5252-val to_json_object : t -> Yojson.Safe.t
5353-5454-(** {1 Patch Operations} *)
5555-5656-(** Set a property in the patch.
5757- @param patch The patch to modify.
5858- @param property The property path (JSON Pointer format).
5959- @param value The value to set.
6060- @return Ok with the updated patch, or Error if the property path is invalid. *)
6161-val set_property : t -> string -> Yojson.Safe.t -> (t, string) result
6262-6363-(** Remove a property in the patch (set to null).
6464- @param patch The patch to modify.
6565- @param property The property path to remove.
6666- @return Ok with the updated patch, or Error if the property path is invalid. *)
6767-val remove_property : t -> string -> (t, string) result
6868-6969-(** Check if a property is set in the patch.
7070- @param patch The patch to check.
7171- @param property The property path to check.
7272- @return True if the property is explicitly set in the patch. *)
7373-val has_property : t -> string -> bool
7474-7575-(** Get a property value from the patch.
7676- @param patch The patch to query.
7777- @param property The property path to get.
7878- @return Some value if the property is set, None if not present. *)
7979-val get_property : t -> string -> Yojson.Safe.t option
8080-8181-(** {1 Patch Composition} *)
8282-8383-(** Merge two patches, with the second patch taking precedence.
8484- @param patch1 The first patch.
8585- @param patch2 The second patch (higher precedence).
8686- @return The merged patch. *)
8787-val merge : t -> t -> t
8888-8989-(** Check if a patch is empty (no operations).
9090- @param patch The patch to check.
9191- @return True if the patch has no operations. *)
9292-val is_empty : t -> bool
9393-9494-(** Get the number of operations in a patch.
9595- @param patch The patch to count.
9696- @return The number of property operations. *)
9797-val size : t -> int
9898-9999-(** {1 Validation} *)
100100-101101-(** Validate a patch according to JMAP constraints.
102102- @param patch The patch to validate.
103103- @return Ok () if valid, Error with description if invalid. *)
104104-val validate : t -> (unit, string) result
105105-106106-(** Validate a JSON Pointer path.
107107- @param path The property path to validate.
108108- @return True if the path is a valid JSON Pointer, false otherwise. *)
109109-val is_valid_property_path : string -> bool
110110-111111-(** {1 Comparison and Utilities} *)
112112-113113-(** Compare two patches for equality.
114114- @param patch1 First patch.
115115- @param patch2 Second patch.
116116- @return True if patches have identical operations, false otherwise. *)
117117-val equal : t -> t -> bool
118118-119119-(** Convert a patch to a human-readable string for debugging.
120120- @param patch The patch to format.
121121- @return A debug string representation. *)
122122-val to_string_debug : t -> string
···55 protocol structures, session management, and error handling into a coherent
66 API for JMAP implementations.
7788- The module organizes protocol functionality into logical groups:
99- - Wire protocol: Request/response structures and invocations
1010- - Session management: Capability discovery and account information
1111- - Error handling: Comprehensive error types and utilities
1212- - Protocol helpers: Convenience functions for common operations
88+ The module provides type aliases and convenience functions that reference
99+ the individual Wire, Session, and Error modules for backwards compatibility.
13101411 @see <https://www.rfc-editor.org/rfc/rfc8620.html> RFC 8620: Core JMAP *)
15121616-(** {1 Wire Protocol Types} *)
1717-1818-(** Wire protocol types for JMAP requests and responses.
1919-2020- This includes the core structures for method invocations, requests,
2121- responses, and result references that enable method call chaining.
2222-2323- @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-3> RFC 8620, Section 3 *)
2424-module Wire = Jmap_wire
2525-2626-(** {1 Session Management} *)
2727-2828-(** Session management and capability discovery.
2929-3030- Provides session resource handling, account enumeration, capability
3131- negotiation, and service autodiscovery functionality.
3232-3333- @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-2> RFC 8620, Section 2 *)
3434-module Session = Jmap_session
3535-3636-(** {1 Error Types} *)
3737-3838-(** Error types used throughout the protocol.
3939-4040- Comprehensive error handling including method errors, set errors,
4141- transport errors, and unified error types with proper RFC references.
4242-4343- @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-3.6> RFC 8620, Section 3.6 *)
4444-module Error = Jmap_error
4545-4613(** {1 Type Aliases for Convenience} *)
4747-4848-(** Convenient type aliases for commonly used protocol types *)
49145015(** A JMAP request *)
5116type request = Wire.Request.t
···205170 val is_message : t -> bool
206171end
207172208208-209173(** {1 Protocol Helpers} *)
210174211175(** Check if a session supports a given capability.
···218182 @param session The session object.
219183 @param capability The capability.
220184 @return The account ID or an error if not found. *)
221221-val get_primary_account : session -> Jmap_capability.t -> (Jmap_types.id, error) result
185185+val get_primary_account : session -> Jmap_capability.t -> (Types.id, error) result
222186223187(** Find a method response by its call ID.
224188 @param response The response object.
···2525 | Email_submission_changes_data of Jmap_methods.Changes_response.t
2626 | Vacation_response_get_data of Yojson.Safe.t Jmap_methods.Get_response.t
2727 | Vacation_response_set_data of (Yojson.Safe.t, Yojson.Safe.t) Jmap_methods.Set_response.t
2828- | Error_data of Jmap_error.error
2828+ | Error_data of Error.error
29293030type t = {
3131 method_name: string;
···182182 (* Not yet implemented methods - return error for now *)
183183 | Some (`Blob_get | `Blob_lookup | `Email_parse | `Email_copy | `SearchSnippet_get
184184 | `Thread_query | `Email_import | `Blob_copy) ->
185185- Error (Jmap_error.Method (`UnknownMethod, Some method_name))
185185+ Error (Error.Method (`UnknownMethod, Some method_name))
186186187187 | None ->
188188- Error (Jmap_error.Method (`UnknownMethod, Some method_name))
188188+ Error (Error.Method (`UnknownMethod, Some method_name))
189189 in
190190 match result with
191191 | Ok data -> Ok { method_name; data; raw_json = json }
192192 | Error err -> Error err
193193 with
194194- | exn -> Error (Jmap_error.Method (`InvalidArguments, Some (Printexc.to_string exn)))
194194+ | exn -> Error (Error.Method (`InvalidArguments, Some (Printexc.to_string exn)))
195195196196let parse_method_response_array json =
197197 let open Yojson.Safe.Util in
···206206 (match parse_method_response ~method_name response_json with
207207 | Ok response -> Ok (method_name, response, call_id)
208208 | Error err -> Error err)
209209- | _ -> Error (Jmap_error.Parse "Invalid method response array format")
209209+ | _ -> Error (Error.Parse "Invalid method response array format")
210210 with
211211- | exn -> Error (Jmap_error.Parse (Printexc.to_string exn))
211211+ | exn -> Error (Error.Parse (Printexc.to_string exn))
212212213213(** {1 Response Pattern Matching} *)
214214···290290 match Jmap_methods.Query_response.of_json json with
291291 | Ok t -> Ok t
292292 | Error err -> Error ("Failed to parse Query_response: " ^ (match err with
293293- | Jmap_error.Parse msg -> msg
293293+ | Error.Parse msg -> msg
294294 | _ -> "unknown error"))
295295296296 let pp fmt t =
···325325 match Jmap_methods.Get_response.of_json ~from_json:(fun j -> j) json with
326326 | Ok t -> Ok t
327327 | Error err -> Error ("Failed to parse Get_response: " ^ (match err with
328328- | Jmap_error.Parse msg -> msg
328328+ | Error.Parse msg -> msg
329329 | _ -> "unknown error"))
330330331331 let pp fmt t =
···368368 match Jmap_methods.Set_response.of_json ~from_created_json:(fun j -> j) ~from_updated_json:(fun j -> j) json with
369369 | Ok t -> Ok t
370370 | Error err -> Error ("Failed to parse Set_response: " ^ (match err with
371371- | Jmap_error.Parse msg -> msg
371371+ | Error.Parse msg -> msg
372372 | _ -> "unknown error"))
373373374374 let pp fmt t =
···406406 match Jmap_methods.Changes_response.of_json json with
407407 | Ok t -> Ok t
408408 | Error err -> Error ("Failed to parse Changes_response: " ^ (match err with
409409- | Jmap_error.Parse msg -> msg
409409+ | Error.Parse msg -> msg
410410 | _ -> "unknown error"))
411411412412 let pp fmt t =
···441441 match Jmap_methods.Get_response.of_json ~from_json:(fun j -> j) json with
442442 | Ok t -> Ok t
443443 | Error err -> Error ("Failed to parse Get_response: " ^ (match err with
444444- | Jmap_error.Parse msg -> msg
444444+ | Error.Parse msg -> msg
445445 | _ -> "unknown error"))
446446447447 let pp fmt t =
···476476 match Jmap_methods.Query_response.of_json json with
477477 | Ok t -> Ok t
478478 | Error err -> Error ("Failed to parse Query_response: " ^ (match err with
479479- | Jmap_error.Parse msg -> msg
479479+ | Error.Parse msg -> msg
480480 | _ -> "unknown error"))
481481482482 let pp fmt t =
···518518 match Jmap_methods.Set_response.of_json ~from_created_json:(fun j -> j) ~from_updated_json:(fun j -> j) json with
519519 | Ok t -> Ok t
520520 | Error err -> Error ("Failed to parse Set_response: " ^ (match err with
521521- | Jmap_error.Parse msg -> msg
521521+ | Error.Parse msg -> msg
522522 | _ -> "unknown error"))
523523524524 let pp fmt t =
···555555 match Jmap_methods.Changes_response.of_json json with
556556 | Ok t -> Ok t
557557 | Error err -> Error ("Failed to parse Changes_response: " ^ (match err with
558558- | Jmap_error.Parse msg -> msg
558558+ | Error.Parse msg -> msg
559559 | _ -> "unknown error"))
560560561561 let pp fmt t =
···590590 match Jmap_methods.Get_response.of_json ~from_json:(fun j -> j) json with
591591 | Ok t -> Ok t
592592 | Error err -> Error ("Failed to parse Get_response: " ^ (match err with
593593- | Jmap_error.Parse msg -> msg
593593+ | Error.Parse msg -> msg
594594 | _ -> "unknown error"))
595595596596 let pp fmt t =
···624624 match Jmap_methods.Changes_response.of_json json with
625625 | Ok t -> Ok t
626626 | Error err -> Error ("Failed to parse Changes_response: " ^ (match err with
627627- | Jmap_error.Parse msg -> msg
627627+ | Error.Parse msg -> msg
628628 | _ -> "unknown error"))
629629630630 let pp fmt t =
···659659 match Jmap_methods.Get_response.of_json ~from_json:(fun j -> j) json with
660660 | Ok t -> Ok t
661661 | Error err -> Error ("Failed to parse Get_response: " ^ (match err with
662662- | Jmap_error.Parse msg -> msg
662662+ | Error.Parse msg -> msg
663663 | _ -> "unknown error"))
664664665665 let pp fmt t =
···701701 match Jmap_methods.Set_response.of_json ~from_created_json:(fun j -> j) ~from_updated_json:(fun j -> j) json with
702702 | Ok t -> Ok t
703703 | Error err -> Error ("Failed to parse Set_response: " ^ (match err with
704704- | Jmap_error.Parse msg -> msg
704704+ | Error.Parse msg -> msg
705705 | _ -> "unknown error"))
706706707707 let pp fmt t =
···738738 match Jmap_methods.Changes_response.of_json json with
739739 | Ok t -> Ok t
740740 | Error err -> Error ("Failed to parse Changes_response: " ^ (match err with
741741- | Jmap_error.Parse msg -> msg
741741+ | Error.Parse msg -> msg
742742 | _ -> "unknown error"))
743743744744 let pp fmt t =
···773773 match Jmap_methods.Get_response.of_json ~from_json:(fun j -> j) json with
774774 | Ok t -> Ok t
775775 | Error err -> Error ("Failed to parse Get_response: " ^ (match err with
776776- | Jmap_error.Parse msg -> msg
776776+ | Error.Parse msg -> msg
777777 | _ -> "unknown error"))
778778779779 let pp fmt t =
···815815 match Jmap_methods.Set_response.of_json ~from_created_json:(fun j -> j) ~from_updated_json:(fun j -> j) json with
816816 | Ok t -> Ok t
817817 | Error err -> Error ("Failed to parse Set_response: " ^ (match err with
818818- | Jmap_error.Parse msg -> msg
818818+ | Error.Parse msg -> msg
819819 | _ -> "unknown error"))
820820821821 let pp fmt t =
···853853 match Jmap_methods.Query_response.of_json json with
854854 | Ok t -> Ok t
855855 | Error err -> Error ("Failed to parse Query_response: " ^ (match err with
856856- | Jmap_error.Parse msg -> msg
856856+ | Error.Parse msg -> msg
857857 | _ -> "unknown error"))
858858859859 let pp fmt t =
···887887 match Jmap_methods.Changes_response.of_json json with
888888 | Ok t -> Ok t
889889 | Error err -> Error ("Failed to parse Changes_response: " ^ (match err with
890890- | Jmap_error.Parse msg -> msg
890890+ | Error.Parse msg -> msg
891891 | _ -> "unknown error"))
892892893893 let pp fmt t =
···922922 match Jmap_methods.Get_response.of_json ~from_json:(fun j -> j) json with
923923 | Ok t -> Ok t
924924 | Error err -> Error ("Failed to parse Get_response: " ^ (match err with
925925- | Jmap_error.Parse msg -> msg
925925+ | Error.Parse msg -> msg
926926 | _ -> "unknown error"))
927927928928 let pp fmt t =
···964964 match Jmap_methods.Set_response.of_json ~from_created_json:(fun j -> j) ~from_updated_json:(fun j -> j) json with
965965 | Ok t -> Ok t
966966 | Error err -> Error ("Failed to parse Set_response: " ^ (match err with
967967- | Jmap_error.Parse msg -> msg
967967+ | Error.Parse msg -> msg
968968 | _ -> "unknown error"))
969969970970 let pp fmt t =
+5-5
jmap/jmap/jmap_response.mli
···2525 @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-3.4> RFC 8620, Section 3.4 (Method Responses)
2626 @see <https://www.rfc-editor.org/rfc/rfc8621.html> RFC 8621 (Email Extensions) *)
27272828-open Jmap_types
2828+open Types
29293030(** {1 Response Types} *)
3131···7474 @param raw_json The original JSON for debugging purposes *)
7575val create_error_response :
7676 method_name:string ->
7777- Jmap_error.error ->
7777+ Error.error ->
7878 Yojson.Safe.t ->
7979 t
8080···9494val parse_method_response :
9595 method_name:string ->
9696 Yojson.Safe.t ->
9797- (t, Jmap_error.error) result
9797+ (t, Error.error) result
98989999(** Parse a complete JMAP method response array.
100100···105105 @return Tuple of (method_name, parsed_response, call_id) or error *)
106106val parse_method_response_array :
107107 Yojson.Safe.t ->
108108- (string * t * string option, Jmap_error.error) result
108108+ (string * t * string option, Error.error) result
109109110110(** {1 Response Pattern Matching} *)
111111···497497(** Extract error information if this is an error response.
498498 @param response The response to check
499499 @return Error details if this is an error response *)
500500-val error : t -> Jmap_error.error option
500500+val error : t -> Error.error option
501501502502(** Get the account ID from responses that include it.
503503 @param response The response to extract from
+2-2
jmap/jmap/jmap_session.ml
jmap/jmap/session.ml
···11-open Jmap_types
11+open Types
2233type account_capability_value = Yojson.Safe.t
44···429429 | No_auth -> []
430430431431 let make_request ~url ~auth =
432432- let headers = ("Accept", Jmap_types.Constants.Content_type.json) :: ("User-Agent", Jmap_types.Constants.User_agent.ocaml_jmap) :: (auth_headers auth) in
432432+ let headers = ("Accept", Types.Constants.Content_type.json) :: ("User-Agent", Types.Constants.User_agent.ocaml_jmap) :: (auth_headers auth) in
433433 try
434434 let response_json = `Assoc [
435435 ("capabilities", `Assoc [
+2-2
jmap/jmap/jmap_session.mli
jmap/jmap/session.mli
···11111212 @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-2> RFC 8620, Section 2 *)
13131414-open Jmap_types
1414+open Types
15151616(** {1 Capability Types} *)
1717···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) result
365365+val discover_and_connect_with_email : email:string -> (Session.t, string) result
-28
jmap/jmap/jmap_types.ml
···11-type id = string
22-33-type jint = int
44-55-type uint = int
66-77-type date = float
88-99-type utc_date = float
1010-1111-type 'v string_map = (string, 'v) Hashtbl.t
1212-1313-type 'v id_map = (id, 'v) Hashtbl.t
1414-1515-type json_pointer = string
1616-1717-module Constants = struct
1818- let vacation_response_id = "singleton"
1919-2020- module Content_type = struct
2121- let json = "application/json"
2222- end
2323-2424- module User_agent = struct
2525- let ocaml_jmap = "OCaml-JMAP/1.0"
2626- let eio_client = "OCaml JMAP Client/Eio"
2727- end
2828-end
-148
jmap/jmap/jmap_types.mli
···11-(** Basic JMAP types as defined in RFC 8620.
22-33- This module defines the fundamental data types used throughout the JMAP
44- protocol. These types provide type-safe representations of JSON values
55- that have specific constraints in the JMAP specification.
66-77- @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-1> RFC 8620, Section 1 *)
88-99-(** {1 Primitive Data Types} *)
1010-1111-(** The Id data type.
1212-1313- A string of 1 to 255 octets in length and MUST consist only of characters
1414- from the base64url alphabet, as defined in Section 5 of RFC 4648. This
1515- includes ASCII alphanumeric characters, plus the characters '-' and '_'.
1616-1717- Ids are used to identify JMAP objects within an account. They are assigned
1818- by the server and are immutable once assigned. The same id MUST refer to
1919- the same object throughout the lifetime of the object.
2020-2121- {b Note}: In this OCaml implementation, ids are represented as regular strings.
2222- Validation of id format is the responsibility of the client/server implementation.
2323-2424- @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-1.2> RFC 8620, Section 1.2 *)
2525-type id = string
2626-2727-(** The Int data type.
2828-2929- A signed 53-bit integer in the range [-2^53+1, 2^53-1]. This corresponds
3030- to the safe integer range in JavaScript and JSON implementations.
3131-3232- In OCaml, this is represented as a regular [int]. Note that OCaml's [int]
3333- on 64-bit platforms has a larger range, but JMAP protocol compliance
3434- requires staying within the specified range.
3535-3636- @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-1.3> RFC 8620, Section 1.3 *)
3737-type jint = int
3838-3939-(** The UnsignedInt data type.
4040-4141- An unsigned integer in the range [0, 2^53-1]. This is the same as [jint]
4242- but restricted to non-negative values.
4343-4444- Common uses include counts, limits, positions, and sizes within the protocol.
4545-4646- @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-1.3> RFC 8620, Section 1.3 *)
4747-type uint = int
4848-4949-(** The Date data type.
5050-5151- A string in RFC 3339 "date-time" format, optionally with timezone information.
5252- For example: "2014-10-30T14:12:00+08:00" or "2014-10-30T06:12:00Z".
5353-5454- In this OCaml implementation, dates are represented as Unix timestamps (float).
5555- Conversion to/from RFC 3339 string format is handled by the wire protocol
5656- serialization layer.
5757-5858- {b Note}: When represented as a float, precision may be lost for sub-second
5959- values. Consider the precision requirements of your application.
6060-6161- @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-1.4> RFC 8620, Section 1.4
6262- @see <https://www.rfc-editor.org/rfc/rfc3339.html> RFC 3339 *)
6363-type date = float
6464-6565-(** The UTCDate data type.
6666-6767- A string in RFC 3339 "date-time" format with timezone restricted to UTC
6868- (i.e., ending with "Z"). For example: "2014-10-30T06:12:00Z".
6969-7070- This is a more restrictive version of the [date] type, used in contexts
7171- where timezone normalization is required.
7272-7373- @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-1.4> RFC 8620, Section 1.4 *)
7474-type utc_date = float
7575-7676-(** {1 Collection Types} *)
7777-7878-(** Represents a JSON object used as a map from String to arbitrary values.
7979-8080- In JMAP, many objects are represented as maps with string keys. This type
8181- provides a convenient OCaml representation using hash tables for efficient
8282- lookup and modification.
8383-8484- {b Usage example}: Account capabilities, session capabilities, and various
8585- property maps throughout the protocol.
8686-8787- @param 'v The type of values stored in the map *)
8888-type 'v string_map = (string, 'v) Hashtbl.t
8989-9090-(** Represents a JSON object used as a map from Id to arbitrary values.
9191-9292- This is similar to [string_map] but specifically for JMAP Id keys. Common
9393- use cases include mapping object IDs to objects, errors, or update information.
9494-9595- {b Usage example}: The "create" argument in /set methods maps client-assigned
9696- IDs to objects to be created.
9797-9898- @param 'v The type of values stored in the map *)
9999-type 'v id_map = (id, 'v) Hashtbl.t
100100-101101-(** {1 Protocol-Specific Types} *)
102102-103103-(** Represents a JSON Pointer path with JMAP extensions.
104104-105105- A JSON Pointer is a string syntax for identifying specific values within
106106- a JSON document. JMAP extends this with additional syntax for referencing
107107- values from previous method calls within the same request.
108108-109109- Examples of valid JSON pointers in JMAP:
110110- - "/property" - References the "property" field in the root object
111111- - "/items/0" - References the first item in the "items" array
112112- - "*" - Represents all properties or all array elements
113113-114114- The pointer syntax is used extensively in result references and patch
115115- operations within JMAP.
116116-117117- @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-3.7> RFC 8620, Section 3.7
118118- @see <https://www.rfc-editor.org/rfc/rfc6901.html> RFC 6901 (JSON Pointer) *)
119119-type json_pointer = string
120120-121121-(** {1 Protocol Constants} *)
122122-123123-(** Protocol constants for common values.
124124-125125- This module contains commonly used constant values throughout the
126126- JMAP protocol, reducing hardcoded strings and providing type safety. *)
127127-module Constants : sig
128128- (** VacationResponse singleton object ID.
129129-130130- VacationResponse objects always use this fixed ID per JMAP specification.
131131- @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-7> RFC 8621, Section 7 *)
132132- val vacation_response_id : string
133133-134134- (** HTTP Content-Type values for JMAP protocol. *)
135135- module Content_type : sig
136136- (** JMAP protocol content type. *)
137137- val json : string
138138- end
139139-140140- (** Default User-Agent strings. *)
141141- module User_agent : sig
142142- (** Default OCaml JMAP client user agent. *)
143143- val ocaml_jmap : string
144144-145145- (** Eio-based client user agent. *)
146146- val eio_client : string
147147- end
148148-end
-85
jmap/jmap/jmap_uint.ml
···11-(** JMAP UnsignedInt data type implementation *)
22-33-type t = int
44-55-(* Maximum safe integer value for JavaScript: 2^53 - 1 *)
66-let max_safe_value = 9007199254740991
77-88-let is_valid_int i = i >= 0 && i <= max_safe_value
99-1010-let of_int i =
1111- if is_valid_int i then Ok i
1212- else if i < 0 then Error "UnsignedInt cannot be negative"
1313- else Error "UnsignedInt cannot exceed 2^53-1"
1414-1515-let to_int uint = uint
1616-1717-let of_string str =
1818- try
1919- let i = int_of_string str in
2020- of_int i
2121- with
2222- | Failure _ -> Error "Invalid integer string format"
2323- | Invalid_argument _ -> Error "Invalid integer string format"
2424-2525-let to_string uint = string_of_int uint
2626-2727-let pp ppf uint = Fmt.int ppf uint
2828-2929-let pp_hum ppf uint = Fmt.pf ppf "UInt(%d)" uint
3030-3131-(* Constants *)
3232-let zero = 0
3333-let one = 1
3434-let max_safe = max_safe_value
3535-3636-let validate uint =
3737- if is_valid_int uint then Ok ()
3838- else Error "UnsignedInt value out of valid range"
3939-4040-(* Arithmetic operations with overflow checking *)
4141-let add uint1 uint2 =
4242- let result = uint1 + uint2 in
4343- if result >= uint1 && result >= uint2 && is_valid_int result then
4444- Ok result
4545- else
4646- Error "UnsignedInt addition overflow"
4747-4848-let sub uint1 uint2 =
4949- if uint1 >= uint2 then Ok (uint1 - uint2)
5050- else Error "UnsignedInt subtraction would result in negative value"
5151-5252-let mul uint1 uint2 =
5353- if uint1 = 0 || uint2 = 0 then Ok 0
5454- else if uint1 <= max_safe_value / uint2 then
5555- Ok (uint1 * uint2)
5656- else
5757- Error "UnsignedInt multiplication overflow"
5858-5959-(* Comparison and utilities *)
6060-let equal = (=)
6161-6262-let compare = compare
6363-6464-let min uint1 uint2 = if uint1 <= uint2 then uint1 else uint2
6565-6666-let max uint1 uint2 = if uint1 >= uint2 then uint1 else uint2
6767-6868-let pp_debug ppf uint = Fmt.pf ppf "UInt(%d)" uint
6969-7070-let to_string_debug uint = Printf.sprintf "UInt(%d)" uint
7171-7272-(* JSON serialization *)
7373-let to_json uint = `Int uint
7474-7575-let of_json = function
7676- | `Int i -> of_int i
7777- | `Float f ->
7878- (* Handle case where JSON parser represents integers as floats *)
7979- if f >= 0.0 && f <= float_of_int max_safe_value && f = Float.round f then
8080- of_int (int_of_float f)
8181- else
8282- Error "Float value is not a valid UnsignedInt"
8383- | json ->
8484- let json_str = Yojson.Safe.to_string json in
8585- Error (Printf.sprintf "Expected JSON number for UnsignedInt, got: %s" json_str)
-128
jmap/jmap/jmap_uint.mli
···11-(** JMAP UnsignedInt data type (RFC 8620).
22-33- The UnsignedInt data type is an unsigned integer in the range [0, 2^53-1].
44- This corresponds to the safe integer range for unsigned values in JavaScript
55- and JSON implementations.
66-77- In OCaml, this is represented as a regular [int]. Note that OCaml's [int]
88- on 64-bit platforms has a larger range, but JMAP protocol compliance
99- requires staying within the specified range and ensuring non-negative values.
1010-1111- Common uses include counts, limits, positions, and sizes within the protocol.
1212-1313- @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-1.3> RFC 8620, Section 1.3 *)
1414-1515-(** Abstract type representing a JMAP UnsignedInt. *)
1616-type t
1717-1818-(** JSON serialization interface *)
1919-include Jmap_sigs.JSONABLE with type t := t
2020-2121-(** Pretty-printing interface *)
2222-include Jmap_sigs.PRINTABLE with type t := t
2323-2424-(** {1 Construction and Access} *)
2525-2626-(** Create an UnsignedInt from an int.
2727- @param i The int value.
2828- @return Ok with the UnsignedInt, or Error if the value is negative or too large. *)
2929-val of_int : int -> (t, string) result
3030-3131-(** Convert an UnsignedInt to an int.
3232- @param uint The UnsignedInt to convert.
3333- @return The int representation. *)
3434-val to_int : t -> int
3535-3636-(** Create an UnsignedInt from a string.
3737- @param str The string representation of a non-negative integer.
3838- @return Ok with the UnsignedInt, or Error if parsing fails or value is invalid. *)
3939-val of_string : string -> (t, string) result
4040-4141-(** Convert an UnsignedInt to a string.
4242- @param uint The UnsignedInt to convert.
4343- @return The string representation. *)
4444-val to_string : t -> string
4545-4646-(** Pretty-print an UnsignedInt.
4747- @param ppf The formatter.
4848- @param uint The UnsignedInt to print. *)
4949-val pp : Format.formatter -> t -> unit
5050-5151-(** {1 Constants} *)
5252-5353-(** Zero value. *)
5454-val zero : t
5555-5656-(** One value. *)
5757-val one : t
5858-5959-(** Maximum safe value (2^53 - 1). *)
6060-val max_safe : t
6161-6262-(** {1 Validation} *)
6363-6464-(** Check if an int is a valid UnsignedInt value.
6565- @param i The int to validate.
6666- @return True if the value is in valid range, false otherwise. *)
6767-val is_valid_int : int -> bool
6868-6969-(** Validate an UnsignedInt according to JMAP constraints.
7070- @param uint The UnsignedInt to validate.
7171- @return Ok () if valid, Error with description if invalid. *)
7272-val validate : t -> (unit, string) result
7373-7474-(** {1 Arithmetic Operations} *)
7575-7676-(** Add two UnsignedInts.
7777- @param uint1 First UnsignedInt.
7878- @param uint2 Second UnsignedInt.
7979- @return Ok with the sum, or Error if overflow would occur. *)
8080-val add : t -> t -> (t, string) result
8181-8282-(** Subtract two UnsignedInts.
8383- @param uint1 First UnsignedInt (minuend).
8484- @param uint2 Second UnsignedInt (subtrahend).
8585- @return Ok with the difference, or Error if result would be negative. *)
8686-val sub : t -> t -> (t, string) result
8787-8888-(** Multiply two UnsignedInts.
8989- @param uint1 First UnsignedInt.
9090- @param uint2 Second UnsignedInt.
9191- @return Ok with the product, or Error if overflow would occur. *)
9292-val mul : t -> t -> (t, string) result
9393-9494-(** {1 Comparison and Utilities} *)
9595-9696-(** Compare two UnsignedInts for equality.
9797- @param uint1 First UnsignedInt.
9898- @param uint2 Second UnsignedInt.
9999- @return True if equal, false otherwise. *)
100100-val equal : t -> t -> bool
101101-102102-(** Compare two UnsignedInts numerically.
103103- @param uint1 First UnsignedInt.
104104- @param uint2 Second UnsignedInt.
105105- @return Negative if uint1 < uint2, zero if equal, positive if uint1 > uint2. *)
106106-val compare : t -> t -> int
107107-108108-(** Get the minimum of two UnsignedInts.
109109- @param uint1 First UnsignedInt.
110110- @param uint2 Second UnsignedInt.
111111- @return The smaller value. *)
112112-val min : t -> t -> t
113113-114114-(** Get the maximum of two UnsignedInts.
115115- @param uint1 First UnsignedInt.
116116- @param uint2 Second UnsignedInt.
117117- @return The larger value. *)
118118-val max : t -> t -> t
119119-120120-(** Pretty-print an UnsignedInt for debugging.
121121- @param ppf The formatter.
122122- @param uint The UnsignedInt to format. *)
123123-val pp_debug : Format.formatter -> t -> unit
124124-125125-(** Convert an UnsignedInt to a human-readable string for debugging.
126126- @param uint The UnsignedInt to format.
127127- @return A debug string representation. *)
128128-val to_string_debug : t -> string
···12121313 @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-3> RFC 8620, Section 3 *)
14141515-open Jmap_types
1515+open Types
16161717(** {1 Method Invocations} *)
1818···6565 with the method call ID for correlation.
66666767 @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-3.6.2> RFC 8620, Section 3.6.2 *)
6868-type method_error = Jmap_error.Method_error.t * string
6868+type method_error = Error.Method_error.t * string
69697070(** A response invocation part, which can be a standard response or an error.
7171···239239 session_state:string ->
240240 unit ->
241241 t
242242-end
242242+end
+432
jmap/jmap/types.ml
···11+(** JMAP Core Types Implementation *)
22+33+(* Id module implementation *)
44+module Id = struct
55+ type t = string
66+77+ let is_base64url_char c =
88+ (c >= 'A' && c <= 'Z') ||
99+ (c >= 'a' && c <= 'z') ||
1010+ (c >= '0' && c <= '9') ||
1111+ c = '-' || c = '_'
1212+1313+ let is_valid_string str =
1414+ let len = String.length str in
1515+ len > 0 && len <= 255 &&
1616+ let rec check i =
1717+ if i >= len then true
1818+ else if is_base64url_char str.[i] then check (i + 1)
1919+ else false
2020+ in
2121+ check 0
2222+2323+ let of_string str =
2424+ if is_valid_string str then Ok str
2525+ else
2626+ let len = String.length str in
2727+ if len = 0 then Error "Id cannot be empty"
2828+ else if len > 255 then Error "Id cannot be longer than 255 octets"
2929+ else Error "Id contains invalid characters (must be base64url alphabet only)"
3030+3131+ let to_string id = id
3232+3333+ let pp ppf id = Format.fprintf ppf "%s" id
3434+3535+ let pp_hum ppf id = Format.fprintf ppf "Id(%s)" id
3636+3737+ let validate id =
3838+ if is_valid_string id then Ok ()
3939+ else Error "Invalid Id format"
4040+4141+ let equal = String.equal
4242+4343+ let compare = String.compare
4444+4545+ let pp_debug ppf id = Format.fprintf ppf "Id(%s)" id
4646+4747+ let to_string_debug id = Printf.sprintf "Id(%s)" id
4848+4949+ (* JSON serialization *)
5050+ let to_json id = `String id
5151+5252+ let of_json = function
5353+ | `String str -> of_string str
5454+ | json ->
5555+ let json_str = Yojson.Safe.to_string json in
5656+ Error (Printf.sprintf "Expected JSON string for Id, got: %s" json_str)
5757+end
5858+5959+(* Date module implementation *)
6060+module Date = struct
6161+ type t = float (* Unix timestamp *)
6262+6363+ (* Basic RFC 3339 parsing - simplified for JMAP usage *)
6464+ let parse_rfc3339 str =
6565+ try
6666+ (* Use Unix.strptime if available, otherwise simplified parsing *)
6767+ let len = String.length str in
6868+ if len < 19 then failwith "Too short for RFC 3339";
6969+7070+ (* Extract year, month, day, hour, minute, second *)
7171+ let year = int_of_string (String.sub str 0 4) in
7272+ let month = int_of_string (String.sub str 5 2) in
7373+ let day = int_of_string (String.sub str 8 2) in
7474+ let hour = int_of_string (String.sub str 11 2) in
7575+ let minute = int_of_string (String.sub str 14 2) in
7676+ let second = int_of_string (String.sub str 17 2) in
7777+7878+ (* Basic validation *)
7979+ if year < 1970 || year > 9999 then failwith "Invalid year";
8080+ if month < 1 || month > 12 then failwith "Invalid month";
8181+ if day < 1 || day > 31 then failwith "Invalid day";
8282+ if hour < 0 || hour > 23 then failwith "Invalid hour";
8383+ if minute < 0 || minute > 59 then failwith "Invalid minute";
8484+ if second < 0 || second > 59 then failwith "Invalid second";
8585+8686+ (* Convert to Unix timestamp using built-in functions *)
8787+ let tm = {
8888+ Unix.tm_year = year - 1900;
8989+ tm_mon = month - 1;
9090+ tm_mday = day;
9191+ tm_hour = hour;
9292+ tm_min = minute;
9393+ tm_sec = second;
9494+ tm_wday = 0;
9595+ tm_yday = 0;
9696+ tm_isdst = false;
9797+ } in
9898+9999+ (* Handle timezone - simplified to assume UTC for 'Z' suffix *)
100100+ let timestamp =
101101+ if len >= 20 && str.[len-1] = 'Z' then
102102+ (* UTC time - convert to UTC timestamp *)
103103+ let local_time = fst (Unix.mktime tm) in
104104+ let gm_tm = Unix.gmtime local_time in
105105+ let utc_time = fst (Unix.mktime gm_tm) in
106106+ utc_time
107107+ else if len >= 25 && (str.[len-6] = '+' || str.[len-6] = '-') then
108108+ (* Timezone offset specified *)
109109+ let sign = if str.[len-6] = '+' then -1.0 else 1.0 in
110110+ let tz_hours = int_of_string (String.sub str (len-5) 2) in
111111+ let tz_minutes = int_of_string (String.sub str (len-2) 2) in
112112+ let offset = sign *. (float_of_int tz_hours *. 3600.0 +. float_of_int tz_minutes *. 60.0) in
113113+ fst (Unix.mktime tm) +. offset
114114+ else
115115+ (* No timezone - assume local time *)
116116+ fst (Unix.mktime tm)
117117+ in
118118+ Ok timestamp
119119+ with
120120+ | Failure msg -> Error ("Invalid RFC 3339 format: " ^ msg)
121121+ | Invalid_argument _ -> Error "Invalid RFC 3339 format: parsing error"
122122+ | _ -> Error "Invalid RFC 3339 format"
123123+124124+ let format_rfc3339 timestamp =
125125+ let tm = Unix.gmtime timestamp in
126126+ Printf.sprintf "%04d-%02d-%02dT%02d:%02d:%02dZ"
127127+ (tm.tm_year + 1900)
128128+ (tm.tm_mon + 1)
129129+ tm.tm_mday
130130+ tm.tm_hour
131131+ tm.tm_min
132132+ tm.tm_sec
133133+134134+ let of_timestamp timestamp = timestamp
135135+136136+ let to_timestamp date = date
137137+138138+ let of_rfc3339 str = parse_rfc3339 str
139139+140140+ let to_rfc3339 date = format_rfc3339 date
141141+142142+ let now () = Unix.time ()
143143+144144+ let validate date =
145145+ if date >= 0.0 && date <= 253402300799.0 (* 9999-12-31T23:59:59Z *) then
146146+ Ok ()
147147+ else
148148+ Error "Date timestamp out of valid range"
149149+150150+ let equal date1 date2 =
151151+ (* Equal within 1 second precision *)
152152+ abs_float (date1 -. date2) < 1.0
153153+154154+ let compare date1 date2 =
155155+ if date1 < date2 then -1
156156+ else if date1 > date2 then 1
157157+ else 0
158158+159159+ let is_before date1 date2 = date1 < date2
160160+161161+ let is_after date1 date2 = date1 > date2
162162+163163+ let pp ppf date = Format.fprintf ppf "%s" (to_rfc3339 date)
164164+165165+ let pp_hum ppf date = Format.fprintf ppf "Date(%s)" (to_rfc3339 date)
166166+167167+ let pp_debug ppf date =
168168+ Format.fprintf ppf "Date(%s)" (to_rfc3339 date)
169169+170170+ let to_string_debug date =
171171+ Printf.sprintf "Date(%s)" (to_rfc3339 date)
172172+173173+ (* JSON serialization *)
174174+ let to_json date = `String (to_rfc3339 date)
175175+176176+ let of_json = function
177177+ | `String str -> of_rfc3339 str
178178+ | json ->
179179+ let json_str = Yojson.Safe.to_string json in
180180+ Error (Printf.sprintf "Expected JSON string for Date, got: %s" json_str)
181181+end
182182+183183+(* UInt module implementation *)
184184+module UInt = struct
185185+ type t = int
186186+187187+ (* Maximum safe integer value for JavaScript: 2^53 - 1 *)
188188+ let max_safe_value = 9007199254740991
189189+190190+ let is_valid_int i = i >= 0 && i <= max_safe_value
191191+192192+ let of_int i =
193193+ if is_valid_int i then Ok i
194194+ else if i < 0 then Error "UnsignedInt cannot be negative"
195195+ else Error "UnsignedInt cannot exceed 2^53-1"
196196+197197+ let to_int uint = uint
198198+199199+ let of_string str =
200200+ try
201201+ let i = int_of_string str in
202202+ of_int i
203203+ with
204204+ | Failure _ -> Error "Invalid integer string format"
205205+ | Invalid_argument _ -> Error "Invalid integer string format"
206206+207207+ let to_string uint = string_of_int uint
208208+209209+ let pp ppf uint = Format.fprintf ppf "%d" uint
210210+211211+ let pp_hum ppf uint = Format.fprintf ppf "UInt(%d)" uint
212212+213213+ (* Constants *)
214214+ let zero = 0
215215+ let one = 1
216216+ let max_safe = max_safe_value
217217+218218+ let validate uint =
219219+ if is_valid_int uint then Ok ()
220220+ else Error "UnsignedInt value out of valid range"
221221+222222+ (* Arithmetic operations with overflow checking *)
223223+ let add uint1 uint2 =
224224+ let result = uint1 + uint2 in
225225+ if result >= uint1 && result >= uint2 && is_valid_int result then
226226+ Ok result
227227+ else
228228+ Error "UnsignedInt addition overflow"
229229+230230+ let sub uint1 uint2 =
231231+ if uint1 >= uint2 then Ok (uint1 - uint2)
232232+ else Error "UnsignedInt subtraction would result in negative value"
233233+234234+ let mul uint1 uint2 =
235235+ if uint1 = 0 || uint2 = 0 then Ok 0
236236+ else if uint1 <= max_safe_value / uint2 then
237237+ Ok (uint1 * uint2)
238238+ else
239239+ Error "UnsignedInt multiplication overflow"
240240+241241+ (* Comparison and utilities *)
242242+ let equal = (=)
243243+244244+ let compare = compare
245245+246246+ let min uint1 uint2 = if uint1 <= uint2 then uint1 else uint2
247247+248248+ let max uint1 uint2 = if uint1 >= uint2 then uint1 else uint2
249249+250250+ let pp_debug ppf uint = Format.fprintf ppf "UInt(%d)" uint
251251+252252+ let to_string_debug uint = Printf.sprintf "UInt(%d)" uint
253253+254254+ (* JSON serialization *)
255255+ let to_json uint = `Int uint
256256+257257+ let of_json = function
258258+ | `Int i -> of_int i
259259+ | `Float f ->
260260+ (* Handle case where JSON parser represents integers as floats *)
261261+ if f >= 0.0 && f <= float_of_int max_safe_value && f = Float.round f then
262262+ of_int (int_of_float f)
263263+ else
264264+ Error "Float value is not a valid UnsignedInt"
265265+ | json ->
266266+ let json_str = Yojson.Safe.to_string json in
267267+ Error (Printf.sprintf "Expected JSON number for UnsignedInt, got: %s" json_str)
268268+end
269269+270270+(* Patch module implementation *)
271271+module Patch = struct
272272+ (* Internal representation as a hash table for efficient operations *)
273273+ type t = (string, Yojson.Safe.t) Hashtbl.t
274274+275275+ (* JSON Pointer validation - simplified but covers common cases *)
276276+ let is_valid_property_path path =
277277+ let len = String.length path in
278278+ if len = 0 then true (* empty path is valid root *)
279279+ else if path.[0] <> '/' then true (* simple property names are valid *)
280280+ else
281281+ (* Check for valid JSON Pointer format *)
282282+ let rec check_escaping i =
283283+ if i >= len then true
284284+ else match path.[i] with
285285+ | '~' when i + 1 < len ->
286286+ (match path.[i + 1] with
287287+ | '0' | '1' -> check_escaping (i + 2)
288288+ | _ -> false)
289289+ | '/' -> check_escaping (i + 1)
290290+ | _ -> check_escaping (i + 1)
291291+ in
292292+ check_escaping 0
293293+294294+ let empty = Hashtbl.create 8
295295+296296+ let of_operations operations =
297297+ let patch = Hashtbl.create (List.length operations) in
298298+ let rec process = function
299299+ | [] -> Ok patch
300300+ | (property, value) :: rest ->
301301+ if is_valid_property_path property then (
302302+ Hashtbl.replace patch property value;
303303+ process rest
304304+ ) else
305305+ Error ("Invalid property path: " ^ property)
306306+ in
307307+ process operations
308308+309309+ let to_operations patch =
310310+ Hashtbl.fold (fun property value acc ->
311311+ (property, value) :: acc
312312+ ) patch []
313313+314314+ let of_json_object = function
315315+ | `Assoc pairs -> of_operations pairs
316316+ | json ->
317317+ let json_str = Yojson.Safe.to_string json in
318318+ Error (Printf.sprintf "Expected JSON object for Patch, got: %s" json_str)
319319+320320+ let to_json_object patch =
321321+ let pairs = to_operations patch in
322322+ `Assoc pairs
323323+324324+ let set_property patch property value =
325325+ if is_valid_property_path property then (
326326+ let new_patch = Hashtbl.copy patch in
327327+ Hashtbl.replace new_patch property value;
328328+ Ok new_patch
329329+ ) else
330330+ Error ("Invalid property path: " ^ property)
331331+332332+ let remove_property patch property =
333333+ set_property patch property `Null
334334+335335+ let has_property patch property =
336336+ Hashtbl.mem patch property
337337+338338+ let get_property patch property =
339339+ try Some (Hashtbl.find patch property)
340340+ with Not_found -> None
341341+342342+ let merge patch1 patch2 =
343343+ let result = Hashtbl.copy patch1 in
344344+ Hashtbl.iter (fun property value ->
345345+ Hashtbl.replace result property value
346346+ ) patch2;
347347+ result
348348+349349+ let is_empty patch =
350350+ Hashtbl.length patch = 0
351351+352352+ let size patch =
353353+ Hashtbl.length patch
354354+355355+ let validate patch =
356356+ (* Validate all property paths *)
357357+ try
358358+ Hashtbl.iter (fun property _value ->
359359+ if not (is_valid_property_path property) then
360360+ failwith ("Invalid property path: " ^ property)
361361+ ) patch;
362362+ Ok ()
363363+ with
364364+ | Failure msg -> Error msg
365365+366366+ let equal patch1 patch2 =
367367+ if Hashtbl.length patch1 <> Hashtbl.length patch2 then false
368368+ else
369369+ try
370370+ Hashtbl.iter (fun property value1 ->
371371+ match get_property patch2 property with
372372+ | None -> failwith "Property not found"
373373+ | Some value2 when Yojson.Safe.equal value1 value2 -> ()
374374+ | Some _ -> failwith "Property values differ"
375375+ ) patch1;
376376+ true
377377+ with
378378+ | Failure _ -> false
379379+380380+ let pp ppf patch =
381381+ Format.fprintf ppf "%s" (Yojson.Safe.to_string (to_json_object patch))
382382+383383+ let pp_hum ppf patch =
384384+ let operations = to_operations patch in
385385+ let op_count = List.length operations in
386386+ let key_list = List.map fst operations in
387387+ let key_str = match key_list with
388388+ | [] -> "none"
389389+ | keys -> String.concat ", " keys
390390+ in
391391+ Format.fprintf ppf "Patch{operations=%d; keys=[%s]}" op_count key_str
392392+393393+ let to_string_debug patch =
394394+ let operations = to_operations patch in
395395+ let op_strings = List.map (fun (prop, value) ->
396396+ Printf.sprintf "%s: %s" prop (Yojson.Safe.to_string value)
397397+ ) operations in
398398+ Printf.sprintf "Patch({%s})" (String.concat "; " op_strings)
399399+400400+ (* JSON serialization *)
401401+ let to_json patch = to_json_object patch
402402+403403+ let of_json json = of_json_object json
404404+end
405405+406406+(* Legacy type aliases *)
407407+type id = string
408408+type jint = int
409409+type uint = int
410410+type date = float
411411+type utc_date = float
412412+413413+(* Collection types *)
414414+type 'v string_map = (string, 'v) Hashtbl.t
415415+type 'v id_map = (id, 'v) Hashtbl.t
416416+417417+(* Protocol-specific types *)
418418+type json_pointer = string
419419+420420+(* Constants module *)
421421+module Constants = struct
422422+ let vacation_response_id = "singleton"
423423+424424+ module Content_type = struct
425425+ let json = "application/json"
426426+ end
427427+428428+ module User_agent = struct
429429+ let ocaml_jmap = "OCaml-JMAP/1.0"
430430+ let eio_client = "OCaml JMAP Client/Eio"
431431+ end
432432+end
+592
jmap/jmap/types.mli
···11+(** JMAP Core Types Library (RFC 8620)
22+33+ This module provides all fundamental JMAP data types in a unified interface.
44+ It consolidates the core primitives (Id, Date, UInt), data structures (Patch),
55+ and collection types used throughout the JMAP protocol.
66+77+ The module is organized into clear sections:
88+ - {!Types.Id}: JMAP Id type with validation and JSON serialization
99+ - {!Types.Date}: JMAP Date type with RFC 3339 support
1010+ - {!Types.UInt}: JMAP UnsignedInt type with range validation
1111+ - {!Types.Patch}: JMAP Patch objects for property updates
1212+ - Legacy type aliases for backwards compatibility
1313+1414+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-1> RFC 8620, Section 1 *)
1515+1616+(** {1 Core JMAP Types} *)
1717+1818+(** JMAP Id data type with validation and JSON serialization.
1919+2020+ The Id data type is a string of 1 to 255 octets in length and MUST consist
2121+ only of characters from the base64url alphabet, as defined in Section 5 of
2222+ RFC 4648. This includes ASCII alphanumeric characters, plus the characters
2323+ '-' and '_'.
2424+2525+ Ids are used to identify JMAP objects within an account. They are assigned
2626+ by the server and are immutable once assigned. The same id MUST refer to
2727+ the same object throughout the lifetime of the object.
2828+2929+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-1.2> RFC 8620, Section 1.2 *)
3030+module Id : sig
3131+ (** Abstract type representing a JMAP Id. *)
3232+ type t
3333+3434+ (** JSON serialization interface *)
3535+ include Jmap_sigs.JSONABLE with type t := t
3636+3737+ (** Pretty-printing interface *)
3838+ include Jmap_sigs.PRINTABLE with type t := t
3939+4040+ (** {2 Construction and Access} *)
4141+4242+ (** Create a new Id from a string.
4343+ @param str The string representation.
4444+ @return Ok with the created Id, or Error if the string violates Id constraints. *)
4545+ val of_string : string -> (t, string) result
4646+4747+ (** Convert an Id to its string representation.
4848+ @param id The Id to convert.
4949+ @return The string representation. *)
5050+ val to_string : t -> string
5151+5252+ (** Pretty-print an Id.
5353+ @param ppf The formatter.
5454+ @param id The Id to print. *)
5555+ val pp : Format.formatter -> t -> unit
5656+5757+ (** {2 Validation} *)
5858+5959+ (** Check if a string is a valid JMAP Id.
6060+ @param str The string to validate.
6161+ @return True if the string meets Id requirements, false otherwise. *)
6262+ val is_valid_string : string -> bool
6363+6464+ (** Validate an Id according to JMAP constraints.
6565+ @param id The Id to validate.
6666+ @return Ok () if valid, Error with description if invalid. *)
6767+ val validate : t -> (unit, string) result
6868+6969+ (** {2 Comparison and Utilities} *)
7070+7171+ (** Compare two Ids for equality.
7272+ @param id1 First Id.
7373+ @param id2 Second Id.
7474+ @return True if equal, false otherwise. *)
7575+ val equal : t -> t -> bool
7676+7777+ (** Compare two Ids lexicographically.
7878+ @param id1 First Id.
7979+ @param id2 Second Id.
8080+ @return Negative if id1 < id2, zero if equal, positive if id1 > id2. *)
8181+ val compare : t -> t -> int
8282+8383+ (** Pretty-print an Id for debugging.
8484+ @param ppf The formatter.
8585+ @param id The Id to format. *)
8686+ val pp_debug : Format.formatter -> t -> unit
8787+8888+ (** Convert an Id to a human-readable string for debugging.
8989+ @param id The Id to format.
9090+ @return A debug string representation. *)
9191+ val to_string_debug : t -> string
9292+end
9393+9494+(** JMAP Date data type with RFC 3339 support and JSON serialization.
9595+9696+ The Date data type is a string in RFC 3339 "date-time" format, optionally
9797+ with timezone information. For example: "2014-10-30T14:12:00+08:00" or
9898+ "2014-10-30T06:12:00Z".
9999+100100+ In this OCaml implementation, dates are internally represented as Unix
101101+ timestamps (float) for efficient computation, with conversion to/from
102102+ RFC 3339 string format handled by the serialization functions.
103103+104104+ {b Note}: When represented as a float, precision may be lost for sub-second
105105+ values. The implementation preserves second-level precision.
106106+107107+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-1.4> RFC 8620, Section 1.4
108108+ @see <https://www.rfc-editor.org/rfc/rfc3339.html> RFC 3339 *)
109109+module Date : sig
110110+ (** Abstract type representing a JMAP Date. *)
111111+ type t
112112+113113+ (** JSON serialization interface *)
114114+ include Jmap_sigs.JSONABLE with type t := t
115115+116116+ (** Pretty-printing interface *)
117117+ include Jmap_sigs.PRINTABLE with type t := t
118118+119119+ (** {2 Construction and Access} *)
120120+121121+ (** Create a Date from a Unix timestamp.
122122+ @param timestamp The Unix timestamp (seconds since epoch).
123123+ @return A Date representing the timestamp. *)
124124+ val of_timestamp : float -> t
125125+126126+ (** Convert a Date to a Unix timestamp.
127127+ @param date The Date to convert.
128128+ @return The Unix timestamp (seconds since epoch). *)
129129+ val to_timestamp : t -> float
130130+131131+ (** Create a Date from an RFC 3339 string.
132132+ @param str The RFC 3339 formatted string.
133133+ @return Ok with the parsed Date, or Error if the string is not valid RFC 3339. *)
134134+ val of_rfc3339 : string -> (t, string) result
135135+136136+ (** Convert a Date to an RFC 3339 string.
137137+ @param date The Date to convert.
138138+ @return The RFC 3339 formatted string. *)
139139+ val to_rfc3339 : t -> string
140140+141141+ (** Create a Date representing the current time.
142142+ @return A Date set to the current time. *)
143143+ val now : unit -> t
144144+145145+ (** {2 Validation} *)
146146+147147+ (** Validate a Date according to JMAP constraints.
148148+ @param date The Date to validate.
149149+ @return Ok () if valid, Error with description if invalid. *)
150150+ val validate : t -> (unit, string) result
151151+152152+ (** {2 Comparison and Utilities} *)
153153+154154+ (** Compare two Dates for equality.
155155+ @param date1 First Date.
156156+ @param date2 Second Date.
157157+ @return True if equal (within 1 second precision), false otherwise. *)
158158+ val equal : t -> t -> bool
159159+160160+ (** Compare two Dates chronologically.
161161+ @param date1 First Date.
162162+ @param date2 Second Date.
163163+ @return Negative if date1 < date2, zero if equal, positive if date1 > date2. *)
164164+ val compare : t -> t -> int
165165+166166+ (** Check if first Date is before second Date.
167167+ @param date1 First Date.
168168+ @param date2 Second Date.
169169+ @return True if date1 is before date2. *)
170170+ val is_before : t -> t -> bool
171171+172172+ (** Check if first Date is after second Date.
173173+ @param date1 First Date.
174174+ @param date2 Second Date.
175175+ @return True if date1 is after date2. *)
176176+ val is_after : t -> t -> bool
177177+178178+ (** Pretty-print a Date in RFC3339 format.
179179+ @param ppf The formatter.
180180+ @param date The Date to print. *)
181181+ val pp : Format.formatter -> t -> unit
182182+183183+ (** Pretty-print a Date for debugging.
184184+ @param ppf The formatter.
185185+ @param date The Date to format. *)
186186+ val pp_debug : Format.formatter -> t -> unit
187187+188188+ (** Convert a Date to a human-readable string for debugging.
189189+ @param date The Date to format.
190190+ @return A debug string representation. *)
191191+ val to_string_debug : t -> string
192192+end
193193+194194+(** JMAP UnsignedInt data type with range validation and JSON serialization.
195195+196196+ The UnsignedInt data type is an unsigned integer in the range [0, 2^53-1].
197197+ This corresponds to the safe integer range for unsigned values in JavaScript
198198+ and JSON implementations.
199199+200200+ In OCaml, this is represented as a regular [int]. Note that OCaml's [int]
201201+ on 64-bit platforms has a larger range, but JMAP protocol compliance
202202+ requires staying within the specified range and ensuring non-negative values.
203203+204204+ Common uses include counts, limits, positions, and sizes within the protocol.
205205+206206+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-1.3> RFC 8620, Section 1.3 *)
207207+module UInt : sig
208208+ (** Abstract type representing a JMAP UnsignedInt. *)
209209+ type t
210210+211211+ (** JSON serialization interface *)
212212+ include Jmap_sigs.JSONABLE with type t := t
213213+214214+ (** Pretty-printing interface *)
215215+ include Jmap_sigs.PRINTABLE with type t := t
216216+217217+ (** {2 Construction and Access} *)
218218+219219+ (** Create an UnsignedInt from an int.
220220+ @param i The int value.
221221+ @return Ok with the UnsignedInt, or Error if the value is negative or too large. *)
222222+ val of_int : int -> (t, string) result
223223+224224+ (** Convert an UnsignedInt to an int.
225225+ @param uint The UnsignedInt to convert.
226226+ @return The int representation. *)
227227+ val to_int : t -> int
228228+229229+ (** Create an UnsignedInt from a string.
230230+ @param str The string representation of a non-negative integer.
231231+ @return Ok with the UnsignedInt, or Error if parsing fails or value is invalid. *)
232232+ val of_string : string -> (t, string) result
233233+234234+ (** Convert an UnsignedInt to a string.
235235+ @param uint The UnsignedInt to convert.
236236+ @return The string representation. *)
237237+ val to_string : t -> string
238238+239239+ (** Pretty-print an UnsignedInt.
240240+ @param ppf The formatter.
241241+ @param uint The UnsignedInt to print. *)
242242+ val pp : Format.formatter -> t -> unit
243243+244244+ (** {2 Constants} *)
245245+246246+ (** Zero value. *)
247247+ val zero : t
248248+249249+ (** One value. *)
250250+ val one : t
251251+252252+ (** Maximum safe value (2^53 - 1). *)
253253+ val max_safe : t
254254+255255+ (** {2 Validation} *)
256256+257257+ (** Check if an int is a valid UnsignedInt value.
258258+ @param i The int to validate.
259259+ @return True if the value is in valid range, false otherwise. *)
260260+ val is_valid_int : int -> bool
261261+262262+ (** Validate an UnsignedInt according to JMAP constraints.
263263+ @param uint The UnsignedInt to validate.
264264+ @return Ok () if valid, Error with description if invalid. *)
265265+ val validate : t -> (unit, string) result
266266+267267+ (** {2 Arithmetic Operations} *)
268268+269269+ (** Add two UnsignedInts.
270270+ @param uint1 First UnsignedInt.
271271+ @param uint2 Second UnsignedInt.
272272+ @return Ok with the sum, or Error if overflow would occur. *)
273273+ val add : t -> t -> (t, string) result
274274+275275+ (** Subtract two UnsignedInts.
276276+ @param uint1 First UnsignedInt (minuend).
277277+ @param uint2 Second UnsignedInt (subtrahend).
278278+ @return Ok with the difference, or Error if result would be negative. *)
279279+ val sub : t -> t -> (t, string) result
280280+281281+ (** Multiply two UnsignedInts.
282282+ @param uint1 First UnsignedInt.
283283+ @param uint2 Second UnsignedInt.
284284+ @return Ok with the product, or Error if overflow would occur. *)
285285+ val mul : t -> t -> (t, string) result
286286+287287+ (** {2 Comparison and Utilities} *)
288288+289289+ (** Compare two UnsignedInts for equality.
290290+ @param uint1 First UnsignedInt.
291291+ @param uint2 Second UnsignedInt.
292292+ @return True if equal, false otherwise. *)
293293+ val equal : t -> t -> bool
294294+295295+ (** Compare two UnsignedInts numerically.
296296+ @param uint1 First UnsignedInt.
297297+ @param uint2 Second UnsignedInt.
298298+ @return Negative if uint1 < uint2, zero if equal, positive if uint1 > uint2. *)
299299+ val compare : t -> t -> int
300300+301301+ (** Get the minimum of two UnsignedInts.
302302+ @param uint1 First UnsignedInt.
303303+ @param uint2 Second UnsignedInt.
304304+ @return The smaller value. *)
305305+ val min : t -> t -> t
306306+307307+ (** Get the maximum of two UnsignedInts.
308308+ @param uint1 First UnsignedInt.
309309+ @param uint2 Second UnsignedInt.
310310+ @return The larger value. *)
311311+ val max : t -> t -> t
312312+313313+ (** Pretty-print an UnsignedInt for debugging.
314314+ @param ppf The formatter.
315315+ @param uint The UnsignedInt to format. *)
316316+ val pp_debug : Format.formatter -> t -> unit
317317+318318+ (** Convert an UnsignedInt to a human-readable string for debugging.
319319+ @param uint The UnsignedInt to format.
320320+ @return A debug string representation. *)
321321+ val to_string_debug : t -> string
322322+end
323323+324324+(** JMAP Patch Object for property updates with JSON serialization.
325325+326326+ A patch object is used to update properties of JMAP objects. It represents
327327+ a JSON object where each key is a property path (using JSON Pointer syntax)
328328+ and each value is the new value to set for that property, or null to remove
329329+ the property.
330330+331331+ Patch objects are commonly used in /set method calls to update existing
332332+ objects without having to send the complete object representation.
333333+334334+ Examples of patch operations:
335335+ - Setting a property: [{"name": "New Name"}]
336336+ - Removing a property: [{"oldProperty": null}]
337337+ - Setting nested properties: [{"address/street": "123 Main St"}]
338338+339339+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-5.3> RFC 8620, Section 5.3
340340+ @see <https://www.rfc-editor.org/rfc/rfc6901.html> RFC 6901 (JSON Pointer) *)
341341+module Patch : sig
342342+ (** Abstract type representing a JMAP Patch Object. *)
343343+ type t
344344+345345+ (** JSON serialization interface *)
346346+ include Jmap_sigs.JSONABLE with type t := t
347347+348348+ (** Pretty-printing interface *)
349349+ include Jmap_sigs.PRINTABLE with type t := t
350350+351351+ (** {2 Construction and Access} *)
352352+353353+ (** Create an empty patch object.
354354+ @return An empty patch with no operations. *)
355355+ val empty : t
356356+357357+ (** Create a patch from a list of property-value pairs.
358358+ @param operations List of (property_path, value) pairs.
359359+ @return Ok with the patch, or Error if any property path is invalid. *)
360360+ val of_operations : (string * Yojson.Safe.t) list -> (t, string) result
361361+362362+ (** Convert a patch to a list of property-value pairs.
363363+ @param patch The patch to convert.
364364+ @return List of (property_path, value) pairs. *)
365365+ val to_operations : t -> (string * Yojson.Safe.t) list
366366+367367+ (** Create a patch from a Yojson.Safe.t object directly.
368368+ @param json The JSON object.
369369+ @return Ok with the patch, or Error if the JSON is not a valid object. *)
370370+ val of_json_object : Yojson.Safe.t -> (t, string) result
371371+372372+ (** Convert a patch to a Yojson.Safe.t object directly.
373373+ @param patch The patch to convert.
374374+ @return The JSON object representation. *)
375375+ val to_json_object : t -> Yojson.Safe.t
376376+377377+ (** {2 Patch Operations} *)
378378+379379+ (** Set a property in the patch.
380380+ @param patch The patch to modify.
381381+ @param property The property path (JSON Pointer format).
382382+ @param value The value to set.
383383+ @return Ok with the updated patch, or Error if the property path is invalid. *)
384384+ val set_property : t -> string -> Yojson.Safe.t -> (t, string) result
385385+386386+ (** Remove a property in the patch (set to null).
387387+ @param patch The patch to modify.
388388+ @param property The property path to remove.
389389+ @return Ok with the updated patch, or Error if the property path is invalid. *)
390390+ val remove_property : t -> string -> (t, string) result
391391+392392+ (** Check if a property is set in the patch.
393393+ @param patch The patch to check.
394394+ @param property The property path to check.
395395+ @return True if the property is explicitly set in the patch. *)
396396+ val has_property : t -> string -> bool
397397+398398+ (** Get a property value from the patch.
399399+ @param patch The patch to query.
400400+ @param property The property path to get.
401401+ @return Some value if the property is set, None if not present. *)
402402+ val get_property : t -> string -> Yojson.Safe.t option
403403+404404+ (** {2 Patch Composition} *)
405405+406406+ (** Merge two patches, with the second patch taking precedence.
407407+ @param patch1 The first patch.
408408+ @param patch2 The second patch (higher precedence).
409409+ @return The merged patch. *)
410410+ val merge : t -> t -> t
411411+412412+ (** Check if a patch is empty (no operations).
413413+ @param patch The patch to check.
414414+ @return True if the patch has no operations. *)
415415+ val is_empty : t -> bool
416416+417417+ (** Get the number of operations in a patch.
418418+ @param patch The patch to count.
419419+ @return The number of property operations. *)
420420+ val size : t -> int
421421+422422+ (** {2 Validation} *)
423423+424424+ (** Validate a patch according to JMAP constraints.
425425+ @param patch The patch to validate.
426426+ @return Ok () if valid, Error with description if invalid. *)
427427+ val validate : t -> (unit, string) result
428428+429429+ (** Validate a JSON Pointer path.
430430+ @param path The property path to validate.
431431+ @return True if the path is a valid JSON Pointer, false otherwise. *)
432432+ val is_valid_property_path : string -> bool
433433+434434+ (** {2 Comparison and Utilities} *)
435435+436436+ (** Compare two patches for equality.
437437+ @param patch1 First patch.
438438+ @param patch2 Second patch.
439439+ @return True if patches have identical operations, false otherwise. *)
440440+ val equal : t -> t -> bool
441441+442442+ (** Convert a patch to a human-readable string for debugging.
443443+ @param patch The patch to format.
444444+ @return A debug string representation. *)
445445+ val to_string_debug : t -> string
446446+end
447447+448448+(** {1 Legacy Types and Collections}
449449+450450+ This section provides type aliases and collection types for compatibility
451451+ and common use cases throughout the JMAP protocol. These types maintain
452452+ backwards compatibility with existing code while the core types above
453453+ provide the preferred interface. *)
454454+455455+(** The Id data type (legacy alias - prefer {!Types.Id}).
456456+457457+ A string of 1 to 255 octets in length and MUST consist only of characters
458458+ from the base64url alphabet, as defined in Section 5 of RFC 4648. This
459459+ includes ASCII alphanumeric characters, plus the characters '-' and '_'.
460460+461461+ Ids are used to identify JMAP objects within an account. They are assigned
462462+ by the server and are immutable once assigned. The same id MUST refer to
463463+ the same object throughout the lifetime of the object.
464464+465465+ {b Note}: In this OCaml implementation, ids are represented as regular strings.
466466+ Validation of id format is the responsibility of the client/server implementation.
467467+468468+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-1.2> RFC 8620, Section 1.2 *)
469469+type id = string
470470+471471+(** The Int data type.
472472+473473+ A signed 53-bit integer in the range [-2^53+1, 2^53-1]. This corresponds
474474+ to the safe integer range in JavaScript and JSON implementations.
475475+476476+ In OCaml, this is represented as a regular [int]. Note that OCaml's [int]
477477+ on 64-bit platforms has a larger range, but JMAP protocol compliance
478478+ requires staying within the specified range.
479479+480480+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-1.3> RFC 8620, Section 1.3 *)
481481+type jint = int
482482+483483+(** The UnsignedInt data type (legacy alias - prefer {!Types.UInt}).
484484+485485+ An unsigned integer in the range [0, 2^53-1]. This is the same as [jint]
486486+ but restricted to non-negative values.
487487+488488+ Common uses include counts, limits, positions, and sizes within the protocol.
489489+490490+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-1.3> RFC 8620, Section 1.3 *)
491491+type uint = int
492492+493493+(** The Date data type (legacy alias - prefer {!Types.Date}).
494494+495495+ A string in RFC 3339 "date-time" format, optionally with timezone information.
496496+ For example: "2014-10-30T14:12:00+08:00" or "2014-10-30T06:12:00Z".
497497+498498+ In this OCaml implementation, dates are represented as Unix timestamps (float).
499499+ Conversion to/from RFC 3339 string format is handled by the wire protocol
500500+ serialization layer.
501501+502502+ {b Note}: When represented as a float, precision may be lost for sub-second
503503+ values. Consider the precision requirements of your application.
504504+505505+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-1.4> RFC 8620, Section 1.4
506506+ @see <https://www.rfc-editor.org/rfc/rfc3339.html> RFC 3339 *)
507507+type date = float
508508+509509+(** The UTCDate data type.
510510+511511+ A string in RFC 3339 "date-time" format with timezone restricted to UTC
512512+ (i.e., ending with "Z"). For example: "2014-10-30T06:12:00Z".
513513+514514+ This is a more restrictive version of the [date] type, used in contexts
515515+ where timezone normalization is required.
516516+517517+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-1.4> RFC 8620, Section 1.4 *)
518518+type utc_date = float
519519+520520+(** {2 Collection Types} *)
521521+522522+(** Represents a JSON object used as a map from String to arbitrary values.
523523+524524+ In JMAP, many objects are represented as maps with string keys. This type
525525+ provides a convenient OCaml representation using hash tables for efficient
526526+ lookup and modification.
527527+528528+ {b Usage example}: Account capabilities, session capabilities, and various
529529+ property maps throughout the protocol.
530530+531531+ @param 'v The type of values stored in the map *)
532532+type 'v string_map = (string, 'v) Hashtbl.t
533533+534534+(** Represents a JSON object used as a map from Id to arbitrary values.
535535+536536+ This is similar to [string_map] but specifically for JMAP Id keys. Common
537537+ use cases include mapping object IDs to objects, errors, or update information.
538538+539539+ {b Usage example}: The "create" argument in /set methods maps client-assigned
540540+ IDs to objects to be created.
541541+542542+ @param 'v The type of values stored in the map *)
543543+type 'v id_map = (id, 'v) Hashtbl.t
544544+545545+(** {2 Protocol-Specific Types} *)
546546+547547+(** Represents a JSON Pointer path with JMAP extensions.
548548+549549+ A JSON Pointer is a string syntax for identifying specific values within
550550+ a JSON document. JMAP extends this with additional syntax for referencing
551551+ values from previous method calls within the same request.
552552+553553+ Examples of valid JSON pointers in JMAP:
554554+ - "/property" - References the "property" field in the root object
555555+ - "/items/0" - References the first item in the "items" array
556556+ - "*" - Represents all properties or all array elements
557557+558558+ The pointer syntax is used extensively in result references and patch
559559+ operations within JMAP.
560560+561561+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-3.7> RFC 8620, Section 3.7
562562+ @see <https://www.rfc-editor.org/rfc/rfc6901.html> RFC 6901 (JSON Pointer) *)
563563+type json_pointer = string
564564+565565+(** {2 Protocol Constants} *)
566566+567567+(** Protocol constants for common values.
568568+569569+ This module contains commonly used constant values throughout the
570570+ JMAP protocol, reducing hardcoded strings and providing type safety. *)
571571+module Constants : sig
572572+ (** VacationResponse singleton object ID.
573573+574574+ VacationResponse objects always use this fixed ID per JMAP specification.
575575+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-7> RFC 8621, Section 7 *)
576576+ val vacation_response_id : string
577577+578578+ (** HTTP Content-Type values for JMAP protocol. *)
579579+ module Content_type : sig
580580+ (** JMAP protocol content type. *)
581581+ val json : string
582582+ end
583583+584584+ (** Default User-Agent strings. *)
585585+ module User_agent : sig
586586+ (** Default OCaml JMAP client user agent. *)
587587+ val ocaml_jmap : string
588588+589589+ (** Eio-based client user agent. *)
590590+ val eio_client : string
591591+ end
592592+end
+23-23
jmap/test_method.ml
···55 printf "Testing JMAP Id module:\n";
6677 (* Test valid ID creation *)
88- let valid_id = Jmap.Id.of_string "abc123-_xyz" in
88+ let valid_id = Jmap.Types.Id.of_string "abc123-_xyz" in
99 match valid_id with
1010 | Ok id ->
1111- printf "✓ Created valid ID: %s\n" (Jmap.Id.to_string id);
1212- printf "✓ Debug representation: %s\n" (Jmap.Id.to_string_debug id)
1111+ printf "✓ Created valid ID: %s\n" (Jmap.Types.Id.to_string id);
1212+ printf "✓ Debug representation: %s\n" (Jmap.Types.Id.to_string_debug id)
1313 | Error msg ->
1414 printf "✗ Failed to create valid ID: %s\n" msg
1515···1717 printf "\nTesting JMAP Date module:\n";
18181919 (* Test RFC 3339 parsing *)
2020- let rfc_date = Jmap.Date.of_rfc3339 "2023-12-01T10:30:00Z" in
2020+ let rfc_date = Jmap.Types.Date.of_rfc3339 "2023-12-01T10:30:00Z" in
2121 match rfc_date with
2222 | Ok date ->
2323- printf "✓ Parsed RFC 3339 date: %s\n" (Jmap.Date.to_rfc3339 date);
2424- printf "✓ Debug representation: %s\n" (Jmap.Date.to_string_debug date)
2323+ printf "✓ Parsed RFC 3339 date: %s\n" (Jmap.Types.Date.to_rfc3339 date);
2424+ printf "✓ Debug representation: %s\n" (Jmap.Types.Date.to_string_debug date)
2525 | Error msg ->
2626 printf "✗ Failed to parse RFC 3339 date: %s\n" msg
2727···2929 printf "\nTesting JMAP UInt module:\n";
30303131 (* Test valid unsigned int *)
3232- let valid_uint = Jmap.UInt.of_int 42 in
3232+ let valid_uint = Jmap.Types.UInt.of_int 42 in
3333 match valid_uint with
3434 | Ok uint ->
3535- printf "✓ Created UInt: %d\n" (Jmap.UInt.to_int uint);
3636- printf "✓ Debug representation: %s\n" (Jmap.UInt.to_string_debug uint)
3535+ printf "✓ Created UInt: %d\n" (Jmap.Types.UInt.to_int uint);
3636+ printf "✓ Debug representation: %s\n" (Jmap.Types.UInt.to_string_debug uint)
3737 | Error msg ->
3838 printf "✗ Failed to create UInt: %s\n" msg;
39394040 (* Test invalid (negative) int *)
4141- let invalid_uint = Jmap.UInt.of_int (-1) in
4141+ let invalid_uint = Jmap.Types.UInt.of_int (-1) in
4242 match invalid_uint with
4343 | Ok _ -> printf "✗ Should have failed for negative value\n"
4444 | Error msg -> printf "✓ Correctly rejected negative value: %s\n" msg
···4747 printf "\nTesting JMAP Patch module:\n";
48484949 (* Test empty patch *)
5050- let empty = Jmap.Patch.empty in
5151- printf "✓ Empty patch created, size: %d\n" (Jmap.Patch.size empty);
5050+ let empty = Jmap.Types.Patch.empty in
5151+ printf "✓ Empty patch created, size: %d\n" (Jmap.Types.Patch.size empty);
52525353 (* Test setting a property *)
5454- match Jmap.Patch.set_property empty "name" (`String "John") with
5454+ match Jmap.Types.Patch.set_property empty "name" (`String "John") with
5555 | Ok patch ->
5656- printf "✓ Set property 'name': %s\n" (Jmap.Patch.to_string_debug patch);
5757- printf "✓ Has property 'name': %b\n" (Jmap.Patch.has_property patch "name")
5656+ printf "✓ Set property 'name': %s\n" (Jmap.Types.Patch.to_string_debug patch);
5757+ printf "✓ Has property 'name': %b\n" (Jmap.Types.Patch.has_property patch "name")
5858 | Error msg ->
5959 printf "✗ Failed to set property: %s\n" msg
6060···6262 printf "\nTesting JSON serialization:\n";
63636464 (* Test Id JSON roundtrip *)
6565- (match Jmap.Id.of_string "test123" with
6565+ (match Jmap.Types.Id.of_string "test123" with
6666 | Ok id ->
6767- let json = Jmap.Id.to_json id in
6868- let parsed = Jmap.Id.of_json json in
6767+ let json = Jmap.Types.Id.to_json id in
6868+ let parsed = Jmap.Types.Id.of_json json in
6969 (match parsed with
7070- | Ok parsed_id when Jmap.Id.equal id parsed_id ->
7070+ | Ok parsed_id when Jmap.Types.Id.equal id parsed_id ->
7171 printf "✓ Id JSON roundtrip successful\n"
7272 | Ok _ -> printf "✗ Id JSON roundtrip failed - values differ\n"
7373 | Error msg -> printf "✗ Id JSON parsing failed: %s\n" msg)
7474 | Error msg -> printf "✗ Failed to create test Id: %s\n" msg);
75757676 (* Test UInt JSON roundtrip *)
7777- (match Jmap.UInt.of_int 100 with
7777+ (match Jmap.Types.UInt.of_int 100 with
7878 | Ok uint ->
7979- let json = Jmap.UInt.to_json uint in
8080- let parsed = Jmap.UInt.of_json json in
7979+ let json = Jmap.Types.UInt.to_json uint in
8080+ let parsed = Jmap.Types.UInt.of_json json in
8181 (match parsed with
8282- | Ok parsed_uint when Jmap.UInt.equal uint parsed_uint ->
8282+ | Ok parsed_uint when Jmap.Types.UInt.equal uint parsed_uint ->
8383 printf "✓ UInt JSON roundtrip successful\n"
8484 | Ok _ -> printf "✗ UInt JSON roundtrip failed - values differ\n"
8585 | Error msg -> printf "✗ UInt JSON parsing failed: %s\n" msg)