My agentic slop goes here. Not intended for anyone else!
1(** Email address representation and address groups implementation.
2
3 This module implements email addresses and address groups with proper
4 validation and JSON serialization support as specified in RFC 8621.
5
6 @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-4.1.2.3> RFC 8621, Section 4.1.2.3
7*)
8
9type t = {
10 name : string option;
11 email : string;
12}
13
14type address = t
15
16let name t = t.name
17let email t = t.email
18
19(* Basic email validation - checks for @ symbol and basic structure *)
20let validate_email email =
21 let len = String.length email in
22 if len = 0 then
23 Error "Email address cannot be empty"
24 else if not (String.contains email '@') then
25 Error "Email address must contain @ symbol"
26 else
27 let at_pos = String.rindex email '@' in
28 if at_pos = 0 then
29 Error "Email address cannot start with @"
30 else if at_pos = len - 1 then
31 Error "Email address cannot end with @"
32 else if String.contains_from email (at_pos + 1) '@' then
33 Error "Email address cannot contain multiple @ symbols"
34 else
35 Ok ()
36
37let create ?name ~email () =
38 match validate_email email with
39 | Ok () -> Ok { name; email }
40 | Error msg -> Error msg
41
42let create_unsafe ?name ~email () = { name; email }
43
44let to_json t =
45 let fields = [("email", `String t.email)] in
46 let fields = match t.name with
47 | Some name -> ("name", `String name) :: fields
48 | None -> fields
49 in
50 `Assoc fields
51
52let of_json = function
53 | `Assoc fields ->
54 (match List.assoc_opt "email" fields with
55 | Some (`String email) ->
56 let name = match List.assoc_opt "name" fields with
57 | Some (`String name) -> Some name
58 | Some `Null | None -> None
59 | Some _ -> failwith "Invalid name field type"
60 in
61 (match validate_email email with
62 | Ok () -> Ok { name; email }
63 | Error msg -> Error ("Invalid email address: " ^ msg))
64 | Some _ -> Error "Email field must be a string"
65 | None -> Error "Missing required email field")
66 | _ -> Error "Email address must be a JSON object"
67
68let pp ppf t =
69 match t.name with
70 | Some name -> Format.fprintf ppf "%s <%s>" name t.email
71 | None -> Format.fprintf ppf "%s" t.email
72
73let pp_hum = pp
74
75module Group = struct
76 type t = {
77 name : string option;
78 addresses : address list;
79 }
80
81 let name t = t.name
82 let addresses t = t.addresses
83
84 let create ?name ~addresses () = { name; addresses }
85
86 let to_json t =
87 let fields = [("addresses", `List (List.map to_json t.addresses))] in
88 let fields = match t.name with
89 | Some name -> ("name", `String name) :: fields
90 | None -> fields
91 in
92 `Assoc fields
93
94 let of_json = function
95 | `Assoc fields ->
96 (match List.assoc_opt "addresses" fields with
97 | Some (`List addr_list) ->
98 let parse_addresses acc_result addr_json =
99 match acc_result with
100 | Error _ as err -> err
101 | Ok acc_addrs ->
102 (match of_json addr_json with
103 | Ok addr -> Ok (addr :: acc_addrs)
104 | Error msg -> Error ("Invalid address in group: " ^ msg))
105 in
106 (match List.fold_left parse_addresses (Ok []) addr_list with
107 | Ok addresses ->
108 let addresses = List.rev addresses in
109 let name = match List.assoc_opt "name" fields with
110 | Some (`String name) -> Some name
111 | Some `Null | None -> None
112 | Some _ -> failwith "Invalid name field type"
113 in
114 Ok { name; addresses }
115 | Error msg -> Error msg)
116 | Some _ -> Error "Addresses field must be a JSON array"
117 | None -> Error "Missing required addresses field")
118 | _ -> Error "Address group must be a JSON object"
119
120 let pp ppf t =
121 let format_addresses addrs =
122 String.concat ", " (List.map (fun addr -> Format.asprintf "%a" pp addr) addrs)
123 in
124 match t.name with
125 | Some name -> Format.fprintf ppf "%s: %s;" name (format_addresses t.addresses)
126 | None -> Format.fprintf ppf "%s" (format_addresses t.addresses)
127
128 let pp_hum = pp
129end