daaki_imap/connection/typed_event.rs
1//! Typed event enum for asynchronous server notifications.
2//!
3//! `TypedEvent` models every piece of asynchronous server data that
4//! the driver task forwards to the caller. `Priority` classifies
5//! events for back-pressure decisions in
6//! [`DriverEventSink`](super::driver::event_sink::DriverEventSink).
7//!
8//! RFC 3501 Section 7 defines the untagged responses that can arrive
9//! asynchronously. RFC 5465 adds NOTIFY-specific events.
10
11use crate::types::fetch::FetchResponse;
12use crate::types::mailbox::MailboxInfo;
13use crate::types::response::{Capability, ResponseCode, UidRange, UntaggedResponse};
14
15/// Asynchronously delivered server data.
16///
17/// Each variant models a distinct class of server notification. The
18/// driver task converts raw [`UntaggedResponse`]s into `TypedEvent`s
19/// and publishes them via
20/// [`DriverEventSink`](super::driver::event_sink::DriverEventSink).
21#[derive(Debug, Clone)]
22pub enum TypedEvent {
23 /// RFC 3501 §7.1 \[ALERT\] — informational, never dropped.
24 Alert(String),
25 /// RFC 3501 §7.1.5 BYE — connection ending.
26 Bye {
27 code: Option<ResponseCode>,
28 text: String,
29 },
30 /// RFC 5465 §5.8 NOTIFICATIONOVERFLOW — NOTIFY registration cleared.
31 NotificationOverflow { code: Option<String>, text: String },
32 /// Internal overflow marker. Emitted when the event queue drops events.
33 QueueOverflow {
34 dropped_count: usize,
35 since: std::time::Instant,
36 },
37
38 /// RFC 3501 §7.3.1 EXISTS — mailbox size change.
39 Exists(u32),
40 /// RFC 3501 §7.3.2 RECENT.
41 Recent(u32),
42 /// RFC 3501 §7.4.1 EXPUNGE — message removed (sequence form).
43 Expunge(u32),
44 /// RFC 7162 VANISHED — message removed (UID form).
45 Vanished { earlier: bool, uids: Vec<UidRange> },
46 /// RFC 3501 §7.4.2 FETCH — flags or other update.
47 FetchUpdate(Box<FetchResponse>),
48 /// RFC 5465 §5 `MailboxName` event.
49 MailboxEvent(MailboxInfo),
50 /// RFC 5465 §5 `MailboxMetadataChange`.
51 MetadataChange {},
52 /// RFC 5465 §5 `ServerMetadataChange`.
53 ServerMetadataChange {},
54 /// RFC 3501 §7.2.1 CAPABILITY change.
55 CapabilityChange(Vec<Capability>),
56 /// Extension or future event.
57 Extension(Box<UntaggedResponse>),
58}
59
60/// Event priority classification for back-pressure handling.
61///
62/// The [`DriverEventSink`](super::driver::event_sink::DriverEventSink)
63/// uses this to decide which events to buffer vs. drop under queue
64/// pressure.
65#[derive(Debug, Clone, Copy, PartialEq, Eq)]
66pub(in crate::connection) enum Priority {
67 /// Never dropped — ALERTs, BYE, NOTIFICATIONOVERFLOW, `QueueOverflow`.
68 Critical,
69 /// Dropped first under queue pressure — capability changes can be
70 /// re-queried with a single CAPABILITY command.
71 Requeryable,
72 /// Dropped next — mailbox state changes that require a full resync
73 /// (EXISTS, EXPUNGE, FETCH updates, NOTIFY events, extensions).
74 Resyncable,
75}
76
77impl From<UntaggedResponse> for TypedEvent {
78 /// Convert a raw untagged response into a typed event.
79 ///
80 /// Used by the driver task when routing unsolicited responses to the
81 /// event sink. Handles the well-known response variants directly;
82 /// everything else is wrapped in [`TypedEvent::Extension`].
83 fn from(resp: UntaggedResponse) -> Self {
84 match resp {
85 UntaggedResponse::Status {
86 status: crate::types::response::UntaggedStatus::Bye,
87 code,
88 text,
89 } => Self::Bye { code, text },
90 // NOTE: Alert and NotificationOverflow are NOT handled here.
91 // They are extracted and emitted by the driver task directly
92 // from the SideEffectDigest, ensuring they reach
93 // the event queue regardless of classification — including
94 // when they appear in tagged responses or in untagged
95 // responses classified as solicited. The From conversion
96 // only fires for the event_sink.emit(resp.into()) path,
97 // which would miss tagged and solicited cases.
98 UntaggedResponse::Status {
99 code: Some(ResponseCode::Capability(caps)),
100 ..
101 }
102 | UntaggedResponse::Capability(caps) => Self::CapabilityChange(caps),
103 UntaggedResponse::Exists(n) => Self::Exists(n),
104 UntaggedResponse::Recent(n) => Self::Recent(n),
105 UntaggedResponse::Expunge(n) => Self::Expunge(n),
106 UntaggedResponse::Fetch(f) => Self::FetchUpdate(f),
107 UntaggedResponse::List(info) | UntaggedResponse::Lsub(info) => Self::MailboxEvent(info),
108 UntaggedResponse::Vanished { earlier, uids } => Self::Vanished { earlier, uids },
109 // Everything else — plain `* OK/NO/BAD` with no special response
110 // code, SEARCH, ESEARCH, ACL, QUOTA, METADATA, THREAD, SORT, etc.
111 // — is either stateless or typically consumed by a command consumer,
112 // not the event queue. Wrap as Extension for forward compatibility
113 //
114 other => Self::Extension(Box::new(other)),
115 }
116 }
117}
118
119impl TypedEvent {
120 /// Returns the back-pressure priority for this event.
121 pub(in crate::connection) fn priority(&self) -> Priority {
122 match self {
123 Self::Alert(_)
124 | Self::Bye { .. }
125 | Self::NotificationOverflow { .. }
126 | Self::QueueOverflow { .. } => Priority::Critical,
127 Self::CapabilityChange(_) => Priority::Requeryable,
128 Self::Exists(_)
129 | Self::Recent(_)
130 | Self::Expunge(_)
131 | Self::Vanished { .. }
132 | Self::FetchUpdate(_)
133 | Self::MailboxEvent(_)
134 | Self::MetadataChange { .. }
135 | Self::ServerMetadataChange { .. }
136 | Self::Extension(_) => Priority::Resyncable,
137 }
138 }
139}