cratestack_core/
events.rs1mod bus;
5
6use std::future::Future;
7use std::pin::Pin;
8
9use serde::{Deserialize, Serialize};
10
11use crate::error::CoolError;
12
13pub use bus::{CoolEventBus, SubscriptionGuard, SubscriptionHandle};
14
15pub type CoolEventFuture = Pin<Box<dyn Future<Output = Result<(), CoolError>> + Send + 'static>>;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
18pub enum ModelEventKind {
19 Created,
20 Updated,
21 Deleted,
22}
23
24impl ModelEventKind {
25 pub const fn as_str(self) -> &'static str {
26 match self {
27 Self::Created => "created",
28 Self::Updated => "updated",
29 Self::Deleted => "deleted",
30 }
31 }
32
33 pub fn parse(value: &str) -> Result<Self, CoolError> {
34 match value {
35 "created" => Ok(Self::Created),
36 "updated" => Ok(Self::Updated),
37 "deleted" => Ok(Self::Deleted),
38 other => Err(CoolError::Validation(format!(
39 "unsupported model event operation `{other}`"
40 ))),
41 }
42 }
43}
44
45#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
46pub struct CoolEventEnvelope {
47 pub event_id: uuid::Uuid,
48 pub model: String,
49 pub operation: ModelEventKind,
50 pub occurred_at: chrono::DateTime<chrono::Utc>,
51 pub data: serde_json::Value,
52}
53
54#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
55pub struct ModelEvent<T> {
56 pub event_id: uuid::Uuid,
57 pub model: String,
58 pub operation: ModelEventKind,
59 pub occurred_at: chrono::DateTime<chrono::Utc>,
60 pub data: T,
61}
62
63impl<T> TryFrom<CoolEventEnvelope> for ModelEvent<T>
64where
65 T: serde::de::DeserializeOwned,
66{
67 type Error = CoolError;
68
69 fn try_from(value: CoolEventEnvelope) -> Result<Self, Self::Error> {
70 Ok(Self {
71 event_id: value.event_id,
72 model: value.model,
73 operation: value.operation,
74 occurred_at: value.occurred_at,
75 data: serde_json::from_value(value.data).map_err(|error| {
76 CoolError::Codec(format!("failed to decode event payload: {error}"))
77 })?,
78 })
79 }
80}
81
82pub fn event_topic(model: &str, operation: ModelEventKind) -> String {
83 format!("{}.{}", model, operation.as_str())
84}
85
86pub fn parse_emit_attribute(raw: &str) -> Result<Vec<ModelEventKind>, String> {
87 let Some(inner) = raw
88 .strip_prefix("@@emit(")
89 .and_then(|value| value.strip_suffix(')'))
90 else {
91 return Err(format!("unsupported event attribute `{raw}`"));
92 };
93
94 let mut operations = Vec::new();
95 for part in inner
96 .split(',')
97 .map(str::trim)
98 .filter(|part| !part.is_empty())
99 {
100 let operation = match part {
101 "created" => ModelEventKind::Created,
102 "updated" => ModelEventKind::Updated,
103 "deleted" => ModelEventKind::Deleted,
104 other => {
105 return Err(format!(
106 "unsupported event operation `{other}` in `{raw}`; expected created, updated, or deleted"
107 ));
108 }
109 };
110 if !operations.contains(&operation) {
111 operations.push(operation);
112 }
113 }
114
115 if operations.is_empty() {
116 return Err(format!(
117 "event attribute `{raw}` must declare at least one operation"
118 ));
119 }
120
121 Ok(operations)
122}