My agentic slop goes here. Not intended for anyone else!
1(** Implementation of JMAP Email/import method (RFC 8621 Section 4.8) *)
2
3(** {1 EmailImport Object} *)
4
5type email_import = {
6 blob_id : Jmap.Id.t;
7 mailbox_ids : (Jmap.Id.t * bool) list;
8 keywords : (string * bool) list;
9 received_at : Jmap.Date.t option;
10}
11
12let create_email_import ~blob_id ~mailbox_ids ?(keywords=[]) ?received_at () = {
13 blob_id;
14 mailbox_ids;
15 keywords;
16 received_at;
17}
18
19(** JSON serialization for EmailImport objects *)
20let email_import_to_json ei =
21 let json_fields = [
22 ("blobId", `String (Jmap.Id.to_string ei.blob_id));
23 ("mailboxIds", `Assoc (List.map (fun (id, v) -> (Jmap.Id.to_string id, `Bool v)) ei.mailbox_ids));
24 ] in
25 let json_fields = if ei.keywords = [] then json_fields
26 else ("keywords", `Assoc (List.map (fun (k, v) -> (k, `Bool v)) ei.keywords)) :: json_fields
27 in
28 let json_fields = match ei.received_at with
29 | Some date -> ("receivedAt", `String (Jmap.Date.to_rfc3339 date)) :: json_fields
30 | None -> json_fields
31 in
32 `Assoc (List.rev json_fields)
33
34let email_import_of_json json =
35 try
36 let open Yojson.Safe.Util in
37 let blob_id_str = json |> member "blobId" |> to_string in
38 let blob_id = match Jmap.Id.of_string blob_id_str with
39 | Ok id -> id
40 | Error _ -> failwith ("Invalid blobId: " ^ blob_id_str)
41 in
42 let mailbox_ids_assoc = json |> member "mailboxIds" |> to_assoc in
43 let mailbox_ids = List.map (fun (id_str, v) ->
44 let id = match Jmap.Id.of_string id_str with
45 | Ok id -> id
46 | Error _ -> failwith ("Invalid mailbox ID: " ^ id_str)
47 in
48 (id, to_bool v)
49 ) mailbox_ids_assoc in
50 let keywords = match json |> member "keywords" with
51 | `Null -> []
52 | keywords_json -> List.map (fun (k, v) -> (k, to_bool v)) (to_assoc keywords_json)
53 in
54 let received_at = match json |> member "receivedAt" with
55 | `Null -> None
56 | date_json -> match Jmap.Date.of_rfc3339 (to_string date_json) with
57 | Ok date -> Some date
58 | Error _ -> failwith "Invalid receivedAt date format"
59 in
60 Ok (create_email_import ~blob_id ~mailbox_ids ~keywords ?received_at ())
61 with
62 | exn -> Error ("Failed to parse EmailImport: " ^ Printexc.to_string exn)
63
64(** {1 Email/import Arguments} *)
65
66module Import_args = struct
67 type t = {
68 account_id : string;
69 if_in_state : string option;
70 emails : (string * email_import) list;
71 }
72
73 let create ~account_id ?if_in_state ~emails () = {
74 account_id;
75 if_in_state;
76 emails;
77 }
78
79 let account_id t = t.account_id
80 let if_in_state t = t.if_in_state
81 let emails t = t.emails
82
83 let to_json t =
84 let json_fields = [
85 ("accountId", `String t.account_id);
86 ("emails", `Assoc (List.map (fun (creation_id, ei) -> (creation_id, email_import_to_json ei)) t.emails));
87 ] in
88 let json_fields = match t.if_in_state with
89 | Some state -> ("ifInState", `String state) :: json_fields
90 | None -> json_fields
91 in
92 `Assoc (List.rev json_fields)
93
94 let of_json json =
95 try
96 let open Yojson.Safe.Util in
97 let account_id = json |> member "accountId" |> to_string in
98 let if_in_state = json |> member "ifInState" |> to_string_option in
99 let emails_assoc = json |> member "emails" |> to_assoc in
100 let emails = List.map (fun (creation_id, ei_json) ->
101 match email_import_of_json ei_json with
102 | Ok ei -> (creation_id, ei)
103 | Error err -> failwith err
104 ) emails_assoc in
105 Ok (create ~account_id ?if_in_state ~emails ())
106 with
107 | exn -> Error ("Failed to parse Email/import args: " ^ Printexc.to_string exn)
108end
109
110(** {1 Email/import Response} *)
111
112type email_creation_result = {
113 id : Jmap.Id.t;
114 blob_id : Jmap.Id.t;
115 thread_id : Jmap.Id.t;
116 size : int;
117}
118
119let email_creation_result_to_json ecr =
120 `Assoc [
121 ("id", `String (Jmap.Id.to_string ecr.id));
122 ("blobId", `String (Jmap.Id.to_string ecr.blob_id));
123 ("threadId", `String (Jmap.Id.to_string ecr.thread_id));
124 ("size", `Int ecr.size);
125 ]
126
127let email_creation_result_of_json json =
128 try
129 let open Yojson.Safe.Util in
130 let id_str = json |> member "id" |> to_string in
131 let id = match Jmap.Id.of_string id_str with
132 | Ok id -> id
133 | Error _ -> failwith ("Invalid id: " ^ id_str)
134 in
135 let blob_id_str = json |> member "blobId" |> to_string in
136 let blob_id = match Jmap.Id.of_string blob_id_str with
137 | Ok id -> id
138 | Error _ -> failwith ("Invalid blobId: " ^ blob_id_str)
139 in
140 let thread_id_str = json |> member "threadId" |> to_string in
141 let thread_id = match Jmap.Id.of_string thread_id_str with
142 | Ok id -> id
143 | Error _ -> failwith ("Invalid threadId: " ^ thread_id_str)
144 in
145 let size = json |> member "size" |> to_int in
146 Ok {id; blob_id; thread_id; size}
147 with
148 | exn -> Error ("Failed to parse EmailCreationResult: " ^ Printexc.to_string exn)
149
150module Import_response = struct
151 type response = {
152 account_id : string;
153 old_state : string option;
154 new_state : string option;
155 created : (string * email_creation_result) list;
156 not_created : (string * Jmap.Error.Set_error.t) list;
157 }
158
159 let create ~account_id ?old_state ?new_state ?(created=[]) ?(not_created=[]) () = {
160 account_id;
161 old_state;
162 new_state;
163 created;
164 not_created;
165 }
166
167 let account_id t = t.account_id
168 let old_state t = t.old_state
169 let new_state t = t.new_state
170 let created t = t.created
171 let not_created t = t.not_created
172
173 let to_json t =
174 let json_fields = [
175 ("accountId", `String t.account_id);
176 ] in
177 let json_fields = match t.old_state with
178 | Some state -> ("oldState", `String state) :: json_fields
179 | None -> json_fields
180 in
181 let json_fields = match t.new_state with
182 | Some state -> ("newState", `String state) :: json_fields
183 | None -> json_fields
184 in
185 let json_fields = if t.created = [] then
186 ("created", `Null) :: json_fields
187 else
188 ("created", `Assoc (List.map (fun (cid, ecr) -> (cid, email_creation_result_to_json ecr)) t.created)) :: json_fields
189 in
190 let json_fields = if t.not_created = [] then
191 ("notCreated", `Null) :: json_fields
192 else
193 ("notCreated", `Assoc (List.map (fun (cid, err) -> (cid, Jmap.Error.Set_error.to_json err)) t.not_created)) :: json_fields
194 in
195 `Assoc (List.rev json_fields)
196
197 let of_json json =
198 try
199 let open Yojson.Safe.Util in
200 let account_id = json |> member "accountId" |> to_string in
201 let old_state = json |> member "oldState" |> to_string_option in
202 let new_state = json |> member "newState" |> to_string_option in
203 let created = match json |> member "created" with
204 | `Null -> []
205 | created_json -> List.map (fun (cid, ecr_json) ->
206 match email_creation_result_of_json ecr_json with
207 | Ok ecr -> (cid, ecr)
208 | Error err -> failwith err
209 ) (to_assoc created_json)
210 in
211 let not_created = match json |> member "notCreated" with
212 | `Null -> []
213 | not_created_json -> List.map (fun (cid, err_json) ->
214 match Jmap.Error.Set_error.of_json err_json with
215 | Ok err -> (cid, err)
216 | Error err_msg -> failwith err_msg
217 ) (to_assoc not_created_json)
218 in
219 Ok (create ~account_id ?old_state ?new_state ~created ~not_created ())
220 with
221 | exn -> Error ("Failed to parse Email/import response: " ^ Printexc.to_string exn)
222end