My agentic slop goes here. Not intended for anyone else!
1(** JMAP HTTP Client - Eio Implementation *)
2
3type t = {
4 session_url : string;
5 get_request : timeout:Requests.Timeout.t -> string -> Requests.Response.t;
6 post_request : timeout:Requests.Timeout.t -> headers:Requests.Headers.t -> body:Requests.Body.t -> string -> Requests.Response.t;
7 conn : Jmap_connection.t;
8 session : Jmap_core.Session.t option ref;
9}
10
11let create ~sw ~env ~conn ~session_url () =
12 let requests_session = Requests.create ~sw env in
13
14 (* Set authentication if configured *)
15 (match Jmap_connection.auth conn with
16 | Some (Jmap_connection.Bearer token) ->
17 Requests.set_auth requests_session (Requests.Auth.bearer ~token)
18 | Some (Jmap_connection.Basic (user, pass)) ->
19 Requests.set_auth requests_session (Requests.Auth.basic ~username:user ~password:pass)
20 | None -> ());
21
22 (* Set user agent *)
23 let config = Jmap_connection.config conn in
24 Requests.set_default_header requests_session "User-Agent"
25 (Jmap_connection.user_agent config);
26
27 { session_url;
28 get_request = (fun ~timeout url -> Requests.get requests_session ~timeout url);
29 post_request = (fun ~timeout ~headers ~body url -> Requests.post requests_session ~timeout ~headers ~body url);
30 conn;
31 session = ref None }
32
33let fetch_session t =
34 let config = Jmap_connection.config t.conn in
35 let timeout = Requests.Timeout.create ~total:(Jmap_connection.timeout config) () in
36
37 let response = t.get_request ~timeout t.session_url in
38
39 if not (Requests.Response.ok response) then
40 failwith (Printf.sprintf "Failed to fetch session: HTTP %d"
41 (Requests.Response.status_code response));
42
43 let body_str =
44 let buf = Buffer.create 4096 in
45 Eio.Flow.copy (Requests.Response.body response) (Eio.Flow.buffer_sink buf);
46 Buffer.contents buf
47 in
48
49 let session = Jmap_core.Session.Parser.of_string body_str in
50 t.session := Some session;
51 session
52
53let get_session t =
54 match !(t.session) with
55 | Some s -> s
56 | None -> fetch_session t
57
58let call t req =
59 let session = get_session t in
60 let api_url = Jmap_core.Session.api_url session in
61 let config = Jmap_connection.config t.conn in
62 let timeout = Requests.Timeout.create ~total:(Jmap_connection.timeout config) () in
63
64 (* Convert request to JSON *)
65 let req_json = Jmap_core.Request.to_json req in
66
67 (* Set up headers *)
68 let headers = Requests.Headers.(empty
69 |> set "Accept" "application/json") in
70
71 (* Make POST request with JSON body *)
72 let body = Requests.Body.json req_json in
73 let response = t.post_request ~timeout ~headers ~body api_url in
74
75 (* Read response body first *)
76 let body_str =
77 let buf = Buffer.create 4096 in
78 Eio.Flow.copy (Requests.Response.body response) (Eio.Flow.buffer_sink buf);
79 Buffer.contents buf
80 in
81
82 if not (Requests.Response.ok response) then (
83 Printf.eprintf "JMAP API call failed: HTTP %d\n" (Requests.Response.status_code response);
84 Printf.eprintf "Response body: %s\n%!" body_str;
85 failwith (Printf.sprintf "JMAP API call failed: HTTP %d"
86 (Requests.Response.status_code response))
87 );
88
89 Jmap_core.Response.Parser.of_string body_str
90
91let upload t ~account_id ~content_type:ct data =
92 let session = get_session t in
93 let upload_url = Jmap_core.Session.upload_url session in
94 let config = Jmap_connection.config t.conn in
95 let timeout = Requests.Timeout.create ~total:(Jmap_connection.timeout config) () in
96
97 (* Replace {accountId} placeholder *)
98 let upload_url = Str.global_replace (Str.regexp_string "{accountId}")
99 account_id upload_url in
100
101 let mime = Requests.Mime.of_string ct in
102 let headers = Requests.Headers.empty in
103
104 let body = Requests.Body.of_string mime data in
105 let response = t.post_request ~timeout ~headers ~body upload_url in
106
107 if not (Requests.Response.ok response) then
108 failwith (Printf.sprintf "Upload failed: HTTP %d"
109 (Requests.Response.status_code response));
110
111 let body_str =
112 let buf = Buffer.create 4096 in
113 Eio.Flow.copy (Requests.Response.body response) (Eio.Flow.buffer_sink buf);
114 Buffer.contents buf
115 in
116
117 let json = Ezjsonm.value_from_string body_str in
118 Jmap_core.Binary.Upload.of_json json
119
120let download t ~account_id ~blob_id ~name =
121 let session = get_session t in
122 let download_url = Jmap_core.Session.download_url session in
123 let config = Jmap_connection.config t.conn in
124 let timeout = Requests.Timeout.create ~total:(Jmap_connection.timeout config) () in
125
126 (* Replace placeholders *)
127 let download_url = download_url
128 |> Str.global_replace (Str.regexp_string "{accountId}") account_id
129 |> Str.global_replace (Str.regexp_string "{blobId}") blob_id
130 |> Str.global_replace (Str.regexp_string "{name}") name in
131
132 let response = t.get_request ~timeout download_url in
133
134 if not (Requests.Response.ok response) then
135 failwith (Printf.sprintf "Download failed: HTTP %d"
136 (Requests.Response.status_code response));
137
138 let buf = Buffer.create 4096 in
139 Eio.Flow.copy (Requests.Response.body response) (Eio.Flow.buffer_sink buf);
140 Buffer.contents buf