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}