evidentsource_core/domain/state_view.rs
1//! State view types.
2//!
3//! State views are materialized views computed from events. They provide
4//! queryable state derived from the event log.
5//!
6//! # StateViewCodec
7//!
8//! The [`StateViewCodec`] trait allows state view types to define their own
9//! serialization format. The SDK calls `from_bytes()` once at the start to
10//! deserialize the previous state, runs `evolve()` for each event, then calls
11//! `to_bytes()` once at the end.
12
13use chrono::{DateTime, Utc};
14
15use super::identifiers::{DatabaseName, StateViewName};
16use super::revision::Revision;
17use super::selectors::EventSelector;
18
19/// State view version number.
20pub type StateViewVersion = u64;
21
22/// A materialized state view.
23#[derive(Debug, Clone)]
24pub struct StateView {
25 /// The database this state view belongs to.
26 pub database: DatabaseName,
27 /// The name of this state view.
28 pub name: StateViewName,
29 /// The version of this state view definition.
30 pub version: StateViewVersion,
31 /// The event selector that determines which events affect this view.
32 pub event_selector: EventSelector,
33 /// The revision at which this view was last modified, if any.
34 pub last_modified_revision: Option<Revision>,
35 /// The timestamp when this view was last modified, if any.
36 pub last_modified_timestamp: Option<DateTime<Utc>>,
37 /// The content type of the serialized state (e.g., "application/json").
38 pub content_type: String,
39 /// Optional URL to the schema for the content.
40 pub content_schema_url: Option<String>,
41 /// The serialized state content, if any.
42 pub content: Option<Vec<u8>>,
43}
44
45impl StateView {
46 /// Check if this state view has content.
47 pub fn has_content(&self) -> bool {
48 self.content.is_some()
49 }
50
51 /// Get the content as bytes, if present.
52 pub fn content_bytes(&self) -> Option<&[u8]> {
53 self.content.as_deref()
54 }
55}
56
57/// Trait for state view types that can serialize/deserialize to bytes.
58///
59/// The SDK calls `from_bytes()` once at the start to deserialize the previous state,
60/// runs `evolve()` for each event, then calls `to_bytes()` once at the end.
61///
62/// # Example
63///
64/// ```ignore
65/// use evidentsource_core::domain::StateViewCodec;
66///
67/// impl StateViewCodec for AccountSummary {
68/// fn from_bytes(
69/// bytes: Option<&[u8]>,
70/// content_type: Option<&str>,
71/// _content_schema: Option<&str>,
72/// ) -> Self {
73/// match bytes {
74/// Some(b) if !b.is_empty() => {
75/// serde_json::from_slice(b).unwrap_or_default()
76/// }
77/// _ => Self::default()
78/// }
79/// }
80///
81/// fn to_bytes(
82/// &self,
83/// _content_type: Option<&str>,
84/// _content_schema: Option<&str>,
85/// ) -> Option<Vec<u8>> {
86/// serde_json::to_vec(self).ok()
87/// }
88/// }
89/// ```
90pub trait StateViewCodec: Sized {
91 /// Deserialize state from bytes.
92 ///
93 /// Called once per state view invocation with the previous state.
94 /// Returns `Default` state if bytes is `None` or empty.
95 ///
96 /// # Arguments
97 ///
98 /// * `bytes` - The serialized state bytes, if any
99 /// * `content_type` - The content type (e.g., "application/json")
100 /// * `content_schema` - Optional schema URL for the content
101 fn from_bytes(
102 bytes: Option<&[u8]>,
103 content_type: Option<&str>,
104 content_schema: Option<&str>,
105 ) -> Self;
106
107 /// Serialize state to bytes.
108 ///
109 /// Called once after all events have been processed.
110 /// Returns `None` if state should not be persisted.
111 ///
112 /// # Arguments
113 ///
114 /// * `content_type` - The content type (e.g., "application/json")
115 /// * `content_schema` - Optional schema URL for the content
116 fn to_bytes(&self, content_type: Option<&str>, content_schema: Option<&str>)
117 -> Option<Vec<u8>>;
118}