Skip to main content

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}