Skip to main content

cratestack_core/
events.rs

1//! Model-event bus: typed `created/updated/deleted` envelopes that
2//! procedure handlers can subscribe to.
3
4mod 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}