1use std::sync::Arc;
7
8use serde::Serialize;
9use tokio::sync::broadcast;
10
11use crate::db::audit::AuditEntry;
12use crate::db::emails::EmailSummary;
13use crate::db::settings::SettingsSection;
14
15pub trait EventSink: Send + Sync + 'static {
17 fn emit(&self, event: CoreEvent);
18}
19
20#[derive(Debug, Clone, Serialize)]
23#[cfg_attr(feature = "specta", derive(specta::Type))]
24#[serde(tag = "kind", rename_all = "camelCase", rename_all_fields = "camelCase")]
25pub enum CoreEvent {
26 NewEmail {
27 mailbox_id: String,
28 email: EmailSummary,
29 },
30 MailboxStateChanged {
31 mailbox_id: String,
32 change: MailboxStateChange,
33 },
34 ServerStatusChanged {
35 status: ServerStatus,
36 },
37 SettingsChanged {
38 section: SettingsSection,
39 },
40 AuditAppended {
41 entry: AuditEntry,
42 },
43}
44
45#[derive(Debug, Clone, Serialize)]
46#[cfg_attr(feature = "specta", derive(specta::Type))]
47#[serde(tag = "kind", rename_all = "camelCase", rename_all_fields = "camelCase")]
48pub enum MailboxStateChange {
49 Created,
50 Updated,
51 Deleted,
52 Started,
53 Stopped,
54 Expired,
55 Failed { error: String },
56}
57
58#[derive(Debug, Clone, Serialize)]
59#[cfg_attr(feature = "specta", derive(specta::Type))]
60#[serde(rename_all = "camelCase")]
61pub struct ServerStatus {
62 pub running_mailboxes: u32,
63 pub http_running: bool,
64 pub errors: Vec<String>,
65}
66
67#[derive(Debug, Clone, Copy, Serialize)]
68#[cfg_attr(feature = "specta", derive(specta::Type))]
69#[serde(rename_all = "lowercase")]
70pub enum BounceKind {
71 Hard,
72 Soft,
73}
74
75impl BounceKind {
76 pub fn as_str(self) -> &'static str {
77 match self {
78 BounceKind::Hard => "hard",
79 BounceKind::Soft => "soft",
80 }
81 }
82 pub fn from_str(s: &str) -> Self {
83 if s.eq_ignore_ascii_case("soft") {
84 BounceKind::Soft
85 } else {
86 BounceKind::Hard
87 }
88 }
89}
90
91impl<'de> serde::Deserialize<'de> for BounceKind {
95 fn deserialize<D>(de: D) -> std::result::Result<Self, D::Error>
96 where
97 D: serde::Deserializer<'de>,
98 {
99 let s = String::deserialize(de)?;
100 Ok(Self::from_str(&s))
101 }
102}
103
104#[derive(Debug, Default, Clone, Copy)]
109pub struct LogSink;
110
111impl EventSink for LogSink {
112 fn emit(&self, event: CoreEvent) {
113 tracing::info!(target: "postcrate::event", event = ?event);
114 }
115}
116
117#[derive(Debug, Clone)]
120pub struct ChannelSink {
121 tx: broadcast::Sender<CoreEvent>,
122}
123
124impl ChannelSink {
125 pub fn new(capacity: usize) -> Self {
126 Self {
127 tx: broadcast::channel(capacity).0,
128 }
129 }
130 pub fn subscribe(&self) -> broadcast::Receiver<CoreEvent> {
131 self.tx.subscribe()
132 }
133}
134
135impl EventSink for ChannelSink {
136 fn emit(&self, event: CoreEvent) {
137 let _ = self.tx.send(event);
138 }
139}
140
141#[derive(Clone)]
144pub struct ComposedSink {
145 sinks: Vec<Arc<dyn EventSink>>,
146}
147
148impl std::fmt::Debug for ComposedSink {
149 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
150 f.debug_struct("ComposedSink")
151 .field("len", &self.sinks.len())
152 .finish()
153 }
154}
155
156impl ComposedSink {
157 pub fn new(sinks: Vec<Arc<dyn EventSink>>) -> Self {
158 Self { sinks }
159 }
160}
161
162impl EventSink for ComposedSink {
163 fn emit(&self, event: CoreEvent) {
164 for s in &self.sinks {
165 s.emit(event.clone());
166 }
167 }
168}