My agentic slop goes here. Not intended for anyone else!
at main 8.0 kB view raw
1(** Simple JMAP client test against Fastmail API 2 3 This test demonstrates the unified Jmap API for clean, ergonomic usage. 4*) 5 6let read_api_key () = 7 let locations = [ 8 "jmap/.api-key"; 9 "../jmap/.api-key"; 10 "../../jmap/.api-key"; 11 ".api-key"; 12 ] in 13 14 let rec try_read = function 15 | [] -> 16 Printf.eprintf "Error: API key file not found. Checked:\n"; 17 List.iter (fun loc -> Printf.eprintf " - %s\n" loc) locations; 18 Printf.eprintf "\nCreate .api-key with your Fastmail API token.\n"; 19 Printf.eprintf "Get one at: https://www.fastmail.com/settings/security/tokens\n"; 20 exit 1 21 | path :: rest -> 22 if Sys.file_exists path then 23 let ic = open_in path in 24 Fun.protect ~finally:(fun () -> close_in ic) (fun () -> 25 let token = input_line ic |> String.trim in 26 if token = "" then ( 27 Printf.eprintf "Error: API key file is empty: %s\n" path; 28 exit 1 29 ); 30 token 31 ) 32 else 33 try_read rest 34 in 35 try_read locations 36 37let () = 38 let () = Mirage_crypto_rng_unix.use_default () in 39 40 Eio_main.run @@ fun env -> 41 Eio.Switch.run @@ fun sw -> 42 43 Printf.printf "=== JMAP Fastmail Test ===\n\n%!"; 44 45 Printf.printf "Reading API key...\n%!"; 46 let api_key = read_api_key () in 47 Printf.printf "✓ API key loaded\n\n%!"; 48 49 let conn = Jmap.Connection.v 50 ~auth:(Jmap.Connection.Bearer api_key) 51 () in 52 53 let session_url = "https://api.fastmail.com/jmap/session" in 54 Printf.printf "Connecting to %s...\n%!" session_url; 55 56 let client = Jmap.Client.create ~sw ~env ~conn ~session_url () in 57 58 Printf.printf "Fetching JMAP session...\n%!"; 59 let session = Jmap.Client.fetch_session client in 60 Printf.printf "✓ Session fetched\n"; 61 Printf.printf " Username: %s\n" (Jmap.Session.username session); 62 Printf.printf " API URL: %s\n\n%!" (Jmap.Session.api_url session); 63 64 (* Get primary mail account *) 65 let primary_accounts = Jmap.Session.primary_accounts session in 66 let account_id = match List.assoc_opt "urn:ietf:params:jmap:mail" primary_accounts with 67 | Some id -> Jmap.Id.to_string id 68 | None -> 69 Printf.eprintf "Error: No mail account found\n"; 70 exit 1 71 in 72 Printf.printf " Account ID: %s\n\n%!" account_id; 73 74 (* Build a JMAP request using the unified Jmap API *) 75 Printf.printf "Querying for 10 most recent emails...\n"; 76 Printf.printf " API URL: %s\n%!" (Jmap.Session.api_url session); 77 78 (* Build Email/query request using typed constructors *) 79 let query_request = Jmap.Email.Query.request_v 80 ~account_id:(Jmap.Id.of_string account_id) 81 ~limit:(Jmap.Primitives.UnsignedInt.of_int 10) 82 ~sort:[Jmap.Comparator.v ~property:"receivedAt" ~is_ascending:false ()] 83 ~calculate_total:true 84 () in 85 86 (* Convert to JSON *) 87 let query_args = Jmap.Email.Query.request_to_json query_request in 88 89 (* Create invocation using Echo witness *) 90 let query_invocation = Jmap.Invocation.Invocation { 91 method_name = "Email/query"; 92 arguments = query_args; 93 call_id = "q1"; 94 witness = Jmap.Invocation.Echo; 95 } in 96 97 (* Build request using constructors *) 98 let req = Jmap.Request.make 99 ~using:[Jmap.Capability.core; Jmap.Capability.mail] 100 [Jmap.Invocation.Packed query_invocation] 101 in 102 103 Printf.printf " Request built using typed Email.Query API\n%!"; 104 105 Printf.printf " Making API call...\n%!"; 106 (try 107 let query_resp = Jmap.Client.call client req in 108 Printf.printf "✓ Query successful!\n"; 109 110 (* Extract email IDs from the query response *) 111 let method_responses = Jmap.Response.method_responses query_resp in 112 let email_ids = match method_responses with 113 | [packed_resp] -> 114 let response_json = Jmap.Invocation.response_to_json packed_resp in 115 (match response_json with 116 | `O fields -> 117 (match List.assoc_opt "ids" fields with 118 | Some (`A ids) -> 119 List.map (fun id -> 120 match id with 121 | `String s -> Jmap.Id.of_string s 122 | _ -> failwith "Expected string ID" 123 ) ids 124 | _ -> failwith "No 'ids' field in query response") 125 | _ -> failwith "Expected object response") 126 | _ -> failwith "Unexpected response structure" 127 in 128 129 Printf.printf " Found %d email(s)\n\n%!" (List.length email_ids); 130 131 if List.length email_ids > 0 then ( 132 (* Fetch the actual emails with Email/get *) 133 let get_request = Jmap.Email.Get.request_v 134 ~account_id:(Jmap.Id.of_string account_id) 135 ~ids:email_ids 136 ~properties:["id"; "subject"; "from"; "receivedAt"] 137 () in 138 139 let get_args = Jmap.Email.Get.request_to_json get_request in 140 141 let get_invocation = Jmap.Invocation.Invocation { 142 method_name = "Email/get"; 143 arguments = get_args; 144 call_id = "g1"; 145 witness = Jmap.Invocation.Echo; 146 } in 147 148 let get_req = Jmap.Request.make 149 ~using:[Jmap.Capability.core; Jmap.Capability.mail] 150 [Jmap.Invocation.Packed get_invocation] 151 in 152 153 let get_resp = Jmap.Client.call client get_req in 154 155 (* Parse and display emails *) 156 let get_method_responses = Jmap.Response.method_responses get_resp in 157 (match get_method_responses with 158 | [packed_resp] -> 159 let response_json = Jmap.Invocation.response_to_json packed_resp in 160 (match response_json with 161 | `O fields -> 162 (match List.assoc_opt "list" fields with 163 | Some (`A emails) -> 164 Printf.printf "Recent emails:\n\n"; 165 List.iteri (fun i email_json -> 166 match email_json with 167 | `O email_fields -> 168 let subject = match List.assoc_opt "subject" email_fields with 169 | Some (`String s) -> s 170 | _ -> "(no subject)" 171 in 172 let from = match List.assoc_opt "from" email_fields with 173 | Some (`A []) -> "(unknown sender)" 174 | Some (`A ((`O addr_fields)::_)) -> 175 (match List.assoc_opt "email" addr_fields with 176 | Some (`String e) -> 177 (match List.assoc_opt "name" addr_fields with 178 | Some (`String n) -> Printf.sprintf "%s <%s>" n e 179 | _ -> e) 180 | _ -> "(unknown)") 181 | _ -> "(unknown sender)" 182 in 183 let date = match List.assoc_opt "receivedAt" email_fields with 184 | Some (`String d) -> d 185 | _ -> "(unknown date)" 186 in 187 Printf.printf "%d. %s\n" (i + 1) subject; 188 Printf.printf " From: %s\n" from; 189 Printf.printf " Date: %s\n\n" date 190 | _ -> () 191 ) emails 192 | _ -> Printf.printf "No emails in response\n") 193 | _ -> Printf.printf "Unexpected response format\n") 194 | _ -> Printf.printf "Unexpected method response structure\n"); 195 196 Printf.printf "\n✓ Test completed successfully!\n%!" 197 ) else ( 198 Printf.printf "No emails found\n"; 199 Printf.printf "\n✓ Test completed successfully!\n%!" 200 ) 201 with 202 | Failure msg when String.starts_with ~prefix:"JMAP API call failed: HTTP" msg -> 203 Printf.eprintf "API call failed with error: %s\n" msg; 204 Printf.eprintf "This likely means the request JSON is malformed.\n"; 205 exit 1 206 | e -> 207 Printf.eprintf "Error making API call: %s\n%!" (Printexc.to_string e); 208 Printexc.print_backtrace stderr; 209 exit 1)