···11+open Jmap_dsl
22+33+(** Example demonstrating GADT-based method chaining *)
44+55+let example_chain account_id =
66+ (* Chain multiple methods with type safety *)
77+ let chain =
88+ (((start @> email_query ~account_id ())
99+ @> mailbox_get_all ~account_id ())
1010+ @> identity_get_all ~account_id ())
1111+ |> done_
1212+ in
1313+ chain
1414+1515+let _process_responses account_id (emails, (mailboxes, (identities, ()))) =
1616+ (* All responses are properly typed! *)
1717+ let email_ids = Email_query_response.ids emails in
1818+ let mailbox_list = Mailbox_get_response.mailboxes mailboxes in
1919+ let identity_list = Identity_get_response.identities identities in
2020+2121+ Printf.printf "Account %s:\n" account_id;
2222+ Printf.printf "- Found %d emails\n" (List.length email_ids);
2323+ Printf.printf "- Found %d mailboxes\n" (List.length mailbox_list);
2424+ Printf.printf "- Found %d identities\n" (List.length identity_list)
2525+2626+(** Demonstrate chaining with email retrieval *)
2727+let _email_retrieval_chain account_id =
2828+ (* First query emails, then get the actual email objects *)
2929+ let query_chain =
3030+ (start @> email_query ~account_id ~limit:10 ())
3131+ |> done_
3232+ in
3333+3434+ (* This would typically be followed by another chain using the email IDs *)
3535+ query_chain
3636+3737+let inspect_chain_info chain =
3838+ Printf.printf "Chain contains %d methods:\n" (chain_length chain);
3939+ let methods = method_names chain in
4040+ List.iteri (fun i name ->
4141+ Printf.printf " %d. %s\n" (i + 1) name
4242+ ) methods
4343+4444+(** Simple demonstration of the DSL without requiring network *)
4545+let demo () =
4646+ let account_id = "account123" in
4747+ let chain = example_chain account_id in
4848+4949+ Printf.printf "=== JMAP DSL Demo ===\n";
5050+ inspect_chain_info chain;
5151+5252+ let _request = to_request chain in
5353+ Printf.printf "\nGenerated request with %d methods\n" (chain_length chain);
5454+ Printf.printf "Methods: %s\n" (String.concat ", " (method_names chain));
5555+5656+ Printf.printf "\nType safety verified at compile time! ✓\n";
5757+ Printf.printf "Methods are properly chained with GADTs! ✓\n"
5858+5959+let () = demo ()
+240
jmap/jmap-dsl/examples_comprehensive.ml
···11+open Jmap_dsl
22+33+(** Comprehensive JMAP DSL Examples
44+55+ This file demonstrates various real-world use cases for the JMAP DSL,
66+ based on the patterns found in bin/examples. Each example shows how
77+ to use the DSL for different email management scenarios.
88+*)
99+1010+(** Helper functions for creating common filters *)
1111+module Filters = struct
1212+ let recent_days days =
1313+ let now = Unix.time () in
1414+ let days_ago = now -. (float_of_int days *. 86400.0) in
1515+ let json = `Assoc [
1616+ "after", `String (Printf.sprintf "%.0f" days_ago)
1717+ ] in
1818+ Jmap.Methods.Filter.condition json
1919+2020+ let unread_only () =
2121+ let json = `Assoc [
2222+ "hasKeyword", `String "$seen";
2323+ "operator", `String "NOT"
2424+ ] in
2525+ Jmap.Methods.Filter.condition json
2626+2727+ let flagged_only () =
2828+ let json = `Assoc [
2929+ "hasKeyword", `String "$flagged"
3030+ ] in
3131+ Jmap.Methods.Filter.condition json
3232+3333+ let from_sender sender =
3434+ let json = `Assoc [
3535+ "from", `String sender
3636+ ] in
3737+ Jmap.Methods.Filter.condition json
3838+3939+ let with_attachment () =
4040+ let json = `Assoc [
4141+ "hasAttachment", `Bool true
4242+ ] in
4343+ Jmap.Methods.Filter.condition json
4444+4545+ let combine_and filters =
4646+ let filter_jsons = List.map (fun _f ->
4747+ (* This is a simplification - in real usage, we'd need to extract JSON from Filter.t *)
4848+ `Assoc ["placeholder", `String "filter"]
4949+ ) filters in
5050+ let json = `Assoc [
5151+ "operator", `String "AND";
5252+ "conditions", `List filter_jsons
5353+ ] in
5454+ Jmap.Methods.Filter.condition json
5555+end
5656+5757+(** Example 1: Recent Unread Emails Dashboard *)
5858+let recent_unread_dashboard account_id =
5959+ Printf.printf "=== Recent Unread Dashboard Example ===\n";
6060+6161+ (* Create filter for unread emails from last 7 days *)
6262+ let filter = Filters.combine_and [
6363+ Filters.unread_only ();
6464+ Filters.recent_days 7
6565+ ] in
6666+6767+ let chain =
6868+ (((start @> email_query ~account_id ~filter ~limit:50 ())
6969+ @> mailbox_get_all ~account_id ())
7070+ @> identity_get_all ~account_id ())
7171+ |> done_
7272+ in
7373+7474+ Printf.printf "Chain created with %d methods:\n" (chain_length chain);
7575+ List.iteri (fun i name ->
7676+ Printf.printf " %d. %s\n" (i + 1) name
7777+ ) (method_names chain);
7878+7979+ chain
8080+8181+(** Example 2: VIP and Flagged Emails *)
8282+let vip_flagged_analysis account_id =
8383+ Printf.printf "\n=== VIP and Flagged Analysis Example ===\n";
8484+8585+ (* Create filter for flagged emails *)
8686+ let flagged_filter = Filters.flagged_only () in
8787+8888+ let chain =
8989+ ((start @> email_query ~account_id ~filter:flagged_filter ~limit:25 ())
9090+ @> mailbox_get_all ~account_id ())
9191+ |> done_
9292+ in
9393+9494+ Printf.printf "VIP analysis chain with %d methods\n" (chain_length chain);
9595+ chain
9696+9797+(** Example 3: Email Search by Sender *)
9898+let sender_analysis account_id sender_email =
9999+ Printf.printf "\n=== Sender Analysis Example ===\n";
100100+101101+ let sender_filter = Filters.from_sender sender_email in
102102+103103+ let chain =
104104+ ((start @> email_query ~account_id ~filter:sender_filter ~limit:30 ())
105105+ @> identity_get_all ~account_id ())
106106+ |> done_
107107+ in
108108+109109+ Printf.printf "Analyzing emails from: %s\n" sender_email;
110110+ Printf.printf "Chain methods: %s\n" (String.concat ", " (method_names chain));
111111+ chain
112112+113113+(** Example 4: Attachment Analysis *)
114114+let attachment_analysis account_id =
115115+ Printf.printf "\n=== Attachment Analysis Example ===\n";
116116+117117+ let attachment_filter = Filters.combine_and [
118118+ Filters.with_attachment ();
119119+ Filters.recent_days 30
120120+ ] in
121121+122122+ let chain =
123123+ (start @> email_query ~account_id ~filter:attachment_filter ~limit:20 ())
124124+ |> done_
125125+ in
126126+127127+ Printf.printf "Looking for emails with attachments from last 30 days\n";
128128+ chain
129129+130130+(** Example 5: Comprehensive Dashboard *)
131131+let comprehensive_dashboard account_id =
132132+ Printf.printf "\n=== Comprehensive Dashboard Example ===\n";
133133+134134+ (* Multi-faceted analysis in a single request *)
135135+ let recent_filter = Filters.recent_days 7 in
136136+137137+ let chain =
138138+ (((start @> email_query ~account_id ~filter:recent_filter ~limit:100 ())
139139+ @> mailbox_get_all ~account_id ())
140140+ @> identity_get_all ~account_id ())
141141+ |> done_
142142+ in
143143+144144+ Printf.printf "Comprehensive dashboard with %d data sources\n" (chain_length chain);
145145+ Printf.printf "Data sources: %s\n" (String.concat " + " (method_names chain));
146146+ chain
147147+148148+(** Example 6: Email Volume Analysis *)
149149+let email_volume_analysis account_id =
150150+ Printf.printf "\n=== Email Volume Analysis Example ===\n";
151151+152152+ let volume_chain =
153153+ ((start @> email_query ~account_id ~limit:200 ())
154154+ @> mailbox_get_all ~account_id ())
155155+ |> done_
156156+ in
157157+158158+ Printf.printf "Analyzing email volume patterns\n";
159159+ volume_chain
160160+161161+(** Helper function to simulate processing responses *)
162162+let _process_dashboard_responses account_id responses =
163163+ Printf.printf "\n=== Processing Dashboard Results ===\n";
164164+ match responses with
165165+ | (emails, (mailboxes, (identities, ()))) ->
166166+ let email_ids = Email_query_response.ids emails in
167167+ let mailbox_list = Mailbox_get_response.mailboxes mailboxes in
168168+ let identity_list = Identity_get_response.identities identities in
169169+170170+ Printf.printf "Account: %s\n" account_id;
171171+ Printf.printf "- Found %d emails\n" (List.length email_ids);
172172+ Printf.printf "- Found %d mailboxes\n" (List.length mailbox_list);
173173+ Printf.printf "- Found %d identities\n" (List.length identity_list);
174174+175175+ (* In a real implementation, we would process the actual data *)
176176+ Printf.printf "- Email processing: analyzing subjects, dates, senders\n";
177177+ Printf.printf "- Mailbox analysis: calculating unread counts, sizes\n";
178178+ Printf.printf "- Identity review: checking send configurations\n"
179179+180180+let _process_simple_responses_2 responses =
181181+ Printf.printf "\n=== Processing Simple Results (2 methods) ===\n";
182182+ let (emails, (mailboxes, ())) = responses in
183183+ let email_ids = Email_query_response.ids emails in
184184+ let mailbox_list = Mailbox_get_response.mailboxes mailboxes in
185185+186186+ Printf.printf "- Query returned %d emails\n" (List.length email_ids);
187187+ Printf.printf "- Available mailboxes: %d\n" (List.length mailbox_list)
188188+189189+let _process_simple_responses_1 responses =
190190+ Printf.printf "\n=== Processing Simple Results (1 method) ===\n";
191191+ let (emails, ()) = responses in
192192+ let email_ids = Email_query_response.ids emails in
193193+ Printf.printf "- Simple query returned %d emails\n" (List.length email_ids)
194194+195195+(** Main demonstration function *)
196196+let demo_comprehensive_usage () =
197197+ Printf.printf "JMAP DSL Comprehensive Examples\n";
198198+ Printf.printf "================================\n\n";
199199+200200+ let account_id = "demo_account_123" in
201201+202202+ (* Example 1: Recent unread dashboard *)
203203+ let _dashboard_chain = recent_unread_dashboard account_id in
204204+205205+ (* Example 2: VIP analysis *)
206206+ let _vip_chain = vip_flagged_analysis account_id in
207207+208208+ (* Example 3: Sender analysis *)
209209+ let _sender_chain = sender_analysis account_id "boss@company.com" in
210210+211211+ (* Example 4: Attachment analysis *)
212212+ let _attachment_chain = attachment_analysis account_id in
213213+214214+ (* Example 5: Comprehensive dashboard *)
215215+ let _comprehensive_chain = comprehensive_dashboard account_id in
216216+217217+ (* Example 6: Volume analysis *)
218218+ let _volume_chain = email_volume_analysis account_id in
219219+220220+ Printf.printf "\n=== Type Safety Demonstration ===\n";
221221+ Printf.printf "✓ All chains are type-checked at compile time\n";
222222+ Printf.printf "✓ Response types are automatically inferred\n";
223223+ Printf.printf "✓ Method chaining prevents runtime errors\n";
224224+ Printf.printf "✓ Filter construction is type-safe\n";
225225+226226+ Printf.printf "\n=== Usage Patterns Demonstrated ===\n";
227227+ Printf.printf "• Email querying with complex filters\n";
228228+ Printf.printf "• Multi-method chains for dashboard data\n";
229229+ Printf.printf "• Mailbox and identity management\n";
230230+ Printf.printf "• Sender and attachment analysis\n";
231231+ Printf.printf "• Volume and trend analysis setup\n";
232232+233233+ Printf.printf "\n=== Next Steps ===\n";
234234+ Printf.printf "• Add network execution with real JMAP server\n";
235235+ Printf.printf "• Implement result reference chaining (#ids syntax)\n";
236236+ Printf.printf "• Add more method types (Set, Changes, etc.)\n";
237237+ Printf.printf "• Enhanced error handling and recovery\n"
238238+239239+(** Entry point *)
240240+let () = demo_comprehensive_usage ()
+213
jmap/jmap-dsl/examples_networking.ml
···11+open Jmap_dsl
22+33+(** Helper function to take first n elements from a list *)
44+let rec take n lst =
55+ match n, lst with
66+ | 0, _ | _, [] -> []
77+ | n, x :: xs -> x :: take (n - 1) xs
88+99+(** Real-world JMAP DSL Example with Network Operations
1010+1111+ This example demonstrates using the JMAP DSL with actual network operations,
1212+ including proper authentication, error handling, and response processing.
1313+ It's based on the patterns from bin/examples/query_recent_unread.ml.
1414+*)
1515+1616+(** Helper function to read API key from file *)
1717+let read_api_key () =
1818+ try
1919+ let ic = open_in ".api-key" in
2020+ let key = input_line ic in
2121+ close_in ic;
2222+ String.trim key
2323+ with
2424+ | Sys_error _ ->
2525+ Printf.eprintf "Error: Create a .api-key file with your JMAP bearer token\n";
2626+ Printf.eprintf "Get your API key from:\n";
2727+ Printf.eprintf "• FastMail: Settings > Password & Security > App Passwords\n";
2828+ Printf.eprintf "• Gmail: Google Account > App passwords\n";
2929+ exit 1
3030+ | End_of_file ->
3131+ Printf.eprintf "Error: .api-key file is empty\n";
3232+ exit 1
3333+3434+(** Helper to create recent unread filter *)
3535+let create_recent_unread_filter days =
3636+ let now = Unix.time () in
3737+ let days_ago = now -. (float_of_int days *. 86400.0) in
3838+ let unread_json = `Assoc [
3939+ "operator", `String "AND";
4040+ "conditions", `List [
4141+ `Assoc [
4242+ "hasKeyword", `String "$seen";
4343+ "operator", `String "NOT"
4444+ ];
4545+ `Assoc [
4646+ "after", `String (Printf.sprintf "%.0f" days_ago)
4747+ ]
4848+ ]
4949+ ] in
5050+ Jmap.Methods.Filter.condition unread_json
5151+5252+(** Example 1: Recent Unread Emails with Real Network *)
5353+let recent_unread_with_network env =
5454+ Printf.printf "=== JMAP DSL with Real Network Example ===\n";
5555+5656+ (* Create Eio switch for resource management *)
5757+ Eio.Switch.run @@ fun _sw ->
5858+5959+ (* Read API credentials *)
6060+ let api_key = read_api_key () in
6161+ Printf.printf "Using API key: %s...\n\n"
6262+ (String.sub api_key 0 (min 20 (String.length api_key)));
6363+6464+ (* Create client configuration *)
6565+ let config = Jmap_unix.default_config () in
6666+ let client = Jmap_unix.create_client ~config () in
6767+6868+ match Jmap_unix.connect env client
6969+ ~host:"api.fastmail.com"
7070+ ~use_tls:true
7171+ ~auth_method:(Jmap_unix.Bearer api_key)
7272+ () with
7373+ | Error error ->
7474+ Printf.printf "Connection failed: %s\n" (Jmap.Protocol.Error.error_to_string error);
7575+ exit 1
7676+ | Ok (ctx, session) ->
7777+ Printf.printf "✓ Connected to JMAP server\n";
7878+ Printf.printf "✓ Retrieved session information\n";
7979+8080+ (* Get primary mail account *)
8181+ let account_id =
8282+ match Jmap.Protocol.get_primary_account session Jmap_email.capability_mail with
8383+ | Ok id -> id
8484+ | Error error ->
8585+ Printf.printf "No mail account found: %s\n" (Jmap.Protocol.Error.error_to_string error);
8686+ exit 1
8787+ in
8888+ Printf.printf "✓ Using account: %s\n\n" account_id;
8989+9090+ (* Create DSL chain for recent unread emails *)
9191+ let filter = create_recent_unread_filter 7 in
9292+ let chain =
9393+ (((start @> email_query ~account_id ~filter ~limit:20 ())
9494+ @> mailbox_get_all ~account_id ())
9595+ @> identity_get_all ~account_id ())
9696+ |> done_
9797+ in
9898+9999+ Printf.printf "Executing JMAP DSL chain with %d methods...\n" (chain_length chain);
100100+ List.iteri (fun i name ->
101101+ Printf.printf " %d. %s\n" (i + 1) name
102102+ ) (method_names chain);
103103+104104+ (* Execute the DSL chain *)
105105+ match execute env ctx chain with
106106+ | Error err ->
107107+ Printf.printf "❌ Execution failed: %s\n" err
108108+ | Ok (identities, (mailboxes, (emails, ()))) ->
109109+ Printf.printf "✅ DSL execution successful!\n\n";
110110+111111+ (* Process results *)
112112+ let email_ids = Email_query_response.ids emails in
113113+ let mailbox_list = Mailbox_get_response.mailboxes mailboxes in
114114+ let identity_list = Identity_get_response.identities identities in
115115+116116+ Printf.printf "=== Results Summary ===\n";
117117+ Printf.printf "📧 Recent unread emails: %d\n" (List.length email_ids);
118118+ Printf.printf "📁 Available mailboxes: %d\n" (List.length mailbox_list);
119119+ Printf.printf "👤 Identities configured: %d\n" (List.length identity_list);
120120+121121+ if List.length email_ids > 0 then (
122122+ Printf.printf "\n=== Sample Email IDs (first 3) ===\n";
123123+ let limited_ids = take 3 email_ids in
124124+ List.iteri (fun i email_id ->
125125+ Printf.printf "%d. %s\n" (i + 1) email_id
126126+ ) limited_ids;
127127+ if List.length email_ids > 3 then
128128+ Printf.printf "... and %d more emails\n" (List.length email_ids - 3)
129129+ ) else (
130130+ Printf.printf "\nNo recent unread emails found.\n"
131131+ );
132132+133133+ Printf.printf "\n=== DSL Benefits Demonstrated ===\n";
134134+ Printf.printf "✓ Type-safe method chaining\n";
135135+ Printf.printf "✓ Automatic JSON serialization/deserialization\n";
136136+ Printf.printf "✓ Single network request for multiple operations\n";
137137+ Printf.printf "✓ Compile-time response type verification\n";
138138+ Printf.printf "✓ Structured error handling\n"
139139+140140+(** Example 2: Simple Email Query with DSL *)
141141+let simple_query_example env =
142142+ Printf.printf "\n=== Simple Query Example ===\n";
143143+144144+ Eio.Switch.run @@ fun _sw ->
145145+146146+ let api_key = read_api_key () in
147147+ let config = Jmap_unix.default_config () in
148148+ let client = Jmap_unix.create_client ~config () in
149149+150150+ match Jmap_unix.connect env client
151151+ ~host:"api.fastmail.com"
152152+ ~use_tls:true
153153+ ~auth_method:(Jmap_unix.Bearer api_key)
154154+ () with
155155+ | Error error ->
156156+ Printf.printf "Connection failed: %s\n" (Jmap.Protocol.Error.error_to_string error)
157157+ | Ok (ctx, session) ->
158158+ let account_id =
159159+ match Jmap.Protocol.get_primary_account session Jmap_email.capability_mail with
160160+ | Ok id -> id
161161+ | Error error ->
162162+ Printf.printf "No mail account found: %s\n" (Jmap.Protocol.Error.error_to_string error);
163163+ exit 1
164164+ in
165165+166166+ (* Simple single-method chain *)
167167+ let simple_chain =
168168+ (start @> email_query ~account_id ~limit:5 ()) |> done_
169169+ in
170170+171171+ Printf.printf "Simple chain with %d method: %s\n"
172172+ (chain_length simple_chain)
173173+ (String.concat ", " (method_names simple_chain));
174174+175175+ match execute env ctx simple_chain with
176176+ | Error err ->
177177+ Printf.printf "❌ Simple query failed: %s\n" err
178178+ | Ok (emails, ()) ->
179179+ let email_ids = Email_query_response.ids emails in
180180+ Printf.printf "✅ Found %d recent emails\n" (List.length email_ids)
181181+182182+(** Main function with proper error handling *)
183183+let main env =
184184+ try
185185+ Printf.printf "JMAP DSL Networking Examples\n";
186186+ Printf.printf "============================\n\n";
187187+188188+ Printf.printf "This example requires:\n";
189189+ Printf.printf "• A .api-key file with your JMAP bearer token\n";
190190+ Printf.printf "• Network access to api.fastmail.com\n\n";
191191+192192+ (* Run comprehensive example *)
193193+ recent_unread_with_network env;
194194+195195+ (* Run simple example *)
196196+ simple_query_example env;
197197+198198+ Printf.printf "\n=== Summary ===\n";
199199+ Printf.printf "JMAP DSL successfully demonstrated with real network operations!\n";
200200+ Printf.printf "The type-safe chaining provides both safety and convenience.\n"
201201+202202+ with
203203+ | Sys_error msg ->
204204+ Printf.printf "System error: %s\n" msg;
205205+ exit 1
206206+ | exn ->
207207+ Printf.printf "Unexpected error: %s\n" (Printexc.to_string exn);
208208+ exit 1
209209+210210+(** Entry point with Eio runtime *)
211211+let () =
212212+ Eio_main.run @@ fun env ->
213213+ main env
+177
jmap/jmap-dsl/examples_offline.ml
···11+open Jmap_dsl
22+33+(** Offline JMAP DSL Examples (no network required)
44+55+ These examples demonstrate the DSL's type safety and method chaining
66+ without requiring actual JMAP server connections. Perfect for testing
77+ and demonstrating the compile-time benefits.
88+*)
99+1010+(** Demonstrate various chain combinations *)
1111+let demo_chain_variations () =
1212+ Printf.printf "JMAP DSL Offline Examples\n";
1313+ Printf.printf "=========================\n\n";
1414+1515+ let account_id = "demo_account" in
1616+1717+ (* Example 1: Single method chain *)
1818+ Printf.printf "=== Single Method Chain ===\n";
1919+ let single_chain =
2020+ (start @> email_query ~account_id ~limit:10 ()) |> done_
2121+ in
2222+2323+ Printf.printf "Methods: %s\n" (String.concat ", " (method_names single_chain));
2424+ Printf.printf "Length: %d\n" (chain_length single_chain);
2525+ Printf.printf "Type: (Email_query_response.t * unit)\n\n";
2626+2727+ (* Example 2: Two method chain *)
2828+ Printf.printf "=== Two Method Chain ===\n";
2929+ let double_chain =
3030+ ((start @> email_query ~account_id ())
3131+ @> mailbox_get_all ~account_id ())
3232+ |> done_
3333+ in
3434+3535+ Printf.printf "Methods: %s\n" (String.concat ", " (method_names double_chain));
3636+ Printf.printf "Length: %d\n" (chain_length double_chain);
3737+ Printf.printf "Type: (Mailbox_get_response.t * (Email_query_response.t * unit))\n\n";
3838+3939+ (* Example 3: Triple method chain *)
4040+ Printf.printf "=== Triple Method Chain ===\n";
4141+ let triple_chain =
4242+ (((start @> email_query ~account_id ~limit:50 ())
4343+ @> mailbox_get_all ~account_id ())
4444+ @> identity_get_all ~account_id ())
4545+ |> done_
4646+ in
4747+4848+ Printf.printf "Methods: %s\n" (String.concat ", " (method_names triple_chain));
4949+ Printf.printf "Length: %d\n" (chain_length triple_chain);
5050+ Printf.printf "Type: (Identity_get_response.t * (Mailbox_get_response.t * (Email_query_response.t * unit)))\n\n";
5151+5252+(** Demonstrate different method configurations *)
5353+let demo_method_configurations () =
5454+ Printf.printf "=== Method Configuration Examples ===\n";
5555+5656+ let account_id = "demo_account" in
5757+5858+ (* Demonstrate method creation (without accessing private fields) *)
5959+ let _basic_query = email_query ~account_id () in
6060+ let _limited_query = email_query ~account_id ~limit:25 () in
6161+ let _positioned_query = email_query ~account_id ~position:10 ~limit:25 () in
6262+6363+ Printf.printf "✓ Basic email query method created\n";
6464+ Printf.printf "✓ Limited email query method created (limit configured)\n";
6565+ Printf.printf "✓ Positioned email query method created (position + limit configured)\n";
6666+6767+ (* Email get with different configurations *)
6868+ let _simple_get = email_get ~account_id ~ids:["email1"; "email2"] () in
6969+ let _property_get = email_get ~account_id ~ids:["email1"]
7070+ ~properties:["id"; "subject"; "from"] () in
7171+7272+ Printf.printf "✓ Simple email get method created\n";
7373+ Printf.printf "✓ Property-filtered email get method created\n";
7474+7575+ (* Mailbox operations *)
7676+ let _all_mailboxes = mailbox_get_all ~account_id () in
7777+ let _specific_mailboxes = mailbox_get ~account_id ~ids:["mailbox1"] () in
7878+7979+ Printf.printf "✓ All mailboxes method created\n";
8080+ Printf.printf "✓ Specific mailboxes method created\n";
8181+8282+ (* Identity operations *)
8383+ let _all_identities = identity_get_all ~account_id () in
8484+ let _specific_identities = identity_get ~account_id ~ids:["identity1"] () in
8585+8686+ Printf.printf "✓ All identities method created\n";
8787+ Printf.printf "✓ Specific identities method created\n";
8888+8989+ Printf.printf "\n";
9090+9191+(** Demonstrate filter creation *)
9292+let demo_filter_usage () =
9393+ Printf.printf "=== Filter Usage Examples ===\n";
9494+9595+ let account_id = "demo_account" in
9696+9797+ (* Create some example filters *)
9898+ let unread_filter =
9999+ let json = `Assoc ["hasKeyword", `String "$seen"; "operator", `String "NOT"] in
100100+ Jmap.Methods.Filter.condition json
101101+ in
102102+103103+ let recent_filter =
104104+ let now = Unix.time () in
105105+ let week_ago = now -. (7.0 *. 86400.0) in
106106+ let json = `Assoc ["after", `String (Printf.sprintf "%.0f" week_ago)] in
107107+ Jmap.Methods.Filter.condition json
108108+ in
109109+110110+ let combined_filter =
111111+ let json = `Assoc [
112112+ "operator", `String "AND";
113113+ "conditions", `List [
114114+ `Assoc ["hasKeyword", `String "$seen"; "operator", `String "NOT"];
115115+ `Assoc ["after", `String "1640995200"]
116116+ ]
117117+ ] in
118118+ Jmap.Methods.Filter.condition json
119119+ in
120120+121121+ (* Use filters in queries *)
122122+ let _unread_query = email_query ~account_id ~filter:unread_filter ~limit:20 () in
123123+ let _recent_query = email_query ~account_id ~filter:recent_filter ~limit:30 () in
124124+ let _combined_query = email_query ~account_id ~filter:combined_filter ~limit:10 () in
125125+126126+ Printf.printf "✓ Unread emails query with filter created\n";
127127+ Printf.printf "✓ Recent emails query with filter created\n";
128128+ Printf.printf "✓ Combined filter query created\n";
129129+130130+ Printf.printf "\n";
131131+132132+(** Demonstrate request generation *)
133133+let demo_request_generation () =
134134+ Printf.printf "=== Request Generation Examples ===\n";
135135+136136+ let account_id = "demo_account" in
137137+138138+ (* Create various chains *)
139139+ let simple_chain = (start @> email_query ~account_id ~limit:5 ()) |> done_ in
140140+ let complex_chain =
141141+ (((start @> email_query ~account_id ~limit:20 ())
142142+ @> mailbox_get_all ~account_id ())
143143+ @> identity_get_all ~account_id ())
144144+ |> done_
145145+ in
146146+147147+ (* Generate requests *)
148148+ let simple_request = to_request simple_chain in
149149+ let complex_request = to_request complex_chain in
150150+151151+ Printf.printf "Simple request generated: %d method calls\n" (chain_length simple_chain);
152152+ Printf.printf "Complex request generated: %d method calls\n" (chain_length complex_chain);
153153+154154+ (* Show that requests are properly formed JMAP requests *)
155155+ Printf.printf "Simple request methods: %s\n"
156156+ (String.concat ", " (method_names simple_chain));
157157+ Printf.printf "Complex request methods: %s\n"
158158+ (String.concat ", " (method_names complex_chain));
159159+160160+ Printf.printf "\n";
161161+162162+(** Main demonstration *)
163163+let () =
164164+ demo_chain_variations ();
165165+ demo_method_configurations ();
166166+ demo_filter_usage ();
167167+ demo_request_generation ();
168168+169169+ Printf.printf "=== Type Safety Summary ===\n";
170170+ Printf.printf "✅ All method chains are validated at compile time\n";
171171+ Printf.printf "✅ Response types are automatically inferred\n";
172172+ Printf.printf "✅ Method arguments are type-checked\n";
173173+ Printf.printf "✅ Filter construction is validated\n";
174174+ Printf.printf "✅ Request generation is automatic\n";
175175+ Printf.printf "✅ No runtime type errors possible\n\n";
176176+ Printf.printf "=== Benefits Demonstrated ===\n";
177177+ Printf.printf "DSL provides fluent method chaining with compile-time type safety!\n"
+381
jmap/jmap-dsl/jmap_dsl.ml
···11+(** Type-safe JMAP method chaining DSL implementation *)
22+33+(** {1 Core GADT Types} *)
44+55+(** A method signature with its arguments and expected response type.
66+ This is similar to Ctypes' function signatures. *)
77+type ('args, 'resp) method_sig = {
88+ method_name : string;
99+ args : 'args;
1010+ args_to_json : 'args -> Yojson.Safe.t;
1111+ resp_of_json : Yojson.Safe.t -> 'resp;
1212+ call_id : string;
1313+}
1414+1515+(** A heterogeneous list of method calls that preserves response types.
1616+ This is the core GADT that enables type-safe chaining. *)
1717+type _ method_chain =
1818+ | Empty : unit method_chain
1919+ | Cons : ('args, 'resp) method_sig * 'rest method_chain -> ('resp * 'rest) method_chain
2020+2121+(** Counter for generating unique call IDs *)
2222+let call_id_counter = ref 0
2323+2424+(** Generate a unique call ID *)
2525+let next_call_id () =
2626+ incr call_id_counter;
2727+ "call-" ^ string_of_int !call_id_counter
2828+2929+(** Create a method signature *)
3030+let make_method_sig ~method_name ~args ~args_to_json ~resp_of_json =
3131+ {
3232+ method_name;
3333+ args;
3434+ args_to_json;
3535+ resp_of_json;
3636+ call_id = next_call_id ();
3737+ }
3838+3939+(** {1 Core Combinators} *)
4040+4141+let empty = Empty
4242+4343+let (@>) chain method_sig = Cons (method_sig, chain)
4444+4545+let start = Empty
4646+4747+let done_ chain = chain
4848+4949+(** {1 Response Types and Parsers} *)
5050+5151+(** Email/query response *)
5252+module Email_query_response = struct
5353+ type t = {
5454+ account_id : string;
5555+ query_state : string;
5656+ can_calculate_changes : bool;
5757+ position : int;
5858+ ids : string list;
5959+ total : int option;
6060+ }
6161+6262+ let ids t = t.ids
6363+ let query_state t = t.query_state
6464+ let total t = t.total
6565+ let position t = t.position
6666+ let can_calculate_changes t = t.can_calculate_changes
6767+6868+ let of_json json =
6969+ let open Yojson.Safe.Util in
7070+ {
7171+ account_id = json |> member "accountId" |> to_string;
7272+ query_state = json |> member "queryState" |> to_string;
7373+ can_calculate_changes = json |> member "canCalculateChanges" |> to_bool;
7474+ position = json |> member "position" |> to_int;
7575+ ids = json |> member "ids" |> to_list |> List.map to_string;
7676+ total = json |> member "total" |> to_int_option;
7777+ }
7878+end
7979+8080+(** Email/get response *)
8181+module Email_get_response = struct
8282+ type t = {
8383+ account_id : string;
8484+ state : string;
8585+ list : Yojson.Safe.t list;
8686+ not_found : string list;
8787+ }
8888+8989+ let emails t = t.list
9090+ let state t = t.state
9191+ let not_found t = t.not_found
9292+ let account_id t = t.account_id
9393+9494+ let of_json json =
9595+ let open Yojson.Safe.Util in
9696+ {
9797+ account_id = json |> member "accountId" |> to_string;
9898+ state = json |> member "state" |> to_string;
9999+ list = json |> member "list" |> to_list;
100100+ not_found = json |> member "notFound" |> to_list |> List.map to_string;
101101+ }
102102+end
103103+104104+(** Mailbox/get response *)
105105+module Mailbox_get_response = struct
106106+ type t = {
107107+ account_id : string;
108108+ state : string;
109109+ list : Yojson.Safe.t list;
110110+ not_found : string list;
111111+ }
112112+113113+ let mailboxes t = t.list
114114+ let state t = t.state
115115+ let not_found t = t.not_found
116116+ let account_id t = t.account_id
117117+118118+ let of_json json =
119119+ let open Yojson.Safe.Util in
120120+ {
121121+ account_id = json |> member "accountId" |> to_string;
122122+ state = json |> member "state" |> to_string;
123123+ list = json |> member "list" |> to_list;
124124+ not_found = json |> member "notFound" |> to_list |> List.map to_string;
125125+ }
126126+end
127127+128128+(** Identity/get response *)
129129+module Identity_get_response = struct
130130+ type t = {
131131+ account_id : string;
132132+ state : string;
133133+ list : Yojson.Safe.t list;
134134+ not_found : string list;
135135+ }
136136+137137+ let identities t = t.list
138138+ let state t = t.state
139139+ let not_found t = t.not_found
140140+ let account_id t = t.account_id
141141+142142+ let of_json json =
143143+ let open Yojson.Safe.Util in
144144+ {
145145+ account_id = json |> member "accountId" |> to_string;
146146+ state = json |> member "state" |> to_string;
147147+ list = json |> member "list" |> to_list;
148148+ not_found = json |> member "notFound" |> to_list |> List.map to_string;
149149+ }
150150+end
151151+152152+(** {1 Argument Types} *)
153153+154154+module Email_query_args = struct
155155+ type t = {
156156+ account_id : string;
157157+ filter : Jmap.Methods.Filter.t option;
158158+ sort : Jmap.Methods.Comparator.t list option;
159159+ position : int;
160160+ limit : int option;
161161+ }
162162+163163+ let create ~account_id ?filter ?sort ?(position=0) ?limit () =
164164+ { account_id; filter; sort; position; limit }
165165+166166+ let to_json t =
167167+ let fields = [
168168+ ("accountId", `String t.account_id);
169169+ ("position", `Int t.position);
170170+ ] in
171171+ let fields = match t.filter with
172172+ | Some f -> ("filter", Jmap.Methods.Filter.to_json f) :: fields
173173+ | None -> fields
174174+ in
175175+ let fields = match t.sort with
176176+ | Some sorts ->
177177+ let sort_json = `List (List.map (fun c ->
178178+ `Assoc [
179179+ ("property", `String (Jmap.Methods.Comparator.property c));
180180+ ("isAscending", match Jmap.Methods.Comparator.is_ascending c with
181181+ | Some b -> `Bool b
182182+ | None -> `Bool false);
183183+ ]
184184+ ) sorts) in
185185+ ("sort", sort_json) :: fields
186186+ | None -> fields
187187+ in
188188+ let fields = match t.limit with
189189+ | Some l -> ("limit", `Int l) :: fields
190190+ | None -> fields
191191+ in
192192+ `Assoc fields
193193+end
194194+195195+module Email_get_args = struct
196196+ type t = {
197197+ account_id : string;
198198+ ids : string list;
199199+ properties : string list option;
200200+ }
201201+202202+ let create ~account_id ~ids ?properties () =
203203+ { account_id; ids; properties }
204204+205205+ let to_json t =
206206+ let fields = [
207207+ ("accountId", `String t.account_id);
208208+ ("ids", `List (List.map (fun id -> `String id) t.ids));
209209+ ] in
210210+ let fields = match t.properties with
211211+ | Some props -> ("properties", `List (List.map (fun p -> `String p) props)) :: fields
212212+ | None -> fields
213213+ in
214214+ `Assoc fields
215215+end
216216+217217+module Mailbox_get_args = struct
218218+ type t = {
219219+ account_id : string;
220220+ ids : string list option;
221221+ properties : string list option;
222222+ }
223223+224224+ let create ~account_id ?ids ?properties () =
225225+ { account_id; ids; properties }
226226+227227+ let to_json t =
228228+ let fields = [
229229+ ("accountId", `String t.account_id);
230230+ ] in
231231+ let fields = match t.ids with
232232+ | Some ids -> ("ids", `List (List.map (fun id -> `String id) ids)) :: fields
233233+ | None -> ("ids", `Null) :: fields
234234+ in
235235+ let fields = match t.properties with
236236+ | Some props -> ("properties", `List (List.map (fun p -> `String p) props)) :: fields
237237+ | None -> fields
238238+ in
239239+ `Assoc fields
240240+end
241241+242242+module Identity_get_args = struct
243243+ type t = {
244244+ account_id : string;
245245+ ids : string list option;
246246+ properties : string list option;
247247+ }
248248+249249+ let create ~account_id ?ids ?properties () =
250250+ { account_id; ids; properties }
251251+252252+ let to_json t =
253253+ let fields = [
254254+ ("accountId", `String t.account_id);
255255+ ] in
256256+ let fields = match t.ids with
257257+ | Some ids -> ("ids", `List (List.map (fun id -> `String id) ids)) :: fields
258258+ | None -> ("ids", `Null) :: fields
259259+ in
260260+ let fields = match t.properties with
261261+ | Some props -> ("properties", `List (List.map (fun p -> `String p) props)) :: fields
262262+ | None -> fields
263263+ in
264264+ `Assoc fields
265265+end
266266+267267+(** {1 Method Constructors} *)
268268+269269+let core_echo ?data () =
270270+ let data = match data with
271271+ | Some d -> d
272272+ | None -> `Assoc []
273273+ in
274274+ make_method_sig
275275+ ~method_name:"Core/echo"
276276+ ~args:data
277277+ ~args_to_json:(fun x -> x)
278278+ ~resp_of_json:(fun x -> x)
279279+280280+let email_query ~account_id ?filter ?sort ?position ?limit () =
281281+ let args = Email_query_args.create ~account_id ?filter ?sort ?position ?limit () in
282282+ make_method_sig
283283+ ~method_name:"Email/query"
284284+ ~args
285285+ ~args_to_json:Email_query_args.to_json
286286+ ~resp_of_json:Email_query_response.of_json
287287+288288+let email_get ~account_id ~ids ?properties () =
289289+ let args = Email_get_args.create ~account_id ~ids ?properties () in
290290+ make_method_sig
291291+ ~method_name:"Email/get"
292292+ ~args
293293+ ~args_to_json:Email_get_args.to_json
294294+ ~resp_of_json:Email_get_response.of_json
295295+296296+let mailbox_get ~account_id ?ids ?properties () =
297297+ let args = Mailbox_get_args.create ~account_id ?ids ?properties () in
298298+ make_method_sig
299299+ ~method_name:"Mailbox/get"
300300+ ~args
301301+ ~args_to_json:Mailbox_get_args.to_json
302302+ ~resp_of_json:Mailbox_get_response.of_json
303303+304304+let mailbox_get_all ~account_id () =
305305+ mailbox_get ~account_id ?ids:None ()
306306+307307+let identity_get ~account_id ?ids ?properties () =
308308+ let args = Identity_get_args.create ~account_id ?ids ?properties () in
309309+ make_method_sig
310310+ ~method_name:"Identity/get"
311311+ ~args
312312+ ~args_to_json:Identity_get_args.to_json
313313+ ~resp_of_json:Identity_get_response.of_json
314314+315315+let identity_get_all ~account_id () =
316316+ identity_get ~account_id ?ids:None ()
317317+318318+(** {1 Chain Processing} *)
319319+320320+(** Convert a method chain to a list of invocations *)
321321+let rec chain_to_invocations : type a. a method_chain -> Jmap.Protocol.Wire.Invocation.t list = function
322322+ | Empty -> []
323323+ | Cons (method_sig, rest) ->
324324+ let invocation = Jmap.Protocol.Wire.Invocation.v
325325+ ~method_name:method_sig.method_name
326326+ ~arguments:(method_sig.args_to_json method_sig.args)
327327+ ~method_call_id:method_sig.call_id
328328+ ()
329329+ in
330330+ invocation :: chain_to_invocations rest
331331+332332+333333+(** Parse responses in the correct order matching the chain structure *)
334334+let rec parse_responses : type a.
335335+ a method_chain ->
336336+ Jmap.Protocol.Wire.Response.t ->
337337+ (a, string) result =
338338+ fun chain response ->
339339+ match chain with
340340+ | Empty -> Ok ()
341341+ | Cons (method_sig, rest) ->
342342+ (* Extract the method response for this call *)
343343+ match Jmap_unix.Response.extract_method
344344+ ~method_name:method_sig.method_name
345345+ ~method_call_id:method_sig.call_id response with
346346+ | Ok response_args ->
347347+ (try
348348+ let parsed_response = method_sig.resp_of_json response_args in
349349+ match parse_responses rest response with
350350+ | Ok rest_responses -> Ok (parsed_response, rest_responses)
351351+ | Error e -> Error e
352352+ with
353353+ | exn -> Error ("Failed to parse " ^ method_sig.method_name ^ ": " ^ Printexc.to_string exn))
354354+ | Error jmap_error ->
355355+ (* Convert JMAP error to string for now *)
356356+ Error ("JMAP error in " ^ method_sig.method_name ^ ": " ^
357357+ Jmap.Protocol.Error.error_to_string jmap_error)
358358+359359+let to_request chain =
360360+ let invocations = chain_to_invocations chain in
361361+ Jmap.Protocol.Wire.Request.v
362362+ ~using:["urn:ietf:params:jmap:core"; "urn:ietf:params:jmap:mail"]
363363+ ~method_calls:invocations
364364+ ()
365365+366366+let execute env ctx chain =
367367+ let request = to_request chain in
368368+ match Jmap_unix.request env ctx request with
369369+ | Ok response -> parse_responses chain response
370370+ | Error jmap_error -> Error (Jmap.Protocol.Error.error_to_string jmap_error)
371371+372372+(** {1 Utility Functions} *)
373373+374374+let rec chain_length : type a. a method_chain -> int = function
375375+ | Empty -> 0
376376+ | Cons (_, rest) -> 1 + chain_length rest
377377+378378+let rec method_names : type a. a method_chain -> string list = function
379379+ | Empty -> []
380380+ | Cons (method_sig, rest) ->
381381+ method_sig.method_name :: method_names rest
+325
jmap/jmap-dsl/jmap_dsl.mli
···11+(** Type-safe JMAP method chaining DSL.
22+33+ This library provides a type-safe way to chain JMAP method calls
44+ with automatic response deserialization. Inspired by Ctypes, it uses
55+ GADTs to track method signatures and response types at compile time.
66+77+ The design separates method definition from execution, allowing for
88+ flexible composition while maintaining type safety.
99+1010+ Example usage:
1111+ {[
1212+ let request =
1313+ email_query ~account_id ~filter () @>
1414+ mailbox_get_all ~account_id () @>
1515+ done_
1616+1717+ match execute env ctx request with
1818+ | Ok (query_resp, mailbox_list) ->
1919+ (* Both responses are properly typed *)
2020+ let emails = Email_query_response.ids query_resp in
2121+ let mailboxes = Mailbox_list.items mailbox_list in
2222+ ...
2323+ ]}
2424+2525+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-3> RFC 8620, Section 3 *)
2626+2727+(** {1 Core Types} *)
2828+2929+(** A method signature that describes a JMAP method call.
3030+3131+ ['args] is the argument type for the method
3232+ ['resp] is the expected response type from the server *)
3333+type ('args, 'resp) method_sig
3434+3535+(** A method chain that represents a sequence of JMAP method calls.
3636+3737+ ['responses] is a type that describes the shape of all responses.
3838+ For a single method, this is just the response type.
3939+ For multiple methods, this becomes a nested tuple of response types. *)
4040+type 'responses method_chain
4141+4242+(** The empty method chain - starting point for building requests *)
4343+val empty : unit method_chain
4444+4545+(** Chain a method onto an existing chain.
4646+4747+ This is the core combinator - it extends a chain with one more method
4848+ and updates the response type to include the new method's response.
4949+5050+ @param chain The existing method chain
5151+ @param method_call A method signature with its arguments
5252+ @return Extended chain with updated response type *)
5353+val (@>) : 'a method_chain -> ('args, 'resp) method_sig -> ('resp * 'a) method_chain
5454+5555+(** Alias for empty chain to improve readability *)
5656+val start : unit method_chain
5757+5858+(** End marker for chains (optional, for readability) *)
5959+val done_ : 'a method_chain -> 'a method_chain
6060+6161+(** {1 Method Execution} *)
6262+6363+(** Execute a method chain and return typed responses.
6464+6565+ This converts the method chain into a JMAP request, sends it,
6666+ and automatically deserializes the responses to the correct types.
6767+6868+ @param env The Eio environment for network operations
6969+ @param ctx The JMAP connection context
7070+ @param chain The method chain to execute
7171+ @return Typed responses matching the chain structure *)
7272+val execute :
7373+ < net : 'a Eio.Net.t ; .. > ->
7474+ Jmap_unix.context ->
7575+ 'responses method_chain ->
7676+ ('responses, string) result
7777+7878+(** {1 Core JMAP Methods} *)
7979+8080+(** Core/echo method for testing connectivity.
8181+8282+ @param data Optional data to echo back (defaults to empty object)
8383+ @return Method signature for Core/echo *)
8484+val core_echo :
8585+ ?data:Yojson.Safe.t ->
8686+ unit ->
8787+ (Yojson.Safe.t, Yojson.Safe.t) method_sig
8888+8989+(** {1 Email Methods} *)
9090+9191+(** Arguments for Email/query method *)
9292+module Email_query_args : sig
9393+ type t
9494+9595+ (** Create Email/query arguments.
9696+ @param account_id The account to query
9797+ @param filter Optional filter conditions
9898+ @param sort Optional sort criteria
9999+ @param position Starting position (default 0)
100100+ @param limit Maximum results (default server limit)
101101+ @return Query arguments *)
102102+ val create :
103103+ account_id:string ->
104104+ ?filter:Jmap.Methods.Filter.t ->
105105+ ?sort:Jmap.Methods.Comparator.t list ->
106106+ ?position:int ->
107107+ ?limit:int ->
108108+ unit ->
109109+ t
110110+end
111111+112112+(** Response from Email/query method *)
113113+module Email_query_response : sig
114114+ type t
115115+116116+ (** Get the email IDs from query response *)
117117+ val ids : t -> string list
118118+119119+ (** Get query state for synchronization *)
120120+ val query_state : t -> string
121121+122122+ (** Get total count if requested *)
123123+ val total : t -> int option
124124+125125+ (** Get current position in results *)
126126+ val position : t -> int
127127+128128+ (** Check if changes can be calculated *)
129129+ val can_calculate_changes : t -> bool
130130+end
131131+132132+(** Email/query method.
133133+ @param account_id The account to query
134134+ @param filter Optional filter conditions
135135+ @param sort Optional sort criteria
136136+ @param position Starting position (default 0)
137137+ @param limit Maximum results (default server limit)
138138+ @return Method signature for Email/query *)
139139+val email_query :
140140+ account_id:string ->
141141+ ?filter:Jmap.Methods.Filter.t ->
142142+ ?sort:Jmap.Methods.Comparator.t list ->
143143+ ?position:int ->
144144+ ?limit:int ->
145145+ unit ->
146146+ (Email_query_args.t, Email_query_response.t) method_sig
147147+148148+(** Arguments for Email/get method *)
149149+module Email_get_args : sig
150150+ type t
151151+152152+ (** Create Email/get arguments.
153153+ @param account_id The account to get from
154154+ @param ids List of email IDs to fetch
155155+ @param properties Optional properties to fetch (default all)
156156+ @return Get arguments *)
157157+ val create :
158158+ account_id:string ->
159159+ ids:string list ->
160160+ ?properties:string list ->
161161+ unit ->
162162+ t
163163+end
164164+165165+(** Response from Email/get method *)
166166+module Email_get_response : sig
167167+ type t
168168+169169+ (** Get the list of email objects *)
170170+ val emails : t -> Yojson.Safe.t list
171171+172172+ (** Get the current state token *)
173173+ val state : t -> string
174174+175175+ (** Get list of IDs that were not found *)
176176+ val not_found : t -> string list
177177+178178+ (** Get the account ID this response is for *)
179179+ val account_id : t -> string
180180+end
181181+182182+(** Email/get method.
183183+ @param account_id The account to get from
184184+ @param ids List of email IDs to fetch
185185+ @param properties Optional properties to fetch (default all)
186186+ @return Method signature for Email/get *)
187187+val email_get :
188188+ account_id:string ->
189189+ ids:string list ->
190190+ ?properties:string list ->
191191+ unit ->
192192+ (Email_get_args.t, Email_get_response.t) method_sig
193193+194194+(** {1 Mailbox Methods} *)
195195+196196+(** Arguments for Mailbox/get method *)
197197+module Mailbox_get_args : sig
198198+ type t
199199+200200+ (** Create Mailbox/get arguments.
201201+ @param account_id The account to get from
202202+ @param ids Optional list of mailbox IDs (default gets all)
203203+ @param properties Optional properties to fetch (default all)
204204+ @return Get arguments *)
205205+ val create :
206206+ account_id:string ->
207207+ ?ids:string list ->
208208+ ?properties:string list ->
209209+ unit ->
210210+ t
211211+end
212212+213213+(** Response from Mailbox/get method *)
214214+module Mailbox_get_response : sig
215215+ type t
216216+217217+ (** Get the list of mailbox objects *)
218218+ val mailboxes : t -> Yojson.Safe.t list
219219+220220+ (** Get the current state token *)
221221+ val state : t -> string
222222+223223+ (** Get list of IDs that were not found *)
224224+ val not_found : t -> string list
225225+226226+ (** Get the account ID this response is for *)
227227+ val account_id : t -> string
228228+end
229229+230230+(** Mailbox/get method.
231231+ @param account_id The account to get from
232232+ @param ids Optional list of mailbox IDs (default gets all)
233233+ @param properties Optional properties to fetch (default all)
234234+ @return Method signature for Mailbox/get *)
235235+val mailbox_get :
236236+ account_id:string ->
237237+ ?ids:string list ->
238238+ ?properties:string list ->
239239+ unit ->
240240+ (Mailbox_get_args.t, Mailbox_get_response.t) method_sig
241241+242242+(** Convenience method to get all mailboxes for an account.
243243+ @param account_id The account to get mailboxes for
244244+ @return Method signature for Mailbox/get with no ID filter *)
245245+val mailbox_get_all :
246246+ account_id:string ->
247247+ unit ->
248248+ (Mailbox_get_args.t, Mailbox_get_response.t) method_sig
249249+250250+(** {1 Identity Methods} *)
251251+252252+(** Arguments for Identity/get method *)
253253+module Identity_get_args : sig
254254+ type t
255255+256256+ (** Create Identity/get arguments.
257257+ @param account_id The account to get from
258258+ @param ids Optional list of identity IDs (default gets all)
259259+ @param properties Optional properties to fetch (default all)
260260+ @return Get arguments *)
261261+ val create :
262262+ account_id:string ->
263263+ ?ids:string list ->
264264+ ?properties:string list ->
265265+ unit ->
266266+ t
267267+end
268268+269269+(** Response from Identity/get method *)
270270+module Identity_get_response : sig
271271+ type t
272272+273273+ (** Get the list of identity objects *)
274274+ val identities : t -> Yojson.Safe.t list
275275+276276+ (** Get the current state token *)
277277+ val state : t -> string
278278+279279+ (** Get list of IDs that were not found *)
280280+ val not_found : t -> string list
281281+282282+ (** Get the account ID this response is for *)
283283+ val account_id : t -> string
284284+end
285285+286286+(** Identity/get method.
287287+ @param account_id The account to get from
288288+ @param ids Optional list of identity IDs (default gets all)
289289+ @param properties Optional properties to fetch (default all)
290290+ @return Method signature for Identity/get *)
291291+val identity_get :
292292+ account_id:string ->
293293+ ?ids:string list ->
294294+ ?properties:string list ->
295295+ unit ->
296296+ (Identity_get_args.t, Identity_get_response.t) method_sig
297297+298298+(** Convenience method to get all identities for an account.
299299+ @param account_id The account to get identities for
300300+ @return Method signature for Identity/get with no ID filter *)
301301+val identity_get_all :
302302+ account_id:string ->
303303+ unit ->
304304+ (Identity_get_args.t, Identity_get_response.t) method_sig
305305+306306+(** {1 Utility Functions} *)
307307+308308+(** Convert a method chain to a raw JMAP request.
309309+310310+ This is useful for debugging or for integrating with existing
311311+ code that expects raw requests.
312312+313313+ @param chain The method chain to convert
314314+ @return Raw JMAP request object *)
315315+val to_request : 'responses method_chain -> Jmap.Protocol.Wire.Request.t
316316+317317+(** Get the number of methods in a chain.
318318+ @param chain The method chain
319319+ @return Number of method calls *)
320320+val chain_length : 'responses method_chain -> int
321321+322322+(** Extract method names from a chain for debugging.
323323+ @param chain The method chain
324324+ @return List of method names in order *)
325325+val method_names : 'responses method_chain -> string list
+37
jmap/jmap-sigs.opam
···11+# This file is generated by dune, edit dune-project instead
22+opam-version: "2.0"
33+synopsis: "Module type signatures for JMAP implementations"
44+description: """
55+This package provides standard module type signatures for JMAP (RFC 8620/8621)
66+implementations in OCaml. It defines consistent interfaces for JSON serialization,
77+pretty-printing with Fmt, RFC compliance tracking, and JMAP-specific patterns
88+like method arguments/responses and patchable objects.
99+1010+These signatures ensure consistency across JMAP libraries and make it easy to
1111+identify missing implementations."""
1212+maintainer: ["JMAP OCaml Maintainers"]
1313+authors: ["JMAP OCaml Contributors"]
1414+license: "ISC"
1515+homepage: "https://github.com/example/ocaml-jmap"
1616+bug-reports: "https://github.com/example/ocaml-jmap/issues"
1717+depends: [
1818+ "dune" {>= "2.9"}
1919+ "ocaml" {>= "4.08.0"}
2020+ "yojson" {>= "1.7.0"}
2121+ "fmt" {>= "0.8.0"}
2222+ "odoc" {with-doc}
2323+]
2424+build: [
2525+ ["dune" "subst"] {dev}
2626+ [
2727+ "dune"
2828+ "build"
2929+ "-p"
3030+ name
3131+ "-j"
3232+ jobs
3333+ "@install"
3434+ "@runtest" {with-test}
3535+ "@doc" {with-doc}
3636+ ]
3737+]
···11+(** JMAP Module Type Signatures implementation *)
22+33+(** Core Signatures *)
44+55+module type JSONABLE = sig
66+ type t
77+ val to_json : t -> Yojson.Safe.t
88+ val of_json : Yojson.Safe.t -> t
99+end
1010+1111+module type PRINTABLE = sig
1212+ type t
1313+ val pp : Format.formatter -> t -> unit
1414+ val pp_hum : Format.formatter -> t -> unit
1515+end
1616+1717+(** Wire Protocol Signatures *)
1818+1919+module type WIRE_TYPE = sig
2020+ type t
2121+ include JSONABLE with type t := t
2222+ include PRINTABLE with type t := t
2323+ val validate : t -> (unit, string) result
2424+end
2525+2626+(** JMAP Object Signatures *)
2727+2828+module type JMAP_OBJECT = sig
2929+ type t
3030+ type id_type = string
3131+ include JSONABLE with type t := t
3232+ include PRINTABLE with type t := t
3333+ val id : t -> id_type option
3434+ val create : ?id:id_type -> unit -> t
3535+ val to_json_with_properties : properties:string list -> t -> Yojson.Safe.t
3636+ val valid_properties : unit -> string list
3737+end
3838+3939+(** Method Signatures *)
4040+4141+module type METHOD_ARGS = sig
4242+ type t
4343+ type account_id = string
4444+ include JSONABLE with type t := t
4545+ include PRINTABLE with type t := t
4646+ val account_id : t -> account_id
4747+ val validate : t -> (unit, string) result
4848+ val method_name : unit -> string
4949+end
5050+5151+module type METHOD_RESPONSE = sig
5252+ type t
5353+ type account_id = string
5454+ type state = string
5555+ include JSONABLE with type t := t
5656+ include PRINTABLE with type t := t
5757+ val account_id : t -> account_id
5858+ val state : t -> state option
5959+ val is_error : t -> bool
6060+end
6161+6262+(** Collection Signatures *)
6363+6464+module type COLLECTION = sig
6565+ type t
6666+ type item
6767+ include JSONABLE with type t := t
6868+ include PRINTABLE with type t := t
6969+ val items : t -> item list
7070+ val total : t -> int option
7171+ val create : items:item list -> ?total:int -> unit -> t
7272+ val map : (item -> item) -> t -> t
7373+ val filter : (item -> bool) -> t -> t
7474+end
7575+7676+(** Error Handling Signatures *)
7777+7878+module type ERROR_TYPE = sig
7979+ type t
8080+ include JSONABLE with type t := t
8181+ include PRINTABLE with type t := t
8282+ val error_type : t -> string
8383+ val description : t -> string option
8484+ val create : error_type:string -> ?description:string -> unit -> t
8585+end
8686+8787+(** RFC Compliance Signatures *)
8888+8989+module type RFC_COMPLIANT = sig
9090+ type t
9191+ val rfc_section : unit -> string
9292+ val rfc_url : unit -> string
9393+ val implementation_notes : unit -> string list
9494+ val is_complete : unit -> bool
9595+ val unimplemented_features : unit -> string list
9696+end
9797+9898+(** Vendor Extension Signatures *)
9999+100100+module type VENDOR_EXTENSION = sig
101101+ type t
102102+ include JSONABLE with type t := t
103103+ include PRINTABLE with type t := t
104104+ val vendor : unit -> string
105105+ val extension_name : unit -> string
106106+ val capability_uri : unit -> string option
107107+ val is_experimental : unit -> bool
108108+end
109109+110110+(** Patch Operations Signatures *)
111111+112112+module type PATCHABLE = sig
113113+ type t
114114+ type patch
115115+ include JSONABLE with type t := t
116116+ val create_patch : from:t -> to_:t -> patch
117117+ val apply_patch : patch:patch -> t -> (t, string) result
118118+ val patch_to_operations : patch -> (string * Yojson.Safe.t) list
119119+end
120120+121121+(** Composite Signatures *)
122122+123123+module type FULL_JMAP_OBJECT = sig
124124+ include JMAP_OBJECT
125125+ include PATCHABLE with type t := t
126126+ include RFC_COMPLIANT with type t := t
127127+end
128128+129129+module type JMAP_METHOD = sig
130130+ val name : unit -> string
131131+ module Args : METHOD_ARGS
132132+ module Response : METHOD_RESPONSE
133133+ val execute : Args.t -> (Response.t, string) result
134134+end
+342
jmap/jmap-sigs/jmap_sigs.mli
···11+(** JMAP Module Type Signatures.
22+33+ This module defines the standard module type signatures used throughout
44+ the JMAP implementation. These signatures ensure consistency across all
55+ JMAP types and provide a discoverable interface for developers.
66+77+ All wire protocol types, data objects, and method arguments/responses
88+ should conform to these signatures as appropriate.
99+1010+ @see <https://www.rfc-editor.org/rfc/rfc8620.html> RFC 8620 - The JSON Meta Application Protocol (JMAP)
1111+ @see <https://www.rfc-editor.org/rfc/rfc8621.html> RFC 8621 - JMAP Mail *)
1212+1313+(** {1 Core Signatures} *)
1414+1515+(** Signature for types that can be serialized to/from JSON.
1616+1717+ This is the fundamental signature for any type that needs to be
1818+ transmitted over the JMAP wire protocol or stored as JSON. *)
1919+module type JSONABLE = sig
2020+ type t
2121+2222+ (** Convert to JSON representation.
2323+ @return JSON representation suitable for wire transmission *)
2424+ val to_json : t -> Yojson.Safe.t
2525+2626+ (** Parse from JSON representation.
2727+ @param json The JSON value to parse
2828+ @return The parsed value
2929+ @raise Failure if JSON structure is invalid or required fields are missing *)
3030+ val of_json : Yojson.Safe.t -> t
3131+end
3232+3333+(** Signature for types that can be pretty-printed using Fmt.
3434+3535+ This provides composable formatting for debugging, logging, and
3636+ human-readable output. Using Fmt allows for better integration
3737+ with logging libraries and testing frameworks. *)
3838+module type PRINTABLE = sig
3939+ type t
4040+4141+ (** Pretty-printer for the type.
4242+ @param ppf The formatter to write to
4343+ @param t The value to print *)
4444+ val pp : Format.formatter -> t -> unit
4545+4646+ (** Alternative name for pp, following Fmt conventions *)
4747+ val pp_hum : Format.formatter -> t -> unit
4848+end
4949+5050+(** {1 Wire Protocol Signatures} *)
5151+5252+(** Signature for JMAP wire protocol types.
5353+5454+ Types that travel over HTTP as part of the JMAP protocol should
5555+ implement this signature. This includes requests, responses, and
5656+ all their component parts. *)
5757+module type WIRE_TYPE = sig
5858+ type t
5959+6060+ include JSONABLE with type t := t
6161+ include PRINTABLE with type t := t
6262+6363+ (** Validate the structure according to JMAP constraints.
6464+ @return Ok () if valid, Error with description if invalid *)
6565+ val validate : t -> (unit, string) result
6666+end
6767+6868+(** {1 JMAP Object Signatures} *)
6969+7070+(** Signature for JMAP data objects.
7171+7272+ This signature is for the core JMAP data types like Email, Mailbox,
7373+ Thread, Identity, etc. These objects have IDs and support property
7474+ selection for efficient data transfer. *)
7575+module type JMAP_OBJECT = sig
7676+ type t
7777+ type id_type = string (* Jmap_types.id *)
7878+7979+ include JSONABLE with type t := t
8080+ include PRINTABLE with type t := t
8181+8282+ (** Get the object's identifier.
8383+ @return The object ID if present (may be None for unsaved objects) *)
8484+ val id : t -> id_type option
8585+8686+ (** Create a minimal valid object.
8787+ @param id Optional identifier for the object
8888+ @return A new object with default/empty values for optional fields *)
8989+ val create : ?id:id_type -> unit -> t
9090+9191+ (** Serialize to JSON with only specified properties.
9292+9393+ This is used to implement the JMAP properties selection mechanism,
9494+ allowing clients to request only the fields they need.
9595+9696+ @param properties List of property names to include
9797+ @param t The object to serialize
9898+ @return JSON with only the requested properties *)
9999+ val to_json_with_properties : properties:string list -> t -> Yojson.Safe.t
100100+101101+ (** Get the list of all valid property names for this object type.
102102+ @return List of property names that can be requested *)
103103+ val valid_properties : unit -> string list
104104+end
105105+106106+(** {1 Method Signatures} *)
107107+108108+(** Signature for JMAP method argument types.
109109+110110+ All JMAP method calls take an arguments object. This signature
111111+ ensures consistency across all method argument types. *)
112112+module type METHOD_ARGS = sig
113113+ type t
114114+ type account_id = string (* Jmap_types.id *)
115115+116116+ include JSONABLE with type t := t
117117+ include PRINTABLE with type t := t
118118+119119+ (** Get the account ID these arguments apply to.
120120+ @return The account ID for this method call *)
121121+ val account_id : t -> account_id
122122+123123+ (** Validate arguments according to JMAP method constraints.
124124+ @return Ok () if valid, Error with description if invalid *)
125125+ val validate : t -> (unit, string) result
126126+127127+ (** Get the method name these arguments are for.
128128+ @return The JMAP method name (e.g., "Email/get") *)
129129+ val method_name : unit -> string
130130+end
131131+132132+(** Signature for JMAP method response types.
133133+134134+ All JMAP method responses follow a similar pattern with account IDs
135135+ and state tokens for synchronization. *)
136136+module type METHOD_RESPONSE = sig
137137+ type t
138138+ type account_id = string (* Jmap_types.id *)
139139+ type state = string
140140+141141+ include JSONABLE with type t := t
142142+ include PRINTABLE with type t := t
143143+144144+ (** Get the account ID this response applies to.
145145+ @return The account ID for this response *)
146146+ val account_id : t -> account_id
147147+148148+ (** Get the state token for synchronization.
149149+ @return The state token if present (used for changes/updates) *)
150150+ val state : t -> state option
151151+152152+ (** Check if this response indicates an error condition.
153153+ @return true if this is an error response *)
154154+ val is_error : t -> bool
155155+end
156156+157157+(** {1 Collection Signatures} *)
158158+159159+(** Signature for types that represent collections of JMAP objects.
160160+161161+ This is used for query results, batch operations, and any other
162162+ operation that deals with multiple objects. *)
163163+module type COLLECTION = sig
164164+ type t
165165+ type item
166166+167167+ include JSONABLE with type t := t
168168+ include PRINTABLE with type t := t
169169+170170+ (** Get the items in the collection.
171171+ @return List of items in the collection *)
172172+ val items : t -> item list
173173+174174+ (** Get the total count of items (may be different from length of items).
175175+ @return Total count if known *)
176176+ val total : t -> int option
177177+178178+ (** Create a collection from a list of items.
179179+ @param items The items to include
180180+ @param total Optional total count (for paginated results)
181181+ @return A new collection *)
182182+ val create : items:item list -> ?total:int -> unit -> t
183183+184184+ (** Map a function over the items in the collection.
185185+ @param f Function to apply to each item
186186+ @return New collection with transformed items *)
187187+ val map : (item -> item) -> t -> t
188188+189189+ (** Filter items in the collection.
190190+ @param f Predicate function
191191+ @return New collection with only items where f returns true *)
192192+ val filter : (item -> bool) -> t -> t
193193+end
194194+195195+(** {1 Error Handling Signatures} *)
196196+197197+(** Signature for JMAP error types.
198198+199199+ JMAP has a structured error model with specific error codes
200200+ and optional additional properties. *)
201201+module type ERROR_TYPE = sig
202202+ type t
203203+204204+ include JSONABLE with type t := t
205205+ include PRINTABLE with type t := t
206206+207207+ (** Get the JMAP error type string.
208208+ @return The error type (e.g., "accountNotFound", "invalidArguments") *)
209209+ val error_type : t -> string
210210+211211+ (** Get the human-readable error description.
212212+ @return Optional description of the error *)
213213+ val description : t -> string option
214214+215215+ (** Create an error with the given type and description.
216216+ @param error_type The JMAP error type
217217+ @param description Optional human-readable description
218218+ @return A new error *)
219219+ val create : error_type:string -> ?description:string -> unit -> t
220220+end
221221+222222+(** {1 RFC Compliance Signatures} *)
223223+224224+(** Signature for types that implement specific RFC sections.
225225+226226+ This provides metadata about which parts of the JMAP RFCs
227227+ are implemented by each type, making it easier to track
228228+ compliance and find documentation. *)
229229+module type RFC_COMPLIANT = sig
230230+ type t
231231+232232+ (** Get the RFC section this type implements.
233233+ @return RFC section reference (e.g., "RFC 8620, Section 5.1") *)
234234+ val rfc_section : unit -> string
235235+236236+ (** Get the URL to the RFC section.
237237+ @return Direct URL to the RFC section *)
238238+ val rfc_url : unit -> string
239239+240240+ (** Get implementation notes specific to this OCaml binding.
241241+ @return List of implementation notes and decisions *)
242242+ val implementation_notes : unit -> string list
243243+244244+ (** Check if this implementation is complete.
245245+ @return true if all required RFC features are implemented *)
246246+ val is_complete : unit -> bool
247247+248248+ (** Get list of unimplemented features from the RFC.
249249+ @return List of features not yet implemented *)
250250+ val unimplemented_features : unit -> string list
251251+end
252252+253253+(** {1 Vendor Extension Signatures} *)
254254+255255+(** Signature for vendor-specific extensions to JMAP.
256256+257257+ Vendors like Fastmail, Cyrus, etc. may add custom properties
258258+ or methods. This signature helps track these extensions. *)
259259+module type VENDOR_EXTENSION = sig
260260+ type t
261261+262262+ include JSONABLE with type t := t
263263+ include PRINTABLE with type t := t
264264+265265+ (** Get the vendor namespace.
266266+ @return The vendor identifier (e.g., "com.fastmail") *)
267267+ val vendor : unit -> string
268268+269269+ (** Get the extension name.
270270+ @return The name of this extension *)
271271+ val extension_name : unit -> string
272272+273273+ (** Get the capability URI for this extension.
274274+ @return The capability URI if this adds a new capability *)
275275+ val capability_uri : unit -> string option
276276+277277+ (** Check if this extension is experimental.
278278+ @return true if this is an experimental/unstable extension *)
279279+ val is_experimental : unit -> bool
280280+end
281281+282282+(** {1 Patch Operations Signatures} *)
283283+284284+(** Signature for types that support JSON Patch operations.
285285+286286+ JMAP uses a subset of JSON Patch (RFC 6902) for partial updates.
287287+ This signature is for types that can generate or apply patches. *)
288288+module type PATCHABLE = sig
289289+ type t
290290+ type patch
291291+292292+ include JSONABLE with type t := t
293293+294294+ (** Create a patch to transform one value into another.
295295+ @param from The original value
296296+ @param to_ The target value
297297+ @return A patch that transforms from to to_ *)
298298+ val create_patch : from:t -> to_:t -> patch
299299+300300+ (** Apply a patch to a value.
301301+ @param patch The patch to apply
302302+ @param t The value to patch
303303+ @return Ok with patched value or Error if patch cannot be applied *)
304304+ val apply_patch : patch:patch -> t -> (t, string) result
305305+306306+ (** Convert a patch to JSON Pointer operations.
307307+ @param patch The patch to convert
308308+ @return List of (path, value) pairs for JSON Pointer operations *)
309309+ val patch_to_operations : patch -> (string * Yojson.Safe.t) list
310310+end
311311+312312+(** {1 Composite Signatures} *)
313313+314314+(** Full signature for a complete JMAP object implementation.
315315+316316+ This combines all the relevant signatures for a fully-featured
317317+ JMAP object that supports all standard operations. *)
318318+module type FULL_JMAP_OBJECT = sig
319319+ include JMAP_OBJECT
320320+ include PATCHABLE with type t := t
321321+ include RFC_COMPLIANT with type t := t
322322+end
323323+324324+(** Full signature for a complete method implementation.
325325+326326+ This represents a complete JMAP method with both request and
327327+ response types, following all conventions. *)
328328+module type JMAP_METHOD = sig
329329+ (** The method name (e.g., "Email/get") *)
330330+ val name : unit -> string
331331+332332+ (** The request arguments type *)
333333+ module Args : METHOD_ARGS
334334+335335+ (** The response type *)
336336+ module Response : METHOD_RESPONSE
337337+338338+ (** Execute the method (client-side stub).
339339+ @param args The method arguments
340340+ @return A response or error *)
341341+ val execute : Args.t -> (Response.t, string) result
342342+end