this repo has no description
0
fork

Configure Feed

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

vibw

+2790 -1638
+1
jmap/.gitignore
··· 1 1 _build 2 2 .env 3 3 .api-key 4 + .*.swp
+204 -210
jmap/TODO.md
··· 1 - # JMAP Library Improvements - TODO List 1 + # JMAP Library Architecture - TODO List 2 2 3 - Based on comprehensive analysis of the example binaries in `bin/examples/`, this document outlines missing functionality and improvements needed to eliminate all remaining manual JSON handling and provide better developer ergonomics. 3 + ## **Major Architecture Update (January 2025)** 4 4 5 - ## **Status Update (December 2024)** 5 + ### 🔄 **Architecture Pivot: From DSL to ADT-based Design** 6 6 7 - **✅ COMPLETED ITEMS (Phase 1-4 Complete)**: 8 - - Items 1-12 from original TODO list have been implemented 9 - - Core JSON handling, method support, vendor extensions, and utilities are functional 10 - - Apple Mail color flag support, size-based filtering, request builders, and analytics are working 7 + The library has undergone a significant architectural change, moving from a complex GADT-based DSL to a simpler ADT-based approach with abstract types and constructor functions. 11 8 12 - **🔄 REMAINING WORK**: Despite significant progress, **extensive manual JSON parsing still exists** in all example binaries. The analysis below identifies 38+ missing functions needed for complete elimination. 9 + **Previous Architecture (REMOVED)**: 10 + - `jmap-dsl` module with GADT-based method chaining 11 + - Complex type-level programming with `@>` operators 12 + - Automatic method execution and response deserialization 13 + 14 + **New Architecture (IMPLEMENTED)**: 15 + - ADT-based method construction with `Jmap_method` module 16 + - Type-safe response parsing with `Jmap_response` module 17 + - High-level request building with `Jmap_request` module 18 + - Constructor functions with optional arguments and sensible defaults 19 + - Abstract types for better encapsulation 13 20 14 21 --- 15 22 16 - ## **Critical Priority - Remaining JSON Parsing Elimination** 23 + ## **✅ Completed in This Refactoring** 17 24 18 - ### 13. **Method-Specific Request Builders** 25 + ### 1. **Core ADT Infrastructure** 26 + - [x] Removed `jmap-dsl` module completely 27 + - [x] Created `Jmap_method` module with: 28 + - Abstract type `t` for methods 29 + - Constructor functions for all JMAP methods 30 + - Optional arguments with sensible defaults 31 + - Internal JSON serialization 32 + - Basic jmap-sigs METHOD_ARGS integration 33 + - [x] Created `Jmap_response` module with: 34 + - Abstract type `t` for responses 35 + - Pattern matching support via `response_type` 36 + - Typed accessor modules for each method 37 + - Safe extraction functions with Result types 38 + - Full jmap-sigs METHOD_RESPONSE signature compliance 39 + - [x] Created `Jmap_request` module with: 40 + - Type-safe request building 41 + - Method management and call ID generation 42 + - Result reference support 43 + - Wire protocol conversion 19 44 20 - **Problem**: All 10 examples still manually construct method-specific JSON using `Assoc` patterns. 45 + ### 2. **Method Constructors Implemented** 46 + - [x] Core/echo 47 + - [x] Email/query, Email/get, Email/set, Email/changes, Email/copy, Email/import, Email/parse 48 + - [x] Mailbox/query, Mailbox/get, Mailbox/set, Mailbox/changes 49 + - [x] Thread/get, Thread/changes 50 + - [x] Identity/get, Identity/set, Identity/changes 51 + - [x] EmailSubmission/set, EmailSubmission/query, EmailSubmission/get, EmailSubmission/changes 52 + - [x] VacationResponse/get, VacationResponse/set 53 + - [x] SearchSnippet/get 21 54 22 - **Required Functions**: 55 + ### 3. **Response Parsers Implemented** 56 + - [x] All method response types with typed accessors 57 + - [x] Error response handling 58 + - [x] Pattern matching support for response type discrimination 59 + 60 + ### 4. **jmap-sigs Integration & Code Quality (NEW)** 61 + - [x] Fixed all build warnings by implementing missing parser cases 62 + - [x] Removed unused opens and cleaned up code structure 63 + - [x] Applied jmap-sigs METHOD_RESPONSE signature to all response modules 64 + - [x] Simplified interface files using signature includes 65 + - [x] Consistent error handling with Jmap_error.error throughout 66 + - [x] ~29% reduction in jmap_response.mli interface size (364 → 259 lines) 67 + - [x] Clean builds with no warnings: `opam exec -- dune build @check` 68 + - [x] Documentation builds successfully: `opam exec -- dune build @doc` 69 + 70 + --- 71 + 72 + ## **📋 Remaining Work** 73 + 74 + ### Priority 1: **Complete Object Serialization/Deserialization** 75 + 76 + While the ADT infrastructure is in place, the actual JMAP objects (Email, Mailbox, Thread, etc.) still need complete JSON serialization: 77 + 78 + #### **Email Object** 23 79 ```ocaml 24 - (* High-level method request builders *) 25 80 module Jmap_email : sig 26 - module Query : sig 27 - val to_json : Query_args.t -> Yojson.Safe.t 28 - end 29 - 30 - module Get : sig 31 - val to_json : Get_args.t -> Yojson.Safe.t 81 + module Email : sig 82 + type t (* Already exists *) 83 + val of_json : Yojson.Safe.t -> (t, string) result (* TODO *) 84 + val to_json : t -> Yojson.Safe.t (* TODO *) 85 + val to_json_with_properties : properties:string list -> t -> Yojson.Safe.t (* TODO *) 32 86 end 33 87 end 88 + ``` 34 89 90 + #### **Mailbox Object** 91 + ```ocaml 92 + module Jmap_mailbox : sig 93 + type t (* Already exists *) 94 + val of_json : Yojson.Safe.t -> (t, string) result (* TODO *) 95 + val to_json : t -> Yojson.Safe.t (* TODO *) 96 + end 97 + ``` 98 + 99 + #### **Thread Object** 100 + ```ocaml 35 101 module Jmap_thread : sig 36 - module Query : sig 37 - val to_json : Query_args.t -> Yojson.Safe.t 38 - end 39 - 40 - module Get : sig 41 - val to_json : Get_args.t -> Yojson.Safe.t 42 - end 102 + type t (* Already exists *) 103 + val of_json : Yojson.Safe.t -> (t, string) result (* TODO *) 104 + val to_json : t -> Yojson.Safe.t (* TODO *) 43 105 end 44 - 45 - (* Similar patterns for Mailbox, Submission, Identity, VacationResponse *) 46 106 ``` 47 107 48 - **Impact**: Eliminates ~40-50 lines per example. **Critical for all 10 examples.** 49 - 50 - --- 51 - 52 - ### 14. **Result Reference Management** 53 - 54 - **Problem**: All examples manually construct result reference JSON with hardcoded paths. 55 - 56 - **Required Functions**: 108 + #### **Identity Object** 57 109 ```ocaml 58 - (* In jmap/jmap_protocol.ml *) 59 - module Result_reference : sig 60 - type t 61 - 62 - val create : method_call_id:string -> method_name:string -> path:string -> t 63 - val to_json : t -> Yojson.Safe.t 64 - val ids_reference : method_call_id:string -> t (* shorthand for /ids path *) 65 - val list_reference : method_call_id:string -> t (* shorthand for /list path *) 110 + module Jmap_identity : sig 111 + type t (* Already exists *) 112 + val of_json : Yojson.Safe.t -> (t, string) result (* TODO *) 113 + val to_json : t -> Yojson.Safe.t (* TODO *) 66 114 end 67 115 ``` 68 116 69 - **Impact**: Eliminates ~10 lines per example. **Affects all 10 examples.** 70 - 71 - --- 72 - 73 - ### 15. **Method Response Parsers** 74 - 75 - **Problem**: All examples use `Yojson.Safe.Util` for extensive response parsing. 76 - 77 - **Required Functions**: 117 + #### **EmailSubmission Object** 78 118 ```ocaml 79 - (* Response parsing for all method types *) 80 - module Jmap_email : sig 81 - module Query : sig 82 - module Response : sig 83 - val from_json : Yojson.Safe.t -> (t, Error.t) result 84 - val ids : t -> string list 85 - val total : t -> int option 86 - end 87 - end 88 - 89 - module Get : sig 90 - module Response : sig 91 - val from_json : Yojson.Safe.t -> (t, Error.t) result 92 - val emails : t -> Email.t list 93 - val not_found : t -> string list 94 - end 119 + module Jmap_submission : sig 120 + module EmailSubmission : sig 121 + type t (* Already exists *) 122 + val of_json : Yojson.Safe.t -> (t, string) result (* TODO *) 123 + val to_json : t -> Yojson.Safe.t (* TODO *) 95 124 end 96 125 end 126 + ``` 97 127 98 - (* Similar for Thread, Mailbox, Submission, Identity, VacationResponse *) 128 + #### **VacationResponse Object** 129 + ```ocaml 130 + module Jmap_vacation : sig 131 + type t (* Already exists *) 132 + val of_json : Yojson.Safe.t -> (t, string) result (* TODO *) 133 + val to_json : t -> Yojson.Safe.t (* TODO *) 134 + end 99 135 ``` 100 136 101 - **Impact**: Eliminates ~20-30 lines per example. **Affects all 10 examples.** 102 - 103 137 --- 104 138 105 - ### 16. **Object Type Parsers** 139 + ### Priority 2: **Filter and Comparator Serialization** 106 140 107 - **Problem**: Extensive manual parsing of JMAP object types using `member` and `to_*` functions. 141 + Complete the serialization for query filters and sorting: 108 142 109 - **Required Functions**: 110 143 ```ocaml 111 - (* Object parsing - the most critical missing piece *) 112 - module Jmap_email_types : sig 113 - module Email : sig 114 - val from_json : Yojson.Safe.t -> (t, Error.t) result 115 - val from_json_list : Yojson.Safe.t -> (t list, Error.t) result 116 - end 117 - 118 - module EmailAddress : sig 119 - val from_json : Yojson.Safe.t -> (t, Error.t) result 120 - val list_from_json : Yojson.Safe.t -> (t list, Error.t) result 121 - end 122 - 123 - module Keywords : sig 124 - val from_json : Yojson.Safe.t -> (t, Error.t) result 125 - val to_string_list : t -> string list 126 - val has_keyword : keyword -> t -> bool 127 - end 128 - 129 - module Thread : sig 130 - val from_json : Yojson.Safe.t -> (t, Error.t) result 131 - end 132 - 133 - module Mailbox : sig 134 - val from_json : Yojson.Safe.t -> (t, Error.t) result 135 - end 136 - 137 - module EmailSubmission : sig 138 - val from_json : Yojson.Safe.t -> (t, Error.t) result 139 - 140 - module Envelope : sig 141 - val from_json : Yojson.Safe.t -> (t, Error.t) result 142 - end 143 - end 144 - 145 - module Identity : sig 146 - val from_json : Yojson.Safe.t -> (t, Error.t) result 147 - end 148 - 149 - module VacationResponse : sig 150 - val from_json : Yojson.Safe.t -> (t, Error.t) result 151 - end 144 + module Jmap.Methods.Filter : sig 145 + type t (* Already exists *) 146 + val to_json : t -> Yojson.Safe.t (* Partial - needs completion *) 147 + val of_json : Yojson.Safe.t -> (t, string) result (* TODO *) 152 148 end 153 - ``` 154 149 155 - **Impact**: Eliminates ~30-40 lines per example. **Most critical missing functionality.** 150 + module Jmap.Methods.Comparator : sig 151 + type t (* Already exists *) 152 + val to_json : t -> Yojson.Safe.t (* TODO *) 153 + val of_json : Yojson.Safe.t -> (t, string) result (* TODO *) 154 + end 155 + ``` 156 156 157 157 --- 158 158 159 - ### 17. **Attachment and Body Part Parsers** 159 + ### Priority 3: **Patch Operations** 160 160 161 - **Problem**: Manual parsing of attachment lists and body structures in `query_large_attachments.ml`. 161 + For /set methods, implement patch operations: 162 162 163 - **Required Functions**: 164 163 ```ocaml 165 - module Jmap_email_types : sig 166 - module Attachment : sig 167 - val from_json : Yojson.Safe.t -> (t, Error.t) result 168 - val list_from_json : Yojson.Safe.t -> (t list, Error.t) result 169 - val name : t -> string option 170 - val size : t -> int option 171 - val content_type : t -> string option 172 - end 173 - 174 - module BodyPart : sig 175 - val from_json : Yojson.Safe.t -> (t, Error.t) result 176 - val attachments : t -> Attachment.t list 177 - end 164 + module Email_patch : sig 165 + type t 166 + val create : unit -> t 167 + val set_keywords : t -> keywords:Keywords.t -> t 168 + val add_keyword : t -> keyword:string -> t 169 + val remove_keyword : t -> keyword:string -> t 170 + val set_mailbox_ids : t -> mailbox_ids:string list -> t 171 + val to_json : t -> Yojson.Safe.t 178 172 end 173 + 174 + (* Similar for Mailbox_patch, Identity_patch, etc. *) 179 175 ``` 180 176 181 - **Impact**: Eliminates ~20 lines in attachment-related examples. 182 - 183 177 --- 184 178 185 - ### 18. **High-Level Client Interface** 179 + ### Priority 4: **Set Error Handling** 186 180 187 - **Problem**: All examples manually orchestrate request building, sending, and response parsing. 181 + Complete error types for /set operations: 188 182 189 - **Required Functions**: 190 183 ```ocaml 191 - (* In jmap-unix/jmap_unix.ml - high-level client interface *) 192 - module Client : sig 193 - type t 194 - 195 - val query_emails : t -> account_id:string -> filter:Email_filter.t -> 196 - ?sort:Email_sort.t list -> ?limit:int -> unit -> 197 - (Email.t list * int option) Error.result 198 - 199 - val get_emails : t -> account_id:string -> ids:string list -> 200 - ?properties:string list -> unit -> 201 - Email.t list Error.result 202 - 203 - val query_and_get_emails : t -> account_id:string -> filter:Email_filter.t -> 204 - ?sort:Email_sort.t list -> ?limit:int -> 205 - ?properties:string list -> unit -> 206 - (Email.t list * int option) Error.result 207 - 208 - (* Similar high-level functions for threads, submissions, etc. *) 184 + module Set_error : sig 185 + type t = { 186 + error_type : [`InvalidProperties | `NotFound | `OverQuota | `TooLarge | `Other of string]; 187 + description : string option; 188 + properties : string list option; 189 + } 190 + val of_json : Yojson.Safe.t -> (t, string) result 191 + val to_json : t -> Yojson.Safe.t 209 192 end 210 193 ``` 211 194 212 - **Impact**: Could reduce examples from ~300 lines to ~50-100 lines each. 195 + --- 213 196 214 - --- 197 + ### Priority 5: **Update Examples** 215 198 216 - ## **Implementation Analysis** 199 + Update all example binaries to use the new ADT-based API: 217 200 218 - ### **Current Status After Phase 1-4**: 219 - - ✅ Core infrastructure is solid (types, basic JSON, method builders) 220 - - ✅ Request building framework exists 221 - - ✅ Response processing utilities exist 222 - - ✅ Vendor extensions (Apple Mail) implemented 223 - - ✅ Analytics and filtering utilities added 201 + - [ ] Update `bin/jmap_email_search.ml` 202 + - [ ] Update `bin/jmap_mailbox_explorer.ml` 203 + - [ ] Update `bin/jmap_thread_analyzer.ml` 204 + - [ ] Update `bin/jmap_identity_monitor.ml` 205 + - [ ] Update `bin/jmap_flag_manager.ml` 206 + - [ ] Update `bin/jmap_email_composer.ml` 207 + - [ ] Update `bin/jmap_blob_downloader.ml` 208 + - [ ] Update `bin/jmap_push_listener.ml` 209 + - [ ] Update `bin/jmap_vacation_manager.ml` 210 + - [ ] Update all `bin/examples/*.ml` files 224 211 225 - ### **Remaining Work (Phase 5)**: 226 - The examples still contain **extensive manual JSON parsing** because: 212 + --- 227 213 228 - 1. **Method-specific builders** (items 13-14) are not fully integrated 229 - 2. **Response parsing** (item 15) exists in core but not exposed at method level 230 - 3. **Object type parsers** (item 16) are the most critical missing piece - these handle the bulk of manual JSON parsing 231 - 4. **High-level client interface** (item 18) would provide the biggest developer experience improvement 214 + ### Priority 6: **Testing** 232 215 233 - ### **Critical Path for Complete Elimination**: 216 + Create comprehensive tests for the new ADT-based API: 234 217 235 - **Priority 1: Object Type Parsers (Item 16)** 236 - - `Email.from_json`, `Thread.from_json`, etc. - eliminates ~30 lines per example 237 - - This is the **single most impactful missing functionality** 218 + - [ ] Method constructor tests 219 + - [ ] Response parser tests 220 + - [ ] Request builder tests 221 + - [ ] Round-trip serialization tests 222 + - [ ] Error handling tests 238 223 239 - **Priority 2: Method Response Integration (Item 15)** 240 - - Connect existing response parsers to method-specific interfaces 241 - - Eliminates ~20 lines per example 224 + --- 242 225 243 - **Priority 3: Request Builder Integration (Items 13-14)** 244 - - Connect existing request builders to method-specific interfaces 245 - - Eliminates ~40 lines per example 226 + ## **Implementation Strategy** 246 227 247 - **Priority 4: High-Level Client (Item 18)** 248 - - Combines everything into developer-friendly API 249 - - Could eliminate ~150+ lines per example 228 + ### Phase 1: **Object Serialization** (Highest Priority) 229 + Focus on implementing `of_json`/`to_json` for all JMAP objects. This will eliminate the most manual JSON handling in examples. 250 230 251 - ## **Updated Success Metrics** 231 + ### Phase 2: **Complete ADT Integration** 232 + Ensure all filters, comparators, and patch operations work seamlessly with the ADT approach. 252 233 253 - - **Current**: Examples average ~300 lines with ~70% still manual JSON handling 254 - - **After Phase 5**: Examples should average ~100-150 lines with 0% manual JSON handling 255 - - **Type Safety**: All JMAP operations use library types instead of raw JSON 256 - - **Developer Experience**: Simple function calls instead of multi-step JSON orchestration 234 + ### Phase 3: **Example Migration** 235 + Update all examples to demonstrate the new API, showing best practices and common patterns. 257 236 258 - ## **Next Steps** 237 + ### Phase 4: **Documentation** 238 + - Update module documentation with examples 239 + - Create a migration guide from DSL to ADT 240 + - Write a comprehensive README showing the new approach 259 241 260 - The **object type parsers** (Item 16) should be the immediate focus, as they will eliminate the most manual JSON handling with the least implementation effort. The infrastructure is in place - what's missing is connecting the parsers to the high-level interfaces used by the examples. 242 + ### Phase 5: **Testing & Validation** 243 + - Implement comprehensive test suite 244 + - Validate against real JMAP servers 245 + - Performance benchmarking 261 246 262 247 --- 263 248 264 - ## Implementation Priority 249 + ## **Benefits of New Architecture** 265 250 266 - 1. **Phase 1**: Items 1-3 (JSON handling) - Eliminates most manual JSON usage 267 - 2. **Phase 2**: Items 4-7 (Method support) - Enables all JMAP methods 268 - 3. **Phase 3**: Items 8-9 (Extensions) - Adds vendor support and advanced filtering 269 - 4. **Phase 4**: Items 10-12 (Utilities) - Improves developer experience 251 + 1. **Simpler API**: Constructor functions are more intuitive than DSL operators 252 + 2. **Better IDE Support**: Autocomplete works better with regular functions 253 + 3. **Easier Debugging**: No complex type-level computations to trace through 254 + 4. **More Flexible**: Users can build requests in any order or pattern they prefer 255 + 5. **Maintainable**: Straightforward code that's easier to extend and modify 270 256 271 - ## Success Metrics 257 + --- 272 258 273 - - **Before**: Examples average ~300 lines with ~60% manual JSON handling 274 - - **After**: Examples should average ~180 lines with 0% manual JSON handling 275 - - **Type Safety**: All JMAP operations use library types instead of raw JSON 276 - - **Vendor Support**: Apple Mail and other vendor extensions properly supported 259 + ## **Migration Guide Summary** 277 260 278 - ## Testing Strategy 261 + **Old DSL Approach**: 262 + ```ocaml 263 + let request = 264 + email_query ~account_id ~filter () @> 265 + email_get ~account_id ~ids:[] () @> 266 + done_ 267 + ``` 279 268 280 - For each improvement: 281 - 1. Update the relevant example binary to use the new functionality 282 - 2. Verify the binary compiles and produces identical JMAP output 283 - 3. Add unit tests for the new library functions 284 - 4. Update documentation with the new capabilities 269 + **New ADT Approach**: 270 + ```ocaml 271 + let request = 272 + Jmap_request.create ~using:[...] () 273 + |> Jmap_request.add_method 274 + (Jmap_method.email_query ~account_id ~filter ()) 275 + |> Jmap_request.add_method_with_ref 276 + (Jmap_method.email_get ~account_id ()) 277 + ~reference:("#call-1", "/ids") 278 + ``` 285 279 286 - This TODO list provides a roadmap for transforming the JMAP library from a type-safe foundation into a fully-featured, developer-friendly JMAP client library. 280 + The new approach is more verbose but significantly clearer and more flexible.
-4
jmap/dune-project
··· 20 20 (synopsis "JMAP Unix networking implementation") 21 21 (depends ocaml dune jmap jmap-email jmap-sigs yojson uri eio tls-eio cohttp-eio fmt)) 22 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))
+194
jmap/examples/simple_email_query.ml
··· 1 + (* simple_email_query.ml - Demonstrate the new ADT-based JMAP API 2 + * 3 + * This example shows how to use the simplified JMAP library without any DSL, 4 + * using constructor functions with optional arguments and abstract types. 5 + *) 6 + 7 + open Printf 8 + 9 + let main env = 10 + Eio.Switch.run @@ fun _sw -> 11 + 12 + printf "JMAP ADT-based API Example\n"; 13 + printf "==========================\n\n"; 14 + 15 + (* Read API credentials *) 16 + let api_key = 17 + try 18 + let ic = open_in ".api-key" in 19 + let key = input_line ic in 20 + close_in ic; 21 + String.trim key 22 + with 23 + | Sys_error _ -> 24 + eprintf "Error: Create a .api-key file with your JMAP bearer token\n"; 25 + exit 1 26 + in 27 + 28 + (* Step 1: Connect to JMAP server *) 29 + printf "Connecting to JMAP server...\n"; 30 + let config = Jmap_unix.default_config () in 31 + let client = Jmap_unix.create_client ~config () in 32 + 33 + let (ctx, session) = 34 + match Jmap_unix.connect env client 35 + ~host:"api.fastmail.com" 36 + ~use_tls:true 37 + ~auth_method:(Jmap_unix.Bearer api_key) 38 + () 39 + with 40 + | Ok result -> 41 + printf "Connected successfully\n\n"; 42 + result 43 + | Error error -> 44 + eprintf "Connection failed: %s\n" 45 + (Jmap.Protocol.Error.error_to_string error); 46 + exit 1 47 + in 48 + 49 + (* Step 2: Get primary mail account *) 50 + let account_id = 51 + match Jmap.Protocol.get_primary_account session Jmap_email.capability_mail with 52 + | Ok id -> 53 + printf "Using account: %s\n\n" id; 54 + id 55 + | Error error -> 56 + eprintf "No mail account found: %s\n" 57 + (Jmap.Protocol.Error.error_to_string error); 58 + exit 1 59 + in 60 + 61 + (* Step 3: Build request using ADT constructors *) 62 + printf "Building JMAP request using ADT constructors...\n"; 63 + 64 + (* Create Email/query method with constructor function *) 65 + let email_query_method = 66 + (* Constructor with optional arguments and defaults *) 67 + Jmap_method.email_query 68 + ~account_id 69 + ~filter:(Some ( 70 + Jmap_email.Email_filter.and_ [ 71 + Jmap_email.Email_filter.in_mailbox "INBOX"; 72 + Jmap_email.Email_filter.has_keyword Jmap_email.Types.Keywords.Seen 73 + |> Jmap_email.Email_filter.not_; 74 + ] 75 + )) 76 + ~sort:(Some [ 77 + Jmap.Methods.Comparator.field "receivedAt" ~is_ascending:false 78 + ]) 79 + ~limit:(Some 10) 80 + ~calculate_total:(Some true) 81 + () (* Unit at the end for optional arguments *) 82 + in 83 + 84 + (* Create Email/get method to fetch details *) 85 + let email_get_method = 86 + Jmap_method.email_get 87 + ~account_id 88 + ~ids:[] (* Will be filled by reference *) 89 + ~properties:(Some ["id"; "subject"; "from"; "receivedAt"; "preview"]) 90 + ~fetch_text_body_values:(Some true) 91 + ~max_body_value_bytes:(Some 256) 92 + () 93 + in 94 + 95 + (* Step 4: Build request with multiple methods *) 96 + let request = 97 + Jmap_request.create 98 + ~using:["urn:ietf:params:jmap:core"; "urn:ietf:params:jmap:mail"] 99 + () 100 + |> Jmap_request.add_method email_query_method ~call_id:"query-1" 101 + |> Jmap_request.add_method_with_ref 102 + email_get_method 103 + ~call_id:"get-1" 104 + ~reference:("#query-1", "/ids") 105 + in 106 + 107 + printf "Request built with %d methods\n\n" 108 + (List.length (Jmap_request.get_methods request)); 109 + 110 + (* Step 5: Send request *) 111 + printf "Sending request...\n"; 112 + let response = 113 + match Jmap_unix.send_request env ctx request with 114 + | Ok resp -> 115 + printf "Response received\n\n"; 116 + resp 117 + | Error error -> 118 + eprintf "Request failed: %s\n" 119 + (Jmap.Protocol.Error.error_to_string error); 120 + exit 1 121 + in 122 + 123 + (* Step 6: Parse responses using ADT pattern matching *) 124 + printf "Processing responses...\n\n"; 125 + 126 + (* Extract Email/query response *) 127 + match Jmap_response.get_response response "query-1" with 128 + | Ok query_response -> ( 129 + match Jmap_response.response_type query_response with 130 + | `Email_query -> 131 + let query_data = Jmap_response.Email_query.extract query_response in 132 + (match query_data with 133 + | Ok data -> 134 + printf "Email/query results:\n"; 135 + printf " Total emails: %s\n" 136 + (match Jmap_response.Email_query.total data with 137 + | Some n -> string_of_int n 138 + | None -> "unknown"); 139 + printf " Returned: %d\n" 140 + (List.length (Jmap_response.Email_query.ids data)); 141 + printf " Query state: %s\n\n" 142 + (Jmap_response.Email_query.query_state data); 143 + | Error e -> 144 + eprintf "Failed to extract query data: %s\n" e) 145 + | _ -> 146 + eprintf "Unexpected response type for query-1\n" 147 + ) 148 + | Error e -> 149 + eprintf "Failed to get query response: %s\n" e; 150 + 151 + (* Extract Email/get response *) 152 + match Jmap_response.get_response response "get-1" with 153 + | Ok get_response -> ( 154 + match Jmap_response.response_type get_response with 155 + | `Email_get -> 156 + let get_data = Jmap_response.Email_get.extract get_response in 157 + (match get_data with 158 + | Ok data -> 159 + printf "Email/get results:\n"; 160 + printf " State: %s\n" (Jmap_response.Email_get.state data); 161 + let emails = Jmap_response.Email_get.list data in 162 + printf " Fetched %d emails:\n\n" (List.length emails); 163 + 164 + (* Display email details *) 165 + List.iteri (fun i email_json -> 166 + let open Yojson.Safe.Util in 167 + printf " [%d] %s\n" (i + 1) 168 + (email_json |> member "subject" |> to_string_option 169 + |> Option.value ~default:"(no subject)"); 170 + printf " From: %s\n" 171 + (try 172 + email_json 173 + |> member "from" 174 + |> to_list 175 + |> List.hd 176 + |> member "email" 177 + |> to_string 178 + with _ -> "(unknown)"); 179 + printf " Preview: %.60s...\n\n" 180 + (email_json |> member "preview" |> to_string_option 181 + |> Option.value ~default:"(no preview)") 182 + ) emails; 183 + | Error e -> 184 + eprintf "Failed to extract get data: %s\n" e) 185 + | _ -> 186 + eprintf "Unexpected response type for get-1\n" 187 + ) 188 + | Error e -> 189 + eprintf "Failed to get email response: %s\n" e; 190 + 191 + printf "Example complete!\n" 192 + 193 + let () = 194 + Eio_main.run main
-27
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 -
-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
+5 -2
jmap/jmap/dune
··· 1 1 (library 2 2 (name jmap) 3 3 (public_name jmap) 4 - (libraries yojson uri unix base64) 4 + (libraries yojson uri unix base64 jmap-sigs) 5 5 (modules 6 6 jmap 7 7 jmap_types ··· 12 12 jmap_binary 13 13 jmap_push 14 14 jmap_protocol 15 - jmap_client)) 15 + jmap_client 16 + jmap_method 17 + jmap_response 18 + jmap_request))
+4
jmap/jmap/jmap.ml
··· 2 2 3 3 module Methods = Jmap_methods 4 4 5 + module Response = Jmap_response 6 + 7 + module Request = Jmap_request 8 + 5 9 module Binary = Jmap_binary 6 10 7 11 module Push = Jmap_push
+8
jmap/jmap/jmap.mli
··· 33 33 @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-5> RFC 8620, Section 5 *) 34 34 module Methods = Jmap_methods 35 35 36 + (** Type-safe JMAP response parsing and pattern matching 37 + @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-3.4> RFC 8620, Section 3.4 *) 38 + module Response = Jmap_response 39 + 40 + (** Type-safe JMAP request building 41 + @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-3.3> RFC 8620, Section 3.3 *) 42 + module Request = Jmap_request 43 + 36 44 (** Binary data upload/download types 37 45 @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-6> RFC 8620, Section 6 *) 38 46 module Binary = Jmap_binary
+90
jmap/jmap/jmap_method.ml
··· 1 + (** Implementation of type-safe JMAP method representation and construction. *) 2 + 3 + (* Keep the original abstract type for backward compatibility *) 4 + type t = { 5 + method_name: string; 6 + arguments: Yojson.Safe.t; 7 + call_id: string option; 8 + } 9 + 10 + (** {1 Method Argument Types} *) 11 + 12 + type core_echo_args = { 13 + data: Yojson.Safe.t; 14 + call_id: string option; 15 + } 16 + 17 + 18 + (** {1 METHOD_ARGS Module Implementations} *) 19 + 20 + module Core_echo_args = struct 21 + type t = core_echo_args 22 + type account_id = string 23 + 24 + let account_id _t = "primary" (* Core/echo doesn't use account_id *) 25 + let to_json t = t.data 26 + let of_json json = { data = json; call_id = None } 27 + let pp_hum fmt t = Format.fprintf fmt "Core_echo_args(%s)" (Yojson.Safe.to_string t.data) 28 + let pp = pp_hum 29 + let validate _t = Ok () 30 + let method_name () = "Core/echo" 31 + end 32 + 33 + (** {1 Method Identification} *) 34 + 35 + let method_name (t : t) = t.method_name 36 + let arguments (t : t) = t.arguments 37 + let call_id (t : t) = t.call_id 38 + 39 + (** {1 JSON Serialization} *) 40 + 41 + let to_json (t : t) = 42 + let call_id_json = match t.call_id with 43 + | None -> `Null 44 + | Some id -> `String id 45 + in 46 + `List [`String t.method_name; t.arguments; call_id_json] 47 + 48 + let arguments_to_json t = t.arguments 49 + 50 + (** {1 Constructor Functions} *) 51 + 52 + let core_echo ?(data=`Assoc []) ?call_id () = 53 + { data; call_id } 54 + 55 + (** {1 Method Call Conversion Functions} *) 56 + 57 + let of_core_echo_args args = 58 + { method_name = "Core/echo"; arguments = Core_echo_args.to_json args; call_id = args.call_id } 59 + 60 + (** {1 Utility Functions} *) 61 + 62 + let with_call_id (method_call : t) call_id = 63 + { method_call with call_id = Some call_id } 64 + 65 + let supports_result_reference_ids method_call = 66 + match method_call.arguments with 67 + | `Assoc fields -> List.exists (fun (k, _) -> k = "ids") fields 68 + | _ -> false 69 + 70 + (** {1 Result References} *) 71 + 72 + let with_result_reference_ids method_call ~result_of ~name ~path = 73 + (* Create result reference JSON *) 74 + let result_ref = `Assoc [ 75 + ("resultOf", `String result_of); 76 + ("name", `String name); 77 + ("path", `String path); 78 + ] in 79 + 80 + (* Update arguments to replace "ids" field with result reference *) 81 + let updated_args = match method_call.arguments with 82 + | `Assoc fields -> 83 + let updated_fields = List.map (fun (k, v) -> 84 + if k = "ids" then (k, result_ref) else (k, v) 85 + ) fields in 86 + `Assoc updated_fields 87 + | _ -> method_call.arguments (* Shouldn't happen with well-formed methods *) 88 + in 89 + 90 + { method_call with arguments = updated_args }
+149
jmap/jmap/jmap_method.mli
··· 1 + (** Type-safe JMAP method representation and construction. 2 + 3 + This module provides abstract types for JMAP methods with constructor functions 4 + that use optional arguments and sensible defaults. It demonstrates the use of 5 + jmap-sigs METHOD_ARGS signatures for consistent method argument handling. 6 + 7 + Each method constructor returns an opaque method value that can be serialized 8 + to JSON for wire protocol transmission. The module maintains RFC compliance 9 + while providing a clean, type-safe OCaml interface. 10 + 11 + Example usage: 12 + {[ 13 + let echo_method = 14 + Jmap_method.core_echo 15 + ~data:(`Assoc [("test", `String "hello")]) 16 + () 17 + 18 + let json = Jmap_method.to_json echo_method 19 + ]} 20 + 21 + @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-5> RFC 8620, Section 5 (Standard Methods) 22 + @see <https://www.rfc-editor.org/rfc/rfc8621.html> RFC 8621 (Email Extensions) *) 23 + 24 + 25 + (** {1 Method Types} *) 26 + 27 + (** Abstract type representing a JMAP method call. 28 + 29 + This type encapsulates all information needed to perform a JMAP method 30 + invocation, including the method name, arguments, and any result references. 31 + Methods are serializable to JSON and can be combined into requests. *) 32 + type t 33 + 34 + (** {1 Method Argument Types} *) 35 + 36 + (** Core/echo method arguments *) 37 + type core_echo_args 38 + 39 + (** {1 METHOD_ARGS Module Implementations} *) 40 + 41 + (** Core/echo method arguments module. 42 + @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-4> RFC 8620, Section 4 *) 43 + module Core_echo_args : sig 44 + include Jmap_sigs.METHOD_ARGS with type t = core_echo_args 45 + end 46 + 47 + (** {1 Method Identification} *) 48 + 49 + (** Get the method name (e.g., "Core/echo", "Email/query"). 50 + @param method_call The method to inspect 51 + @return The JMAP method name *) 52 + val method_name : t -> string 53 + 54 + (** Get the method arguments as JSON. 55 + @param method_call The method to inspect 56 + @return The method arguments as a JSON object *) 57 + val arguments : t -> Yojson.Safe.t 58 + 59 + (** Get the method call ID if specified. 60 + @param method_call The method to inspect 61 + @return Optional method call identifier for result references *) 62 + val call_id : t -> string option 63 + 64 + (** {1 JSON Serialization} *) 65 + 66 + (** Convert a method to JSON for wire protocol transmission. 67 + 68 + This produces a JSON array in the JMAP method call format: 69 + ["MethodName", {arguments}, "methodCallId"] 70 + 71 + @param method_call The method to serialize 72 + @return JSON array representing the method call 73 + @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-3.3> RFC 8620, Section 3.3 *) 74 + val to_json : t -> Yojson.Safe.t 75 + 76 + (** Convert a method to its arguments JSON object only. 77 + 78 + This is useful when you need just the arguments portion for debugging 79 + or integration with existing code that expects raw arguments. 80 + 81 + @param method_call The method to extract arguments from 82 + @return JSON object containing the method arguments *) 83 + val arguments_to_json : t -> Yojson.Safe.t 84 + 85 + (** {1 Core JMAP Methods} *) 86 + 87 + (** Core/echo method for testing connectivity and server responsiveness. 88 + 89 + The echo method simply returns the same arguments that were provided, 90 + allowing clients to test the connection and measure round-trip time. 91 + 92 + @param ?data Optional data to echo back (defaults to empty object) 93 + @param ?call_id Optional identifier for result references 94 + @param unit Required unit parameter 95 + @return Method call for Core/echo 96 + @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-4> RFC 8620, Section 4 *) 97 + val core_echo : 98 + ?data:Yojson.Safe.t -> 99 + ?call_id:string -> 100 + unit -> 101 + Core_echo_args.t 102 + 103 + (** {1 Method Call Conversion} *) 104 + 105 + (** Convert typed method arguments to abstract method call. 106 + 107 + This function allows conversion from the typed argument modules back to the 108 + abstract method call type for use with existing APIs that expect `t`. 109 + 110 + @param args The typed method arguments 111 + @return Abstract method call suitable for request building *) 112 + val of_core_echo_args : Core_echo_args.t -> t 113 + 114 + (** {1 Utility Functions} *) 115 + 116 + (** Create a method call with a specific call ID. 117 + 118 + @param method_call The method call to modify 119 + @param call_id The new call ID to assign 120 + @return Method call with the specified call ID *) 121 + val with_call_id : t -> string -> t 122 + 123 + (** Check if a method supports result references for IDs. 124 + 125 + @param method_call The method to check 126 + @return True if the method accepts result references for ID parameters *) 127 + val supports_result_reference_ids : t -> bool 128 + 129 + (** {1 Result References} *) 130 + 131 + (** Create a method with result reference for IDs. 132 + 133 + This creates a new method identical to the input method, but with 134 + the IDs field replaced by a result reference to another method call. 135 + Useful for chaining methods where one method's results become the 136 + input to another. 137 + 138 + @param method_call The base method call to modify 139 + @param result_of The method call ID to reference for results 140 + @param name The method name being referenced 141 + @param path The JSON pointer path to the result data (e.g., "/ids") 142 + @return New method call with result reference for IDs 143 + @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-3.7> RFC 8620, Section 3.7 *) 144 + val with_result_reference_ids : 145 + t -> 146 + result_of:string -> 147 + name:string -> 148 + path:string -> 149 + t
+253
jmap/jmap/jmap_request.ml
··· 1 + (** Implementation of type-safe JMAP request building and management. *) 2 + 3 + open Jmap_types 4 + 5 + (** Internal representation of a JMAP request under construction *) 6 + type t = { 7 + using: string list; 8 + methods: (Jmap_method.t * string) list; (* (method, call_id) pairs *) 9 + created_ids: (string) id_map option; 10 + call_id_counter: int; 11 + } 12 + 13 + (** {1 Request Creation} *) 14 + 15 + let create ~using ?created_ids () = 16 + { 17 + using; 18 + methods = []; 19 + created_ids; 20 + call_id_counter = 1; 21 + } 22 + 23 + let create_with_standard_capabilities ?additional_capabilities ?created_ids () = 24 + let standard_caps = [ 25 + "urn:ietf:params:jmap:core"; 26 + "urn:ietf:params:jmap:mail"; 27 + "urn:ietf:params:jmap:submission"; 28 + "urn:ietf:params:jmap:vacationresponse"; 29 + ] in 30 + let all_caps = match additional_capabilities with 31 + | None -> standard_caps 32 + | Some additional -> standard_caps @ additional 33 + in 34 + create ~using:all_caps ?created_ids () 35 + 36 + (** {1 Helper Functions} *) 37 + 38 + (* Generate a unique call ID *) 39 + let generate_call_id t = 40 + let call_id = "call" ^ string_of_int t.call_id_counter in 41 + (call_id, { t with call_id_counter = t.call_id_counter + 1 }) 42 + 43 + (* Find method by call ID *) 44 + let find_method_by_call_id t call_id = 45 + let rec find_index methods index = 46 + match methods with 47 + | [] -> None 48 + | (_, id) :: _ when id = call_id -> Some index 49 + | _ :: rest -> find_index rest (index + 1) 50 + in 51 + find_index (List.rev t.methods) 0 (* Reverse to maintain insertion order *) 52 + 53 + (** {1 Method Management} *) 54 + 55 + let add_method t method_call = 56 + let call_id = match Jmap_method.call_id method_call with 57 + | Some id -> id 58 + | None -> 59 + let (id, _) = generate_call_id t in 60 + id 61 + in 62 + let method_with_id = Jmap_method.with_call_id method_call call_id in 63 + let (final_call_id, updated_t) = if Jmap_method.call_id method_with_id = Some call_id then 64 + (call_id, t) 65 + else 66 + generate_call_id t 67 + in 68 + let final_method = Jmap_method.with_call_id method_call final_call_id in 69 + { 70 + updated_t with 71 + methods = updated_t.methods @ [(final_method, final_call_id)] 72 + } 73 + 74 + let add_methods t method_calls = 75 + List.fold_left add_method t method_calls 76 + 77 + (** {1 Method Call ID Management} *) 78 + 79 + let get_call_id t index = 80 + let methods_list = List.rev t.methods in (* Reverse to get insertion order *) 81 + if index >= 0 && index < List.length methods_list then 82 + Some (snd (List.nth methods_list index)) 83 + else 84 + None 85 + 86 + let get_all_call_ids t = 87 + List.rev t.methods |> List.map snd 88 + 89 + let find_method_index t ~call_id = 90 + find_method_by_call_id t call_id 91 + 92 + (** {1 Result References} *) 93 + 94 + let with_result_reference t ~target_call_id ~result_of ~name ~path = 95 + let updated_methods = List.map (fun (method_call, call_id) -> 96 + if call_id = target_call_id then 97 + let updated_method = Jmap_method.with_result_reference_ids 98 + method_call ~result_of ~name ~path in 99 + (updated_method, call_id) 100 + else 101 + (method_call, call_id) 102 + ) t.methods in 103 + { t with methods = updated_methods } 104 + 105 + let with_result_reference_for_last_method t ~result_of ~name ~path = 106 + match List.rev t.methods with 107 + | [] -> t (* No methods to modify *) 108 + | (last_method, call_id) :: rest_rev -> 109 + let updated_last = Jmap_method.with_result_reference_ids 110 + last_method ~result_of ~name ~path in 111 + let updated_methods = List.rev ((updated_last, call_id) :: rest_rev) in 112 + { t with methods = updated_methods } 113 + 114 + (** {1 Capability Management} *) 115 + 116 + let add_capabilities t capabilities = 117 + let updated_using = t.using @ capabilities |> List.sort_uniq String.compare in 118 + { t with using = updated_using } 119 + 120 + let get_capabilities t = t.using 121 + 122 + let has_capability t capability = 123 + List.mem capability t.using 124 + 125 + (** {1 Request Inspection} *) 126 + 127 + let method_count t = List.length t.methods 128 + 129 + let get_method t index = 130 + let methods_list = List.rev t.methods in 131 + if index >= 0 && index < List.length methods_list then 132 + Some (fst (List.nth methods_list index)) 133 + else 134 + None 135 + 136 + let get_all_methods t = 137 + List.rev t.methods |> List.map fst 138 + 139 + let is_empty t = List.length t.methods = 0 140 + 141 + (** {1 Wire Protocol Conversion} *) 142 + 143 + let to_wire_request t = 144 + let invocations = List.rev t.methods |> List.map (fun (method_call, call_id) -> 145 + let method_name = Jmap_method.method_name method_call in 146 + let arguments = Jmap_method.arguments method_call in 147 + Jmap_wire.Invocation.v 148 + ~method_name 149 + ~method_call_id:call_id 150 + ~arguments 151 + () 152 + ) in 153 + Jmap_wire.Request.v 154 + ~using:t.using 155 + ~method_calls:invocations 156 + ?created_ids:t.created_ids 157 + () 158 + 159 + let to_json t = 160 + (* Create a basic JSON structure *) 161 + let method_calls_json = List.rev t.methods |> List.map (fun (method_call, call_id) -> 162 + let method_name = Jmap_method.method_name method_call in 163 + let arguments = Jmap_method.arguments method_call in 164 + `List [`String method_name; arguments; `String call_id] 165 + ) in 166 + let created_ids_json = match t.created_ids with 167 + | None -> [] 168 + | Some ids -> [("createdIds", `Assoc (Hashtbl.fold (fun k v acc -> (k, `String v) :: acc) ids []))] 169 + in 170 + `Assoc ([ 171 + ("using", `List (List.map (fun s -> `String s) t.using)); 172 + ("methodCalls", `List method_calls_json); 173 + ] @ created_ids_json) 174 + 175 + (** {1 Request Validation} *) 176 + 177 + let validate_result_references t = 178 + (* Create a map of call_id -> index for quick lookup *) 179 + let call_id_to_index = Hashtbl.create (List.length t.methods) in 180 + let methods_list = List.rev t.methods in 181 + List.iteri (fun i (_, call_id) -> 182 + Hashtbl.add call_id_to_index call_id i) methods_list; 183 + 184 + (* Check each method for result references *) 185 + let check_method _index (method_call, _call_id) = 186 + (* This is a simplified check - would need more sophisticated parsing 187 + of the method arguments to find result references *) 188 + (* For now, just check if method supports result references *) 189 + if Jmap_method.supports_result_reference_ids method_call then 190 + (* Would need to parse JSON to find actual references and validate them *) 191 + Ok () 192 + else 193 + Ok () 194 + in 195 + 196 + let methods_with_index = List.mapi (fun i method_info -> (i, method_info)) methods_list in 197 + let results = List.map (fun (i, method_info) -> check_method i method_info) methods_with_index in 198 + 199 + (* Combine all results *) 200 + let rec combine_results results = 201 + match results with 202 + | [] -> Ok () 203 + | Ok () :: rest -> combine_results rest 204 + | Error msg :: _ -> Error msg 205 + in 206 + combine_results results 207 + 208 + let validate_capabilities t = 209 + (* Check that required capabilities are present *) 210 + let required_caps = [ 211 + "urn:ietf:params:jmap:core" (* Always required *) 212 + ] in 213 + let missing_caps = List.filter (fun cap -> not (has_capability t cap)) required_caps in 214 + if missing_caps = [] then 215 + Ok () 216 + else 217 + Error missing_caps 218 + 219 + let validate t = 220 + match validate_result_references t with 221 + | Error msg -> Error msg 222 + | Ok () -> 223 + match validate_capabilities t with 224 + | Error missing_caps -> 225 + Error ("Missing required capabilities: " ^ String.concat ", " missing_caps) 226 + | Ok () -> Ok () 227 + 228 + (** {1 Request Debugging} *) 229 + 230 + let describe t = 231 + let cap_str = "Capabilities: [" ^ String.concat "; " t.using ^ "]" in 232 + let method_count_str = "Methods: " ^ string_of_int (List.length t.methods) in 233 + let methods_str = List.rev t.methods |> List.mapi (fun i (method_call, call_id) -> 234 + let method_name = Jmap_method.method_name method_call in 235 + Printf.sprintf " %d. %s (call_id: %s)" i method_name call_id 236 + ) |> String.concat "\n" in 237 + let created_ids_str = match t.created_ids with 238 + | None -> "Created IDs: none" 239 + | Some ids -> 240 + let count = Hashtbl.length ids in 241 + Printf.sprintf "Created IDs: %d entries" count 242 + in 243 + String.concat "\n" [cap_str; method_count_str; methods_str; created_ids_str] 244 + 245 + let analyze_dependencies t = 246 + (* This is a simplified implementation - would need full JSON parsing 247 + to properly detect result references *) 248 + let methods_list = List.rev t.methods in 249 + List.mapi (fun i (_method_call, _call_id) -> 250 + (* For now, assume no dependencies - would need to parse arguments 251 + for result reference objects *) 252 + (i, []) 253 + ) methods_list
+273
jmap/jmap/jmap_request.mli
··· 1 + (** Type-safe JMAP request building and management. 2 + 3 + This module provides a high-level interface for building JMAP requests 4 + from method objects. It handles capability management, method call ID 5 + generation, result references, and conversion to wire format. 6 + 7 + The module supports both single-method requests and complex multi-method 8 + requests with automatic method call ID management and result reference 9 + support. 10 + 11 + Example usage: 12 + {[ 13 + let request = 14 + Jmap_request.create ~using:["urn:ietf:params:jmap:mail"] () 15 + |> Jmap_request.add_method (Jmap_method.email_query ~account_id ~filter ()) 16 + |> Jmap_request.add_method (Jmap_method.email_get ~account_id ~ids:[] ()) 17 + |> Jmap_request.with_result_reference "get1" ~result_of:"query1" ~name:"ids" ~path:"*" 18 + 19 + let wire_request = Jmap_request.to_wire_request request 20 + ]} 21 + 22 + @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-3.3> RFC 8620, Section 3.3 (Request Object) 23 + @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-3.7> RFC 8620, Section 3.7 (Result References) *) 24 + 25 + open Jmap_types 26 + 27 + (** {1 Request Types} *) 28 + 29 + (** Abstract type representing a JMAP request under construction. 30 + 31 + This type encapsulates all the information needed to build a complete 32 + JMAP request, including capability URIs, method calls, and result 33 + references. *) 34 + type t 35 + 36 + (** {1 Request Creation} *) 37 + 38 + (** Create a new empty request. 39 + 40 + @param using List of JMAP capability URIs that this request will use 41 + @param ?created_ids Optional map of client-generated IDs for testing 42 + @param unit Required unit parameter 43 + @return A new empty request object 44 + @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-3.3> RFC 8620, Section 3.3 *) 45 + val create : 46 + using:string list -> 47 + ?created_ids:(string) id_map -> 48 + unit -> 49 + t 50 + 51 + (** Create a request with common capability URIs pre-configured. 52 + 53 + This convenience function includes the most commonly used JMAP capabilities: 54 + - urn:ietf:params:jmap:core (Core JMAP protocol) 55 + - urn:ietf:params:jmap:mail (Email extensions) 56 + - urn:ietf:params:jmap:submission (Email submission) 57 + - urn:ietf:params:jmap:vacationresponse (Vacation response) 58 + 59 + @param ?additional_capabilities Optional list of additional capability URIs 60 + @param ?created_ids Optional map of client-generated IDs for testing 61 + @param unit Required unit parameter 62 + @return A new request with standard capabilities *) 63 + val create_with_standard_capabilities : 64 + ?additional_capabilities:string list -> 65 + ?created_ids:(string) id_map -> 66 + unit -> 67 + t 68 + 69 + (** {1 Method Management} *) 70 + 71 + (** Add a method to the request. 72 + 73 + Methods are executed in the order they are added to the request. 74 + Each method is automatically assigned a unique call ID if one 75 + was not explicitly provided. 76 + 77 + @param request The request to add the method to 78 + @param method_call The method call to add 79 + @return Updated request with the method added *) 80 + val add_method : t -> Jmap_method.t -> t 81 + 82 + (** Add multiple methods to the request. 83 + 84 + This is equivalent to calling add_method multiple times but 85 + more convenient for batch operations. 86 + 87 + @param request The request to add methods to 88 + @param method_calls List of method calls to add 89 + @return Updated request with all methods added *) 90 + val add_methods : t -> Jmap_method.t list -> t 91 + 92 + (** {1 Method Call ID Management} *) 93 + 94 + (** Get the call ID for a method at a specific position. 95 + 96 + Methods are indexed starting from 0 in the order they were added. 97 + 98 + @param request The request to inspect 99 + @param index The method index (0-based) 100 + @return The call ID for the method at that position, or None if index is invalid *) 101 + val get_call_id : t -> int -> string option 102 + 103 + (** Get all call IDs in order. 104 + 105 + @param request The request to inspect 106 + @return List of call IDs in the order methods were added *) 107 + val get_all_call_ids : t -> string list 108 + 109 + (** Find the index of a method with a specific call ID. 110 + 111 + @param request The request to search 112 + @param call_id The call ID to search for 113 + @return The method index, or None if not found *) 114 + val find_method_index : t -> call_id:string -> int option 115 + 116 + (** {1 Result References} *) 117 + 118 + (** Add a result reference to a method in the request. 119 + 120 + This modifies an existing method call to use a result reference for 121 + its IDs parameter, allowing it to use results from a previous method. 122 + 123 + @param request The request to modify 124 + @param target_call_id The call ID of the method to modify 125 + @param result_of The call ID of the method to reference results from 126 + @param name The response property name to read from (e.g., "ids", "list") 127 + @param path The JSON pointer path within that property (e.g., "*", "/0") 128 + @return Updated request with result reference added 129 + @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-3.7> RFC 8620, Section 3.7 *) 130 + val with_result_reference : 131 + t -> 132 + target_call_id:string -> 133 + result_of:string -> 134 + name:string -> 135 + path:string -> 136 + t 137 + 138 + (** Add a result reference to the most recently added method. 139 + 140 + This is a convenience function equivalent to calling with_result_reference 141 + on the last method added to the request. 142 + 143 + @param request The request to modify 144 + @param result_of The call ID of the method to reference results from 145 + @param name The response property name to read from 146 + @param path The JSON pointer path within that property 147 + @return Updated request with result reference added to last method *) 148 + val with_result_reference_for_last_method : 149 + t -> 150 + result_of:string -> 151 + name:string -> 152 + path:string -> 153 + t 154 + 155 + (** {1 Capability Management} *) 156 + 157 + (** Add additional capability URIs to the request. 158 + 159 + @param request The request to modify 160 + @param capabilities Additional capability URIs to include 161 + @return Updated request with additional capabilities *) 162 + val add_capabilities : t -> string list -> t 163 + 164 + (** Get the list of capability URIs used by this request. 165 + 166 + @param request The request to inspect 167 + @return List of capability URIs *) 168 + val get_capabilities : t -> string list 169 + 170 + (** Check if a specific capability is included in the request. 171 + 172 + @param request The request to check 173 + @param capability The capability URI to check for 174 + @return True if the capability is included *) 175 + val has_capability : t -> string -> bool 176 + 177 + (** {1 Request Inspection} *) 178 + 179 + (** Get the number of methods in the request. 180 + 181 + @param request The request to inspect 182 + @return Number of method calls *) 183 + val method_count : t -> int 184 + 185 + (** Get the method at a specific index. 186 + 187 + @param request The request to inspect 188 + @param index The method index (0-based) 189 + @return The method call at that index, or None if invalid index *) 190 + val get_method : t -> int -> Jmap_method.t option 191 + 192 + (** Get all methods in the request. 193 + 194 + @param request The request to inspect 195 + @return List of method calls in order *) 196 + val get_all_methods : t -> Jmap_method.t list 197 + 198 + (** Check if the request is empty (contains no methods). 199 + 200 + @param request The request to check 201 + @return True if the request has no method calls *) 202 + val is_empty : t -> bool 203 + 204 + (** {1 Wire Protocol Conversion} *) 205 + 206 + (** Convert the request to a wire protocol Request object. 207 + 208 + This produces the low-level structure that can be serialized to JSON 209 + and sent over HTTP to a JMAP server. 210 + 211 + @param request The high-level request to convert 212 + @return Wire protocol Request object *) 213 + val to_wire_request : t -> Jmap_wire.Request.t 214 + 215 + (** Convert the request directly to JSON. 216 + 217 + This is a convenience function that combines to_wire_request with 218 + JSON serialization. 219 + 220 + @param request The request to convert 221 + @return JSON representation ready for HTTP transmission *) 222 + val to_json : t -> Yojson.Safe.t 223 + 224 + (** {1 Request Validation} *) 225 + 226 + (** Validate that all result references in the request are valid. 227 + 228 + This checks that: 229 + - Referenced method call IDs exist and come before the referencing method 230 + - Referenced property names are appropriate for the method type 231 + - JSON pointer paths are syntactically valid 232 + 233 + @param request The request to validate 234 + @return Ok if valid, Error with description if invalid *) 235 + val validate_result_references : t -> (unit, string) result 236 + 237 + (** Validate that all required capabilities are declared. 238 + 239 + This checks that the request's capability list includes all capabilities 240 + needed by the methods in the request. 241 + 242 + @param request The request to validate 243 + @return Ok if valid, Error with missing capabilities if invalid *) 244 + val validate_capabilities : t -> (unit, string list) result 245 + 246 + (** Perform comprehensive validation of the request. 247 + 248 + This combines all validation checks including result references, 249 + capabilities, method compatibility, and other constraints. 250 + 251 + @param request The request to validate 252 + @return Ok if fully valid, Error with description if invalid *) 253 + val validate : t -> (unit, string) result 254 + 255 + (** {1 Request Debugging} *) 256 + 257 + (** Get a human-readable description of the request. 258 + 259 + This produces a string describing the methods, capabilities, and 260 + result references in the request for debugging purposes. 261 + 262 + @param request The request to describe 263 + @return Multi-line string description *) 264 + val describe : t -> string 265 + 266 + (** Get detailed information about method call dependencies. 267 + 268 + This analyzes result references to determine which methods depend 269 + on results from other methods. 270 + 271 + @param request The request to analyze 272 + @return List of (method_index, depends_on_indices) pairs *) 273 + val analyze_dependencies : t -> (int * int list) list
+1102
jmap/jmap/jmap_response.ml
··· 1 + (** Implementation of type-safe JMAP response parsing and pattern matching. *) 2 + 3 + (* Internal representation of a JMAP response *) 4 + type response_data = 5 + | Core_echo_data of Yojson.Safe.t 6 + | Email_query_data of Jmap_methods.Query_response.t 7 + | Email_get_data of Yojson.Safe.t Jmap_methods.Get_response.t (* Using Yojson.Safe.t as placeholder *) 8 + | Email_set_data of (Yojson.Safe.t, Yojson.Safe.t) Jmap_methods.Set_response.t 9 + | Email_changes_data of Jmap_methods.Changes_response.t 10 + (* | Email_query_changes_data of Jmap_methods.Query_changes_response.t (* Not yet implemented *) *) 11 + | Mailbox_get_data of Yojson.Safe.t Jmap_methods.Get_response.t 12 + | Mailbox_set_data of (Yojson.Safe.t, Yojson.Safe.t) Jmap_methods.Set_response.t 13 + | Mailbox_query_data of Jmap_methods.Query_response.t 14 + | Mailbox_changes_data of Jmap_methods.Changes_response.t 15 + | Thread_get_data of Yojson.Safe.t Jmap_methods.Get_response.t 16 + | Thread_changes_data of Jmap_methods.Changes_response.t 17 + | Identity_get_data of Yojson.Safe.t Jmap_methods.Get_response.t 18 + | Identity_set_data of (Yojson.Safe.t, Yojson.Safe.t) Jmap_methods.Set_response.t 19 + | Identity_changes_data of Jmap_methods.Changes_response.t 20 + | Email_submission_get_data of Yojson.Safe.t Jmap_methods.Get_response.t 21 + | Email_submission_set_data of (Yojson.Safe.t, Yojson.Safe.t) Jmap_methods.Set_response.t 22 + | Email_submission_query_data of Jmap_methods.Query_response.t 23 + | Email_submission_changes_data of Jmap_methods.Changes_response.t 24 + | Vacation_response_get_data of Yojson.Safe.t Jmap_methods.Get_response.t 25 + | Vacation_response_set_data of (Yojson.Safe.t, Yojson.Safe.t) Jmap_methods.Set_response.t 26 + | Error_data of Jmap_error.error 27 + 28 + type t = { 29 + method_name: string; 30 + data: response_data; 31 + raw_json: Yojson.Safe.t; (* Keep original for debugging *) 32 + } 33 + 34 + (** Response types for pattern matching - simplified to use JSON placeholders *) 35 + type response_type = 36 + | Core_echo_response of Yojson.Safe.t 37 + | Email_query_response of Jmap_methods.Query_response.t 38 + | Email_get_response of Yojson.Safe.t Jmap_methods.Get_response.t 39 + | Email_set_response of (Yojson.Safe.t, Yojson.Safe.t) Jmap_methods.Set_response.t 40 + | Email_changes_response of Jmap_methods.Changes_response.t 41 + (* | Email_query_changes_response of Jmap_methods.Query_changes_response.t (* Not yet implemented *) *) 42 + | Mailbox_get_response of Yojson.Safe.t Jmap_methods.Get_response.t 43 + | Mailbox_set_response of (Yojson.Safe.t, Yojson.Safe.t) Jmap_methods.Set_response.t 44 + | Mailbox_query_response of Jmap_methods.Query_response.t 45 + | Mailbox_changes_response of Jmap_methods.Changes_response.t 46 + | Thread_get_response of Yojson.Safe.t Jmap_methods.Get_response.t 47 + | Thread_changes_response of Jmap_methods.Changes_response.t 48 + | Identity_get_response of Yojson.Safe.t Jmap_methods.Get_response.t 49 + | Identity_set_response of (Yojson.Safe.t, Yojson.Safe.t) Jmap_methods.Set_response.t 50 + | Identity_changes_response of Jmap_methods.Changes_response.t 51 + | Email_submission_get_response of Yojson.Safe.t Jmap_methods.Get_response.t 52 + | Email_submission_set_response of (Yojson.Safe.t, Yojson.Safe.t) Jmap_methods.Set_response.t 53 + | Email_submission_query_response of Jmap_methods.Query_response.t 54 + | Email_submission_changes_response of Jmap_methods.Changes_response.t 55 + | Vacation_response_get_response of Yojson.Safe.t Jmap_methods.Get_response.t 56 + | Vacation_response_set_response of (Yojson.Safe.t, Yojson.Safe.t) Jmap_methods.Set_response.t 57 + 58 + (** {1 Response Creation} *) 59 + 60 + (** Create an error response manually *) 61 + let create_error_response ~method_name error raw_json = 62 + { method_name; data = Error_data error; raw_json } 63 + 64 + (** {1 Response Parsing} *) 65 + 66 + let parse_method_response ~method_name json = 67 + try 68 + let result = match method_name with 69 + | "Core/echo" -> 70 + Ok (Core_echo_data json) 71 + 72 + | "Email/query" -> 73 + (match Jmap_methods.Query_response.of_json json with 74 + | Ok query_resp -> Ok (Email_query_data query_resp) 75 + | Error err -> Error err) 76 + 77 + | "Email/get" -> 78 + (match Jmap_methods.Get_response.of_json ~from_json:(fun j -> j) json with 79 + | Ok get_resp -> Ok (Email_get_data get_resp) 80 + | Error err -> Error err) 81 + 82 + | "Email/set" -> 83 + (match Jmap_methods.Set_response.of_json 84 + ~from_created_json:(fun j -> j) 85 + ~from_updated_json:(fun j -> j) json with 86 + | Ok set_resp -> Ok (Email_set_data set_resp) 87 + | Error err -> Error err) 88 + 89 + | "Email/changes" -> 90 + (match Jmap_methods.Changes_response.of_json json with 91 + | Ok changes_resp -> Ok (Email_changes_data changes_resp) 92 + | Error err -> Error err) 93 + 94 + | "Mailbox/get" -> 95 + (match Jmap_methods.Get_response.of_json ~from_json:(fun j -> j) json with 96 + | Ok get_resp -> Ok (Mailbox_get_data get_resp) 97 + | Error err -> Error err) 98 + 99 + | "Mailbox/query" -> 100 + (match Jmap_methods.Query_response.of_json json with 101 + | Ok query_resp -> Ok (Mailbox_query_data query_resp) 102 + | Error err -> Error err) 103 + 104 + | "Thread/get" -> 105 + (match Jmap_methods.Get_response.of_json ~from_json:(fun j -> j) json with 106 + | Ok get_resp -> Ok (Thread_get_data get_resp) 107 + | Error err -> Error err) 108 + 109 + | "Identity/get" -> 110 + (match Jmap_methods.Get_response.of_json ~from_json:(fun j -> j) json with 111 + | Ok get_resp -> Ok (Identity_get_data get_resp) 112 + | Error err -> Error err) 113 + 114 + | "EmailSubmission/get" -> 115 + (match Jmap_methods.Get_response.of_json ~from_json:(fun j -> j) json with 116 + | Ok get_resp -> Ok (Email_submission_get_data get_resp) 117 + | Error err -> Error err) 118 + 119 + | "EmailSubmission/query" -> 120 + (match Jmap_methods.Query_response.of_json json with 121 + | Ok query_resp -> Ok (Email_submission_query_data query_resp) 122 + | Error err -> Error err) 123 + 124 + | "VacationResponse/get" -> 125 + (match Jmap_methods.Get_response.of_json ~from_json:(fun j -> j) json with 126 + | Ok get_resp -> Ok (Vacation_response_get_data get_resp) 127 + | Error err -> Error err) 128 + 129 + (* Email/queryChanges - not yet implemented *) 130 + | "Email/queryChanges" -> 131 + Error (Jmap_error.Method (`UnknownMethod, Some method_name)) 132 + 133 + | "Mailbox/set" -> 134 + (match Jmap_methods.Set_response.of_json 135 + ~from_created_json:(fun j -> j) 136 + ~from_updated_json:(fun j -> j) json with 137 + | Ok set_resp -> Ok (Mailbox_set_data set_resp) 138 + | Error err -> Error err) 139 + 140 + | "Mailbox/changes" -> 141 + (match Jmap_methods.Changes_response.of_json json with 142 + | Ok changes_resp -> Ok (Mailbox_changes_data changes_resp) 143 + | Error err -> Error err) 144 + 145 + | "Thread/changes" -> 146 + (match Jmap_methods.Changes_response.of_json json with 147 + | Ok changes_resp -> Ok (Thread_changes_data changes_resp) 148 + | Error err -> Error err) 149 + 150 + | "Identity/set" -> 151 + (match Jmap_methods.Set_response.of_json 152 + ~from_created_json:(fun j -> j) 153 + ~from_updated_json:(fun j -> j) json with 154 + | Ok set_resp -> Ok (Identity_set_data set_resp) 155 + | Error err -> Error err) 156 + 157 + | "Identity/changes" -> 158 + (match Jmap_methods.Changes_response.of_json json with 159 + | Ok changes_resp -> Ok (Identity_changes_data changes_resp) 160 + | Error err -> Error err) 161 + 162 + | "EmailSubmission/set" -> 163 + (match Jmap_methods.Set_response.of_json 164 + ~from_created_json:(fun j -> j) 165 + ~from_updated_json:(fun j -> j) json with 166 + | Ok set_resp -> Ok (Email_submission_set_data set_resp) 167 + | Error err -> Error err) 168 + 169 + | "EmailSubmission/changes" -> 170 + (match Jmap_methods.Changes_response.of_json json with 171 + | Ok changes_resp -> Ok (Email_submission_changes_data changes_resp) 172 + | Error err -> Error err) 173 + 174 + | "VacationResponse/set" -> 175 + (match Jmap_methods.Set_response.of_json 176 + ~from_created_json:(fun j -> j) 177 + ~from_updated_json:(fun j -> j) json with 178 + | Ok set_resp -> Ok (Vacation_response_set_data set_resp) 179 + | Error err -> Error err) 180 + 181 + | _ -> 182 + Error (Jmap_error.Method (`UnknownMethod, Some method_name)) 183 + in 184 + match result with 185 + | Ok data -> Ok { method_name; data; raw_json = json } 186 + | Error err -> Error err 187 + with 188 + | exn -> Error (Jmap_error.Method (`InvalidArguments, Some (Printexc.to_string exn))) 189 + 190 + let parse_method_response_array json = 191 + let open Yojson.Safe.Util in 192 + try 193 + match json with 194 + | `List [method_name_json; response_json; call_id_json] -> 195 + let method_name = to_string method_name_json in 196 + let call_id = match call_id_json with 197 + | `Null -> None 198 + | `String s -> Some s 199 + | _ -> None in 200 + (match parse_method_response ~method_name response_json with 201 + | Ok response -> Ok (method_name, response, call_id) 202 + | Error err -> Error err) 203 + | _ -> Error (Jmap_error.Parse "Invalid method response array format") 204 + with 205 + | exn -> Error (Jmap_error.Parse (Printexc.to_string exn)) 206 + 207 + (** {1 Response Pattern Matching} *) 208 + 209 + let response_type t = 210 + match t.data with 211 + | Core_echo_data data -> Core_echo_response data 212 + | Email_query_data data -> Email_query_response data 213 + | Email_get_data data -> Email_get_response data 214 + | Email_set_data data -> Email_set_response data 215 + | Email_changes_data data -> Email_changes_response data 216 + (* | Email_query_changes_data data -> Email_query_changes_response data (* Not yet implemented *) *) 217 + | Mailbox_get_data data -> Mailbox_get_response data 218 + | Mailbox_set_data data -> Mailbox_set_response data 219 + | Mailbox_query_data data -> Mailbox_query_response data 220 + | Mailbox_changes_data data -> Mailbox_changes_response data 221 + | Thread_get_data data -> Thread_get_response data 222 + | Thread_changes_data data -> Thread_changes_response data 223 + | Identity_get_data data -> Identity_get_response data 224 + | Identity_set_data data -> Identity_set_response data 225 + | Identity_changes_data data -> Identity_changes_response data 226 + | Email_submission_get_data data -> Email_submission_get_response data 227 + | Email_submission_set_data data -> Email_submission_set_response data 228 + | Email_submission_query_data data -> Email_submission_query_response data 229 + | Email_submission_changes_data data -> Email_submission_changes_response data 230 + | Vacation_response_get_data data -> Vacation_response_get_response data 231 + | Vacation_response_set_data data -> Vacation_response_set_response data 232 + | Error_data _ -> failwith "Error response does not have a response_type" 233 + 234 + let method_name t = t.method_name 235 + 236 + (** {1 Helper functions for extractors} *) 237 + 238 + (* Note: These helper functions were replaced by direct implementations in each module *) 239 + 240 + (** {1 Method Response Modules using Jmap-sigs Signatures} *) 241 + 242 + module Core_echo = struct 243 + type t = Yojson.Safe.t 244 + type account_id = string 245 + type state = string 246 + 247 + let to_json t = t 248 + let of_json json = json 249 + 250 + let pp fmt t = 251 + Format.fprintf fmt "Core_echo: %s" (Yojson.Safe.pretty_to_string t) 252 + let pp_hum = pp 253 + 254 + let account_id t = 255 + let open Yojson.Safe.Util in 256 + try 257 + t |> member "accountId" |> to_string 258 + with 259 + | _ -> failwith "No accountId found in Core/echo response" 260 + let state _ = None (* Core/echo doesn't have state *) 261 + let is_error _ = false (* Core/echo is always successful *) 262 + 263 + let data t = t 264 + end 265 + 266 + module Email_query = struct 267 + type t = Jmap_methods.Query_response.t 268 + type account_id = string 269 + type state = string 270 + 271 + (* Note: Query_response doesn't have to_json, using raw JSON instead *) 272 + let to_json t = 273 + let json = `Assoc [ 274 + ("accountId", `String (Jmap_methods.Query_response.account_id t)); 275 + ("queryState", `String (Jmap_methods.Query_response.query_state t)); 276 + ("ids", `List (List.map (fun s -> `String s) (Jmap_methods.Query_response.ids t))); 277 + ("position", `Int (Jmap_methods.Query_response.position t)); 278 + ] in 279 + (match Jmap_methods.Query_response.total t with 280 + | Some total -> `Assoc [("total", `Int total)] |> Yojson.Safe.Util.combine json 281 + | None -> json) 282 + 283 + let of_json json = 284 + match Jmap_methods.Query_response.of_json json with 285 + | Ok t -> t 286 + | Error err -> failwith ("Failed to parse Query_response: " ^ (match err with 287 + | Jmap_error.Parse msg -> msg 288 + | _ -> "unknown error")) 289 + 290 + let pp fmt t = 291 + let json = to_json t in 292 + Format.fprintf fmt "Email_query: %s" (Yojson.Safe.pretty_to_string json) 293 + let pp_hum = pp 294 + 295 + let account_id t = Jmap_methods.Query_response.account_id t 296 + let state t = Some (Jmap_methods.Query_response.query_state t) 297 + let is_error _ = false 298 + 299 + let ids t = Jmap_methods.Query_response.ids t 300 + let query_state t = Jmap_methods.Query_response.query_state t 301 + let total t = Jmap_methods.Query_response.total t 302 + let position t = Jmap_methods.Query_response.position t 303 + end 304 + 305 + module Email_get = struct 306 + type t = Yojson.Safe.t Jmap_methods.Get_response.t 307 + type account_id = string 308 + type state = string 309 + 310 + let to_json t = 311 + `Assoc [ 312 + ("accountId", `String (Jmap_methods.Get_response.account_id t)); 313 + ("state", `String (Jmap_methods.Get_response.state t)); 314 + ("list", `List (Jmap_methods.Get_response.list t)); 315 + ("notFound", `List (List.map (fun s -> `String s) (Jmap_methods.Get_response.not_found t))); 316 + ] 317 + 318 + let of_json json = 319 + match Jmap_methods.Get_response.of_json ~from_json:(fun j -> j) json with 320 + | Ok t -> t 321 + | Error err -> failwith ("Failed to parse Get_response: " ^ (match err with 322 + | Jmap_error.Parse msg -> msg 323 + | _ -> "unknown error")) 324 + 325 + let pp fmt t = 326 + let json = to_json t in 327 + Format.fprintf fmt "Email_get: %s" (Yojson.Safe.pretty_to_string json) 328 + let pp_hum = pp 329 + 330 + let account_id t = Jmap_methods.Get_response.account_id t 331 + let state t = Some (Jmap_methods.Get_response.state t) 332 + let is_error _ = false 333 + 334 + let list t = Jmap_methods.Get_response.list t 335 + let not_found t = Jmap_methods.Get_response.not_found t 336 + end 337 + 338 + module Email_set = struct 339 + type t = (Yojson.Safe.t, Yojson.Safe.t) Jmap_methods.Set_response.t 340 + type account_id = string 341 + type state = string 342 + 343 + let to_json t = 344 + let created_json = match Jmap_methods.Set_response.created t with 345 + | Some map -> `Assoc (Hashtbl.fold (fun k v acc -> (k, v) :: acc) map []) 346 + | None -> `Null in 347 + let updated_json = match Jmap_methods.Set_response.updated t with 348 + | Some map -> `Assoc (Hashtbl.fold (fun k v acc -> (k, match v with Some v -> v | None -> `Null) :: acc) map []) 349 + | None -> `Null in 350 + let destroyed_json = match Jmap_methods.Set_response.destroyed t with 351 + | Some ids -> `List (List.map (fun s -> `String s) ids) 352 + | None -> `Null in 353 + `Assoc [ 354 + ("accountId", `String (Jmap_methods.Set_response.account_id t)); 355 + ("newState", `String (Jmap_methods.Set_response.new_state t)); 356 + ("created", created_json); 357 + ("updated", updated_json); 358 + ("destroyed", destroyed_json); 359 + ] 360 + 361 + let of_json json = 362 + match Jmap_methods.Set_response.of_json ~from_created_json:(fun j -> j) ~from_updated_json:(fun j -> j) json with 363 + | Ok t -> t 364 + | Error err -> failwith ("Failed to parse Set_response: " ^ (match err with 365 + | Jmap_error.Parse msg -> msg 366 + | _ -> "unknown error")) 367 + 368 + let pp fmt t = 369 + let json = to_json t in 370 + Format.fprintf fmt "Email_set: %s" (Yojson.Safe.pretty_to_string json) 371 + let pp_hum = pp 372 + 373 + let account_id t = Jmap_methods.Set_response.account_id t 374 + let state t = Some (Jmap_methods.Set_response.new_state t) 375 + let is_error _ = false 376 + 377 + let created t = Jmap_methods.Set_response.created t 378 + let updated t = Jmap_methods.Set_response.updated t 379 + let destroyed t = Jmap_methods.Set_response.destroyed t 380 + let new_state t = Jmap_methods.Set_response.new_state t 381 + end 382 + 383 + module Email_changes = struct 384 + type t = Jmap_methods.Changes_response.t 385 + type account_id = string 386 + type state = string 387 + 388 + (* Note: Changes_response doesn't have to_json, constructing manually *) 389 + let to_json t = 390 + `Assoc [ 391 + ("accountId", `String (Jmap_methods.Changes_response.account_id t)); 392 + ("newState", `String (Jmap_methods.Changes_response.new_state t)); 393 + ("hasMoreChanges", `Bool (Jmap_methods.Changes_response.has_more_changes t)); 394 + ("created", `List (List.map (fun s -> `String s) (Jmap_methods.Changes_response.created t))); 395 + ("updated", `List (List.map (fun s -> `String s) (Jmap_methods.Changes_response.updated t))); 396 + ("destroyed", `List (List.map (fun s -> `String s) (Jmap_methods.Changes_response.destroyed t))); 397 + ] 398 + 399 + let of_json json = 400 + match Jmap_methods.Changes_response.of_json json with 401 + | Ok t -> t 402 + | Error err -> failwith ("Failed to parse Changes_response: " ^ (match err with 403 + | Jmap_error.Parse msg -> msg 404 + | _ -> "unknown error")) 405 + 406 + let pp fmt t = 407 + let json = to_json t in 408 + Format.fprintf fmt "Email_changes: %s" (Yojson.Safe.pretty_to_string json) 409 + let pp_hum = pp 410 + 411 + let account_id t = Jmap_methods.Changes_response.account_id t 412 + let state t = Some (Jmap_methods.Changes_response.new_state t) 413 + let is_error _ = false 414 + 415 + let created t = Jmap_methods.Changes_response.created t 416 + let updated t = Jmap_methods.Changes_response.updated t 417 + let destroyed t = Jmap_methods.Changes_response.destroyed t 418 + let new_state t = Jmap_methods.Changes_response.new_state t 419 + end 420 + 421 + module Mailbox_get = struct 422 + type t = Yojson.Safe.t Jmap_methods.Get_response.t 423 + type account_id = string 424 + type state = string 425 + 426 + let to_json t = 427 + `Assoc [ 428 + ("accountId", `String (Jmap_methods.Get_response.account_id t)); 429 + ("state", `String (Jmap_methods.Get_response.state t)); 430 + ("list", `List (Jmap_methods.Get_response.list t)); 431 + ("notFound", `List (List.map (fun s -> `String s) (Jmap_methods.Get_response.not_found t))); 432 + ] 433 + 434 + let of_json json = 435 + match Jmap_methods.Get_response.of_json ~from_json:(fun j -> j) json with 436 + | Ok t -> t 437 + | Error err -> failwith ("Failed to parse Get_response: " ^ (match err with 438 + | Jmap_error.Parse msg -> msg 439 + | _ -> "unknown error")) 440 + 441 + let pp fmt t = 442 + let json = to_json t in 443 + Format.fprintf fmt "Mailbox_get: %s" (Yojson.Safe.pretty_to_string json) 444 + let pp_hum = pp 445 + 446 + let account_id t = Jmap_methods.Get_response.account_id t 447 + let state t = Some (Jmap_methods.Get_response.state t) 448 + let is_error _ = false 449 + 450 + let list t = Jmap_methods.Get_response.list t 451 + end 452 + 453 + module Mailbox_query = struct 454 + type t = Jmap_methods.Query_response.t 455 + type account_id = string 456 + type state = string 457 + 458 + let to_json t = 459 + let json = `Assoc [ 460 + ("accountId", `String (Jmap_methods.Query_response.account_id t)); 461 + ("queryState", `String (Jmap_methods.Query_response.query_state t)); 462 + ("ids", `List (List.map (fun s -> `String s) (Jmap_methods.Query_response.ids t))); 463 + ("position", `Int (Jmap_methods.Query_response.position t)); 464 + ] in 465 + (match Jmap_methods.Query_response.total t with 466 + | Some total -> `Assoc [("total", `Int total)] |> Yojson.Safe.Util.combine json 467 + | None -> json) 468 + 469 + let of_json json = 470 + match Jmap_methods.Query_response.of_json json with 471 + | Ok t -> t 472 + | Error err -> failwith ("Failed to parse Query_response: " ^ (match err with 473 + | Jmap_error.Parse msg -> msg 474 + | _ -> "unknown error")) 475 + 476 + let pp fmt t = 477 + let json = to_json t in 478 + Format.fprintf fmt "Mailbox_query: %s" (Yojson.Safe.pretty_to_string json) 479 + let pp_hum = pp 480 + 481 + let account_id t = Jmap_methods.Query_response.account_id t 482 + let state t = Some (Jmap_methods.Query_response.query_state t) 483 + let is_error _ = false 484 + 485 + let ids t = Jmap_methods.Query_response.ids t 486 + end 487 + 488 + module Mailbox_set = struct 489 + type t = (Yojson.Safe.t, Yojson.Safe.t) Jmap_methods.Set_response.t 490 + type account_id = string 491 + type state = string 492 + 493 + let to_json t = 494 + let created_json = match Jmap_methods.Set_response.created t with 495 + | Some map -> `Assoc (Hashtbl.fold (fun k v acc -> (k, v) :: acc) map []) 496 + | None -> `Null in 497 + let updated_json = match Jmap_methods.Set_response.updated t with 498 + | Some map -> `Assoc (Hashtbl.fold (fun k v acc -> (k, match v with Some v -> v | None -> `Null) :: acc) map []) 499 + | None -> `Null in 500 + let destroyed_json = match Jmap_methods.Set_response.destroyed t with 501 + | Some ids -> `List (List.map (fun s -> `String s) ids) 502 + | None -> `Null in 503 + `Assoc [ 504 + ("accountId", `String (Jmap_methods.Set_response.account_id t)); 505 + ("newState", `String (Jmap_methods.Set_response.new_state t)); 506 + ("created", created_json); 507 + ("updated", updated_json); 508 + ("destroyed", destroyed_json); 509 + ] 510 + 511 + let of_json json = 512 + match Jmap_methods.Set_response.of_json ~from_created_json:(fun j -> j) ~from_updated_json:(fun j -> j) json with 513 + | Ok t -> t 514 + | Error err -> failwith ("Failed to parse Set_response: " ^ (match err with 515 + | Jmap_error.Parse msg -> msg 516 + | _ -> "unknown error")) 517 + 518 + let pp fmt t = 519 + let json = to_json t in 520 + Format.fprintf fmt "Mailbox_set: %s" (Yojson.Safe.pretty_to_string json) 521 + let pp_hum = pp 522 + 523 + let account_id t = Jmap_methods.Set_response.account_id t 524 + let state t = Some (Jmap_methods.Set_response.new_state t) 525 + let is_error _ = false 526 + 527 + let created t = Jmap_methods.Set_response.created t 528 + let updated t = Jmap_methods.Set_response.updated t 529 + let destroyed t = Jmap_methods.Set_response.destroyed t 530 + let new_state t = Jmap_methods.Set_response.new_state t 531 + end 532 + 533 + module Mailbox_changes = struct 534 + type t = Jmap_methods.Changes_response.t 535 + type account_id = string 536 + type state = string 537 + 538 + let to_json t = 539 + `Assoc [ 540 + ("accountId", `String (Jmap_methods.Changes_response.account_id t)); 541 + ("newState", `String (Jmap_methods.Changes_response.new_state t)); 542 + ("hasMoreChanges", `Bool (Jmap_methods.Changes_response.has_more_changes t)); 543 + ("created", `List (List.map (fun s -> `String s) (Jmap_methods.Changes_response.created t))); 544 + ("updated", `List (List.map (fun s -> `String s) (Jmap_methods.Changes_response.updated t))); 545 + ("destroyed", `List (List.map (fun s -> `String s) (Jmap_methods.Changes_response.destroyed t))); 546 + ] 547 + 548 + let of_json json = 549 + match Jmap_methods.Changes_response.of_json json with 550 + | Ok t -> t 551 + | Error err -> failwith ("Failed to parse Changes_response: " ^ (match err with 552 + | Jmap_error.Parse msg -> msg 553 + | _ -> "unknown error")) 554 + 555 + let pp fmt t = 556 + let json = to_json t in 557 + Format.fprintf fmt "Mailbox_changes: %s" (Yojson.Safe.pretty_to_string json) 558 + let pp_hum = pp 559 + 560 + let account_id t = Jmap_methods.Changes_response.account_id t 561 + let state t = Some (Jmap_methods.Changes_response.new_state t) 562 + let is_error _ = false 563 + 564 + let created t = Jmap_methods.Changes_response.created t 565 + let updated t = Jmap_methods.Changes_response.updated t 566 + let destroyed t = Jmap_methods.Changes_response.destroyed t 567 + let new_state t = Jmap_methods.Changes_response.new_state t 568 + end 569 + 570 + module Thread_get = struct 571 + type t = Yojson.Safe.t Jmap_methods.Get_response.t 572 + type account_id = string 573 + type state = string 574 + 575 + let to_json t = 576 + `Assoc [ 577 + ("accountId", `String (Jmap_methods.Get_response.account_id t)); 578 + ("state", `String (Jmap_methods.Get_response.state t)); 579 + ("list", `List (Jmap_methods.Get_response.list t)); 580 + ("notFound", `List (List.map (fun s -> `String s) (Jmap_methods.Get_response.not_found t))); 581 + ] 582 + 583 + let of_json json = 584 + match Jmap_methods.Get_response.of_json ~from_json:(fun j -> j) json with 585 + | Ok t -> t 586 + | Error err -> failwith ("Failed to parse Get_response: " ^ (match err with 587 + | Jmap_error.Parse msg -> msg 588 + | _ -> "unknown error")) 589 + 590 + let pp fmt t = 591 + let json = to_json t in 592 + Format.fprintf fmt "Thread_get: %s" (Yojson.Safe.pretty_to_string json) 593 + let pp_hum = pp 594 + 595 + let account_id t = Jmap_methods.Get_response.account_id t 596 + let state t = Some (Jmap_methods.Get_response.state t) 597 + let is_error _ = false 598 + 599 + let list t = Jmap_methods.Get_response.list t 600 + end 601 + 602 + module Thread_changes = struct 603 + type t = Jmap_methods.Changes_response.t 604 + type account_id = string 605 + type state = string 606 + 607 + let to_json t = 608 + `Assoc [ 609 + ("accountId", `String (Jmap_methods.Changes_response.account_id t)); 610 + ("newState", `String (Jmap_methods.Changes_response.new_state t)); 611 + ("hasMoreChanges", `Bool (Jmap_methods.Changes_response.has_more_changes t)); 612 + ("created", `List (List.map (fun s -> `String s) (Jmap_methods.Changes_response.created t))); 613 + ("updated", `List (List.map (fun s -> `String s) (Jmap_methods.Changes_response.updated t))); 614 + ("destroyed", `List (List.map (fun s -> `String s) (Jmap_methods.Changes_response.destroyed t))); 615 + ] 616 + 617 + let of_json json = 618 + match Jmap_methods.Changes_response.of_json json with 619 + | Ok t -> t 620 + | Error err -> failwith ("Failed to parse Changes_response: " ^ (match err with 621 + | Jmap_error.Parse msg -> msg 622 + | _ -> "unknown error")) 623 + 624 + let pp fmt t = 625 + let json = to_json t in 626 + Format.fprintf fmt "Thread_changes: %s" (Yojson.Safe.pretty_to_string json) 627 + let pp_hum = pp 628 + 629 + let account_id t = Jmap_methods.Changes_response.account_id t 630 + let state t = Some (Jmap_methods.Changes_response.new_state t) 631 + let is_error _ = false 632 + 633 + let created t = Jmap_methods.Changes_response.created t 634 + let updated t = Jmap_methods.Changes_response.updated t 635 + let destroyed t = Jmap_methods.Changes_response.destroyed t 636 + let new_state t = Jmap_methods.Changes_response.new_state t 637 + end 638 + 639 + module Identity_get = struct 640 + type t = Yojson.Safe.t Jmap_methods.Get_response.t 641 + type account_id = string 642 + type state = string 643 + 644 + let to_json t = 645 + `Assoc [ 646 + ("accountId", `String (Jmap_methods.Get_response.account_id t)); 647 + ("state", `String (Jmap_methods.Get_response.state t)); 648 + ("list", `List (Jmap_methods.Get_response.list t)); 649 + ("notFound", `List (List.map (fun s -> `String s) (Jmap_methods.Get_response.not_found t))); 650 + ] 651 + 652 + let of_json json = 653 + match Jmap_methods.Get_response.of_json ~from_json:(fun j -> j) json with 654 + | Ok t -> t 655 + | Error err -> failwith ("Failed to parse Get_response: " ^ (match err with 656 + | Jmap_error.Parse msg -> msg 657 + | _ -> "unknown error")) 658 + 659 + let pp fmt t = 660 + let json = to_json t in 661 + Format.fprintf fmt "Identity_get: %s" (Yojson.Safe.pretty_to_string json) 662 + let pp_hum = pp 663 + 664 + let account_id t = Jmap_methods.Get_response.account_id t 665 + let state t = Some (Jmap_methods.Get_response.state t) 666 + let is_error _ = false 667 + 668 + let list t = Jmap_methods.Get_response.list t 669 + end 670 + 671 + module Identity_set = struct 672 + type t = (Yojson.Safe.t, Yojson.Safe.t) Jmap_methods.Set_response.t 673 + type account_id = string 674 + type state = string 675 + 676 + let to_json t = 677 + let created_json = match Jmap_methods.Set_response.created t with 678 + | Some map -> `Assoc (Hashtbl.fold (fun k v acc -> (k, v) :: acc) map []) 679 + | None -> `Null in 680 + let updated_json = match Jmap_methods.Set_response.updated t with 681 + | Some map -> `Assoc (Hashtbl.fold (fun k v acc -> (k, match v with Some v -> v | None -> `Null) :: acc) map []) 682 + | None -> `Null in 683 + let destroyed_json = match Jmap_methods.Set_response.destroyed t with 684 + | Some ids -> `List (List.map (fun s -> `String s) ids) 685 + | None -> `Null in 686 + `Assoc [ 687 + ("accountId", `String (Jmap_methods.Set_response.account_id t)); 688 + ("newState", `String (Jmap_methods.Set_response.new_state t)); 689 + ("created", created_json); 690 + ("updated", updated_json); 691 + ("destroyed", destroyed_json); 692 + ] 693 + 694 + let of_json json = 695 + match Jmap_methods.Set_response.of_json ~from_created_json:(fun j -> j) ~from_updated_json:(fun j -> j) json with 696 + | Ok t -> t 697 + | Error err -> failwith ("Failed to parse Set_response: " ^ (match err with 698 + | Jmap_error.Parse msg -> msg 699 + | _ -> "unknown error")) 700 + 701 + let pp fmt t = 702 + let json = to_json t in 703 + Format.fprintf fmt "Identity_set: %s" (Yojson.Safe.pretty_to_string json) 704 + let pp_hum = pp 705 + 706 + let account_id t = Jmap_methods.Set_response.account_id t 707 + let state t = Some (Jmap_methods.Set_response.new_state t) 708 + let is_error _ = false 709 + 710 + let created t = Jmap_methods.Set_response.created t 711 + let updated t = Jmap_methods.Set_response.updated t 712 + let destroyed t = Jmap_methods.Set_response.destroyed t 713 + let new_state t = Jmap_methods.Set_response.new_state t 714 + end 715 + 716 + module Identity_changes = struct 717 + type t = Jmap_methods.Changes_response.t 718 + type account_id = string 719 + type state = string 720 + 721 + let to_json t = 722 + `Assoc [ 723 + ("accountId", `String (Jmap_methods.Changes_response.account_id t)); 724 + ("newState", `String (Jmap_methods.Changes_response.new_state t)); 725 + ("hasMoreChanges", `Bool (Jmap_methods.Changes_response.has_more_changes t)); 726 + ("created", `List (List.map (fun s -> `String s) (Jmap_methods.Changes_response.created t))); 727 + ("updated", `List (List.map (fun s -> `String s) (Jmap_methods.Changes_response.updated t))); 728 + ("destroyed", `List (List.map (fun s -> `String s) (Jmap_methods.Changes_response.destroyed t))); 729 + ] 730 + 731 + let of_json json = 732 + match Jmap_methods.Changes_response.of_json json with 733 + | Ok t -> t 734 + | Error err -> failwith ("Failed to parse Changes_response: " ^ (match err with 735 + | Jmap_error.Parse msg -> msg 736 + | _ -> "unknown error")) 737 + 738 + let pp fmt t = 739 + let json = to_json t in 740 + Format.fprintf fmt "Identity_changes: %s" (Yojson.Safe.pretty_to_string json) 741 + let pp_hum = pp 742 + 743 + let account_id t = Jmap_methods.Changes_response.account_id t 744 + let state t = Some (Jmap_methods.Changes_response.new_state t) 745 + let is_error _ = false 746 + 747 + let created t = Jmap_methods.Changes_response.created t 748 + let updated t = Jmap_methods.Changes_response.updated t 749 + let destroyed t = Jmap_methods.Changes_response.destroyed t 750 + let new_state t = Jmap_methods.Changes_response.new_state t 751 + end 752 + 753 + module Email_submission_get = struct 754 + type t = Yojson.Safe.t Jmap_methods.Get_response.t 755 + type account_id = string 756 + type state = string 757 + 758 + let to_json t = 759 + `Assoc [ 760 + ("accountId", `String (Jmap_methods.Get_response.account_id t)); 761 + ("state", `String (Jmap_methods.Get_response.state t)); 762 + ("list", `List (Jmap_methods.Get_response.list t)); 763 + ("notFound", `List (List.map (fun s -> `String s) (Jmap_methods.Get_response.not_found t))); 764 + ] 765 + 766 + let of_json json = 767 + match Jmap_methods.Get_response.of_json ~from_json:(fun j -> j) json with 768 + | Ok t -> t 769 + | Error err -> failwith ("Failed to parse Get_response: " ^ (match err with 770 + | Jmap_error.Parse msg -> msg 771 + | _ -> "unknown error")) 772 + 773 + let pp fmt t = 774 + let json = to_json t in 775 + Format.fprintf fmt "Email_submission_get: %s" (Yojson.Safe.pretty_to_string json) 776 + let pp_hum = pp 777 + 778 + let account_id t = Jmap_methods.Get_response.account_id t 779 + let state t = Some (Jmap_methods.Get_response.state t) 780 + let is_error _ = false 781 + 782 + let list t = Jmap_methods.Get_response.list t 783 + end 784 + 785 + module Email_submission_set = struct 786 + type t = (Yojson.Safe.t, Yojson.Safe.t) Jmap_methods.Set_response.t 787 + type account_id = string 788 + type state = string 789 + 790 + let to_json t = 791 + let created_json = match Jmap_methods.Set_response.created t with 792 + | Some map -> `Assoc (Hashtbl.fold (fun k v acc -> (k, v) :: acc) map []) 793 + | None -> `Null in 794 + let updated_json = match Jmap_methods.Set_response.updated t with 795 + | Some map -> `Assoc (Hashtbl.fold (fun k v acc -> (k, match v with Some v -> v | None -> `Null) :: acc) map []) 796 + | None -> `Null in 797 + let destroyed_json = match Jmap_methods.Set_response.destroyed t with 798 + | Some ids -> `List (List.map (fun s -> `String s) ids) 799 + | None -> `Null in 800 + `Assoc [ 801 + ("accountId", `String (Jmap_methods.Set_response.account_id t)); 802 + ("newState", `String (Jmap_methods.Set_response.new_state t)); 803 + ("created", created_json); 804 + ("updated", updated_json); 805 + ("destroyed", destroyed_json); 806 + ] 807 + 808 + let of_json json = 809 + match Jmap_methods.Set_response.of_json ~from_created_json:(fun j -> j) ~from_updated_json:(fun j -> j) json with 810 + | Ok t -> t 811 + | Error err -> failwith ("Failed to parse Set_response: " ^ (match err with 812 + | Jmap_error.Parse msg -> msg 813 + | _ -> "unknown error")) 814 + 815 + let pp fmt t = 816 + let json = to_json t in 817 + Format.fprintf fmt "Email_submission_set: %s" (Yojson.Safe.pretty_to_string json) 818 + let pp_hum = pp 819 + 820 + let account_id t = Jmap_methods.Set_response.account_id t 821 + let state t = Some (Jmap_methods.Set_response.new_state t) 822 + let is_error _ = false 823 + 824 + let created t = Jmap_methods.Set_response.created t 825 + let updated t = Jmap_methods.Set_response.updated t 826 + let destroyed t = Jmap_methods.Set_response.destroyed t 827 + let new_state t = Jmap_methods.Set_response.new_state t 828 + end 829 + 830 + module Email_submission_query = struct 831 + type t = Jmap_methods.Query_response.t 832 + type account_id = string 833 + type state = string 834 + 835 + let to_json t = 836 + let json = `Assoc [ 837 + ("accountId", `String (Jmap_methods.Query_response.account_id t)); 838 + ("queryState", `String (Jmap_methods.Query_response.query_state t)); 839 + ("ids", `List (List.map (fun s -> `String s) (Jmap_methods.Query_response.ids t))); 840 + ("position", `Int (Jmap_methods.Query_response.position t)); 841 + ] in 842 + (match Jmap_methods.Query_response.total t with 843 + | Some total -> `Assoc [("total", `Int total)] |> Yojson.Safe.Util.combine json 844 + | None -> json) 845 + 846 + let of_json json = 847 + match Jmap_methods.Query_response.of_json json with 848 + | Ok t -> t 849 + | Error err -> failwith ("Failed to parse Query_response: " ^ (match err with 850 + | Jmap_error.Parse msg -> msg 851 + | _ -> "unknown error")) 852 + 853 + let pp fmt t = 854 + let json = to_json t in 855 + Format.fprintf fmt "Email_submission_query: %s" (Yojson.Safe.pretty_to_string json) 856 + let pp_hum = pp 857 + 858 + let account_id t = Jmap_methods.Query_response.account_id t 859 + let state t = Some (Jmap_methods.Query_response.query_state t) 860 + let is_error _ = false 861 + 862 + let ids t = Jmap_methods.Query_response.ids t 863 + end 864 + 865 + module Email_submission_changes = struct 866 + type t = Jmap_methods.Changes_response.t 867 + type account_id = string 868 + type state = string 869 + 870 + let to_json t = 871 + `Assoc [ 872 + ("accountId", `String (Jmap_methods.Changes_response.account_id t)); 873 + ("newState", `String (Jmap_methods.Changes_response.new_state t)); 874 + ("hasMoreChanges", `Bool (Jmap_methods.Changes_response.has_more_changes t)); 875 + ("created", `List (List.map (fun s -> `String s) (Jmap_methods.Changes_response.created t))); 876 + ("updated", `List (List.map (fun s -> `String s) (Jmap_methods.Changes_response.updated t))); 877 + ("destroyed", `List (List.map (fun s -> `String s) (Jmap_methods.Changes_response.destroyed t))); 878 + ] 879 + 880 + let of_json json = 881 + match Jmap_methods.Changes_response.of_json json with 882 + | Ok t -> t 883 + | Error err -> failwith ("Failed to parse Changes_response: " ^ (match err with 884 + | Jmap_error.Parse msg -> msg 885 + | _ -> "unknown error")) 886 + 887 + let pp fmt t = 888 + let json = to_json t in 889 + Format.fprintf fmt "Email_submission_changes: %s" (Yojson.Safe.pretty_to_string json) 890 + let pp_hum = pp 891 + 892 + let account_id t = Jmap_methods.Changes_response.account_id t 893 + let state t = Some (Jmap_methods.Changes_response.new_state t) 894 + let is_error _ = false 895 + 896 + let created t = Jmap_methods.Changes_response.created t 897 + let updated t = Jmap_methods.Changes_response.updated t 898 + let destroyed t = Jmap_methods.Changes_response.destroyed t 899 + let new_state t = Jmap_methods.Changes_response.new_state t 900 + end 901 + 902 + module Vacation_response_get = struct 903 + type t = Yojson.Safe.t Jmap_methods.Get_response.t 904 + type account_id = string 905 + type state = string 906 + 907 + let to_json t = 908 + `Assoc [ 909 + ("accountId", `String (Jmap_methods.Get_response.account_id t)); 910 + ("state", `String (Jmap_methods.Get_response.state t)); 911 + ("list", `List (Jmap_methods.Get_response.list t)); 912 + ("notFound", `List (List.map (fun s -> `String s) (Jmap_methods.Get_response.not_found t))); 913 + ] 914 + 915 + let of_json json = 916 + match Jmap_methods.Get_response.of_json ~from_json:(fun j -> j) json with 917 + | Ok t -> t 918 + | Error err -> failwith ("Failed to parse Get_response: " ^ (match err with 919 + | Jmap_error.Parse msg -> msg 920 + | _ -> "unknown error")) 921 + 922 + let pp fmt t = 923 + let json = to_json t in 924 + Format.fprintf fmt "Vacation_response_get: %s" (Yojson.Safe.pretty_to_string json) 925 + let pp_hum = pp 926 + 927 + let account_id t = Jmap_methods.Get_response.account_id t 928 + let state t = Some (Jmap_methods.Get_response.state t) 929 + let is_error _ = false 930 + 931 + let list t = Jmap_methods.Get_response.list t 932 + end 933 + 934 + module Vacation_response_set = struct 935 + type t = (Yojson.Safe.t, Yojson.Safe.t) Jmap_methods.Set_response.t 936 + type account_id = string 937 + type state = string 938 + 939 + let to_json t = 940 + let created_json = match Jmap_methods.Set_response.created t with 941 + | Some map -> `Assoc (Hashtbl.fold (fun k v acc -> (k, v) :: acc) map []) 942 + | None -> `Null in 943 + let updated_json = match Jmap_methods.Set_response.updated t with 944 + | Some map -> `Assoc (Hashtbl.fold (fun k v acc -> (k, match v with Some v -> v | None -> `Null) :: acc) map []) 945 + | None -> `Null in 946 + let destroyed_json = match Jmap_methods.Set_response.destroyed t with 947 + | Some ids -> `List (List.map (fun s -> `String s) ids) 948 + | None -> `Null in 949 + `Assoc [ 950 + ("accountId", `String (Jmap_methods.Set_response.account_id t)); 951 + ("newState", `String (Jmap_methods.Set_response.new_state t)); 952 + ("created", created_json); 953 + ("updated", updated_json); 954 + ("destroyed", destroyed_json); 955 + ] 956 + 957 + let of_json json = 958 + match Jmap_methods.Set_response.of_json ~from_created_json:(fun j -> j) ~from_updated_json:(fun j -> j) json with 959 + | Ok t -> t 960 + | Error err -> failwith ("Failed to parse Set_response: " ^ (match err with 961 + | Jmap_error.Parse msg -> msg 962 + | _ -> "unknown error")) 963 + 964 + let pp fmt t = 965 + let json = to_json t in 966 + Format.fprintf fmt "Vacation_response_set: %s" (Yojson.Safe.pretty_to_string json) 967 + let pp_hum = pp 968 + 969 + let account_id t = Jmap_methods.Set_response.account_id t 970 + let state t = Some (Jmap_methods.Set_response.new_state t) 971 + let is_error _ = false 972 + 973 + let created t = Jmap_methods.Set_response.created t 974 + let updated t = Jmap_methods.Set_response.updated t 975 + let destroyed t = Jmap_methods.Set_response.destroyed t 976 + let new_state t = Jmap_methods.Set_response.new_state t 977 + end 978 + 979 + (** {1 Response Data Extraction Functions} *) 980 + 981 + (** Extract typed response data from the main response type *) 982 + let get_core_echo t : Core_echo.t option = 983 + match t.data with 984 + | Core_echo_data data -> Some data 985 + | _ -> None 986 + 987 + let get_email_query t : Email_query.t option = 988 + match t.data with 989 + | Email_query_data data -> Some data 990 + | _ -> None 991 + 992 + let get_email_get t : Email_get.t option = 993 + match t.data with 994 + | Email_get_data data -> Some data 995 + | _ -> None 996 + 997 + let get_email_set t : Email_set.t option = 998 + match t.data with 999 + | Email_set_data data -> Some data 1000 + | _ -> None 1001 + 1002 + let get_email_changes t : Email_changes.t option = 1003 + match t.data with 1004 + | Email_changes_data data -> Some data 1005 + | _ -> None 1006 + 1007 + let get_mailbox_get t : Mailbox_get.t option = 1008 + match t.data with 1009 + | Mailbox_get_data data -> Some data 1010 + | _ -> None 1011 + 1012 + let get_mailbox_query t : Mailbox_query.t option = 1013 + match t.data with 1014 + | Mailbox_query_data data -> Some data 1015 + | _ -> None 1016 + 1017 + let get_mailbox_set t : Mailbox_set.t option = 1018 + match t.data with 1019 + | Mailbox_set_data data -> Some data 1020 + | _ -> None 1021 + 1022 + let get_mailbox_changes t : Mailbox_changes.t option = 1023 + match t.data with 1024 + | Mailbox_changes_data data -> Some data 1025 + | _ -> None 1026 + 1027 + let get_thread_get t : Thread_get.t option = 1028 + match t.data with 1029 + | Thread_get_data data -> Some data 1030 + | _ -> None 1031 + 1032 + let get_thread_changes t : Thread_changes.t option = 1033 + match t.data with 1034 + | Thread_changes_data data -> Some data 1035 + | _ -> None 1036 + 1037 + let get_identity_get t : Identity_get.t option = 1038 + match t.data with 1039 + | Identity_get_data data -> Some data 1040 + | _ -> None 1041 + 1042 + let get_identity_set t : Identity_set.t option = 1043 + match t.data with 1044 + | Identity_set_data data -> Some data 1045 + | _ -> None 1046 + 1047 + let get_identity_changes t : Identity_changes.t option = 1048 + match t.data with 1049 + | Identity_changes_data data -> Some data 1050 + | _ -> None 1051 + 1052 + let get_email_submission_get t : Email_submission_get.t option = 1053 + match t.data with 1054 + | Email_submission_get_data data -> Some data 1055 + | _ -> None 1056 + 1057 + let get_email_submission_set t : Email_submission_set.t option = 1058 + match t.data with 1059 + | Email_submission_set_data data -> Some data 1060 + | _ -> None 1061 + 1062 + let get_email_submission_query t : Email_submission_query.t option = 1063 + match t.data with 1064 + | Email_submission_query_data data -> Some data 1065 + | _ -> None 1066 + 1067 + let get_email_submission_changes t : Email_submission_changes.t option = 1068 + match t.data with 1069 + | Email_submission_changes_data data -> Some data 1070 + | _ -> None 1071 + 1072 + let get_vacation_response_get t : Vacation_response_get.t option = 1073 + match t.data with 1074 + | Vacation_response_get_data data -> Some data 1075 + | _ -> None 1076 + 1077 + let get_vacation_response_set t : Vacation_response_set.t option = 1078 + match t.data with 1079 + | Vacation_response_set_data data -> Some data 1080 + | _ -> None 1081 + 1082 + (** {1 Utility Functions} *) 1083 + 1084 + let is_error t = 1085 + match t.data with 1086 + | Error_data _ -> true 1087 + | _ -> false 1088 + 1089 + let error t = 1090 + match t.data with 1091 + | Error_data err -> Some err 1092 + | _ -> None 1093 + 1094 + let account_id t = 1095 + (* Try to extract account ID from various response types *) 1096 + let open Yojson.Safe.Util in 1097 + try 1098 + Some (t.raw_json |> member "accountId" |> to_string) 1099 + with 1100 + | _ -> None 1101 + 1102 + let to_json t = t.raw_json
+507
jmap/jmap/jmap_response.mli
··· 1 + (** Type-safe JMAP response parsing and pattern matching. 2 + 3 + This module provides abstract types for JMAP method responses with 4 + parser functions to convert JSON responses to typed OCaml values. 5 + It supports pattern matching on different response types and provides 6 + safe extraction of response data. 7 + 8 + The module handles both successful responses and error responses, 9 + providing a type-safe way to work with JMAP server responses without 10 + manual JSON parsing. 11 + 12 + Example usage: 13 + {[ 14 + match Jmap_response.parse_method_response json with 15 + | Ok (Email_query_response resp) -> 16 + let ids = Jmap_response.Email_query.ids resp in 17 + (* Work with typed email IDs *) 18 + | Ok (Email_get_response resp) -> 19 + let emails = Jmap_response.Email_get.list resp in 20 + (* Work with typed email objects *) 21 + | Error err -> 22 + (* Handle parsing errors *) 23 + ]} 24 + 25 + @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-3.4> RFC 8620, Section 3.4 (Method Responses) 26 + @see <https://www.rfc-editor.org/rfc/rfc8621.html> RFC 8621 (Email Extensions) *) 27 + 28 + open Jmap_types 29 + 30 + (** {1 Response Types} *) 31 + 32 + (** Abstract type representing any JMAP method response. 33 + 34 + This type can hold responses from any JMAP method and provides 35 + type-safe pattern matching to determine the specific response type. *) 36 + type t 37 + 38 + (** Specific response types for pattern matching *) 39 + type response_type = 40 + | Core_echo_response of Yojson.Safe.t 41 + | Email_query_response of Jmap_methods.Query_response.t 42 + | Email_get_response of Yojson.Safe.t Jmap_methods.Get_response.t 43 + | Email_set_response of (Yojson.Safe.t, Yojson.Safe.t) Jmap_methods.Set_response.t 44 + | Email_changes_response of Jmap_methods.Changes_response.t 45 + (* | Email_query_changes_response of Jmap_methods.Query_changes_response.t (* Not yet implemented *) *) 46 + | Mailbox_get_response of Yojson.Safe.t Jmap_methods.Get_response.t 47 + | Mailbox_set_response of (Yojson.Safe.t, Yojson.Safe.t) Jmap_methods.Set_response.t 48 + | Mailbox_query_response of Jmap_methods.Query_response.t 49 + | Mailbox_changes_response of Jmap_methods.Changes_response.t 50 + | Thread_get_response of Yojson.Safe.t Jmap_methods.Get_response.t 51 + | Thread_changes_response of Jmap_methods.Changes_response.t 52 + | Identity_get_response of Yojson.Safe.t Jmap_methods.Get_response.t 53 + | Identity_set_response of (Yojson.Safe.t, Yojson.Safe.t) Jmap_methods.Set_response.t 54 + | Identity_changes_response of Jmap_methods.Changes_response.t 55 + | Email_submission_get_response of Yojson.Safe.t Jmap_methods.Get_response.t 56 + | Email_submission_set_response of (Yojson.Safe.t, Yojson.Safe.t) Jmap_methods.Set_response.t 57 + | Email_submission_query_response of Jmap_methods.Query_response.t 58 + | Email_submission_changes_response of Jmap_methods.Changes_response.t 59 + | Vacation_response_get_response of Yojson.Safe.t Jmap_methods.Get_response.t 60 + | Vacation_response_set_response of (Yojson.Safe.t, Yojson.Safe.t) Jmap_methods.Set_response.t 61 + 62 + (** {1 Response Creation} *) 63 + 64 + (** Create an error response manually. 65 + 66 + This function allows manual creation of error responses when they 67 + need to be constructed directly rather than parsed from JSON. 68 + 69 + @param method_name The name of the method that failed 70 + @param error The error that occurred 71 + @param raw_json The original JSON for debugging purposes *) 72 + val create_error_response : 73 + method_name:string -> 74 + Jmap_error.error -> 75 + Yojson.Safe.t -> 76 + t 77 + 78 + (** {1 Response Parsing} *) 79 + 80 + (** Parse a JMAP method response from JSON. 81 + 82 + This function takes a JSON method response (the second element of a 83 + JMAP method response array) and converts it to a typed response object. 84 + The resulting response can be pattern-matched or accessed through the 85 + method-specific modules that implement METHOD_RESPONSE signatures. 86 + 87 + @param method_name The name of the method (e.g., "Email/query") 88 + @param json The JSON response object to parse 89 + @return Parsed response or error 90 + @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-3.4> RFC 8620, Section 3.4 *) 91 + val parse_method_response : 92 + method_name:string -> 93 + Yojson.Safe.t -> 94 + (t, Jmap_error.error) result 95 + 96 + (** Parse a complete JMAP method response array. 97 + 98 + This function parses the full method response array format: 99 + ["MethodName", {response}, "methodCallId"] 100 + 101 + @param json The JSON array representing the method response 102 + @return Tuple of (method_name, parsed_response, call_id) or error *) 103 + val parse_method_response_array : 104 + Yojson.Safe.t -> 105 + (string * t * string option, Jmap_error.error) result 106 + 107 + (** {1 Response Pattern Matching} *) 108 + 109 + (** Get the response type for pattern matching. 110 + 111 + @param response The response to inspect 112 + @return The specific response type for pattern matching *) 113 + val response_type : t -> response_type 114 + 115 + (** Get the method name that produced this response. 116 + 117 + @param response The response to inspect 118 + @return The JMAP method name (e.g., "Email/query") *) 119 + val method_name : t -> string 120 + 121 + (** {1 Method Response Modules using Jmap-sigs Signatures} *) 122 + 123 + (** Core/echo response - implements METHOD_RESPONSE for echo data *) 124 + module Core_echo : sig 125 + include Jmap_sigs.METHOD_RESPONSE with type t = Yojson.Safe.t 126 + 127 + (** Extract echo response data (convenience alias for the underlying data) *) 128 + val data : t -> t 129 + end 130 + 131 + (** Email/query response - implements METHOD_RESPONSE for query operations *) 132 + module Email_query : sig 133 + include Jmap_sigs.METHOD_RESPONSE with type t = Jmap_methods.Query_response.t 134 + 135 + (** Extract email IDs from query response *) 136 + val ids : t -> string list 137 + 138 + (** Extract query state from response *) 139 + val query_state : t -> string 140 + 141 + (** Extract total count from response *) 142 + val total : t -> uint option 143 + 144 + (** Extract current position from response *) 145 + val position : t -> uint 146 + end 147 + 148 + (** Email/get response - implements METHOD_RESPONSE for get operations *) 149 + module Email_get : sig 150 + include Jmap_sigs.METHOD_RESPONSE with type t = Yojson.Safe.t Jmap_methods.Get_response.t 151 + 152 + (** Extract email objects from get response *) 153 + val list : t -> Yojson.Safe.t list 154 + 155 + (** Extract not found IDs from response *) 156 + val not_found : t -> string list 157 + end 158 + 159 + (** Email/set response - implements METHOD_RESPONSE for set operations *) 160 + module Email_set : sig 161 + include Jmap_sigs.METHOD_RESPONSE with type t = (Yojson.Safe.t, Yojson.Safe.t) Jmap_methods.Set_response.t 162 + 163 + (** Extract created emails from response *) 164 + val created : t -> Yojson.Safe.t id_map option 165 + 166 + (** Extract updated emails from response *) 167 + val updated : t -> Yojson.Safe.t option id_map option 168 + 169 + (** Extract destroyed email IDs from response *) 170 + val destroyed : t -> string list option 171 + 172 + (** Extract new state from response *) 173 + val new_state : t -> string 174 + end 175 + 176 + (** Email/changes response - implements METHOD_RESPONSE for changes operations *) 177 + module Email_changes : sig 178 + include Jmap_sigs.METHOD_RESPONSE with type t = Jmap_methods.Changes_response.t 179 + 180 + (** Extract created email IDs from response *) 181 + val created : t -> string list 182 + 183 + (** Extract updated email IDs from response *) 184 + val updated : t -> string list 185 + 186 + (** Extract destroyed email IDs from response *) 187 + val destroyed : t -> string list 188 + 189 + (** Extract new state from response *) 190 + val new_state : t -> string 191 + end 192 + 193 + (** Mailbox/get response - implements METHOD_RESPONSE for get operations *) 194 + module Mailbox_get : sig 195 + include Jmap_sigs.METHOD_RESPONSE with type t = Yojson.Safe.t Jmap_methods.Get_response.t 196 + 197 + (** Extract mailbox objects from get response *) 198 + val list : t -> Yojson.Safe.t list 199 + end 200 + 201 + (** Mailbox/query response - implements METHOD_RESPONSE for query operations *) 202 + module Mailbox_query : sig 203 + include Jmap_sigs.METHOD_RESPONSE with type t = Jmap_methods.Query_response.t 204 + 205 + (** Extract mailbox IDs from query response *) 206 + val ids : t -> string list 207 + end 208 + 209 + (** Mailbox/set response - implements METHOD_RESPONSE for set operations *) 210 + module Mailbox_set : sig 211 + include Jmap_sigs.METHOD_RESPONSE with type t = (Yojson.Safe.t, Yojson.Safe.t) Jmap_methods.Set_response.t 212 + 213 + (** Extract created mailboxes from response *) 214 + val created : t -> Yojson.Safe.t id_map option 215 + 216 + (** Extract updated mailboxes from response *) 217 + val updated : t -> Yojson.Safe.t option id_map option 218 + 219 + (** Extract destroyed mailbox IDs from response *) 220 + val destroyed : t -> string list option 221 + 222 + (** Extract new state from response *) 223 + val new_state : t -> string 224 + end 225 + 226 + (** Mailbox/changes response - implements METHOD_RESPONSE for changes operations *) 227 + module Mailbox_changes : sig 228 + include Jmap_sigs.METHOD_RESPONSE with type t = Jmap_methods.Changes_response.t 229 + 230 + (** Extract created mailbox IDs from response *) 231 + val created : t -> string list 232 + 233 + (** Extract updated mailbox IDs from response *) 234 + val updated : t -> string list 235 + 236 + (** Extract destroyed mailbox IDs from response *) 237 + val destroyed : t -> string list 238 + 239 + (** Extract new state from response *) 240 + val new_state : t -> string 241 + end 242 + 243 + (** Thread/get response - implements METHOD_RESPONSE for get operations *) 244 + module Thread_get : sig 245 + include Jmap_sigs.METHOD_RESPONSE with type t = Yojson.Safe.t Jmap_methods.Get_response.t 246 + 247 + (** Extract thread objects from get response *) 248 + val list : t -> Yojson.Safe.t list 249 + end 250 + 251 + (** Thread/changes response - implements METHOD_RESPONSE for changes operations *) 252 + module Thread_changes : sig 253 + include Jmap_sigs.METHOD_RESPONSE with type t = Jmap_methods.Changes_response.t 254 + 255 + (** Extract created thread IDs from response *) 256 + val created : t -> string list 257 + 258 + (** Extract updated thread IDs from response *) 259 + val updated : t -> string list 260 + 261 + (** Extract destroyed thread IDs from response *) 262 + val destroyed : t -> string list 263 + 264 + (** Extract new state from response *) 265 + val new_state : t -> string 266 + end 267 + 268 + (** Identity/get response - implements METHOD_RESPONSE for get operations *) 269 + module Identity_get : sig 270 + include Jmap_sigs.METHOD_RESPONSE with type t = Yojson.Safe.t Jmap_methods.Get_response.t 271 + 272 + (** Extract identity objects from get response *) 273 + val list : t -> Yojson.Safe.t list 274 + end 275 + 276 + (** Identity/set response - implements METHOD_RESPONSE for set operations *) 277 + module Identity_set : sig 278 + include Jmap_sigs.METHOD_RESPONSE with type t = (Yojson.Safe.t, Yojson.Safe.t) Jmap_methods.Set_response.t 279 + 280 + (** Extract created identities from response *) 281 + val created : t -> Yojson.Safe.t id_map option 282 + 283 + (** Extract updated identities from response *) 284 + val updated : t -> Yojson.Safe.t option id_map option 285 + 286 + (** Extract destroyed identity IDs from response *) 287 + val destroyed : t -> string list option 288 + 289 + (** Extract new state from response *) 290 + val new_state : t -> string 291 + end 292 + 293 + (** Identity/changes response - implements METHOD_RESPONSE for changes operations *) 294 + module Identity_changes : sig 295 + include Jmap_sigs.METHOD_RESPONSE with type t = Jmap_methods.Changes_response.t 296 + 297 + (** Extract created identity IDs from response *) 298 + val created : t -> string list 299 + 300 + (** Extract updated identity IDs from response *) 301 + val updated : t -> string list 302 + 303 + (** Extract destroyed identity IDs from response *) 304 + val destroyed : t -> string list 305 + 306 + (** Extract new state from response *) 307 + val new_state : t -> string 308 + end 309 + 310 + (** EmailSubmission/get response - implements METHOD_RESPONSE for get operations *) 311 + module Email_submission_get : sig 312 + include Jmap_sigs.METHOD_RESPONSE with type t = Yojson.Safe.t Jmap_methods.Get_response.t 313 + 314 + (** Extract email submission objects from get response *) 315 + val list : t -> Yojson.Safe.t list 316 + end 317 + 318 + (** EmailSubmission/set response - implements METHOD_RESPONSE for set operations *) 319 + module Email_submission_set : sig 320 + include Jmap_sigs.METHOD_RESPONSE with type t = (Yojson.Safe.t, Yojson.Safe.t) Jmap_methods.Set_response.t 321 + 322 + (** Extract created email submissions from response *) 323 + val created : t -> Yojson.Safe.t id_map option 324 + 325 + (** Extract updated email submissions from response *) 326 + val updated : t -> Yojson.Safe.t option id_map option 327 + 328 + (** Extract destroyed email submission IDs from response *) 329 + val destroyed : t -> string list option 330 + 331 + (** Extract new state from response *) 332 + val new_state : t -> string 333 + end 334 + 335 + (** EmailSubmission/query response - implements METHOD_RESPONSE for query operations *) 336 + module Email_submission_query : sig 337 + include Jmap_sigs.METHOD_RESPONSE with type t = Jmap_methods.Query_response.t 338 + 339 + (** Extract email submission IDs from query response *) 340 + val ids : t -> string list 341 + end 342 + 343 + (** EmailSubmission/changes response - implements METHOD_RESPONSE for changes operations *) 344 + module Email_submission_changes : sig 345 + include Jmap_sigs.METHOD_RESPONSE with type t = Jmap_methods.Changes_response.t 346 + 347 + (** Extract created email submission IDs from response *) 348 + val created : t -> string list 349 + 350 + (** Extract updated email submission IDs from response *) 351 + val updated : t -> string list 352 + 353 + (** Extract destroyed email submission IDs from response *) 354 + val destroyed : t -> string list 355 + 356 + (** Extract new state from response *) 357 + val new_state : t -> string 358 + end 359 + 360 + (** VacationResponse/get response - implements METHOD_RESPONSE for get operations *) 361 + module Vacation_response_get : sig 362 + include Jmap_sigs.METHOD_RESPONSE with type t = Yojson.Safe.t Jmap_methods.Get_response.t 363 + 364 + (** Extract vacation response objects from get response *) 365 + val list : t -> Yojson.Safe.t list 366 + end 367 + 368 + (** VacationResponse/set response - implements METHOD_RESPONSE for set operations *) 369 + module Vacation_response_set : sig 370 + include Jmap_sigs.METHOD_RESPONSE with type t = (Yojson.Safe.t, Yojson.Safe.t) Jmap_methods.Set_response.t 371 + 372 + (** Extract created vacation responses from response *) 373 + val created : t -> Yojson.Safe.t id_map option 374 + 375 + (** Extract updated vacation responses from response *) 376 + val updated : t -> Yojson.Safe.t option id_map option 377 + 378 + (** Extract destroyed vacation response IDs from response *) 379 + val destroyed : t -> string list option 380 + 381 + (** Extract new state from response *) 382 + val new_state : t -> string 383 + end 384 + 385 + (** {1 Response Data Extraction Functions} *) 386 + 387 + (** Extract Core/echo response data if this is a Core/echo response. 388 + @param response The response to extract from 389 + @return The echo data if this is a Core/echo response *) 390 + val get_core_echo : t -> Core_echo.t option 391 + 392 + (** Extract Email/query response data if this is an Email/query response. 393 + @param response The response to extract from 394 + @return The query response data if this is an Email/query response *) 395 + val get_email_query : t -> Email_query.t option 396 + 397 + (** Extract Email/get response data if this is an Email/get response. 398 + @param response The response to extract from 399 + @return The get response data if this is an Email/get response *) 400 + val get_email_get : t -> Email_get.t option 401 + 402 + (** Extract Email/set response data if this is an Email/set response. 403 + @param response The response to extract from 404 + @return The set response data if this is an Email/set response *) 405 + val get_email_set : t -> Email_set.t option 406 + 407 + (** Extract Email/changes response data if this is an Email/changes response. 408 + @param response The response to extract from 409 + @return The changes response data if this is an Email/changes response *) 410 + val get_email_changes : t -> Email_changes.t option 411 + 412 + (** Extract Mailbox/get response data if this is a Mailbox/get response. 413 + @param response The response to extract from 414 + @return The get response data if this is a Mailbox/get response *) 415 + val get_mailbox_get : t -> Mailbox_get.t option 416 + 417 + (** Extract Mailbox/query response data if this is a Mailbox/query response. 418 + @param response The response to extract from 419 + @return The query response data if this is a Mailbox/query response *) 420 + val get_mailbox_query : t -> Mailbox_query.t option 421 + 422 + (** Extract Mailbox/set response data if this is a Mailbox/set response. 423 + @param response The response to extract from 424 + @return The set response data if this is a Mailbox/set response *) 425 + val get_mailbox_set : t -> Mailbox_set.t option 426 + 427 + (** Extract Mailbox/changes response data if this is a Mailbox/changes response. 428 + @param response The response to extract from 429 + @return The changes response data if this is a Mailbox/changes response *) 430 + val get_mailbox_changes : t -> Mailbox_changes.t option 431 + 432 + (** Extract Thread/get response data if this is a Thread/get response. 433 + @param response The response to extract from 434 + @return The get response data if this is a Thread/get response *) 435 + val get_thread_get : t -> Thread_get.t option 436 + 437 + (** Extract Thread/changes response data if this is a Thread/changes response. 438 + @param response The response to extract from 439 + @return The changes response data if this is a Thread/changes response *) 440 + val get_thread_changes : t -> Thread_changes.t option 441 + 442 + (** Extract Identity/get response data if this is an Identity/get response. 443 + @param response The response to extract from 444 + @return The get response data if this is an Identity/get response *) 445 + val get_identity_get : t -> Identity_get.t option 446 + 447 + (** Extract Identity/set response data if this is an Identity/set response. 448 + @param response The response to extract from 449 + @return The set response data if this is an Identity/set response *) 450 + val get_identity_set : t -> Identity_set.t option 451 + 452 + (** Extract Identity/changes response data if this is an Identity/changes response. 453 + @param response The response to extract from 454 + @return The changes response data if this is an Identity/changes response *) 455 + val get_identity_changes : t -> Identity_changes.t option 456 + 457 + (** Extract EmailSubmission/get response data if this is an EmailSubmission/get response. 458 + @param response The response to extract from 459 + @return The get response data if this is an EmailSubmission/get response *) 460 + val get_email_submission_get : t -> Email_submission_get.t option 461 + 462 + (** Extract EmailSubmission/set response data if this is an EmailSubmission/set response. 463 + @param response The response to extract from 464 + @return The set response data if this is an EmailSubmission/set response *) 465 + val get_email_submission_set : t -> Email_submission_set.t option 466 + 467 + (** Extract EmailSubmission/query response data if this is an EmailSubmission/query response. 468 + @param response The response to extract from 469 + @return The query response data if this is an EmailSubmission/query response *) 470 + val get_email_submission_query : t -> Email_submission_query.t option 471 + 472 + (** Extract EmailSubmission/changes response data if this is an EmailSubmission/changes response. 473 + @param response The response to extract from 474 + @return The changes response data if this is an EmailSubmission/changes response *) 475 + val get_email_submission_changes : t -> Email_submission_changes.t option 476 + 477 + (** Extract VacationResponse/get response data if this is a VacationResponse/get response. 478 + @param response The response to extract from 479 + @return The get response data if this is a VacationResponse/get response *) 480 + val get_vacation_response_get : t -> Vacation_response_get.t option 481 + 482 + (** Extract VacationResponse/set response data if this is a VacationResponse/set response. 483 + @param response The response to extract from 484 + @return The set response data if this is a VacationResponse/set response *) 485 + val get_vacation_response_set : t -> Vacation_response_set.t option 486 + 487 + (** {1 Utility Functions} *) 488 + 489 + (** Check if response indicates an error. 490 + @param response The response to check 491 + @return True if this is an error response *) 492 + val is_error : t -> bool 493 + 494 + (** Extract error information if this is an error response. 495 + @param response The response to check 496 + @return Error details if this is an error response *) 497 + val error : t -> Jmap_error.error option 498 + 499 + (** Get the account ID from responses that include it. 500 + @param response The response to extract from 501 + @return Account ID if present in response *) 502 + val account_id : t -> string option 503 + 504 + (** Convert response back to JSON for debugging. 505 + @param response The response to convert 506 + @return JSON representation of the response *) 507 + val to_json : t -> Yojson.Safe.t