this repo has no description
1(*---------------------------------------------------------------------------
2 Copyright (c) 2025 Anil Madhavapeddy. All rights reserved.
3 SPDX-License-Identifier: ISC
4 ---------------------------------------------------------------------------*)
5
6(** High-level JMAP client using Requests
7
8 This module provides a full-featured JMAP client with session management,
9 request execution, and blob upload/download capabilities. *)
10
11(** {1 Types} *)
12
13type t
14(** A JMAP client with session state and HTTP connection management. *)
15
16type error =
17 | Http_error of int * string
18 (** HTTP error with status code and message. *)
19 | Jmap_error of Jmap_proto.Error.Request_error.t
20 (** JMAP protocol error at request level. *)
21 | Json_error of Jsont.Error.t
22 (** JSON encoding/decoding error. *)
23 | Session_error of string
24 (** Session fetch or parse error. *)
25 | Connection_error of string
26 (** Network connection error. *)
27(** Error types that can occur during JMAP operations. *)
28
29val pp_error : Format.formatter -> error -> unit
30(** Pretty-print an error. *)
31
32val error_to_string : error -> string
33(** Convert an error to a string. *)
34
35exception Jmap_client_error of error
36(** Exception wrapper for JMAP client errors. *)
37
38(** {1 Client Creation} *)
39
40val create :
41 ?auth:Requests.Auth.t ->
42 session:Jmap_proto.Session.t ->
43 Requests.t ->
44 t
45(** [create ?auth ~session requests] creates a JMAP client from an existing
46 session and Requests instance.
47
48 @param auth Authentication to use for requests.
49 @param session A pre-fetched JMAP session.
50 @param requests The Requests instance for HTTP operations. *)
51
52val create_from_url :
53 ?auth:Requests.Auth.t ->
54 Requests.t ->
55 string ->
56 (t, error) result
57(** [create_from_url ?auth requests url] creates a JMAP client by fetching
58 the session from the given JMAP API URL or well-known URL.
59
60 The URL can be either:
61 - A direct JMAP API URL (e.g., "https://api.example.com/jmap/")
62 - A well-known URL (e.g., "https://example.com/.well-known/jmap")
63
64 @param auth Authentication to use for the session request and subsequent requests.
65 @param requests The Requests instance for HTTP operations.
66 @param url The JMAP API or well-known URL. *)
67
68val create_from_url_exn :
69 ?auth:Requests.Auth.t ->
70 Requests.t ->
71 string ->
72 t
73(** [create_from_url_exn ?auth requests url] is like {!create_from_url} but
74 raises {!Jmap_client_error} on failure. *)
75
76(** {1 Session Access} *)
77
78val session : t -> Jmap_proto.Session.t
79(** [session client] returns the current JMAP session. *)
80
81val refresh_session : t -> (unit, error) result
82(** [refresh_session client] fetches a fresh session from the server and
83 updates the client's session state. *)
84
85val refresh_session_exn : t -> unit
86(** [refresh_session_exn client] is like {!refresh_session} but raises on error. *)
87
88val api_url : t -> string
89(** [api_url client] returns the JMAP API URL for this client. *)
90
91val upload_url : t -> string
92(** [upload_url client] returns the blob upload URL template. *)
93
94val download_url : t -> string
95(** [download_url client] returns the blob download URL template. *)
96
97(** {1 Request Execution} *)
98
99val request :
100 t ->
101 Jmap_proto.Request.t ->
102 (Jmap_proto.Response.t, error) result
103(** [request client req] executes a JMAP request and returns the response. *)
104
105val request_exn :
106 t ->
107 Jmap_proto.Request.t ->
108 Jmap_proto.Response.t
109(** [request_exn client req] is like {!request} but raises on error. *)
110
111(** {1 Blob Operations} *)
112
113val upload :
114 t ->
115 account_id:Jmap_proto.Id.t ->
116 content_type:string ->
117 data:string ->
118 (Jmap_proto.Blob.upload_response, error) result
119(** [upload client ~account_id ~content_type ~data] uploads a blob.
120
121 @param account_id The account to upload to.
122 @param content_type MIME type of the blob.
123 @param data The blob data as a string. *)
124
125val upload_exn :
126 t ->
127 account_id:Jmap_proto.Id.t ->
128 content_type:string ->
129 data:string ->
130 Jmap_proto.Blob.upload_response
131(** [upload_exn client ~account_id ~content_type ~data] is like {!upload}
132 but raises on error. *)
133
134val download :
135 t ->
136 account_id:Jmap_proto.Id.t ->
137 blob_id:Jmap_proto.Id.t ->
138 ?name:string ->
139 ?accept:string ->
140 unit ->
141 (string, error) result
142(** [download client ~account_id ~blob_id ?name ?accept ()] downloads a blob.
143
144 @param account_id The account containing the blob.
145 @param blob_id The blob ID to download.
146 @param name Optional filename hint for Content-Disposition.
147 @param accept Optional Accept header value. *)
148
149val download_exn :
150 t ->
151 account_id:Jmap_proto.Id.t ->
152 blob_id:Jmap_proto.Id.t ->
153 ?name:string ->
154 ?accept:string ->
155 unit ->
156 string
157(** [download_exn] is like {!download} but raises on error. *)
158
159(** {1 Convenience Builders}
160
161 Helper functions for building common JMAP method invocations. *)
162
163module Build : sig
164 (** {2 Core Methods} *)
165
166 val echo :
167 call_id:string ->
168 Jsont.json ->
169 Jmap_proto.Invocation.t
170 (** [echo ~call_id data] builds a Core/echo invocation. *)
171
172 (** {2 Mailbox Methods} *)
173
174 val mailbox_get :
175 call_id:string ->
176 account_id:Jmap_proto.Id.t ->
177 ?ids:Jmap_proto.Id.t list ->
178 ?properties:string list ->
179 unit ->
180 Jmap_proto.Invocation.t
181 (** [mailbox_get ~call_id ~account_id ?ids ?properties ()] builds a
182 Mailbox/get invocation. *)
183
184 val mailbox_changes :
185 call_id:string ->
186 account_id:Jmap_proto.Id.t ->
187 since_state:string ->
188 ?max_changes:int64 ->
189 unit ->
190 Jmap_proto.Invocation.t
191 (** [mailbox_changes ~call_id ~account_id ~since_state ?max_changes ()]
192 builds a Mailbox/changes invocation. *)
193
194 val mailbox_query :
195 call_id:string ->
196 account_id:Jmap_proto.Id.t ->
197 ?filter:Jmap_mail.Mail_filter.mailbox_filter ->
198 ?sort:Jmap_proto.Filter.comparator list ->
199 ?position:int64 ->
200 ?limit:int64 ->
201 unit ->
202 Jmap_proto.Invocation.t
203 (** [mailbox_query ~call_id ~account_id ?filter ?sort ?position ?limit ()]
204 builds a Mailbox/query invocation. *)
205
206 (** {2 Email Methods} *)
207
208 val email_get :
209 call_id:string ->
210 account_id:Jmap_proto.Id.t ->
211 ?ids:Jmap_proto.Id.t list ->
212 ?properties:string list ->
213 ?body_properties:string list ->
214 ?fetch_text_body_values:bool ->
215 ?fetch_html_body_values:bool ->
216 ?fetch_all_body_values:bool ->
217 ?max_body_value_bytes:int64 ->
218 unit ->
219 Jmap_proto.Invocation.t
220 (** [email_get ~call_id ~account_id ?ids ?properties ...] builds an
221 Email/get invocation. *)
222
223 val email_changes :
224 call_id:string ->
225 account_id:Jmap_proto.Id.t ->
226 since_state:string ->
227 ?max_changes:int64 ->
228 unit ->
229 Jmap_proto.Invocation.t
230 (** [email_changes ~call_id ~account_id ~since_state ?max_changes ()]
231 builds an Email/changes invocation. *)
232
233 val email_query :
234 call_id:string ->
235 account_id:Jmap_proto.Id.t ->
236 ?filter:Jmap_mail.Mail_filter.email_filter ->
237 ?sort:Jmap_proto.Filter.comparator list ->
238 ?position:int64 ->
239 ?limit:int64 ->
240 ?collapse_threads:bool ->
241 unit ->
242 Jmap_proto.Invocation.t
243 (** [email_query ~call_id ~account_id ?filter ?sort ?position ?limit
244 ?collapse_threads ()] builds an Email/query invocation. *)
245
246 (** {2 Thread Methods} *)
247
248 val thread_get :
249 call_id:string ->
250 account_id:Jmap_proto.Id.t ->
251 ?ids:Jmap_proto.Id.t list ->
252 unit ->
253 Jmap_proto.Invocation.t
254 (** [thread_get ~call_id ~account_id ?ids ()] builds a Thread/get invocation. *)
255
256 val thread_changes :
257 call_id:string ->
258 account_id:Jmap_proto.Id.t ->
259 since_state:string ->
260 ?max_changes:int64 ->
261 unit ->
262 Jmap_proto.Invocation.t
263 (** [thread_changes ~call_id ~account_id ~since_state ?max_changes ()]
264 builds a Thread/changes invocation. *)
265
266 (** {2 Identity Methods} *)
267
268 val identity_get :
269 call_id:string ->
270 account_id:Jmap_proto.Id.t ->
271 ?ids:Jmap_proto.Id.t list ->
272 ?properties:string list ->
273 unit ->
274 Jmap_proto.Invocation.t
275 (** [identity_get ~call_id ~account_id ?ids ?properties ()] builds an
276 Identity/get invocation. *)
277
278 (** {2 Submission Methods} *)
279
280 val email_submission_get :
281 call_id:string ->
282 account_id:Jmap_proto.Id.t ->
283 ?ids:Jmap_proto.Id.t list ->
284 ?properties:string list ->
285 unit ->
286 Jmap_proto.Invocation.t
287 (** [email_submission_get ~call_id ~account_id ?ids ?properties ()]
288 builds an EmailSubmission/get invocation. *)
289
290 val email_submission_query :
291 call_id:string ->
292 account_id:Jmap_proto.Id.t ->
293 ?filter:Jmap_mail.Mail_filter.submission_filter ->
294 ?sort:Jmap_proto.Filter.comparator list ->
295 ?position:int64 ->
296 ?limit:int64 ->
297 unit ->
298 Jmap_proto.Invocation.t
299 (** [email_submission_query ~call_id ~account_id ?filter ?sort ?position
300 ?limit ()] builds an EmailSubmission/query invocation. *)
301
302 (** {2 Vacation Response Methods} *)
303
304 val vacation_response_get :
305 call_id:string ->
306 account_id:Jmap_proto.Id.t ->
307 unit ->
308 Jmap_proto.Invocation.t
309 (** [vacation_response_get ~call_id ~account_id ()] builds a
310 VacationResponse/get invocation. The singleton ID is automatically used. *)
311
312 (** {2 Request Building} *)
313
314 val make_request :
315 ?created_ids:(Jmap_proto.Id.t * Jmap_proto.Id.t) list ->
316 capabilities:string list ->
317 Jmap_proto.Invocation.t list ->
318 Jmap_proto.Request.t
319 (** [make_request ?created_ids ~capabilities invocations] builds a JMAP request.
320
321 @param created_ids Optional client-created ID mappings.
322 @param capabilities List of capability URIs to use.
323 @param invocations List of method invocations. *)
324end
325
326(** {1 Response Parsing}
327
328 Helper functions for parsing typed responses from JMAP invocations. *)
329
330module Parse : sig
331 val find_invocation :
332 call_id:string ->
333 Jmap_proto.Response.t ->
334 Jmap_proto.Invocation.t option
335 (** [find_invocation ~call_id response] finds an invocation by call ID. *)
336
337 val get_invocation_exn :
338 call_id:string ->
339 Jmap_proto.Response.t ->
340 Jmap_proto.Invocation.t
341 (** [get_invocation_exn ~call_id response] finds an invocation by call ID.
342 @raise Failure if not found. *)
343
344 val parse_invocation :
345 'a Jsont.t ->
346 Jmap_proto.Invocation.t ->
347 ('a, Jsont.Error.t) result
348 (** [parse_invocation jsont inv] decodes the invocation's arguments. *)
349
350 val parse_response :
351 call_id:string ->
352 'a Jsont.t ->
353 Jmap_proto.Response.t ->
354 ('a, Jsont.Error.t) result
355 (** [parse_response ~call_id jsont response] finds and parses an invocation. *)
356
357 (** {2 Typed Response Codecs} *)
358
359 val get_response : 'a Jsont.t -> 'a Jmap_proto.Method.get_response Jsont.t
360 (** [get_response obj_jsont] creates a Foo/get response codec. *)
361
362 val query_response : Jmap_proto.Method.query_response Jsont.t
363 (** Codec for Foo/query responses. *)
364
365 val changes_response : Jmap_proto.Method.changes_response Jsont.t
366 (** Codec for Foo/changes responses. *)
367
368 val set_response : 'a Jsont.t -> 'a Jmap_proto.Method.set_response Jsont.t
369 (** [set_response obj_jsont] creates a Foo/set response codec. *)
370
371 (** {2 Mail-specific Codecs} *)
372
373 val mailbox_get_response : Jmap_mail.Mailbox.t Jmap_proto.Method.get_response Jsont.t
374 val email_get_response : Jmap_mail.Email.t Jmap_proto.Method.get_response Jsont.t
375 val thread_get_response : Jmap_mail.Thread.t Jmap_proto.Method.get_response Jsont.t
376 val identity_get_response : Jmap_mail.Identity.t Jmap_proto.Method.get_response Jsont.t
377
378 (** {2 Convenience Parsers} *)
379
380 val parse_mailbox_get :
381 call_id:string ->
382 Jmap_proto.Response.t ->
383 (Jmap_mail.Mailbox.t Jmap_proto.Method.get_response, Jsont.Error.t) result
384
385 val parse_email_get :
386 call_id:string ->
387 Jmap_proto.Response.t ->
388 (Jmap_mail.Email.t Jmap_proto.Method.get_response, Jsont.Error.t) result
389
390 val parse_email_query :
391 call_id:string ->
392 Jmap_proto.Response.t ->
393 (Jmap_proto.Method.query_response, Jsont.Error.t) result
394
395 val parse_thread_get :
396 call_id:string ->
397 Jmap_proto.Response.t ->
398 (Jmap_mail.Thread.t Jmap_proto.Method.get_response, Jsont.Error.t) result
399
400 val parse_changes :
401 call_id:string ->
402 Jmap_proto.Response.t ->
403 (Jmap_proto.Method.changes_response, Jsont.Error.t) result
404end