1use async_trait::async_trait;
2use serde::{Deserialize, Serialize};
3use soma_ops::Timestamp;
4
5use crate::{FleetResult, HostId, TopologyRevision};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
9#[serde(rename_all = "snake_case")]
10pub enum FleetEventKind {
11 TopologyLoaded,
13 ConnectionOpened,
15 ConnectionInvalidated,
17 CommandStarted,
19 CommandCompleted,
21 TransferStarted,
23 TransferCompleted,
25 Cancelled,
27 TimedOut,
29}
30
31#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
33pub struct FleetEvent {
34 kind: FleetEventKind,
35 host: Option<HostId>,
36 revision: Option<TopologyRevision>,
37 occurred_at: Timestamp,
38 detail: Option<String>,
39}
40
41impl FleetEvent {
42 #[must_use]
44 pub const fn new(kind: FleetEventKind, occurred_at: Timestamp) -> Self {
45 Self {
46 kind,
47 host: None,
48 revision: None,
49 occurred_at,
50 detail: None,
51 }
52 }
53
54 #[must_use]
56 pub fn with_host(mut self, host: &crate::HostRecord) -> Self {
57 self.host = Some(host.id().clone());
58 self.revision = Some(host.revision().clone());
59 self
60 }
61
62 pub fn with_detail(mut self, detail: impl Into<String>) -> FleetResult<Self> {
64 let detail = detail.into();
65 let count = detail.chars().count();
66 if count == 0 || count > 1024 || detail.chars().any(char::is_control) {
67 return Err(crate::FleetError::EventSink(
68 "invalid fleet event detail".into(),
69 ));
70 }
71 self.detail = Some(detail);
72 Ok(self)
73 }
74
75 #[must_use]
77 pub const fn kind(&self) -> FleetEventKind {
78 self.kind
79 }
80
81 #[must_use]
83 pub fn host(&self) -> Option<&HostId> {
84 self.host.as_ref()
85 }
86
87 #[must_use]
89 pub fn revision(&self) -> Option<&TopologyRevision> {
90 self.revision.as_ref()
91 }
92
93 #[must_use]
95 pub const fn occurred_at(&self) -> Timestamp {
96 self.occurred_at
97 }
98
99 #[must_use]
101 pub fn detail(&self) -> Option<&str> {
102 self.detail.as_deref()
103 }
104}
105
106#[async_trait]
108pub trait FleetEventSink: Send + Sync {
109 async fn emit(&self, event: FleetEvent) -> FleetResult<()>;
111}
112
113#[derive(Debug, Clone, Copy, Default)]
115pub struct NoopFleetEventSink;
116
117#[async_trait]
118impl FleetEventSink for NoopFleetEventSink {
119 async fn emit(&self, _event: FleetEvent) -> FleetResult<()> {
120 Ok(())
121 }
122}
123
124#[cfg(test)]
125#[path = "event_tests.rs"]
126mod tests;