evidentsource_core/domain/events.rs
1//! Event types for the event log.
2//!
3//! This module defines the core event types used in the database.
4//!
5//! EvidentSource uses CloudEvents as its event format, but with specialized semantics:
6//! - `ProspectiveEvent`: Events before storage, where `stream` is the simple stream name
7//! - `Event`: Events after storage, where `source` is a full URI containing the stream path
8
9use chrono::{DateTime, Utc};
10
11/// Event data payload.
12#[derive(Debug, Clone, PartialEq)]
13pub enum EventData {
14 /// Binary data payload.
15 Binary(Vec<u8>),
16 /// String data payload (typically JSON).
17 String(String),
18}
19
20impl EventData {
21 /// Get the data as bytes.
22 pub fn as_bytes(&self) -> &[u8] {
23 match self {
24 EventData::Binary(bytes) => bytes,
25 EventData::String(s) => s.as_bytes(),
26 }
27 }
28}
29
30/// Extension attribute value types supported by CloudEvents.
31#[derive(Debug, Clone, PartialEq)]
32pub enum ExtensionValue {
33 /// Boolean value.
34 Boolean(bool),
35 /// Integer value (CloudEvents spec: -2,147,483,648 to +2,147,483,647).
36 Integer(i64),
37 /// String value.
38 String(String),
39}
40
41/// A prospective event before storage.
42///
43/// In EvidentSource, prospective events use the stream name directly as the source.
44/// When stored, the server transforms this into a full URI in the `Event` type.
45#[derive(Debug, Clone)]
46pub struct ProspectiveEvent {
47 /// Unique identifier for this event (typically UUID).
48 pub id: String,
49 /// The stream this event belongs to. Maps to CloudEvents `source`.
50 pub stream: String,
51 /// The event type (e.g., "com.example.account.opened").
52 pub event_type: String,
53 /// Optional subject identifying the entity this event is about.
54 pub subject: Option<String>,
55 /// Optional event data payload.
56 pub data: Option<EventData>,
57 /// Optional timestamp when the event occurred.
58 pub time: Option<DateTime<Utc>>,
59 /// Optional content type of the data (e.g., "application/json").
60 pub datacontenttype: Option<String>,
61 /// Optional schema URI for the data.
62 pub dataschema: Option<String>,
63 /// Extension attributes.
64 pub extensions: Vec<(String, ExtensionValue)>,
65}
66
67impl ProspectiveEvent {
68 /// Get the stream name.
69 pub fn stream(&self) -> &str {
70 &self.stream
71 }
72
73 /// Get the event type.
74 pub fn event_type(&self) -> &str {
75 &self.event_type
76 }
77
78 /// Get the subject, if present.
79 pub fn subject(&self) -> Option<&str> {
80 self.subject.as_deref()
81 }
82
83 /// Get the data payload, if present.
84 pub fn data(&self) -> Option<&EventData> {
85 self.data.as_ref()
86 }
87
88 /// Get the timestamp, if present.
89 pub fn time(&self) -> Option<DateTime<Utc>> {
90 self.time
91 }
92
93 /// Get an extension value by key.
94 pub fn extension(&self, key: &str) -> Option<&ExtensionValue> {
95 self.extensions
96 .iter()
97 .find(|(k, _)| k == key)
98 .map(|(_, v)| v)
99 }
100}
101
102/// A stored event with a full source URI.
103///
104/// After storage, the EvidentSource server transforms the stream name into a full URI
105/// like `https://host/db/database-name/streams/stream-name`.
106#[derive(Debug, Clone)]
107pub struct Event {
108 /// Unique identifier for this event.
109 pub id: String,
110 /// Full source URI (e.g., "https://host/db/name/streams/stream").
111 pub source: String,
112 /// The event type (e.g., "com.example.account.opened").
113 pub event_type: String,
114 /// Optional subject identifying the entity this event is about.
115 pub subject: Option<String>,
116 /// Optional event data payload.
117 pub data: Option<EventData>,
118 /// Optional timestamp when the event occurred.
119 pub time: Option<DateTime<Utc>>,
120 /// Optional content type of the data.
121 pub datacontenttype: Option<String>,
122 /// Optional schema URI for the data.
123 pub dataschema: Option<String>,
124 /// Extension attributes.
125 pub extensions: Vec<(String, ExtensionValue)>,
126}
127
128impl Event {
129 /// Get the full source URI.
130 pub fn source(&self) -> &str {
131 &self.source
132 }
133
134 /// Extract the stream name from the source URI.
135 ///
136 /// Parses URIs like `https://host/db/name/streams/my-stream` to extract `my-stream`.
137 /// Returns `None` if the URI doesn't match the expected pattern.
138 pub fn stream(&self) -> Option<&str> {
139 // Look for /streams/ in the path and return everything after it
140 self.source
141 .find("/streams/")
142 .map(|idx| &self.source[idx + 9..]) // 9 = len("/streams/")
143 }
144
145 /// Get the event type.
146 pub fn event_type(&self) -> &str {
147 &self.event_type
148 }
149
150 /// Get the subject, if present.
151 pub fn subject(&self) -> Option<&str> {
152 self.subject.as_deref()
153 }
154
155 /// Get the data payload, if present.
156 pub fn data(&self) -> Option<&EventData> {
157 self.data.as_ref()
158 }
159
160 /// Get the timestamp, if present.
161 pub fn time(&self) -> Option<DateTime<Utc>> {
162 self.time
163 }
164
165 /// Get an extension value by key.
166 pub fn extension(&self, key: &str) -> Option<&ExtensionValue> {
167 self.extensions
168 .iter()
169 .find(|(k, _)| k == key)
170 .map(|(_, v)| v)
171 }
172}
173
174/// A transaction of events that were committed together.
175#[derive(Debug, Clone)]
176pub struct Transaction {
177 /// The events in this transaction.
178 pub events: Vec<Event>,
179 /// Summary information about the transaction.
180 pub summary: TransactionSummary,
181}
182
183/// Summary information about a committed transaction.
184#[derive(Debug, Clone)]
185pub struct TransactionSummary {
186 /// Optional transaction identifier.
187 pub transaction_id: Option<String>,
188 /// The revision at which this transaction was committed.
189 pub revision: u64,
190 /// Number of events in the transaction.
191 pub event_count: usize,
192}