this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

jmap

+1960 -3
+13 -3
jmap/dune-project
··· 1 1 (lang dune 3.0) 2 2 3 3 (package 4 + (name jmap-sigs) 5 + (synopsis "Module type signatures for JMAP implementations") 6 + (depends ocaml dune yojson fmt)) 7 + 8 + (package 4 9 (name jmap) 5 10 (synopsis "JMAP protocol implementation") 6 - (depends ocaml dune yojson uri base64)) 11 + (depends ocaml dune jmap-sigs yojson uri base64 fmt)) 7 12 8 13 (package 9 14 (name jmap-email) 10 15 (synopsis "JMAP Email extensions") 11 - (depends ocaml dune jmap yojson uri)) 16 + (depends ocaml dune jmap jmap-sigs yojson uri fmt)) 12 17 13 18 (package 14 19 (name jmap-unix) 15 20 (synopsis "JMAP Unix networking implementation") 16 - (depends ocaml dune jmap jmap-email yojson uri eio tls-eio cohttp-eio)) 21 + (depends ocaml dune jmap jmap-email jmap-sigs yojson uri eio tls-eio cohttp-eio fmt)) 22 + 23 + (package 24 + (name jmap-dsl) 25 + (synopsis "Type-safe JMAP method chaining DSL") 26 + (depends ocaml dune jmap jmap-email jmap-unix jmap-sigs yojson fmt))
+33
jmap/jmap-dsl/dune
··· 1 + (library 2 + (public_name jmap-dsl) 3 + (name jmap_dsl) 4 + (modules jmap_dsl) 5 + (libraries jmap jmap-email jmap-unix jmap-sigs yojson fmt)) 6 + 7 + (executable 8 + (public_name jmap-dsl-example) 9 + (name example) 10 + (package jmap-dsl) 11 + (modules example) 12 + (libraries jmap-dsl)) 13 + 14 + (executable 15 + (public_name jmap-dsl-comprehensive) 16 + (name examples_comprehensive) 17 + (package jmap-dsl) 18 + (modules examples_comprehensive) 19 + (libraries jmap-dsl unix)) 20 + 21 + (executable 22 + (public_name jmap-dsl-networking) 23 + (name examples_networking) 24 + (package jmap-dsl) 25 + (modules examples_networking) 26 + (libraries jmap-dsl eio eio_main unix)) 27 + 28 + (executable 29 + (public_name jmap-dsl-offline) 30 + (name examples_offline) 31 + (package jmap-dsl) 32 + (modules examples_offline) 33 + (libraries jmap-dsl unix))
+59
jmap/jmap-dsl/example.ml
··· 1 + open Jmap_dsl 2 + 3 + (** Example demonstrating GADT-based method chaining *) 4 + 5 + let example_chain account_id = 6 + (* Chain multiple methods with type safety *) 7 + let chain = 8 + (((start @> email_query ~account_id ()) 9 + @> mailbox_get_all ~account_id ()) 10 + @> identity_get_all ~account_id ()) 11 + |> done_ 12 + in 13 + chain 14 + 15 + let _process_responses account_id (emails, (mailboxes, (identities, ()))) = 16 + (* All responses are properly typed! *) 17 + let email_ids = Email_query_response.ids emails in 18 + let mailbox_list = Mailbox_get_response.mailboxes mailboxes in 19 + let identity_list = Identity_get_response.identities identities in 20 + 21 + Printf.printf "Account %s:\n" account_id; 22 + Printf.printf "- Found %d emails\n" (List.length email_ids); 23 + Printf.printf "- Found %d mailboxes\n" (List.length mailbox_list); 24 + Printf.printf "- Found %d identities\n" (List.length identity_list) 25 + 26 + (** Demonstrate chaining with email retrieval *) 27 + let _email_retrieval_chain account_id = 28 + (* First query emails, then get the actual email objects *) 29 + let query_chain = 30 + (start @> email_query ~account_id ~limit:10 ()) 31 + |> done_ 32 + in 33 + 34 + (* This would typically be followed by another chain using the email IDs *) 35 + query_chain 36 + 37 + let inspect_chain_info chain = 38 + Printf.printf "Chain contains %d methods:\n" (chain_length chain); 39 + let methods = method_names chain in 40 + List.iteri (fun i name -> 41 + Printf.printf " %d. %s\n" (i + 1) name 42 + ) methods 43 + 44 + (** Simple demonstration of the DSL without requiring network *) 45 + let demo () = 46 + let account_id = "account123" in 47 + let chain = example_chain account_id in 48 + 49 + Printf.printf "=== JMAP DSL Demo ===\n"; 50 + inspect_chain_info chain; 51 + 52 + let _request = to_request chain in 53 + Printf.printf "\nGenerated request with %d methods\n" (chain_length chain); 54 + Printf.printf "Methods: %s\n" (String.concat ", " (method_names chain)); 55 + 56 + Printf.printf "\nType safety verified at compile time! ✓\n"; 57 + Printf.printf "Methods are properly chained with GADTs! ✓\n" 58 + 59 + let () = demo ()
+240
jmap/jmap-dsl/examples_comprehensive.ml
··· 1 + open Jmap_dsl 2 + 3 + (** Comprehensive JMAP DSL Examples 4 + 5 + This file demonstrates various real-world use cases for the JMAP DSL, 6 + based on the patterns found in bin/examples. Each example shows how 7 + to use the DSL for different email management scenarios. 8 + *) 9 + 10 + (** Helper functions for creating common filters *) 11 + module Filters = struct 12 + let recent_days days = 13 + let now = Unix.time () in 14 + let days_ago = now -. (float_of_int days *. 86400.0) in 15 + let json = `Assoc [ 16 + "after", `String (Printf.sprintf "%.0f" days_ago) 17 + ] in 18 + Jmap.Methods.Filter.condition json 19 + 20 + let unread_only () = 21 + let json = `Assoc [ 22 + "hasKeyword", `String "$seen"; 23 + "operator", `String "NOT" 24 + ] in 25 + Jmap.Methods.Filter.condition json 26 + 27 + let flagged_only () = 28 + let json = `Assoc [ 29 + "hasKeyword", `String "$flagged" 30 + ] in 31 + Jmap.Methods.Filter.condition json 32 + 33 + let from_sender sender = 34 + let json = `Assoc [ 35 + "from", `String sender 36 + ] in 37 + Jmap.Methods.Filter.condition json 38 + 39 + let with_attachment () = 40 + let json = `Assoc [ 41 + "hasAttachment", `Bool true 42 + ] in 43 + Jmap.Methods.Filter.condition json 44 + 45 + let combine_and filters = 46 + let filter_jsons = List.map (fun _f -> 47 + (* This is a simplification - in real usage, we'd need to extract JSON from Filter.t *) 48 + `Assoc ["placeholder", `String "filter"] 49 + ) filters in 50 + let json = `Assoc [ 51 + "operator", `String "AND"; 52 + "conditions", `List filter_jsons 53 + ] in 54 + Jmap.Methods.Filter.condition json 55 + end 56 + 57 + (** Example 1: Recent Unread Emails Dashboard *) 58 + let recent_unread_dashboard account_id = 59 + Printf.printf "=== Recent Unread Dashboard Example ===\n"; 60 + 61 + (* Create filter for unread emails from last 7 days *) 62 + let filter = Filters.combine_and [ 63 + Filters.unread_only (); 64 + Filters.recent_days 7 65 + ] in 66 + 67 + let chain = 68 + (((start @> email_query ~account_id ~filter ~limit:50 ()) 69 + @> mailbox_get_all ~account_id ()) 70 + @> identity_get_all ~account_id ()) 71 + |> done_ 72 + in 73 + 74 + Printf.printf "Chain created with %d methods:\n" (chain_length chain); 75 + List.iteri (fun i name -> 76 + Printf.printf " %d. %s\n" (i + 1) name 77 + ) (method_names chain); 78 + 79 + chain 80 + 81 + (** Example 2: VIP and Flagged Emails *) 82 + let vip_flagged_analysis account_id = 83 + Printf.printf "\n=== VIP and Flagged Analysis Example ===\n"; 84 + 85 + (* Create filter for flagged emails *) 86 + let flagged_filter = Filters.flagged_only () in 87 + 88 + let chain = 89 + ((start @> email_query ~account_id ~filter:flagged_filter ~limit:25 ()) 90 + @> mailbox_get_all ~account_id ()) 91 + |> done_ 92 + in 93 + 94 + Printf.printf "VIP analysis chain with %d methods\n" (chain_length chain); 95 + chain 96 + 97 + (** Example 3: Email Search by Sender *) 98 + let sender_analysis account_id sender_email = 99 + Printf.printf "\n=== Sender Analysis Example ===\n"; 100 + 101 + let sender_filter = Filters.from_sender sender_email in 102 + 103 + let chain = 104 + ((start @> email_query ~account_id ~filter:sender_filter ~limit:30 ()) 105 + @> identity_get_all ~account_id ()) 106 + |> done_ 107 + in 108 + 109 + Printf.printf "Analyzing emails from: %s\n" sender_email; 110 + Printf.printf "Chain methods: %s\n" (String.concat ", " (method_names chain)); 111 + chain 112 + 113 + (** Example 4: Attachment Analysis *) 114 + let attachment_analysis account_id = 115 + Printf.printf "\n=== Attachment Analysis Example ===\n"; 116 + 117 + let attachment_filter = Filters.combine_and [ 118 + Filters.with_attachment (); 119 + Filters.recent_days 30 120 + ] in 121 + 122 + let chain = 123 + (start @> email_query ~account_id ~filter:attachment_filter ~limit:20 ()) 124 + |> done_ 125 + in 126 + 127 + Printf.printf "Looking for emails with attachments from last 30 days\n"; 128 + chain 129 + 130 + (** Example 5: Comprehensive Dashboard *) 131 + let comprehensive_dashboard account_id = 132 + Printf.printf "\n=== Comprehensive Dashboard Example ===\n"; 133 + 134 + (* Multi-faceted analysis in a single request *) 135 + let recent_filter = Filters.recent_days 7 in 136 + 137 + let chain = 138 + (((start @> email_query ~account_id ~filter:recent_filter ~limit:100 ()) 139 + @> mailbox_get_all ~account_id ()) 140 + @> identity_get_all ~account_id ()) 141 + |> done_ 142 + in 143 + 144 + Printf.printf "Comprehensive dashboard with %d data sources\n" (chain_length chain); 145 + Printf.printf "Data sources: %s\n" (String.concat " + " (method_names chain)); 146 + chain 147 + 148 + (** Example 6: Email Volume Analysis *) 149 + let email_volume_analysis account_id = 150 + Printf.printf "\n=== Email Volume Analysis Example ===\n"; 151 + 152 + let volume_chain = 153 + ((start @> email_query ~account_id ~limit:200 ()) 154 + @> mailbox_get_all ~account_id ()) 155 + |> done_ 156 + in 157 + 158 + Printf.printf "Analyzing email volume patterns\n"; 159 + volume_chain 160 + 161 + (** Helper function to simulate processing responses *) 162 + let _process_dashboard_responses account_id responses = 163 + Printf.printf "\n=== Processing Dashboard Results ===\n"; 164 + match responses with 165 + | (emails, (mailboxes, (identities, ()))) -> 166 + let email_ids = Email_query_response.ids emails in 167 + let mailbox_list = Mailbox_get_response.mailboxes mailboxes in 168 + let identity_list = Identity_get_response.identities identities in 169 + 170 + Printf.printf "Account: %s\n" account_id; 171 + Printf.printf "- Found %d emails\n" (List.length email_ids); 172 + Printf.printf "- Found %d mailboxes\n" (List.length mailbox_list); 173 + Printf.printf "- Found %d identities\n" (List.length identity_list); 174 + 175 + (* In a real implementation, we would process the actual data *) 176 + Printf.printf "- Email processing: analyzing subjects, dates, senders\n"; 177 + Printf.printf "- Mailbox analysis: calculating unread counts, sizes\n"; 178 + Printf.printf "- Identity review: checking send configurations\n" 179 + 180 + let _process_simple_responses_2 responses = 181 + Printf.printf "\n=== Processing Simple Results (2 methods) ===\n"; 182 + let (emails, (mailboxes, ())) = responses in 183 + let email_ids = Email_query_response.ids emails in 184 + let mailbox_list = Mailbox_get_response.mailboxes mailboxes in 185 + 186 + Printf.printf "- Query returned %d emails\n" (List.length email_ids); 187 + Printf.printf "- Available mailboxes: %d\n" (List.length mailbox_list) 188 + 189 + let _process_simple_responses_1 responses = 190 + Printf.printf "\n=== Processing Simple Results (1 method) ===\n"; 191 + let (emails, ()) = responses in 192 + let email_ids = Email_query_response.ids emails in 193 + Printf.printf "- Simple query returned %d emails\n" (List.length email_ids) 194 + 195 + (** Main demonstration function *) 196 + let demo_comprehensive_usage () = 197 + Printf.printf "JMAP DSL Comprehensive Examples\n"; 198 + Printf.printf "================================\n\n"; 199 + 200 + let account_id = "demo_account_123" in 201 + 202 + (* Example 1: Recent unread dashboard *) 203 + let _dashboard_chain = recent_unread_dashboard account_id in 204 + 205 + (* Example 2: VIP analysis *) 206 + let _vip_chain = vip_flagged_analysis account_id in 207 + 208 + (* Example 3: Sender analysis *) 209 + let _sender_chain = sender_analysis account_id "boss@company.com" in 210 + 211 + (* Example 4: Attachment analysis *) 212 + let _attachment_chain = attachment_analysis account_id in 213 + 214 + (* Example 5: Comprehensive dashboard *) 215 + let _comprehensive_chain = comprehensive_dashboard account_id in 216 + 217 + (* Example 6: Volume analysis *) 218 + let _volume_chain = email_volume_analysis account_id in 219 + 220 + Printf.printf "\n=== Type Safety Demonstration ===\n"; 221 + Printf.printf "✓ All chains are type-checked at compile time\n"; 222 + Printf.printf "✓ Response types are automatically inferred\n"; 223 + Printf.printf "✓ Method chaining prevents runtime errors\n"; 224 + Printf.printf "✓ Filter construction is type-safe\n"; 225 + 226 + Printf.printf "\n=== Usage Patterns Demonstrated ===\n"; 227 + Printf.printf "• Email querying with complex filters\n"; 228 + Printf.printf "• Multi-method chains for dashboard data\n"; 229 + Printf.printf "• Mailbox and identity management\n"; 230 + Printf.printf "• Sender and attachment analysis\n"; 231 + Printf.printf "• Volume and trend analysis setup\n"; 232 + 233 + Printf.printf "\n=== Next Steps ===\n"; 234 + Printf.printf "• Add network execution with real JMAP server\n"; 235 + Printf.printf "• Implement result reference chaining (#ids syntax)\n"; 236 + Printf.printf "• Add more method types (Set, Changes, etc.)\n"; 237 + Printf.printf "• Enhanced error handling and recovery\n" 238 + 239 + (** Entry point *) 240 + let () = demo_comprehensive_usage ()
+213
jmap/jmap-dsl/examples_networking.ml
··· 1 + open Jmap_dsl 2 + 3 + (** Helper function to take first n elements from a list *) 4 + let rec take n lst = 5 + match n, lst with 6 + | 0, _ | _, [] -> [] 7 + | n, x :: xs -> x :: take (n - 1) xs 8 + 9 + (** Real-world JMAP DSL Example with Network Operations 10 + 11 + This example demonstrates using the JMAP DSL with actual network operations, 12 + including proper authentication, error handling, and response processing. 13 + It's based on the patterns from bin/examples/query_recent_unread.ml. 14 + *) 15 + 16 + (** Helper function to read API key from file *) 17 + let read_api_key () = 18 + try 19 + let ic = open_in ".api-key" in 20 + let key = input_line ic in 21 + close_in ic; 22 + String.trim key 23 + with 24 + | Sys_error _ -> 25 + Printf.eprintf "Error: Create a .api-key file with your JMAP bearer token\n"; 26 + Printf.eprintf "Get your API key from:\n"; 27 + Printf.eprintf "• FastMail: Settings > Password & Security > App Passwords\n"; 28 + Printf.eprintf "• Gmail: Google Account > App passwords\n"; 29 + exit 1 30 + | End_of_file -> 31 + Printf.eprintf "Error: .api-key file is empty\n"; 32 + exit 1 33 + 34 + (** Helper to create recent unread filter *) 35 + let create_recent_unread_filter days = 36 + let now = Unix.time () in 37 + let days_ago = now -. (float_of_int days *. 86400.0) in 38 + let unread_json = `Assoc [ 39 + "operator", `String "AND"; 40 + "conditions", `List [ 41 + `Assoc [ 42 + "hasKeyword", `String "$seen"; 43 + "operator", `String "NOT" 44 + ]; 45 + `Assoc [ 46 + "after", `String (Printf.sprintf "%.0f" days_ago) 47 + ] 48 + ] 49 + ] in 50 + Jmap.Methods.Filter.condition unread_json 51 + 52 + (** Example 1: Recent Unread Emails with Real Network *) 53 + let recent_unread_with_network env = 54 + Printf.printf "=== JMAP DSL with Real Network Example ===\n"; 55 + 56 + (* Create Eio switch for resource management *) 57 + Eio.Switch.run @@ fun _sw -> 58 + 59 + (* Read API credentials *) 60 + let api_key = read_api_key () in 61 + Printf.printf "Using API key: %s...\n\n" 62 + (String.sub api_key 0 (min 20 (String.length api_key))); 63 + 64 + (* Create client configuration *) 65 + let config = Jmap_unix.default_config () in 66 + let client = Jmap_unix.create_client ~config () in 67 + 68 + match Jmap_unix.connect env client 69 + ~host:"api.fastmail.com" 70 + ~use_tls:true 71 + ~auth_method:(Jmap_unix.Bearer api_key) 72 + () with 73 + | Error error -> 74 + Printf.printf "Connection failed: %s\n" (Jmap.Protocol.Error.error_to_string error); 75 + exit 1 76 + | Ok (ctx, session) -> 77 + Printf.printf "✓ Connected to JMAP server\n"; 78 + Printf.printf "✓ Retrieved session information\n"; 79 + 80 + (* Get primary mail account *) 81 + let account_id = 82 + match Jmap.Protocol.get_primary_account session Jmap_email.capability_mail with 83 + | Ok id -> id 84 + | Error error -> 85 + Printf.printf "No mail account found: %s\n" (Jmap.Protocol.Error.error_to_string error); 86 + exit 1 87 + in 88 + Printf.printf "✓ Using account: %s\n\n" account_id; 89 + 90 + (* Create DSL chain for recent unread emails *) 91 + let filter = create_recent_unread_filter 7 in 92 + let chain = 93 + (((start @> email_query ~account_id ~filter ~limit:20 ()) 94 + @> mailbox_get_all ~account_id ()) 95 + @> identity_get_all ~account_id ()) 96 + |> done_ 97 + in 98 + 99 + Printf.printf "Executing JMAP DSL chain with %d methods...\n" (chain_length chain); 100 + List.iteri (fun i name -> 101 + Printf.printf " %d. %s\n" (i + 1) name 102 + ) (method_names chain); 103 + 104 + (* Execute the DSL chain *) 105 + match execute env ctx chain with 106 + | Error err -> 107 + Printf.printf "❌ Execution failed: %s\n" err 108 + | Ok (identities, (mailboxes, (emails, ()))) -> 109 + Printf.printf "✅ DSL execution successful!\n\n"; 110 + 111 + (* Process results *) 112 + let email_ids = Email_query_response.ids emails in 113 + let mailbox_list = Mailbox_get_response.mailboxes mailboxes in 114 + let identity_list = Identity_get_response.identities identities in 115 + 116 + Printf.printf "=== Results Summary ===\n"; 117 + Printf.printf "📧 Recent unread emails: %d\n" (List.length email_ids); 118 + Printf.printf "📁 Available mailboxes: %d\n" (List.length mailbox_list); 119 + Printf.printf "👤 Identities configured: %d\n" (List.length identity_list); 120 + 121 + if List.length email_ids > 0 then ( 122 + Printf.printf "\n=== Sample Email IDs (first 3) ===\n"; 123 + let limited_ids = take 3 email_ids in 124 + List.iteri (fun i email_id -> 125 + Printf.printf "%d. %s\n" (i + 1) email_id 126 + ) limited_ids; 127 + if List.length email_ids > 3 then 128 + Printf.printf "... and %d more emails\n" (List.length email_ids - 3) 129 + ) else ( 130 + Printf.printf "\nNo recent unread emails found.\n" 131 + ); 132 + 133 + Printf.printf "\n=== DSL Benefits Demonstrated ===\n"; 134 + Printf.printf "✓ Type-safe method chaining\n"; 135 + Printf.printf "✓ Automatic JSON serialization/deserialization\n"; 136 + Printf.printf "✓ Single network request for multiple operations\n"; 137 + Printf.printf "✓ Compile-time response type verification\n"; 138 + Printf.printf "✓ Structured error handling\n" 139 + 140 + (** Example 2: Simple Email Query with DSL *) 141 + let simple_query_example env = 142 + Printf.printf "\n=== Simple Query Example ===\n"; 143 + 144 + Eio.Switch.run @@ fun _sw -> 145 + 146 + let api_key = read_api_key () in 147 + let config = Jmap_unix.default_config () in 148 + let client = Jmap_unix.create_client ~config () in 149 + 150 + match Jmap_unix.connect env client 151 + ~host:"api.fastmail.com" 152 + ~use_tls:true 153 + ~auth_method:(Jmap_unix.Bearer api_key) 154 + () with 155 + | Error error -> 156 + Printf.printf "Connection failed: %s\n" (Jmap.Protocol.Error.error_to_string error) 157 + | Ok (ctx, session) -> 158 + let account_id = 159 + match Jmap.Protocol.get_primary_account session Jmap_email.capability_mail with 160 + | Ok id -> id 161 + | Error error -> 162 + Printf.printf "No mail account found: %s\n" (Jmap.Protocol.Error.error_to_string error); 163 + exit 1 164 + in 165 + 166 + (* Simple single-method chain *) 167 + let simple_chain = 168 + (start @> email_query ~account_id ~limit:5 ()) |> done_ 169 + in 170 + 171 + Printf.printf "Simple chain with %d method: %s\n" 172 + (chain_length simple_chain) 173 + (String.concat ", " (method_names simple_chain)); 174 + 175 + match execute env ctx simple_chain with 176 + | Error err -> 177 + Printf.printf "❌ Simple query failed: %s\n" err 178 + | Ok (emails, ()) -> 179 + let email_ids = Email_query_response.ids emails in 180 + Printf.printf "✅ Found %d recent emails\n" (List.length email_ids) 181 + 182 + (** Main function with proper error handling *) 183 + let main env = 184 + try 185 + Printf.printf "JMAP DSL Networking Examples\n"; 186 + Printf.printf "============================\n\n"; 187 + 188 + Printf.printf "This example requires:\n"; 189 + Printf.printf "• A .api-key file with your JMAP bearer token\n"; 190 + Printf.printf "• Network access to api.fastmail.com\n\n"; 191 + 192 + (* Run comprehensive example *) 193 + recent_unread_with_network env; 194 + 195 + (* Run simple example *) 196 + simple_query_example env; 197 + 198 + Printf.printf "\n=== Summary ===\n"; 199 + Printf.printf "JMAP DSL successfully demonstrated with real network operations!\n"; 200 + Printf.printf "The type-safe chaining provides both safety and convenience.\n" 201 + 202 + with 203 + | Sys_error msg -> 204 + Printf.printf "System error: %s\n" msg; 205 + exit 1 206 + | exn -> 207 + Printf.printf "Unexpected error: %s\n" (Printexc.to_string exn); 208 + exit 1 209 + 210 + (** Entry point with Eio runtime *) 211 + let () = 212 + Eio_main.run @@ fun env -> 213 + main env
+177
jmap/jmap-dsl/examples_offline.ml
··· 1 + open Jmap_dsl 2 + 3 + (** Offline JMAP DSL Examples (no network required) 4 + 5 + These examples demonstrate the DSL's type safety and method chaining 6 + without requiring actual JMAP server connections. Perfect for testing 7 + and demonstrating the compile-time benefits. 8 + *) 9 + 10 + (** Demonstrate various chain combinations *) 11 + let demo_chain_variations () = 12 + Printf.printf "JMAP DSL Offline Examples\n"; 13 + Printf.printf "=========================\n\n"; 14 + 15 + let account_id = "demo_account" in 16 + 17 + (* Example 1: Single method chain *) 18 + Printf.printf "=== Single Method Chain ===\n"; 19 + let single_chain = 20 + (start @> email_query ~account_id ~limit:10 ()) |> done_ 21 + in 22 + 23 + Printf.printf "Methods: %s\n" (String.concat ", " (method_names single_chain)); 24 + Printf.printf "Length: %d\n" (chain_length single_chain); 25 + Printf.printf "Type: (Email_query_response.t * unit)\n\n"; 26 + 27 + (* Example 2: Two method chain *) 28 + Printf.printf "=== Two Method Chain ===\n"; 29 + let double_chain = 30 + ((start @> email_query ~account_id ()) 31 + @> mailbox_get_all ~account_id ()) 32 + |> done_ 33 + in 34 + 35 + Printf.printf "Methods: %s\n" (String.concat ", " (method_names double_chain)); 36 + Printf.printf "Length: %d\n" (chain_length double_chain); 37 + Printf.printf "Type: (Mailbox_get_response.t * (Email_query_response.t * unit))\n\n"; 38 + 39 + (* Example 3: Triple method chain *) 40 + Printf.printf "=== Triple Method Chain ===\n"; 41 + let triple_chain = 42 + (((start @> email_query ~account_id ~limit:50 ()) 43 + @> mailbox_get_all ~account_id ()) 44 + @> identity_get_all ~account_id ()) 45 + |> done_ 46 + in 47 + 48 + Printf.printf "Methods: %s\n" (String.concat ", " (method_names triple_chain)); 49 + Printf.printf "Length: %d\n" (chain_length triple_chain); 50 + Printf.printf "Type: (Identity_get_response.t * (Mailbox_get_response.t * (Email_query_response.t * unit)))\n\n"; 51 + 52 + (** Demonstrate different method configurations *) 53 + let demo_method_configurations () = 54 + Printf.printf "=== Method Configuration Examples ===\n"; 55 + 56 + let account_id = "demo_account" in 57 + 58 + (* Demonstrate method creation (without accessing private fields) *) 59 + let _basic_query = email_query ~account_id () in 60 + let _limited_query = email_query ~account_id ~limit:25 () in 61 + let _positioned_query = email_query ~account_id ~position:10 ~limit:25 () in 62 + 63 + Printf.printf "✓ Basic email query method created\n"; 64 + Printf.printf "✓ Limited email query method created (limit configured)\n"; 65 + Printf.printf "✓ Positioned email query method created (position + limit configured)\n"; 66 + 67 + (* Email get with different configurations *) 68 + let _simple_get = email_get ~account_id ~ids:["email1"; "email2"] () in 69 + let _property_get = email_get ~account_id ~ids:["email1"] 70 + ~properties:["id"; "subject"; "from"] () in 71 + 72 + Printf.printf "✓ Simple email get method created\n"; 73 + Printf.printf "✓ Property-filtered email get method created\n"; 74 + 75 + (* Mailbox operations *) 76 + let _all_mailboxes = mailbox_get_all ~account_id () in 77 + let _specific_mailboxes = mailbox_get ~account_id ~ids:["mailbox1"] () in 78 + 79 + Printf.printf "✓ All mailboxes method created\n"; 80 + Printf.printf "✓ Specific mailboxes method created\n"; 81 + 82 + (* Identity operations *) 83 + let _all_identities = identity_get_all ~account_id () in 84 + let _specific_identities = identity_get ~account_id ~ids:["identity1"] () in 85 + 86 + Printf.printf "✓ All identities method created\n"; 87 + Printf.printf "✓ Specific identities method created\n"; 88 + 89 + Printf.printf "\n"; 90 + 91 + (** Demonstrate filter creation *) 92 + let demo_filter_usage () = 93 + Printf.printf "=== Filter Usage Examples ===\n"; 94 + 95 + let account_id = "demo_account" in 96 + 97 + (* Create some example filters *) 98 + let unread_filter = 99 + let json = `Assoc ["hasKeyword", `String "$seen"; "operator", `String "NOT"] in 100 + Jmap.Methods.Filter.condition json 101 + in 102 + 103 + let recent_filter = 104 + let now = Unix.time () in 105 + let week_ago = now -. (7.0 *. 86400.0) in 106 + let json = `Assoc ["after", `String (Printf.sprintf "%.0f" week_ago)] in 107 + Jmap.Methods.Filter.condition json 108 + in 109 + 110 + let combined_filter = 111 + let json = `Assoc [ 112 + "operator", `String "AND"; 113 + "conditions", `List [ 114 + `Assoc ["hasKeyword", `String "$seen"; "operator", `String "NOT"]; 115 + `Assoc ["after", `String "1640995200"] 116 + ] 117 + ] in 118 + Jmap.Methods.Filter.condition json 119 + in 120 + 121 + (* Use filters in queries *) 122 + let _unread_query = email_query ~account_id ~filter:unread_filter ~limit:20 () in 123 + let _recent_query = email_query ~account_id ~filter:recent_filter ~limit:30 () in 124 + let _combined_query = email_query ~account_id ~filter:combined_filter ~limit:10 () in 125 + 126 + Printf.printf "✓ Unread emails query with filter created\n"; 127 + Printf.printf "✓ Recent emails query with filter created\n"; 128 + Printf.printf "✓ Combined filter query created\n"; 129 + 130 + Printf.printf "\n"; 131 + 132 + (** Demonstrate request generation *) 133 + let demo_request_generation () = 134 + Printf.printf "=== Request Generation Examples ===\n"; 135 + 136 + let account_id = "demo_account" in 137 + 138 + (* Create various chains *) 139 + let simple_chain = (start @> email_query ~account_id ~limit:5 ()) |> done_ in 140 + let complex_chain = 141 + (((start @> email_query ~account_id ~limit:20 ()) 142 + @> mailbox_get_all ~account_id ()) 143 + @> identity_get_all ~account_id ()) 144 + |> done_ 145 + in 146 + 147 + (* Generate requests *) 148 + let simple_request = to_request simple_chain in 149 + let complex_request = to_request complex_chain in 150 + 151 + Printf.printf "Simple request generated: %d method calls\n" (chain_length simple_chain); 152 + Printf.printf "Complex request generated: %d method calls\n" (chain_length complex_chain); 153 + 154 + (* Show that requests are properly formed JMAP requests *) 155 + Printf.printf "Simple request methods: %s\n" 156 + (String.concat ", " (method_names simple_chain)); 157 + Printf.printf "Complex request methods: %s\n" 158 + (String.concat ", " (method_names complex_chain)); 159 + 160 + Printf.printf "\n"; 161 + 162 + (** Main demonstration *) 163 + let () = 164 + demo_chain_variations (); 165 + demo_method_configurations (); 166 + demo_filter_usage (); 167 + demo_request_generation (); 168 + 169 + Printf.printf "=== Type Safety Summary ===\n"; 170 + Printf.printf "✅ All method chains are validated at compile time\n"; 171 + Printf.printf "✅ Response types are automatically inferred\n"; 172 + Printf.printf "✅ Method arguments are type-checked\n"; 173 + Printf.printf "✅ Filter construction is validated\n"; 174 + Printf.printf "✅ Request generation is automatic\n"; 175 + Printf.printf "✅ No runtime type errors possible\n\n"; 176 + Printf.printf "=== Benefits Demonstrated ===\n"; 177 + Printf.printf "DSL provides fluent method chaining with compile-time type safety!\n"
+381
jmap/jmap-dsl/jmap_dsl.ml
··· 1 + (** Type-safe JMAP method chaining DSL implementation *) 2 + 3 + (** {1 Core GADT Types} *) 4 + 5 + (** A method signature with its arguments and expected response type. 6 + This is similar to Ctypes' function signatures. *) 7 + type ('args, 'resp) method_sig = { 8 + method_name : string; 9 + args : 'args; 10 + args_to_json : 'args -> Yojson.Safe.t; 11 + resp_of_json : Yojson.Safe.t -> 'resp; 12 + call_id : string; 13 + } 14 + 15 + (** A heterogeneous list of method calls that preserves response types. 16 + This is the core GADT that enables type-safe chaining. *) 17 + type _ method_chain = 18 + | Empty : unit method_chain 19 + | Cons : ('args, 'resp) method_sig * 'rest method_chain -> ('resp * 'rest) method_chain 20 + 21 + (** Counter for generating unique call IDs *) 22 + let call_id_counter = ref 0 23 + 24 + (** Generate a unique call ID *) 25 + let next_call_id () = 26 + incr call_id_counter; 27 + "call-" ^ string_of_int !call_id_counter 28 + 29 + (** Create a method signature *) 30 + let make_method_sig ~method_name ~args ~args_to_json ~resp_of_json = 31 + { 32 + method_name; 33 + args; 34 + args_to_json; 35 + resp_of_json; 36 + call_id = next_call_id (); 37 + } 38 + 39 + (** {1 Core Combinators} *) 40 + 41 + let empty = Empty 42 + 43 + let (@>) chain method_sig = Cons (method_sig, chain) 44 + 45 + let start = Empty 46 + 47 + let done_ chain = chain 48 + 49 + (** {1 Response Types and Parsers} *) 50 + 51 + (** Email/query response *) 52 + module Email_query_response = struct 53 + type t = { 54 + account_id : string; 55 + query_state : string; 56 + can_calculate_changes : bool; 57 + position : int; 58 + ids : string list; 59 + total : int option; 60 + } 61 + 62 + let ids t = t.ids 63 + let query_state t = t.query_state 64 + let total t = t.total 65 + let position t = t.position 66 + let can_calculate_changes t = t.can_calculate_changes 67 + 68 + let of_json json = 69 + let open Yojson.Safe.Util in 70 + { 71 + account_id = json |> member "accountId" |> to_string; 72 + query_state = json |> member "queryState" |> to_string; 73 + can_calculate_changes = json |> member "canCalculateChanges" |> to_bool; 74 + position = json |> member "position" |> to_int; 75 + ids = json |> member "ids" |> to_list |> List.map to_string; 76 + total = json |> member "total" |> to_int_option; 77 + } 78 + end 79 + 80 + (** Email/get response *) 81 + module Email_get_response = struct 82 + type t = { 83 + account_id : string; 84 + state : string; 85 + list : Yojson.Safe.t list; 86 + not_found : string list; 87 + } 88 + 89 + let emails t = t.list 90 + let state t = t.state 91 + let not_found t = t.not_found 92 + let account_id t = t.account_id 93 + 94 + let of_json json = 95 + let open Yojson.Safe.Util in 96 + { 97 + account_id = json |> member "accountId" |> to_string; 98 + state = json |> member "state" |> to_string; 99 + list = json |> member "list" |> to_list; 100 + not_found = json |> member "notFound" |> to_list |> List.map to_string; 101 + } 102 + end 103 + 104 + (** Mailbox/get response *) 105 + module Mailbox_get_response = struct 106 + type t = { 107 + account_id : string; 108 + state : string; 109 + list : Yojson.Safe.t list; 110 + not_found : string list; 111 + } 112 + 113 + let mailboxes t = t.list 114 + let state t = t.state 115 + let not_found t = t.not_found 116 + let account_id t = t.account_id 117 + 118 + let of_json json = 119 + let open Yojson.Safe.Util in 120 + { 121 + account_id = json |> member "accountId" |> to_string; 122 + state = json |> member "state" |> to_string; 123 + list = json |> member "list" |> to_list; 124 + not_found = json |> member "notFound" |> to_list |> List.map to_string; 125 + } 126 + end 127 + 128 + (** Identity/get response *) 129 + module Identity_get_response = struct 130 + type t = { 131 + account_id : string; 132 + state : string; 133 + list : Yojson.Safe.t list; 134 + not_found : string list; 135 + } 136 + 137 + let identities t = t.list 138 + let state t = t.state 139 + let not_found t = t.not_found 140 + let account_id t = t.account_id 141 + 142 + let of_json json = 143 + let open Yojson.Safe.Util in 144 + { 145 + account_id = json |> member "accountId" |> to_string; 146 + state = json |> member "state" |> to_string; 147 + list = json |> member "list" |> to_list; 148 + not_found = json |> member "notFound" |> to_list |> List.map to_string; 149 + } 150 + end 151 + 152 + (** {1 Argument Types} *) 153 + 154 + module Email_query_args = struct 155 + type t = { 156 + account_id : string; 157 + filter : Jmap.Methods.Filter.t option; 158 + sort : Jmap.Methods.Comparator.t list option; 159 + position : int; 160 + limit : int option; 161 + } 162 + 163 + let create ~account_id ?filter ?sort ?(position=0) ?limit () = 164 + { account_id; filter; sort; position; limit } 165 + 166 + let to_json t = 167 + let fields = [ 168 + ("accountId", `String t.account_id); 169 + ("position", `Int t.position); 170 + ] in 171 + let fields = match t.filter with 172 + | Some f -> ("filter", Jmap.Methods.Filter.to_json f) :: fields 173 + | None -> fields 174 + in 175 + let fields = match t.sort with 176 + | Some sorts -> 177 + let sort_json = `List (List.map (fun c -> 178 + `Assoc [ 179 + ("property", `String (Jmap.Methods.Comparator.property c)); 180 + ("isAscending", match Jmap.Methods.Comparator.is_ascending c with 181 + | Some b -> `Bool b 182 + | None -> `Bool false); 183 + ] 184 + ) sorts) in 185 + ("sort", sort_json) :: fields 186 + | None -> fields 187 + in 188 + let fields = match t.limit with 189 + | Some l -> ("limit", `Int l) :: fields 190 + | None -> fields 191 + in 192 + `Assoc fields 193 + end 194 + 195 + module Email_get_args = struct 196 + type t = { 197 + account_id : string; 198 + ids : string list; 199 + properties : string list option; 200 + } 201 + 202 + let create ~account_id ~ids ?properties () = 203 + { account_id; ids; properties } 204 + 205 + let to_json t = 206 + let fields = [ 207 + ("accountId", `String t.account_id); 208 + ("ids", `List (List.map (fun id -> `String id) t.ids)); 209 + ] in 210 + let fields = match t.properties with 211 + | Some props -> ("properties", `List (List.map (fun p -> `String p) props)) :: fields 212 + | None -> fields 213 + in 214 + `Assoc fields 215 + end 216 + 217 + module Mailbox_get_args = struct 218 + type t = { 219 + account_id : string; 220 + ids : string list option; 221 + properties : string list option; 222 + } 223 + 224 + let create ~account_id ?ids ?properties () = 225 + { account_id; ids; properties } 226 + 227 + let to_json t = 228 + let fields = [ 229 + ("accountId", `String t.account_id); 230 + ] in 231 + let fields = match t.ids with 232 + | Some ids -> ("ids", `List (List.map (fun id -> `String id) ids)) :: fields 233 + | None -> ("ids", `Null) :: fields 234 + in 235 + let fields = match t.properties with 236 + | Some props -> ("properties", `List (List.map (fun p -> `String p) props)) :: fields 237 + | None -> fields 238 + in 239 + `Assoc fields 240 + end 241 + 242 + module Identity_get_args = struct 243 + type t = { 244 + account_id : string; 245 + ids : string list option; 246 + properties : string list option; 247 + } 248 + 249 + let create ~account_id ?ids ?properties () = 250 + { account_id; ids; properties } 251 + 252 + let to_json t = 253 + let fields = [ 254 + ("accountId", `String t.account_id); 255 + ] in 256 + let fields = match t.ids with 257 + | Some ids -> ("ids", `List (List.map (fun id -> `String id) ids)) :: fields 258 + | None -> ("ids", `Null) :: fields 259 + in 260 + let fields = match t.properties with 261 + | Some props -> ("properties", `List (List.map (fun p -> `String p) props)) :: fields 262 + | None -> fields 263 + in 264 + `Assoc fields 265 + end 266 + 267 + (** {1 Method Constructors} *) 268 + 269 + let core_echo ?data () = 270 + let data = match data with 271 + | Some d -> d 272 + | None -> `Assoc [] 273 + in 274 + make_method_sig 275 + ~method_name:"Core/echo" 276 + ~args:data 277 + ~args_to_json:(fun x -> x) 278 + ~resp_of_json:(fun x -> x) 279 + 280 + let email_query ~account_id ?filter ?sort ?position ?limit () = 281 + let args = Email_query_args.create ~account_id ?filter ?sort ?position ?limit () in 282 + make_method_sig 283 + ~method_name:"Email/query" 284 + ~args 285 + ~args_to_json:Email_query_args.to_json 286 + ~resp_of_json:Email_query_response.of_json 287 + 288 + let email_get ~account_id ~ids ?properties () = 289 + let args = Email_get_args.create ~account_id ~ids ?properties () in 290 + make_method_sig 291 + ~method_name:"Email/get" 292 + ~args 293 + ~args_to_json:Email_get_args.to_json 294 + ~resp_of_json:Email_get_response.of_json 295 + 296 + let mailbox_get ~account_id ?ids ?properties () = 297 + let args = Mailbox_get_args.create ~account_id ?ids ?properties () in 298 + make_method_sig 299 + ~method_name:"Mailbox/get" 300 + ~args 301 + ~args_to_json:Mailbox_get_args.to_json 302 + ~resp_of_json:Mailbox_get_response.of_json 303 + 304 + let mailbox_get_all ~account_id () = 305 + mailbox_get ~account_id ?ids:None () 306 + 307 + let identity_get ~account_id ?ids ?properties () = 308 + let args = Identity_get_args.create ~account_id ?ids ?properties () in 309 + make_method_sig 310 + ~method_name:"Identity/get" 311 + ~args 312 + ~args_to_json:Identity_get_args.to_json 313 + ~resp_of_json:Identity_get_response.of_json 314 + 315 + let identity_get_all ~account_id () = 316 + identity_get ~account_id ?ids:None () 317 + 318 + (** {1 Chain Processing} *) 319 + 320 + (** Convert a method chain to a list of invocations *) 321 + let rec chain_to_invocations : type a. a method_chain -> Jmap.Protocol.Wire.Invocation.t list = function 322 + | Empty -> [] 323 + | Cons (method_sig, rest) -> 324 + let invocation = Jmap.Protocol.Wire.Invocation.v 325 + ~method_name:method_sig.method_name 326 + ~arguments:(method_sig.args_to_json method_sig.args) 327 + ~method_call_id:method_sig.call_id 328 + () 329 + in 330 + invocation :: chain_to_invocations rest 331 + 332 + 333 + (** Parse responses in the correct order matching the chain structure *) 334 + let rec parse_responses : type a. 335 + a method_chain -> 336 + Jmap.Protocol.Wire.Response.t -> 337 + (a, string) result = 338 + fun chain response -> 339 + match chain with 340 + | Empty -> Ok () 341 + | Cons (method_sig, rest) -> 342 + (* Extract the method response for this call *) 343 + match Jmap_unix.Response.extract_method 344 + ~method_name:method_sig.method_name 345 + ~method_call_id:method_sig.call_id response with 346 + | Ok response_args -> 347 + (try 348 + let parsed_response = method_sig.resp_of_json response_args in 349 + match parse_responses rest response with 350 + | Ok rest_responses -> Ok (parsed_response, rest_responses) 351 + | Error e -> Error e 352 + with 353 + | exn -> Error ("Failed to parse " ^ method_sig.method_name ^ ": " ^ Printexc.to_string exn)) 354 + | Error jmap_error -> 355 + (* Convert JMAP error to string for now *) 356 + Error ("JMAP error in " ^ method_sig.method_name ^ ": " ^ 357 + Jmap.Protocol.Error.error_to_string jmap_error) 358 + 359 + let to_request chain = 360 + let invocations = chain_to_invocations chain in 361 + Jmap.Protocol.Wire.Request.v 362 + ~using:["urn:ietf:params:jmap:core"; "urn:ietf:params:jmap:mail"] 363 + ~method_calls:invocations 364 + () 365 + 366 + let execute env ctx chain = 367 + let request = to_request chain in 368 + match Jmap_unix.request env ctx request with 369 + | Ok response -> parse_responses chain response 370 + | Error jmap_error -> Error (Jmap.Protocol.Error.error_to_string jmap_error) 371 + 372 + (** {1 Utility Functions} *) 373 + 374 + let rec chain_length : type a. a method_chain -> int = function 375 + | Empty -> 0 376 + | Cons (_, rest) -> 1 + chain_length rest 377 + 378 + let rec method_names : type a. a method_chain -> string list = function 379 + | Empty -> [] 380 + | Cons (method_sig, rest) -> 381 + method_sig.method_name :: method_names rest
+325
jmap/jmap-dsl/jmap_dsl.mli
··· 1 + (** Type-safe JMAP method chaining DSL. 2 + 3 + This library provides a type-safe way to chain JMAP method calls 4 + with automatic response deserialization. Inspired by Ctypes, it uses 5 + GADTs to track method signatures and response types at compile time. 6 + 7 + The design separates method definition from execution, allowing for 8 + flexible composition while maintaining type safety. 9 + 10 + Example usage: 11 + {[ 12 + let request = 13 + email_query ~account_id ~filter () @> 14 + mailbox_get_all ~account_id () @> 15 + done_ 16 + 17 + match execute env ctx request with 18 + | Ok (query_resp, mailbox_list) -> 19 + (* Both responses are properly typed *) 20 + let emails = Email_query_response.ids query_resp in 21 + let mailboxes = Mailbox_list.items mailbox_list in 22 + ... 23 + ]} 24 + 25 + @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-3> RFC 8620, Section 3 *) 26 + 27 + (** {1 Core Types} *) 28 + 29 + (** A method signature that describes a JMAP method call. 30 + 31 + ['args] is the argument type for the method 32 + ['resp] is the expected response type from the server *) 33 + type ('args, 'resp) method_sig 34 + 35 + (** A method chain that represents a sequence of JMAP method calls. 36 + 37 + ['responses] is a type that describes the shape of all responses. 38 + For a single method, this is just the response type. 39 + For multiple methods, this becomes a nested tuple of response types. *) 40 + type 'responses method_chain 41 + 42 + (** The empty method chain - starting point for building requests *) 43 + val empty : unit method_chain 44 + 45 + (** Chain a method onto an existing chain. 46 + 47 + This is the core combinator - it extends a chain with one more method 48 + and updates the response type to include the new method's response. 49 + 50 + @param chain The existing method chain 51 + @param method_call A method signature with its arguments 52 + @return Extended chain with updated response type *) 53 + val (@>) : 'a method_chain -> ('args, 'resp) method_sig -> ('resp * 'a) method_chain 54 + 55 + (** Alias for empty chain to improve readability *) 56 + val start : unit method_chain 57 + 58 + (** End marker for chains (optional, for readability) *) 59 + val done_ : 'a method_chain -> 'a method_chain 60 + 61 + (** {1 Method Execution} *) 62 + 63 + (** Execute a method chain and return typed responses. 64 + 65 + This converts the method chain into a JMAP request, sends it, 66 + and automatically deserializes the responses to the correct types. 67 + 68 + @param env The Eio environment for network operations 69 + @param ctx The JMAP connection context 70 + @param chain The method chain to execute 71 + @return Typed responses matching the chain structure *) 72 + val execute : 73 + < net : 'a Eio.Net.t ; .. > -> 74 + Jmap_unix.context -> 75 + 'responses method_chain -> 76 + ('responses, string) result 77 + 78 + (** {1 Core JMAP Methods} *) 79 + 80 + (** Core/echo method for testing connectivity. 81 + 82 + @param data Optional data to echo back (defaults to empty object) 83 + @return Method signature for Core/echo *) 84 + val core_echo : 85 + ?data:Yojson.Safe.t -> 86 + unit -> 87 + (Yojson.Safe.t, Yojson.Safe.t) method_sig 88 + 89 + (** {1 Email Methods} *) 90 + 91 + (** Arguments for Email/query method *) 92 + module Email_query_args : sig 93 + type t 94 + 95 + (** Create Email/query arguments. 96 + @param account_id The account to query 97 + @param filter Optional filter conditions 98 + @param sort Optional sort criteria 99 + @param position Starting position (default 0) 100 + @param limit Maximum results (default server limit) 101 + @return Query arguments *) 102 + val create : 103 + account_id:string -> 104 + ?filter:Jmap.Methods.Filter.t -> 105 + ?sort:Jmap.Methods.Comparator.t list -> 106 + ?position:int -> 107 + ?limit:int -> 108 + unit -> 109 + t 110 + end 111 + 112 + (** Response from Email/query method *) 113 + module Email_query_response : sig 114 + type t 115 + 116 + (** Get the email IDs from query response *) 117 + val ids : t -> string list 118 + 119 + (** Get query state for synchronization *) 120 + val query_state : t -> string 121 + 122 + (** Get total count if requested *) 123 + val total : t -> int option 124 + 125 + (** Get current position in results *) 126 + val position : t -> int 127 + 128 + (** Check if changes can be calculated *) 129 + val can_calculate_changes : t -> bool 130 + end 131 + 132 + (** Email/query method. 133 + @param account_id The account to query 134 + @param filter Optional filter conditions 135 + @param sort Optional sort criteria 136 + @param position Starting position (default 0) 137 + @param limit Maximum results (default server limit) 138 + @return Method signature for Email/query *) 139 + val email_query : 140 + account_id:string -> 141 + ?filter:Jmap.Methods.Filter.t -> 142 + ?sort:Jmap.Methods.Comparator.t list -> 143 + ?position:int -> 144 + ?limit:int -> 145 + unit -> 146 + (Email_query_args.t, Email_query_response.t) method_sig 147 + 148 + (** Arguments for Email/get method *) 149 + module Email_get_args : sig 150 + type t 151 + 152 + (** Create Email/get arguments. 153 + @param account_id The account to get from 154 + @param ids List of email IDs to fetch 155 + @param properties Optional properties to fetch (default all) 156 + @return Get arguments *) 157 + val create : 158 + account_id:string -> 159 + ids:string list -> 160 + ?properties:string list -> 161 + unit -> 162 + t 163 + end 164 + 165 + (** Response from Email/get method *) 166 + module Email_get_response : sig 167 + type t 168 + 169 + (** Get the list of email objects *) 170 + val emails : t -> Yojson.Safe.t list 171 + 172 + (** Get the current state token *) 173 + val state : t -> string 174 + 175 + (** Get list of IDs that were not found *) 176 + val not_found : t -> string list 177 + 178 + (** Get the account ID this response is for *) 179 + val account_id : t -> string 180 + end 181 + 182 + (** Email/get method. 183 + @param account_id The account to get from 184 + @param ids List of email IDs to fetch 185 + @param properties Optional properties to fetch (default all) 186 + @return Method signature for Email/get *) 187 + val email_get : 188 + account_id:string -> 189 + ids:string list -> 190 + ?properties:string list -> 191 + unit -> 192 + (Email_get_args.t, Email_get_response.t) method_sig 193 + 194 + (** {1 Mailbox Methods} *) 195 + 196 + (** Arguments for Mailbox/get method *) 197 + module Mailbox_get_args : sig 198 + type t 199 + 200 + (** Create Mailbox/get arguments. 201 + @param account_id The account to get from 202 + @param ids Optional list of mailbox IDs (default gets all) 203 + @param properties Optional properties to fetch (default all) 204 + @return Get arguments *) 205 + val create : 206 + account_id:string -> 207 + ?ids:string list -> 208 + ?properties:string list -> 209 + unit -> 210 + t 211 + end 212 + 213 + (** Response from Mailbox/get method *) 214 + module Mailbox_get_response : sig 215 + type t 216 + 217 + (** Get the list of mailbox objects *) 218 + val mailboxes : t -> Yojson.Safe.t list 219 + 220 + (** Get the current state token *) 221 + val state : t -> string 222 + 223 + (** Get list of IDs that were not found *) 224 + val not_found : t -> string list 225 + 226 + (** Get the account ID this response is for *) 227 + val account_id : t -> string 228 + end 229 + 230 + (** Mailbox/get method. 231 + @param account_id The account to get from 232 + @param ids Optional list of mailbox IDs (default gets all) 233 + @param properties Optional properties to fetch (default all) 234 + @return Method signature for Mailbox/get *) 235 + val mailbox_get : 236 + account_id:string -> 237 + ?ids:string list -> 238 + ?properties:string list -> 239 + unit -> 240 + (Mailbox_get_args.t, Mailbox_get_response.t) method_sig 241 + 242 + (** Convenience method to get all mailboxes for an account. 243 + @param account_id The account to get mailboxes for 244 + @return Method signature for Mailbox/get with no ID filter *) 245 + val mailbox_get_all : 246 + account_id:string -> 247 + unit -> 248 + (Mailbox_get_args.t, Mailbox_get_response.t) method_sig 249 + 250 + (** {1 Identity Methods} *) 251 + 252 + (** Arguments for Identity/get method *) 253 + module Identity_get_args : sig 254 + type t 255 + 256 + (** Create Identity/get arguments. 257 + @param account_id The account to get from 258 + @param ids Optional list of identity IDs (default gets all) 259 + @param properties Optional properties to fetch (default all) 260 + @return Get arguments *) 261 + val create : 262 + account_id:string -> 263 + ?ids:string list -> 264 + ?properties:string list -> 265 + unit -> 266 + t 267 + end 268 + 269 + (** Response from Identity/get method *) 270 + module Identity_get_response : sig 271 + type t 272 + 273 + (** Get the list of identity objects *) 274 + val identities : t -> Yojson.Safe.t list 275 + 276 + (** Get the current state token *) 277 + val state : t -> string 278 + 279 + (** Get list of IDs that were not found *) 280 + val not_found : t -> string list 281 + 282 + (** Get the account ID this response is for *) 283 + val account_id : t -> string 284 + end 285 + 286 + (** Identity/get method. 287 + @param account_id The account to get from 288 + @param ids Optional list of identity IDs (default gets all) 289 + @param properties Optional properties to fetch (default all) 290 + @return Method signature for Identity/get *) 291 + val identity_get : 292 + account_id:string -> 293 + ?ids:string list -> 294 + ?properties:string list -> 295 + unit -> 296 + (Identity_get_args.t, Identity_get_response.t) method_sig 297 + 298 + (** Convenience method to get all identities for an account. 299 + @param account_id The account to get identities for 300 + @return Method signature for Identity/get with no ID filter *) 301 + val identity_get_all : 302 + account_id:string -> 303 + unit -> 304 + (Identity_get_args.t, Identity_get_response.t) method_sig 305 + 306 + (** {1 Utility Functions} *) 307 + 308 + (** Convert a method chain to a raw JMAP request. 309 + 310 + This is useful for debugging or for integrating with existing 311 + code that expects raw requests. 312 + 313 + @param chain The method chain to convert 314 + @return Raw JMAP request object *) 315 + val to_request : 'responses method_chain -> Jmap.Protocol.Wire.Request.t 316 + 317 + (** Get the number of methods in a chain. 318 + @param chain The method chain 319 + @return Number of method calls *) 320 + val chain_length : 'responses method_chain -> int 321 + 322 + (** Extract method names from a chain for debugging. 323 + @param chain The method chain 324 + @return List of method names in order *) 325 + val method_names : 'responses method_chain -> string list
+37
jmap/jmap-sigs.opam
··· 1 + # This file is generated by dune, edit dune-project instead 2 + opam-version: "2.0" 3 + synopsis: "Module type signatures for JMAP implementations" 4 + description: """ 5 + This package provides standard module type signatures for JMAP (RFC 8620/8621) 6 + implementations in OCaml. It defines consistent interfaces for JSON serialization, 7 + pretty-printing with Fmt, RFC compliance tracking, and JMAP-specific patterns 8 + like method arguments/responses and patchable objects. 9 + 10 + These signatures ensure consistency across JMAP libraries and make it easy to 11 + identify missing implementations.""" 12 + maintainer: ["JMAP OCaml Maintainers"] 13 + authors: ["JMAP OCaml Contributors"] 14 + license: "ISC" 15 + homepage: "https://github.com/example/ocaml-jmap" 16 + bug-reports: "https://github.com/example/ocaml-jmap/issues" 17 + depends: [ 18 + "dune" {>= "2.9"} 19 + "ocaml" {>= "4.08.0"} 20 + "yojson" {>= "1.7.0"} 21 + "fmt" {>= "0.8.0"} 22 + "odoc" {with-doc} 23 + ] 24 + build: [ 25 + ["dune" "subst"] {dev} 26 + [ 27 + "dune" 28 + "build" 29 + "-p" 30 + name 31 + "-j" 32 + jobs 33 + "@install" 34 + "@runtest" {with-test} 35 + "@doc" {with-doc} 36 + ] 37 + ]
+6
jmap/jmap-sigs/dune
··· 1 + (library 2 + (public_name jmap-sigs) 3 + (name jmap_sigs) 4 + (synopsis "Module type signatures for JMAP implementations") 5 + (libraries yojson fmt) 6 + (flags (:standard -w -49))) ; Disable warning about unused module types
+134
jmap/jmap-sigs/jmap_sigs.ml
··· 1 + (** JMAP Module Type Signatures implementation *) 2 + 3 + (** Core Signatures *) 4 + 5 + module type JSONABLE = sig 6 + type t 7 + val to_json : t -> Yojson.Safe.t 8 + val of_json : Yojson.Safe.t -> t 9 + end 10 + 11 + module type PRINTABLE = sig 12 + type t 13 + val pp : Format.formatter -> t -> unit 14 + val pp_hum : Format.formatter -> t -> unit 15 + end 16 + 17 + (** Wire Protocol Signatures *) 18 + 19 + module type WIRE_TYPE = sig 20 + type t 21 + include JSONABLE with type t := t 22 + include PRINTABLE with type t := t 23 + val validate : t -> (unit, string) result 24 + end 25 + 26 + (** JMAP Object Signatures *) 27 + 28 + module type JMAP_OBJECT = sig 29 + type t 30 + type id_type = string 31 + include JSONABLE with type t := t 32 + include PRINTABLE with type t := t 33 + val id : t -> id_type option 34 + val create : ?id:id_type -> unit -> t 35 + val to_json_with_properties : properties:string list -> t -> Yojson.Safe.t 36 + val valid_properties : unit -> string list 37 + end 38 + 39 + (** Method Signatures *) 40 + 41 + module type METHOD_ARGS = sig 42 + type t 43 + type account_id = string 44 + include JSONABLE with type t := t 45 + include PRINTABLE with type t := t 46 + val account_id : t -> account_id 47 + val validate : t -> (unit, string) result 48 + val method_name : unit -> string 49 + end 50 + 51 + module type METHOD_RESPONSE = sig 52 + type t 53 + type account_id = string 54 + type state = string 55 + include JSONABLE with type t := t 56 + include PRINTABLE with type t := t 57 + val account_id : t -> account_id 58 + val state : t -> state option 59 + val is_error : t -> bool 60 + end 61 + 62 + (** Collection Signatures *) 63 + 64 + module type COLLECTION = sig 65 + type t 66 + type item 67 + include JSONABLE with type t := t 68 + include PRINTABLE with type t := t 69 + val items : t -> item list 70 + val total : t -> int option 71 + val create : items:item list -> ?total:int -> unit -> t 72 + val map : (item -> item) -> t -> t 73 + val filter : (item -> bool) -> t -> t 74 + end 75 + 76 + (** Error Handling Signatures *) 77 + 78 + module type ERROR_TYPE = sig 79 + type t 80 + include JSONABLE with type t := t 81 + include PRINTABLE with type t := t 82 + val error_type : t -> string 83 + val description : t -> string option 84 + val create : error_type:string -> ?description:string -> unit -> t 85 + end 86 + 87 + (** RFC Compliance Signatures *) 88 + 89 + module type RFC_COMPLIANT = sig 90 + type t 91 + val rfc_section : unit -> string 92 + val rfc_url : unit -> string 93 + val implementation_notes : unit -> string list 94 + val is_complete : unit -> bool 95 + val unimplemented_features : unit -> string list 96 + end 97 + 98 + (** Vendor Extension Signatures *) 99 + 100 + module type VENDOR_EXTENSION = sig 101 + type t 102 + include JSONABLE with type t := t 103 + include PRINTABLE with type t := t 104 + val vendor : unit -> string 105 + val extension_name : unit -> string 106 + val capability_uri : unit -> string option 107 + val is_experimental : unit -> bool 108 + end 109 + 110 + (** Patch Operations Signatures *) 111 + 112 + module type PATCHABLE = sig 113 + type t 114 + type patch 115 + include JSONABLE with type t := t 116 + val create_patch : from:t -> to_:t -> patch 117 + val apply_patch : patch:patch -> t -> (t, string) result 118 + val patch_to_operations : patch -> (string * Yojson.Safe.t) list 119 + end 120 + 121 + (** Composite Signatures *) 122 + 123 + module type FULL_JMAP_OBJECT = sig 124 + include JMAP_OBJECT 125 + include PATCHABLE with type t := t 126 + include RFC_COMPLIANT with type t := t 127 + end 128 + 129 + module type JMAP_METHOD = sig 130 + val name : unit -> string 131 + module Args : METHOD_ARGS 132 + module Response : METHOD_RESPONSE 133 + val execute : Args.t -> (Response.t, string) result 134 + end
+342
jmap/jmap-sigs/jmap_sigs.mli
··· 1 + (** JMAP Module Type Signatures. 2 + 3 + This module defines the standard module type signatures used throughout 4 + the JMAP implementation. These signatures ensure consistency across all 5 + JMAP types and provide a discoverable interface for developers. 6 + 7 + All wire protocol types, data objects, and method arguments/responses 8 + should conform to these signatures as appropriate. 9 + 10 + @see <https://www.rfc-editor.org/rfc/rfc8620.html> RFC 8620 - The JSON Meta Application Protocol (JMAP) 11 + @see <https://www.rfc-editor.org/rfc/rfc8621.html> RFC 8621 - JMAP Mail *) 12 + 13 + (** {1 Core Signatures} *) 14 + 15 + (** Signature for types that can be serialized to/from JSON. 16 + 17 + This is the fundamental signature for any type that needs to be 18 + transmitted over the JMAP wire protocol or stored as JSON. *) 19 + module type JSONABLE = sig 20 + type t 21 + 22 + (** Convert to JSON representation. 23 + @return JSON representation suitable for wire transmission *) 24 + val to_json : t -> Yojson.Safe.t 25 + 26 + (** Parse from JSON representation. 27 + @param json The JSON value to parse 28 + @return The parsed value 29 + @raise Failure if JSON structure is invalid or required fields are missing *) 30 + val of_json : Yojson.Safe.t -> t 31 + end 32 + 33 + (** Signature for types that can be pretty-printed using Fmt. 34 + 35 + This provides composable formatting for debugging, logging, and 36 + human-readable output. Using Fmt allows for better integration 37 + with logging libraries and testing frameworks. *) 38 + module type PRINTABLE = sig 39 + type t 40 + 41 + (** Pretty-printer for the type. 42 + @param ppf The formatter to write to 43 + @param t The value to print *) 44 + val pp : Format.formatter -> t -> unit 45 + 46 + (** Alternative name for pp, following Fmt conventions *) 47 + val pp_hum : Format.formatter -> t -> unit 48 + end 49 + 50 + (** {1 Wire Protocol Signatures} *) 51 + 52 + (** Signature for JMAP wire protocol types. 53 + 54 + Types that travel over HTTP as part of the JMAP protocol should 55 + implement this signature. This includes requests, responses, and 56 + all their component parts. *) 57 + module type WIRE_TYPE = sig 58 + type t 59 + 60 + include JSONABLE with type t := t 61 + include PRINTABLE with type t := t 62 + 63 + (** Validate the structure according to JMAP constraints. 64 + @return Ok () if valid, Error with description if invalid *) 65 + val validate : t -> (unit, string) result 66 + end 67 + 68 + (** {1 JMAP Object Signatures} *) 69 + 70 + (** Signature for JMAP data objects. 71 + 72 + This signature is for the core JMAP data types like Email, Mailbox, 73 + Thread, Identity, etc. These objects have IDs and support property 74 + selection for efficient data transfer. *) 75 + module type JMAP_OBJECT = sig 76 + type t 77 + type id_type = string (* Jmap_types.id *) 78 + 79 + include JSONABLE with type t := t 80 + include PRINTABLE with type t := t 81 + 82 + (** Get the object's identifier. 83 + @return The object ID if present (may be None for unsaved objects) *) 84 + val id : t -> id_type option 85 + 86 + (** Create a minimal valid object. 87 + @param id Optional identifier for the object 88 + @return A new object with default/empty values for optional fields *) 89 + val create : ?id:id_type -> unit -> t 90 + 91 + (** Serialize to JSON with only specified properties. 92 + 93 + This is used to implement the JMAP properties selection mechanism, 94 + allowing clients to request only the fields they need. 95 + 96 + @param properties List of property names to include 97 + @param t The object to serialize 98 + @return JSON with only the requested properties *) 99 + val to_json_with_properties : properties:string list -> t -> Yojson.Safe.t 100 + 101 + (** Get the list of all valid property names for this object type. 102 + @return List of property names that can be requested *) 103 + val valid_properties : unit -> string list 104 + end 105 + 106 + (** {1 Method Signatures} *) 107 + 108 + (** Signature for JMAP method argument types. 109 + 110 + All JMAP method calls take an arguments object. This signature 111 + ensures consistency across all method argument types. *) 112 + module type METHOD_ARGS = sig 113 + type t 114 + type account_id = string (* Jmap_types.id *) 115 + 116 + include JSONABLE with type t := t 117 + include PRINTABLE with type t := t 118 + 119 + (** Get the account ID these arguments apply to. 120 + @return The account ID for this method call *) 121 + val account_id : t -> account_id 122 + 123 + (** Validate arguments according to JMAP method constraints. 124 + @return Ok () if valid, Error with description if invalid *) 125 + val validate : t -> (unit, string) result 126 + 127 + (** Get the method name these arguments are for. 128 + @return The JMAP method name (e.g., "Email/get") *) 129 + val method_name : unit -> string 130 + end 131 + 132 + (** Signature for JMAP method response types. 133 + 134 + All JMAP method responses follow a similar pattern with account IDs 135 + and state tokens for synchronization. *) 136 + module type METHOD_RESPONSE = sig 137 + type t 138 + type account_id = string (* Jmap_types.id *) 139 + type state = string 140 + 141 + include JSONABLE with type t := t 142 + include PRINTABLE with type t := t 143 + 144 + (** Get the account ID this response applies to. 145 + @return The account ID for this response *) 146 + val account_id : t -> account_id 147 + 148 + (** Get the state token for synchronization. 149 + @return The state token if present (used for changes/updates) *) 150 + val state : t -> state option 151 + 152 + (** Check if this response indicates an error condition. 153 + @return true if this is an error response *) 154 + val is_error : t -> bool 155 + end 156 + 157 + (** {1 Collection Signatures} *) 158 + 159 + (** Signature for types that represent collections of JMAP objects. 160 + 161 + This is used for query results, batch operations, and any other 162 + operation that deals with multiple objects. *) 163 + module type COLLECTION = sig 164 + type t 165 + type item 166 + 167 + include JSONABLE with type t := t 168 + include PRINTABLE with type t := t 169 + 170 + (** Get the items in the collection. 171 + @return List of items in the collection *) 172 + val items : t -> item list 173 + 174 + (** Get the total count of items (may be different from length of items). 175 + @return Total count if known *) 176 + val total : t -> int option 177 + 178 + (** Create a collection from a list of items. 179 + @param items The items to include 180 + @param total Optional total count (for paginated results) 181 + @return A new collection *) 182 + val create : items:item list -> ?total:int -> unit -> t 183 + 184 + (** Map a function over the items in the collection. 185 + @param f Function to apply to each item 186 + @return New collection with transformed items *) 187 + val map : (item -> item) -> t -> t 188 + 189 + (** Filter items in the collection. 190 + @param f Predicate function 191 + @return New collection with only items where f returns true *) 192 + val filter : (item -> bool) -> t -> t 193 + end 194 + 195 + (** {1 Error Handling Signatures} *) 196 + 197 + (** Signature for JMAP error types. 198 + 199 + JMAP has a structured error model with specific error codes 200 + and optional additional properties. *) 201 + module type ERROR_TYPE = sig 202 + type t 203 + 204 + include JSONABLE with type t := t 205 + include PRINTABLE with type t := t 206 + 207 + (** Get the JMAP error type string. 208 + @return The error type (e.g., "accountNotFound", "invalidArguments") *) 209 + val error_type : t -> string 210 + 211 + (** Get the human-readable error description. 212 + @return Optional description of the error *) 213 + val description : t -> string option 214 + 215 + (** Create an error with the given type and description. 216 + @param error_type The JMAP error type 217 + @param description Optional human-readable description 218 + @return A new error *) 219 + val create : error_type:string -> ?description:string -> unit -> t 220 + end 221 + 222 + (** {1 RFC Compliance Signatures} *) 223 + 224 + (** Signature for types that implement specific RFC sections. 225 + 226 + This provides metadata about which parts of the JMAP RFCs 227 + are implemented by each type, making it easier to track 228 + compliance and find documentation. *) 229 + module type RFC_COMPLIANT = sig 230 + type t 231 + 232 + (** Get the RFC section this type implements. 233 + @return RFC section reference (e.g., "RFC 8620, Section 5.1") *) 234 + val rfc_section : unit -> string 235 + 236 + (** Get the URL to the RFC section. 237 + @return Direct URL to the RFC section *) 238 + val rfc_url : unit -> string 239 + 240 + (** Get implementation notes specific to this OCaml binding. 241 + @return List of implementation notes and decisions *) 242 + val implementation_notes : unit -> string list 243 + 244 + (** Check if this implementation is complete. 245 + @return true if all required RFC features are implemented *) 246 + val is_complete : unit -> bool 247 + 248 + (** Get list of unimplemented features from the RFC. 249 + @return List of features not yet implemented *) 250 + val unimplemented_features : unit -> string list 251 + end 252 + 253 + (** {1 Vendor Extension Signatures} *) 254 + 255 + (** Signature for vendor-specific extensions to JMAP. 256 + 257 + Vendors like Fastmail, Cyrus, etc. may add custom properties 258 + or methods. This signature helps track these extensions. *) 259 + module type VENDOR_EXTENSION = sig 260 + type t 261 + 262 + include JSONABLE with type t := t 263 + include PRINTABLE with type t := t 264 + 265 + (** Get the vendor namespace. 266 + @return The vendor identifier (e.g., "com.fastmail") *) 267 + val vendor : unit -> string 268 + 269 + (** Get the extension name. 270 + @return The name of this extension *) 271 + val extension_name : unit -> string 272 + 273 + (** Get the capability URI for this extension. 274 + @return The capability URI if this adds a new capability *) 275 + val capability_uri : unit -> string option 276 + 277 + (** Check if this extension is experimental. 278 + @return true if this is an experimental/unstable extension *) 279 + val is_experimental : unit -> bool 280 + end 281 + 282 + (** {1 Patch Operations Signatures} *) 283 + 284 + (** Signature for types that support JSON Patch operations. 285 + 286 + JMAP uses a subset of JSON Patch (RFC 6902) for partial updates. 287 + This signature is for types that can generate or apply patches. *) 288 + module type PATCHABLE = sig 289 + type t 290 + type patch 291 + 292 + include JSONABLE with type t := t 293 + 294 + (** Create a patch to transform one value into another. 295 + @param from The original value 296 + @param to_ The target value 297 + @return A patch that transforms from to to_ *) 298 + val create_patch : from:t -> to_:t -> patch 299 + 300 + (** Apply a patch to a value. 301 + @param patch The patch to apply 302 + @param t The value to patch 303 + @return Ok with patched value or Error if patch cannot be applied *) 304 + val apply_patch : patch:patch -> t -> (t, string) result 305 + 306 + (** Convert a patch to JSON Pointer operations. 307 + @param patch The patch to convert 308 + @return List of (path, value) pairs for JSON Pointer operations *) 309 + val patch_to_operations : patch -> (string * Yojson.Safe.t) list 310 + end 311 + 312 + (** {1 Composite Signatures} *) 313 + 314 + (** Full signature for a complete JMAP object implementation. 315 + 316 + This combines all the relevant signatures for a fully-featured 317 + JMAP object that supports all standard operations. *) 318 + module type FULL_JMAP_OBJECT = sig 319 + include JMAP_OBJECT 320 + include PATCHABLE with type t := t 321 + include RFC_COMPLIANT with type t := t 322 + end 323 + 324 + (** Full signature for a complete method implementation. 325 + 326 + This represents a complete JMAP method with both request and 327 + response types, following all conventions. *) 328 + module type JMAP_METHOD = sig 329 + (** The method name (e.g., "Email/get") *) 330 + val name : unit -> string 331 + 332 + (** The request arguments type *) 333 + module Args : METHOD_ARGS 334 + 335 + (** The response type *) 336 + module Response : METHOD_RESPONSE 337 + 338 + (** Execute the method (client-side stub). 339 + @param args The method arguments 340 + @return A response or error *) 341 + val execute : Args.t -> (Response.t, string) result 342 + end