this repo has no description

Convert to polymorphic variants and add extended keywords/roles

- Convert filter operator, error types, mailbox roles, and submission
statuses from regular variants to polymorphic variants
- Add draft-ietf-mailmaint extended keywords: $notify, $muted, $followed,
$memo, $hasmemo, $hasattachment, $hasnoattachment, $autosent,
$unsubscribed, $canunsubscribe, $imported, $istrusted, $maskedemail, $new
- Add Apple Mail flag color keywords ($MailFlagBit0/1/2) with flag_color
type and conversion functions
- Add extended mailbox roles: snoozed, scheduled, memos
- Add RFC 8621 submission-specific errors: forbiddenMailFrom,
forbiddenFrom, forbiddenToSend

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

+1 -1
bin/jmap.ml
··· 752 752 (* Find inbox *) 753 753 let inbox = 754 754 List.find_opt (fun (m : Jmap.Proto.Mailbox.t) -> 755 - m.role = Some Jmap.Proto.Mailbox.Inbox 755 + m.role = Some `Inbox 756 756 ) mbox_result.list 757 757 in 758 758 match inbox with
+31 -24
lib/core/jmap.ml
··· 130 130 let is_subscribed m = Proto.Mailbox.is_subscribed m 131 131 132 132 let role m = 133 - let convert_role = function 134 - | Proto.Mailbox.Inbox -> `Inbox 135 - | Proto.Mailbox.Sent -> `Sent 136 - | Proto.Mailbox.Drafts -> `Drafts 137 - | Proto.Mailbox.Trash -> `Trash 138 - | Proto.Mailbox.Junk -> `Junk 139 - | Proto.Mailbox.Archive -> `Archive 140 - | Proto.Mailbox.Flagged -> `Flagged 141 - | Proto.Mailbox.Important -> `Important 142 - | Proto.Mailbox.All -> `All 143 - | Proto.Mailbox.Subscribed -> `Subscribed 144 - | Proto.Mailbox.Other s -> `Custom s 133 + (* Proto.Mailbox.role now returns polymorphic variants directly *) 134 + let convert_role : Proto.Mailbox.role -> Role.t = function 135 + | `Inbox -> `Inbox 136 + | `Sent -> `Sent 137 + | `Drafts -> `Drafts 138 + | `Trash -> `Trash 139 + | `Junk -> `Junk 140 + | `Archive -> `Archive 141 + | `Flagged -> `Flagged 142 + | `Important -> `Important 143 + | `All -> `All 144 + | `Subscribed -> `Subscribed 145 + | `Snoozed -> `Snoozed 146 + | `Scheduled -> `Scheduled 147 + | `Memos -> `Memos 148 + | `Other s -> `Custom s 145 149 in 146 150 Option.map convert_role (Proto.Mailbox.role m) 147 151 ··· 347 351 module Mailbox_filter = struct 348 352 type condition = Proto.Mailbox.Filter_condition.t 349 353 350 - let convert_role = function 351 - | `Inbox -> Proto.Mailbox.Inbox 352 - | `Sent -> Proto.Mailbox.Sent 353 - | `Drafts -> Proto.Mailbox.Drafts 354 - | `Trash -> Proto.Mailbox.Trash 355 - | `Junk -> Proto.Mailbox.Junk 356 - | `Archive -> Proto.Mailbox.Archive 357 - | `Flagged -> Proto.Mailbox.Flagged 358 - | `Important -> Proto.Mailbox.Important 359 - | `All -> Proto.Mailbox.All 360 - | `Subscribed -> Proto.Mailbox.Subscribed 361 - | `Custom s -> Proto.Mailbox.Other s 354 + let convert_role : Role.t -> Proto.Mailbox.role = function 355 + | `Inbox -> `Inbox 356 + | `Sent -> `Sent 357 + | `Drafts -> `Drafts 358 + | `Trash -> `Trash 359 + | `Junk -> `Junk 360 + | `Archive -> `Archive 361 + | `Flagged -> `Flagged 362 + | `Important -> `Important 363 + | `All -> `All 364 + | `Subscribed -> `Subscribed 365 + | `Snoozed -> `Snoozed 366 + | `Scheduled -> `Scheduled 367 + | `Memos -> `Memos 368 + | `Custom s -> `Other s 362 369 363 370 (** Create a mailbox filter condition. 364 371
+65 -2
lib/core/jmap.mli
··· 99 99 Standard keywords are represented as polymorphic variants. 100 100 Custom keywords use [`Custom of string]. *) 101 101 module Keyword : sig 102 - type t = [ 102 + (** RFC 8621 standard keywords *) 103 + type standard = [ 103 104 | `Seen 104 105 | `Flagged 105 106 | `Answered ··· 108 109 | `Phishing 109 110 | `Junk 110 111 | `NotJunk 112 + ] 113 + 114 + (** draft-ietf-mailmaint extended keywords *) 115 + type extended = [ 116 + | `Notify 117 + | `Muted 118 + | `Followed 119 + | `Memo 120 + | `HasMemo 121 + | `HasAttachment 122 + | `HasNoAttachment 123 + | `AutoSent 124 + | `Unsubscribed 125 + | `CanUnsubscribe 126 + | `Imported 127 + | `IsTrusted 128 + | `MaskedEmail 129 + | `New 130 + ] 131 + 132 + (** Apple Mail flag color keywords *) 133 + type flag_bits = [ 134 + | `MailFlagBit0 135 + | `MailFlagBit1 136 + | `MailFlagBit2 137 + ] 138 + 139 + type t = [ 140 + | standard 141 + | extended 142 + | flag_bits 111 143 | `Custom of string 112 144 ] 113 145 114 146 val of_string : string -> t 115 147 val to_string : t -> string 116 148 val pp : Format.formatter -> t -> unit 149 + 150 + (** Apple Mail flag colors *) 151 + type flag_color = [ 152 + | `Red 153 + | `Orange 154 + | `Yellow 155 + | `Green 156 + | `Blue 157 + | `Purple 158 + | `Gray 159 + ] 160 + 161 + val flag_color_of_keywords : t list -> flag_color option 162 + (** [flag_color_of_keywords keywords] extracts the flag color from a list 163 + of keywords. Returns [None] for invalid bit combinations. *) 164 + 165 + val flag_color_to_keywords : flag_color -> t list 166 + (** [flag_color_to_keywords color] returns the keywords to set for the color. *) 117 167 end 118 168 119 169 (** Mailbox role type. ··· 121 171 Standard roles are represented as polymorphic variants. 122 172 Custom roles use [`Custom of string]. *) 123 173 module Role : sig 124 - type t = [ 174 + (** RFC 8621 standard roles *) 175 + type standard = [ 125 176 | `Inbox 126 177 | `Sent 127 178 | `Drafts ··· 132 183 | `Important 133 184 | `All 134 185 | `Subscribed 186 + ] 187 + 188 + (** draft-ietf-mailmaint extended roles *) 189 + type extended = [ 190 + | `Snoozed 191 + | `Scheduled 192 + | `Memos 193 + ] 194 + 195 + type t = [ 196 + | standard 197 + | extended 135 198 | `Custom of string 136 199 ] 137 200
+132 -2
lib/core/jmap_types.ml
··· 82 82 (** {1 Keyword Type} *) 83 83 84 84 module Keyword = struct 85 - type t = [ 85 + (** RFC 8621 standard keywords *) 86 + type standard = [ 86 87 | `Seen 87 88 | `Flagged 88 89 | `Answered ··· 91 92 | `Phishing 92 93 | `Junk 93 94 | `NotJunk 95 + ] 96 + 97 + (** draft-ietf-mailmaint extended keywords *) 98 + type extended = [ 99 + | `Notify 100 + | `Muted 101 + | `Followed 102 + | `Memo 103 + | `HasMemo 104 + | `HasAttachment 105 + | `HasNoAttachment 106 + | `AutoSent 107 + | `Unsubscribed 108 + | `CanUnsubscribe 109 + | `Imported 110 + | `IsTrusted 111 + | `MaskedEmail 112 + | `New 113 + ] 114 + 115 + (** Apple Mail flag color keywords *) 116 + type flag_bits = [ 117 + | `MailFlagBit0 118 + | `MailFlagBit1 119 + | `MailFlagBit2 120 + ] 121 + 122 + type t = [ 123 + | standard 124 + | extended 125 + | flag_bits 94 126 | `Custom of string 95 127 ] 96 128 97 129 let of_string = function 130 + (* RFC 8621 standard keywords *) 98 131 | "$seen" -> `Seen 99 132 | "$flagged" -> `Flagged 100 133 | "$answered" -> `Answered ··· 103 136 | "$phishing" -> `Phishing 104 137 | "$junk" -> `Junk 105 138 | "$notjunk" -> `NotJunk 139 + (* draft-ietf-mailmaint extended keywords *) 140 + | "$notify" -> `Notify 141 + | "$muted" -> `Muted 142 + | "$followed" -> `Followed 143 + | "$memo" -> `Memo 144 + | "$hasmemo" -> `HasMemo 145 + | "$hasattachment" -> `HasAttachment 146 + | "$hasnoattachment" -> `HasNoAttachment 147 + | "$autosent" -> `AutoSent 148 + | "$unsubscribed" -> `Unsubscribed 149 + | "$canunsubscribe" -> `CanUnsubscribe 150 + | "$imported" -> `Imported 151 + | "$istrusted" -> `IsTrusted 152 + | "$maskedemail" -> `MaskedEmail 153 + | "$new" -> `New 154 + (* Apple Mail flag color keywords *) 155 + | "$MailFlagBit0" -> `MailFlagBit0 156 + | "$MailFlagBit1" -> `MailFlagBit1 157 + | "$MailFlagBit2" -> `MailFlagBit2 106 158 | s -> `Custom s 107 159 108 160 let to_string = function 161 + (* RFC 8621 standard keywords *) 109 162 | `Seen -> "$seen" 110 163 | `Flagged -> "$flagged" 111 164 | `Answered -> "$answered" ··· 114 167 | `Phishing -> "$phishing" 115 168 | `Junk -> "$junk" 116 169 | `NotJunk -> "$notjunk" 170 + (* draft-ietf-mailmaint extended keywords *) 171 + | `Notify -> "$notify" 172 + | `Muted -> "$muted" 173 + | `Followed -> "$followed" 174 + | `Memo -> "$memo" 175 + | `HasMemo -> "$hasmemo" 176 + | `HasAttachment -> "$hasattachment" 177 + | `HasNoAttachment -> "$hasnoattachment" 178 + | `AutoSent -> "$autosent" 179 + | `Unsubscribed -> "$unsubscribed" 180 + | `CanUnsubscribe -> "$canunsubscribe" 181 + | `Imported -> "$imported" 182 + | `IsTrusted -> "$istrusted" 183 + | `MaskedEmail -> "$maskedemail" 184 + | `New -> "$new" 185 + (* Apple Mail flag color keywords *) 186 + | `MailFlagBit0 -> "$MailFlagBit0" 187 + | `MailFlagBit1 -> "$MailFlagBit1" 188 + | `MailFlagBit2 -> "$MailFlagBit2" 117 189 | `Custom s -> s 118 190 119 191 let pp ppf k = Format.pp_print_string ppf (to_string k) 192 + 193 + (** Apple Mail flag colors *) 194 + type flag_color = [ 195 + | `Red 196 + | `Orange 197 + | `Yellow 198 + | `Green 199 + | `Blue 200 + | `Purple 201 + | `Gray 202 + ] 203 + 204 + let flag_color_of_keywords (keywords : t list) : flag_color option = 205 + let has k = List.mem k keywords in 206 + let bit0 = has `MailFlagBit0 in 207 + let bit1 = has `MailFlagBit1 in 208 + let bit2 = has `MailFlagBit2 in 209 + match (bit0, bit1, bit2) with 210 + | (false, false, false) -> Some `Red 211 + | (true, false, false) -> Some `Orange 212 + | (false, true, false) -> Some `Yellow 213 + | (true, true, true) -> Some `Green 214 + | (false, false, true) -> Some `Blue 215 + | (true, false, true) -> Some `Purple 216 + | (false, true, true) -> Some `Gray 217 + | (true, true, false) -> None 218 + 219 + let flag_color_to_keywords : flag_color -> t list = function 220 + | `Red -> [] 221 + | `Orange -> [`MailFlagBit0] 222 + | `Yellow -> [`MailFlagBit1] 223 + | `Green -> [`MailFlagBit0; `MailFlagBit1; `MailFlagBit2] 224 + | `Blue -> [`MailFlagBit2] 225 + | `Purple -> [`MailFlagBit0; `MailFlagBit2] 226 + | `Gray -> [`MailFlagBit1; `MailFlagBit2] 120 227 end 121 228 122 229 (** {1 Mailbox Role Type} *) 123 230 124 231 module Role = struct 125 - type t = [ 232 + (** RFC 8621 standard roles *) 233 + type standard = [ 126 234 | `Inbox 127 235 | `Sent 128 236 | `Drafts ··· 133 241 | `Important 134 242 | `All 135 243 | `Subscribed 244 + ] 245 + 246 + (** draft-ietf-mailmaint extended roles *) 247 + type extended = [ 248 + | `Snoozed 249 + | `Scheduled 250 + | `Memos 251 + ] 252 + 253 + type t = [ 254 + | standard 255 + | extended 136 256 | `Custom of string 137 257 ] 138 258 139 259 let of_string = function 260 + (* RFC 8621 standard roles *) 140 261 | "inbox" -> `Inbox 141 262 | "sent" -> `Sent 142 263 | "drafts" -> `Drafts ··· 147 268 | "important" -> `Important 148 269 | "all" -> `All 149 270 | "subscribed" -> `Subscribed 271 + (* draft-ietf-mailmaint extended roles *) 272 + | "snoozed" -> `Snoozed 273 + | "scheduled" -> `Scheduled 274 + | "memos" -> `Memos 150 275 | s -> `Custom s 151 276 152 277 let to_string = function 278 + (* RFC 8621 standard roles *) 153 279 | `Inbox -> "inbox" 154 280 | `Sent -> "sent" 155 281 | `Drafts -> "drafts" ··· 160 286 | `Important -> "important" 161 287 | `All -> "all" 162 288 | `Subscribed -> "subscribed" 289 + (* draft-ietf-mailmaint extended roles *) 290 + | `Snoozed -> "snoozed" 291 + | `Scheduled -> "scheduled" 292 + | `Memos -> "memos" 163 293 | `Custom s -> s 164 294 165 295 let pp ppf r = Format.pp_print_string ppf (to_string r)
+59
lib/mail/mail_email.ml
··· 4 4 ---------------------------------------------------------------------------*) 5 5 6 6 module Keyword = struct 7 + (* RFC 8621 Standard Keywords *) 7 8 let draft = "$draft" 8 9 let seen = "$seen" 9 10 let flagged = "$flagged" ··· 12 13 let phishing = "$phishing" 13 14 let junk = "$junk" 14 15 let not_junk = "$notjunk" 16 + 17 + (* draft-ietf-mailmaint Extended Keywords *) 18 + let notify = "$notify" 19 + let muted = "$muted" 20 + let followed = "$followed" 21 + let memo = "$memo" 22 + let has_memo = "$hasmemo" 23 + let has_attachment = "$hasattachment" 24 + let has_no_attachment = "$hasnoattachment" 25 + let auto_sent = "$autosent" 26 + let unsubscribed = "$unsubscribed" 27 + let can_unsubscribe = "$canunsubscribe" 28 + let imported = "$imported" 29 + let is_trusted = "$istrusted" 30 + let masked_email = "$maskedemail" 31 + let new_ = "$new" 32 + 33 + (* Apple Mail Flag Color Keywords *) 34 + let mail_flag_bit0 = "$MailFlagBit0" 35 + let mail_flag_bit1 = "$MailFlagBit1" 36 + let mail_flag_bit2 = "$MailFlagBit2" 37 + 38 + type flag_color = [ 39 + | `Red 40 + | `Orange 41 + | `Yellow 42 + | `Green 43 + | `Blue 44 + | `Purple 45 + | `Gray 46 + ] 47 + 48 + (* Flag color bitmask: 49 + - 000 = red, 100 = orange, 010 = yellow, 111 = green 50 + - 001 = blue, 101 = purple, 011 = gray *) 51 + let flag_color_to_keywords = function 52 + | `Red -> [] (* 000 - no bits set *) 53 + | `Orange -> [mail_flag_bit0] (* 100 *) 54 + | `Yellow -> [mail_flag_bit1] (* 010 *) 55 + | `Green -> [mail_flag_bit0; mail_flag_bit1; mail_flag_bit2] (* 111 *) 56 + | `Blue -> [mail_flag_bit2] (* 001 *) 57 + | `Purple -> [mail_flag_bit0; mail_flag_bit2] (* 101 *) 58 + | `Gray -> [mail_flag_bit1; mail_flag_bit2] (* 011 *) 59 + 60 + let flag_color_of_keywords keywords = 61 + let has k = List.mem k keywords in 62 + let bit0 = has mail_flag_bit0 in 63 + let bit1 = has mail_flag_bit1 in 64 + let bit2 = has mail_flag_bit2 in 65 + match (bit0, bit1, bit2) with 66 + | (false, false, false) -> Some `Red 67 + | (true, false, false) -> Some `Orange 68 + | (false, true, false) -> Some `Yellow 69 + | (true, true, true) -> Some `Green 70 + | (false, false, true) -> Some `Blue 71 + | (true, false, true) -> Some `Purple 72 + | (false, true, true) -> Some `Gray 73 + | (true, true, false) -> None (* Invalid combination *) 15 74 end 16 75 17 76 type t = {
+106 -9
lib/mail/mail_email.mli
··· 9 9 10 10 (** {1 Standard Keywords} *) 11 11 12 - (** Standard email keywords per RFC 8621. *) 12 + (** Standard email keywords per RFC 8621 and draft-ietf-mailmaint. 13 + 14 + Keywords are stored as strings in JMAP, but these constants provide 15 + type-safe access to standard keywords. *) 13 16 module Keyword : sig 17 + 18 + (** {2 RFC 8621 Standard Keywords} *) 19 + 14 20 val draft : string 15 - (** ["$draft"] *) 21 + (** ["$draft"] - The Email is a draft the user is composing. *) 16 22 17 23 val seen : string 18 - (** ["$seen"] *) 24 + (** ["$seen"] - The Email has been read. *) 19 25 20 26 val flagged : string 21 - (** ["$flagged"] *) 27 + (** ["$flagged"] - The Email has been flagged for urgent/special attention. *) 22 28 23 29 val answered : string 24 - (** ["$answered"] *) 30 + (** ["$answered"] - The Email has been replied to. *) 25 31 26 32 val forwarded : string 27 - (** ["$forwarded"] *) 33 + (** ["$forwarded"] - The Email has been forwarded. *) 28 34 29 35 val phishing : string 30 - (** ["$phishing"] *) 36 + (** ["$phishing"] - The Email is highly likely to be phishing. *) 31 37 32 38 val junk : string 33 - (** ["$junk"] *) 39 + (** ["$junk"] - The Email is definitely spam. *) 34 40 35 41 val not_junk : string 36 - (** ["$notjunk"] *) 42 + (** ["$notjunk"] - The Email is definitely not spam. *) 43 + 44 + (** {2 draft-ietf-mailmaint Extended Keywords} *) 45 + 46 + val notify : string 47 + (** ["$notify"] - A notification should be shown for this message. *) 48 + 49 + val muted : string 50 + (** ["$muted"] - The user is not interested in future replies to this thread. *) 51 + 52 + val followed : string 53 + (** ["$followed"] - The user is particularly interested in future replies 54 + to this thread. Mutually exclusive with muted. *) 55 + 56 + val memo : string 57 + (** ["$memo"] - The message is a note-to-self regarding another message 58 + in the same thread. *) 59 + 60 + val has_memo : string 61 + (** ["$hasmemo"] - The message has an associated memo with the $memo keyword. *) 62 + 63 + val has_attachment : string 64 + (** ["$hasattachment"] - The message has an attachment (server-set). *) 65 + 66 + val has_no_attachment : string 67 + (** ["$hasnoattachment"] - The message does not have an attachment (server-set). *) 68 + 69 + val auto_sent : string 70 + (** ["$autosent"] - The message was sent automatically as a response 71 + due to a user rule or setting (e.g., vacation response). *) 72 + 73 + val unsubscribed : string 74 + (** ["$unsubscribed"] - The client has unsubscribed from this mailing list. *) 75 + 76 + val can_unsubscribe : string 77 + (** ["$canunsubscribe"] - The message has an RFC8058-compliant 78 + List-Unsubscribe header. *) 79 + 80 + val imported : string 81 + (** ["$imported"] - The message was imported from another mailbox. *) 82 + 83 + val is_trusted : string 84 + (** ["$istrusted"] - The authenticity of the from name and email address 85 + have been verified with complete confidence by the server. *) 86 + 87 + val masked_email : string 88 + (** ["$maskedemail"] - The message was received via an alias created for 89 + an individual sender to hide the user's real email address. *) 90 + 91 + val new_ : string 92 + (** ["$new"] - The message should be made more prominent to the user 93 + due to a recent action (e.g., awakening from snooze). *) 94 + 95 + (** {2 Apple Mail Flag Color Keywords} 96 + 97 + These 3 keywords form a 3-bit bitmask defining the flag color: 98 + - 000 = red, 100 = orange, 010 = yellow, 111 = green 99 + - 001 = blue, 101 = purple, 011 = gray 100 + 101 + These are only meaningful when the message has the $flagged keyword set. *) 102 + 103 + val mail_flag_bit0 : string 104 + (** ["$MailFlagBit0"] - Bit 0 of the flag color bitmask. *) 105 + 106 + val mail_flag_bit1 : string 107 + (** ["$MailFlagBit1"] - Bit 1 of the flag color bitmask. *) 108 + 109 + val mail_flag_bit2 : string 110 + (** ["$MailFlagBit2"] - Bit 2 of the flag color bitmask. *) 111 + 112 + (** {2 Flag Color Type} 113 + 114 + High-level type for working with Apple Mail flag colors. *) 115 + 116 + type flag_color = [ 117 + | `Red (** Bits: 000 *) 118 + | `Orange (** Bits: 100 *) 119 + | `Yellow (** Bits: 010 *) 120 + | `Green (** Bits: 111 *) 121 + | `Blue (** Bits: 001 *) 122 + | `Purple (** Bits: 101 *) 123 + | `Gray (** Bits: 011 *) 124 + ] 125 + 126 + val flag_color_to_keywords : flag_color -> string list 127 + (** [flag_color_to_keywords color] returns the list of $MailFlagBit keywords 128 + that should be set for the given color. *) 129 + 130 + val flag_color_of_keywords : string list -> flag_color option 131 + (** [flag_color_of_keywords keywords] extracts the flag color from a list 132 + of keywords, if the $MailFlagBit keywords are present. Returns [None] 133 + if no color bits are set (defaults to red when $flagged is set). *) 37 134 end 38 135 39 136 (** {1 Email Object} *)
+44 -34
lib/mail/mail_mailbox.ml
··· 46 46 |> Jsont.Object.finish 47 47 end 48 48 49 - type role = 50 - | All 51 - | Archive 52 - | Drafts 53 - | Flagged 54 - | Important 55 - | Inbox 56 - | Junk 57 - | Sent 58 - | Subscribed 59 - | Trash 60 - | Other of string 49 + type role = [ 50 + | `All 51 + | `Archive 52 + | `Drafts 53 + | `Flagged 54 + | `Important 55 + | `Inbox 56 + | `Junk 57 + | `Sent 58 + | `Subscribed 59 + | `Trash 60 + | `Snoozed 61 + | `Scheduled 62 + | `Memos 63 + | `Other of string 64 + ] 61 65 62 66 let role_to_string = function 63 - | All -> "all" 64 - | Archive -> "archive" 65 - | Drafts -> "drafts" 66 - | Flagged -> "flagged" 67 - | Important -> "important" 68 - | Inbox -> "inbox" 69 - | Junk -> "junk" 70 - | Sent -> "sent" 71 - | Subscribed -> "subscribed" 72 - | Trash -> "trash" 73 - | Other s -> s 67 + | `All -> "all" 68 + | `Archive -> "archive" 69 + | `Drafts -> "drafts" 70 + | `Flagged -> "flagged" 71 + | `Important -> "important" 72 + | `Inbox -> "inbox" 73 + | `Junk -> "junk" 74 + | `Sent -> "sent" 75 + | `Subscribed -> "subscribed" 76 + | `Trash -> "trash" 77 + | `Snoozed -> "snoozed" 78 + | `Scheduled -> "scheduled" 79 + | `Memos -> "memos" 80 + | `Other s -> s 74 81 75 82 let role_of_string = function 76 - | "all" -> All 77 - | "archive" -> Archive 78 - | "drafts" -> Drafts 79 - | "flagged" -> Flagged 80 - | "important" -> Important 81 - | "inbox" -> Inbox 82 - | "junk" -> Junk 83 - | "sent" -> Sent 84 - | "subscribed" -> Subscribed 85 - | "trash" -> Trash 86 - | s -> Other s 83 + | "all" -> `All 84 + | "archive" -> `Archive 85 + | "drafts" -> `Drafts 86 + | "flagged" -> `Flagged 87 + | "important" -> `Important 88 + | "inbox" -> `Inbox 89 + | "junk" -> `Junk 90 + | "sent" -> `Sent 91 + | "subscribed" -> `Subscribed 92 + | "trash" -> `Trash 93 + | "snoozed" -> `Snoozed 94 + | "scheduled" -> `Scheduled 95 + | "memos" -> `Memos 96 + | s -> `Other s 87 97 88 98 let role_jsont = 89 99 Jsont.map ~kind:"MailboxRole"
+17 -13
lib/mail/mail_mailbox.mli
··· 38 38 39 39 (** {1 Standard Roles} *) 40 40 41 - (** Standard mailbox roles per RFC 8621 Section 2. *) 42 - type role = 43 - | All 44 - | Archive 45 - | Drafts 46 - | Flagged 47 - | Important 48 - | Inbox 49 - | Junk 50 - | Sent 51 - | Subscribed 52 - | Trash 53 - | Other of string 41 + (** Standard mailbox roles per RFC 8621 Section 2 and draft-ietf-mailmaint. *) 42 + type role = [ 43 + | `All 44 + | `Archive 45 + | `Drafts 46 + | `Flagged 47 + | `Important 48 + | `Inbox 49 + | `Junk 50 + | `Sent 51 + | `Subscribed 52 + | `Trash 53 + | `Snoozed (** draft-ietf-mailmaint: Messages snoozed until a later time. *) 54 + | `Scheduled (** draft-ietf-mailmaint: Messages scheduled to send. *) 55 + | `Memos (** draft-ietf-mailmaint: Messages with the $memo keyword. *) 56 + | `Other of string 57 + ] 54 58 55 59 val role_to_string : role -> string 56 60 val role_of_string : string -> role
+21 -21
lib/mail/mail_submission.ml
··· 42 42 end 43 43 44 44 module Delivery_status = struct 45 - type delivered = Queued | Yes | No | Unknown 45 + type delivered = [ `Queued | `Yes | `No | `Unknown ] 46 46 47 47 let delivered_to_string = function 48 - | Queued -> "queued" 49 - | Yes -> "yes" 50 - | No -> "no" 51 - | Unknown -> "unknown" 48 + | `Queued -> "queued" 49 + | `Yes -> "yes" 50 + | `No -> "no" 51 + | `Unknown -> "unknown" 52 52 53 53 let delivered_of_string = function 54 - | "queued" -> Queued 55 - | "yes" -> Yes 56 - | "no" -> No 57 - | _ -> Unknown 54 + | "queued" -> `Queued 55 + | "yes" -> `Yes 56 + | "no" -> `No 57 + | _ -> `Unknown 58 58 59 59 let delivered_jsont = 60 60 Jsont.map ~kind:"DeliveryStatus.delivered" 61 61 ~dec:delivered_of_string ~enc:delivered_to_string Jsont.string 62 62 63 - type displayed = Unknown | Yes 63 + type displayed = [ `Unknown | `Yes ] 64 64 65 65 let displayed_to_string = function 66 - | Unknown -> "unknown" 67 - | Yes -> "yes" 66 + | `Unknown -> "unknown" 67 + | `Yes -> "yes" 68 68 69 69 let displayed_of_string = function 70 - | "yes" -> Yes 71 - | _ -> Unknown 70 + | "yes" -> `Yes 71 + | _ -> `Unknown 72 72 73 73 let displayed_jsont = 74 74 Jsont.map ~kind:"DeliveryStatus.displayed" ··· 96 96 |> Jsont.Object.finish 97 97 end 98 98 99 - type undo_status = Pending | Final | Canceled 99 + type undo_status = [ `Pending | `Final | `Canceled ] 100 100 101 101 let undo_status_to_string = function 102 - | Pending -> "pending" 103 - | Final -> "final" 104 - | Canceled -> "canceled" 102 + | `Pending -> "pending" 103 + | `Final -> "final" 104 + | `Canceled -> "canceled" 105 105 106 106 let undo_status_of_string = function 107 - | "pending" -> Pending 108 - | "final" -> Final 109 - | "canceled" -> Canceled 107 + | "pending" -> `Pending 108 + | "final" -> `Final 109 + | "canceled" -> `Canceled 110 110 | s -> Jsont.Error.msgf Jsont.Meta.none "Unknown undo status: %s" s 111 111 112 112 let undo_status_jsont =
+15 -12
lib/mail/mail_submission.mli
··· 45 45 46 46 (** Status of delivery to a recipient. *) 47 47 module Delivery_status : sig 48 - type delivered = 49 - | Queued 50 - | Yes 51 - | No 52 - | Unknown 48 + type delivered = [ 49 + | `Queued 50 + | `Yes 51 + | `No 52 + | `Unknown 53 + ] 53 54 54 - type displayed = 55 - | Unknown 56 - | Yes 55 + type displayed = [ 56 + | `Unknown 57 + | `Yes 58 + ] 57 59 58 60 type t = { 59 61 smtp_reply : string; ··· 73 75 74 76 (** {1 Undo Status} *) 75 77 76 - type undo_status = 77 - | Pending 78 - | Final 79 - | Canceled 78 + type undo_status = [ 79 + | `Pending 80 + | `Final 81 + | `Canceled 82 + ] 80 83 81 84 val undo_status_jsont : undo_status Jsont.t 82 85
+93 -81
lib/proto/proto_error.ml
··· 4 4 ---------------------------------------------------------------------------*) 5 5 6 6 module Request_error = struct 7 - type urn = 8 - | Unknown_capability 9 - | Not_json 10 - | Not_request 11 - | Limit 12 - | Other of string 7 + type urn = [ 8 + | `Unknown_capability 9 + | `Not_json 10 + | `Not_request 11 + | `Limit 12 + | `Other of string 13 + ] 13 14 14 15 let urn_to_string = function 15 - | Unknown_capability -> "urn:ietf:params:jmap:error:unknownCapability" 16 - | Not_json -> "urn:ietf:params:jmap:error:notJSON" 17 - | Not_request -> "urn:ietf:params:jmap:error:notRequest" 18 - | Limit -> "urn:ietf:params:jmap:error:limit" 19 - | Other s -> s 16 + | `Unknown_capability -> "urn:ietf:params:jmap:error:unknownCapability" 17 + | `Not_json -> "urn:ietf:params:jmap:error:notJSON" 18 + | `Not_request -> "urn:ietf:params:jmap:error:notRequest" 19 + | `Limit -> "urn:ietf:params:jmap:error:limit" 20 + | `Other s -> s 20 21 21 22 let urn_of_string = function 22 - | "urn:ietf:params:jmap:error:unknownCapability" -> Unknown_capability 23 - | "urn:ietf:params:jmap:error:notJSON" -> Not_json 24 - | "urn:ietf:params:jmap:error:notRequest" -> Not_request 25 - | "urn:ietf:params:jmap:error:limit" -> Limit 26 - | s -> Other s 23 + | "urn:ietf:params:jmap:error:unknownCapability" -> `Unknown_capability 24 + | "urn:ietf:params:jmap:error:notJSON" -> `Not_json 25 + | "urn:ietf:params:jmap:error:notRequest" -> `Not_request 26 + | "urn:ietf:params:jmap:error:limit" -> `Limit 27 + | s -> `Other s 27 28 28 29 let urn_jsont = 29 30 let kind = "Request error URN" in ··· 60 61 |> Jsont.Object.finish 61 62 end 62 63 63 - type method_error_type = 64 - | Server_unavailable 65 - | Server_fail 66 - | Server_partial_fail 67 - | Unknown_method 68 - | Invalid_arguments 69 - | Invalid_result_reference 70 - | Forbidden 71 - | Account_not_found 72 - | Account_not_supported_by_method 73 - | Account_read_only 74 - | Other of string 64 + type method_error_type = [ 65 + | `Server_unavailable 66 + | `Server_fail 67 + | `Server_partial_fail 68 + | `Unknown_method 69 + | `Invalid_arguments 70 + | `Invalid_result_reference 71 + | `Forbidden 72 + | `Account_not_found 73 + | `Account_not_supported_by_method 74 + | `Account_read_only 75 + | `Other of string 76 + ] 75 77 76 78 let method_error_type_to_string = function 77 - | Server_unavailable -> "serverUnavailable" 78 - | Server_fail -> "serverFail" 79 - | Server_partial_fail -> "serverPartialFail" 80 - | Unknown_method -> "unknownMethod" 81 - | Invalid_arguments -> "invalidArguments" 82 - | Invalid_result_reference -> "invalidResultReference" 83 - | Forbidden -> "forbidden" 84 - | Account_not_found -> "accountNotFound" 85 - | Account_not_supported_by_method -> "accountNotSupportedByMethod" 86 - | Account_read_only -> "accountReadOnly" 87 - | Other s -> s 79 + | `Server_unavailable -> "serverUnavailable" 80 + | `Server_fail -> "serverFail" 81 + | `Server_partial_fail -> "serverPartialFail" 82 + | `Unknown_method -> "unknownMethod" 83 + | `Invalid_arguments -> "invalidArguments" 84 + | `Invalid_result_reference -> "invalidResultReference" 85 + | `Forbidden -> "forbidden" 86 + | `Account_not_found -> "accountNotFound" 87 + | `Account_not_supported_by_method -> "accountNotSupportedByMethod" 88 + | `Account_read_only -> "accountReadOnly" 89 + | `Other s -> s 88 90 89 91 let method_error_type_of_string = function 90 - | "serverUnavailable" -> Server_unavailable 91 - | "serverFail" -> Server_fail 92 - | "serverPartialFail" -> Server_partial_fail 93 - | "unknownMethod" -> Unknown_method 94 - | "invalidArguments" -> Invalid_arguments 95 - | "invalidResultReference" -> Invalid_result_reference 96 - | "forbidden" -> Forbidden 97 - | "accountNotFound" -> Account_not_found 98 - | "accountNotSupportedByMethod" -> Account_not_supported_by_method 99 - | "accountReadOnly" -> Account_read_only 100 - | s -> Other s 92 + | "serverUnavailable" -> `Server_unavailable 93 + | "serverFail" -> `Server_fail 94 + | "serverPartialFail" -> `Server_partial_fail 95 + | "unknownMethod" -> `Unknown_method 96 + | "invalidArguments" -> `Invalid_arguments 97 + | "invalidResultReference" -> `Invalid_result_reference 98 + | "forbidden" -> `Forbidden 99 + | "accountNotFound" -> `Account_not_found 100 + | "accountNotSupportedByMethod" -> `Account_not_supported_by_method 101 + | "accountReadOnly" -> `Account_read_only 102 + | s -> `Other s 101 103 102 104 let method_error_type_jsont = 103 105 let kind = "Method error type" in ··· 122 124 |> Jsont.Object.opt_mem "description" Jsont.string ~enc:method_error_description 123 125 |> Jsont.Object.finish 124 126 125 - type set_error_type = 126 - | Forbidden 127 - | Over_quota 128 - | Too_large 129 - | Rate_limit 130 - | Not_found 131 - | Invalid_patch 132 - | Will_destroy 133 - | Invalid_properties 134 - | Singleton 135 - | Other of string 127 + type set_error_type = [ 128 + | `Forbidden 129 + | `Over_quota 130 + | `Too_large 131 + | `Rate_limit 132 + | `Not_found 133 + | `Invalid_patch 134 + | `Will_destroy 135 + | `Invalid_properties 136 + | `Singleton 137 + | `Forbidden_mail_from 138 + | `Forbidden_from 139 + | `Forbidden_to_send 140 + | `Other of string 141 + ] 136 142 137 143 let set_error_type_to_string = function 138 - | Forbidden -> "forbidden" 139 - | Over_quota -> "overQuota" 140 - | Too_large -> "tooLarge" 141 - | Rate_limit -> "rateLimit" 142 - | Not_found -> "notFound" 143 - | Invalid_patch -> "invalidPatch" 144 - | Will_destroy -> "willDestroy" 145 - | Invalid_properties -> "invalidProperties" 146 - | Singleton -> "singleton" 147 - | Other s -> s 144 + | `Forbidden -> "forbidden" 145 + | `Over_quota -> "overQuota" 146 + | `Too_large -> "tooLarge" 147 + | `Rate_limit -> "rateLimit" 148 + | `Not_found -> "notFound" 149 + | `Invalid_patch -> "invalidPatch" 150 + | `Will_destroy -> "willDestroy" 151 + | `Invalid_properties -> "invalidProperties" 152 + | `Singleton -> "singleton" 153 + | `Forbidden_mail_from -> "forbiddenMailFrom" 154 + | `Forbidden_from -> "forbiddenFrom" 155 + | `Forbidden_to_send -> "forbiddenToSend" 156 + | `Other s -> s 148 157 149 158 let set_error_type_of_string = function 150 - | "forbidden" -> Forbidden 151 - | "overQuota" -> Over_quota 152 - | "tooLarge" -> Too_large 153 - | "rateLimit" -> Rate_limit 154 - | "notFound" -> Not_found 155 - | "invalidPatch" -> Invalid_patch 156 - | "willDestroy" -> Will_destroy 157 - | "invalidProperties" -> Invalid_properties 158 - | "singleton" -> Singleton 159 - | s -> Other s 159 + | "forbidden" -> `Forbidden 160 + | "overQuota" -> `Over_quota 161 + | "tooLarge" -> `Too_large 162 + | "rateLimit" -> `Rate_limit 163 + | "notFound" -> `Not_found 164 + | "invalidPatch" -> `Invalid_patch 165 + | "willDestroy" -> `Will_destroy 166 + | "invalidProperties" -> `Invalid_properties 167 + | "singleton" -> `Singleton 168 + | "forbiddenMailFrom" -> `Forbidden_mail_from 169 + | "forbiddenFrom" -> `Forbidden_from 170 + | "forbiddenToSend" -> `Forbidden_to_send 171 + | s -> `Other s 160 172 161 173 let set_error_type_jsont = 162 174 let kind = "SetError type" in
+40 -30
lib/proto/proto_error.mli
··· 14 14 15 15 (** Request-level error URNs *) 16 16 module Request_error : sig 17 - type urn = 18 - | Unknown_capability 17 + type urn = [ 18 + | `Unknown_capability 19 19 (** urn:ietf:params:jmap:error:unknownCapability 20 20 The client included a capability in "using" that the server does not support. *) 21 - | Not_json 21 + | `Not_json 22 22 (** urn:ietf:params:jmap:error:notJSON 23 23 The content type was not application/json or the request was not valid JSON. *) 24 - | Not_request 24 + | `Not_request 25 25 (** urn:ietf:params:jmap:error:notRequest 26 26 The request was valid JSON but not a valid JMAP Request object. *) 27 - | Limit 27 + | `Limit 28 28 (** urn:ietf:params:jmap:error:limit 29 29 A server-defined limit was reached. *) 30 - | Other of string 30 + | `Other of string 31 31 (** Other URN not in the standard set. *) 32 + ] 32 33 33 34 val urn_to_string : urn -> string 34 35 (** [urn_to_string urn] returns the URN string. *) ··· 60 61 when a method call fails. *) 61 62 62 63 (** Standard method error types per RFC 8620 Section 3.6.2 *) 63 - type method_error_type = 64 - | Server_unavailable 64 + type method_error_type = [ 65 + | `Server_unavailable 65 66 (** The server is temporarily unavailable. *) 66 - | Server_fail 67 + | `Server_fail 67 68 (** An unexpected error occurred. *) 68 - | Server_partial_fail 69 + | `Server_partial_fail 69 70 (** Some, but not all, changes were successfully made. *) 70 - | Unknown_method 71 + | `Unknown_method 71 72 (** The method name is not recognized. *) 72 - | Invalid_arguments 73 + | `Invalid_arguments 73 74 (** One or more arguments are invalid. *) 74 - | Invalid_result_reference 75 + | `Invalid_result_reference 75 76 (** A result reference could not be resolved. *) 76 - | Forbidden 77 + | `Forbidden 77 78 (** The method/arguments are valid but forbidden. *) 78 - | Account_not_found 79 + | `Account_not_found 79 80 (** The accountId does not correspond to a valid account. *) 80 - | Account_not_supported_by_method 81 + | `Account_not_supported_by_method 81 82 (** The account does not support this method. *) 82 - | Account_read_only 83 + | `Account_read_only 83 84 (** The account is read-only. *) 84 - | Other of string 85 + | `Other of string 85 86 (** Other error type not in the standard set. *) 87 + ] 86 88 87 89 val method_error_type_to_string : method_error_type -> string 88 90 (** [method_error_type_to_string t] returns the type string. *) ··· 105 107 106 108 Errors returned in notCreated/notUpdated/notDestroyed responses. *) 107 109 108 - (** Standard SetError types per RFC 8620 Section 5.3 *) 109 - type set_error_type = 110 - | Forbidden 110 + (** Standard SetError types per RFC 8620 Section 5.3 and RFC 8621 Section 7 *) 111 + type set_error_type = [ 112 + | `Forbidden 111 113 (** The operation is not permitted. *) 112 - | Over_quota 114 + | `Over_quota 113 115 (** The maximum server quota has been reached. *) 114 - | Too_large 116 + | `Too_large 115 117 (** The object is too large. *) 116 - | Rate_limit 118 + | `Rate_limit 117 119 (** Too many objects of this type have been created recently. *) 118 - | Not_found 120 + | `Not_found 119 121 (** The id does not exist (for update/destroy). *) 120 - | Invalid_patch 122 + | `Invalid_patch 121 123 (** The PatchObject is invalid. *) 122 - | Will_destroy 124 + | `Will_destroy 123 125 (** The object will be destroyed by another operation in the request. *) 124 - | Invalid_properties 126 + | `Invalid_properties 125 127 (** Some properties were invalid. *) 126 - | Singleton 128 + | `Singleton 127 129 (** Only one object of this type can exist (for create). *) 128 - | Other of string 130 + | `Forbidden_mail_from 131 + (** RFC 8621: The server does not permit the user to send from the address. *) 132 + | `Forbidden_from 133 + (** RFC 8621: The server does not permit the user to send a message with 134 + the From header of the message to be sent. *) 135 + | `Forbidden_to_send 136 + (** RFC 8621: The user does not have permission to send at all. *) 137 + | `Other of string 129 138 (** Other error type. *) 139 + ] 130 140 131 141 val set_error_type_to_string : set_error_type -> string 132 142 val set_error_type_of_string : string -> set_error_type
+7 -7
lib/proto/proto_filter.ml
··· 3 3 SPDX-License-Identifier: ISC 4 4 ---------------------------------------------------------------------------*) 5 5 6 - type operator = And | Or | Not 6 + type operator = [ `And | `Or | `Not ] 7 7 8 8 let operator_to_string = function 9 - | And -> "AND" 10 - | Or -> "OR" 11 - | Not -> "NOT" 9 + | `And -> "AND" 10 + | `Or -> "OR" 11 + | `Not -> "NOT" 12 12 13 13 let operator_of_string = function 14 - | "AND" -> And 15 - | "OR" -> Or 16 - | "NOT" -> Not 14 + | "AND" -> `And 15 + | "OR" -> `Or 16 + | "NOT" -> `Not 17 17 | s -> Jsont.Error.msgf Jsont.Meta.none "Unknown filter operator: %s" s 18 18 19 19 let operator_jsont =
+5 -4
lib/proto/proto_filter.mli
··· 10 10 (** {1 Filter Operators} *) 11 11 12 12 (** Filter operator types. *) 13 - type operator = 14 - | And (** All conditions must match *) 15 - | Or (** At least one condition must match *) 16 - | Not (** Inverts a single condition *) 13 + type operator = [ 14 + | `And (** All conditions must match *) 15 + | `Or (** At least one condition must match *) 16 + | `Not (** Inverts a single condition *) 17 + ] 17 18 18 19 val operator_jsont : operator Jsont.t 19 20 (** JSON codec for filter operators. *)
+5 -5
test/proto/test_proto.ml
··· 512 512 match decode Error.method_error_jsont json with 513 513 | Error e -> Alcotest.failf "decode failed: %s" (Jsont.Error.to_string e) 514 514 | Ok err -> 515 - Alcotest.(check method_error_type_testable) "type" Error.Unknown_method err.type_ 515 + Alcotest.(check method_error_type_testable) "type" `Unknown_method err.type_ 516 516 517 517 (* Additional error type tests *) 518 518 let test_set_error_forbidden () = ··· 561 561 match decode Error.set_error_jsont json with 562 562 | Error e -> Alcotest.failf "decode failed: %s" (Jsont.Error.to_string e) 563 563 | Ok err -> 564 - Alcotest.(check set_error_type_testable) "type" Error.Invalid_properties err.Error.type_; 564 + Alcotest.(check set_error_type_testable) "type" `Invalid_properties err.Error.type_; 565 565 match err.Error.properties with 566 566 | None -> Alcotest.fail "expected properties" 567 567 | Some props -> Alcotest.(check int) "properties count" 2 (List.length props) ··· 609 609 | Ok mb -> 610 610 Alcotest.(check string) "id" "mb1" (Jmap.Proto.Id.to_string (Mailbox.id mb)); 611 611 Alcotest.(check string) "name" "Inbox" (Mailbox.name mb); 612 - Alcotest.(check (option role_testable)) "role" (Some Mailbox.Inbox) (Mailbox.role mb); 612 + Alcotest.(check (option role_testable)) "role" (Some `Inbox) (Mailbox.role mb); 613 613 Alcotest.(check int64) "totalEmails" 150L (Mailbox.total_emails mb); 614 614 Alcotest.(check int64) "unreadEmails" 5L (Mailbox.unread_emails mb) 615 615 ··· 627 627 match decode Mailbox.jsont json with 628 628 | Error e -> Alcotest.failf "decode failed: %s" (Jsont.Error.to_string e) 629 629 | Ok mb -> 630 - Alcotest.(check (option role_testable)) "role" (Some Mailbox.Archive) (Mailbox.role mb); 630 + Alcotest.(check (option role_testable)) "role" (Some `Archive) (Mailbox.role mb); 631 631 Alcotest.(check int64) "totalEmails" 1000L (Mailbox.total_emails mb) 632 632 633 633 let tests = [ ··· 951 951 Alcotest.(check string) "id" "sub1" (Jmap.Proto.Id.to_string (Submission.id sub)); 952 952 (* Check undoStatus is Pending *) 953 953 match Submission.undo_status sub with 954 - | Submission.Pending -> () 954 + | `Pending -> () 955 955 | _ -> Alcotest.fail "expected undoStatus to be pending" 956 956 957 957 let tests = [