Skip to main content

kmp_domain/projection/
events.rs

1use serde::{Deserialize, Serialize};
2use std::collections::BTreeMap;
3
4use crate::repositories::PortError;
5use crate::value_objects::{RelationExplanation, RelationSemanticClass};
6
7use super::ProjectionCheckpoint;
8
9#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
10pub struct ProjectionEnvelope {
11    pub event_id: String,
12    pub correlation_id: String,
13    pub causation_id: String,
14    pub occurred_at: String,
15    pub aggregate_id: String,
16    pub aggregate_type: String,
17    pub schema_version: String,
18}
19
20#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
21pub struct RelatedNodeReference {
22    pub node_id: String,
23    pub relation_type: String,
24    pub explanation: RelatedNodeExplanationData,
25}
26
27#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
28pub struct RelatedNodeExplanationData {
29    pub semantic_class: RelationSemanticClass,
30    #[serde(default)]
31    pub rationale: Option<String>,
32    #[serde(default)]
33    pub motivation: Option<String>,
34    #[serde(default)]
35    pub method: Option<String>,
36    #[serde(default)]
37    pub decision_id: Option<String>,
38    #[serde(default)]
39    pub caused_by_node_id: Option<String>,
40    #[serde(default)]
41    pub evidence: Option<String>,
42    #[serde(default)]
43    pub confidence: Option<String>,
44    #[serde(default)]
45    pub sequence: Option<u32>,
46}
47
48impl TryFrom<RelatedNodeExplanationData> for RelationExplanation {
49    type Error = crate::DomainError;
50
51    fn try_from(value: RelatedNodeExplanationData) -> Result<Self, Self::Error> {
52        Ok(RelationExplanation::new(value.semantic_class)
53            .with_optional_rationale(value.rationale)
54            .with_optional_motivation(value.motivation)
55            .with_optional_method(value.method)
56            .with_optional_decision_id(value.decision_id)
57            .with_optional_caused_by_node_id(value.caused_by_node_id)
58            .with_optional_evidence(value.evidence)
59            .with_optional_confidence(value.confidence)
60            .with_optional_sequence(value.sequence))
61    }
62}
63
64#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
65pub struct GraphNodeMaterializedData {
66    pub node_id: String,
67    pub node_kind: String,
68    pub title: String,
69    pub summary: String,
70    pub status: String,
71    pub labels: Vec<String>,
72    pub properties: BTreeMap<String, String>,
73    pub related_nodes: Vec<RelatedNodeReference>,
74    /// Provenance: who produced this node and when.
75    #[serde(default)]
76    pub source_kind: Option<String>,
77    #[serde(default)]
78    pub source_agent: Option<String>,
79    #[serde(default)]
80    pub observed_at: Option<String>,
81}
82
83#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
84pub struct GraphNodeMaterializedEvent {
85    #[serde(flatten)]
86    pub envelope: ProjectionEnvelope,
87    pub data: GraphNodeMaterializedData,
88}
89
90#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
91pub struct GraphRelationMaterializedData {
92    pub source_node_id: String,
93    pub target_node_id: String,
94    pub relation_type: String,
95    pub explanation: RelatedNodeExplanationData,
96}
97
98#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
99pub struct GraphRelationMaterializedEvent {
100    #[serde(flatten)]
101    pub envelope: ProjectionEnvelope,
102    pub data: GraphRelationMaterializedData,
103}
104
105#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
106pub struct NodeDetailMaterializedData {
107    pub node_id: String,
108    pub detail: String,
109    pub content_hash: String,
110    pub revision: u64,
111}
112
113#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
114pub struct NodeDetailMaterializedEvent {
115    #[serde(flatten)]
116    pub envelope: ProjectionEnvelope,
117    pub data: NodeDetailMaterializedData,
118}
119
120#[derive(Debug, Clone, PartialEq, Eq)]
121pub enum ProjectionEvent {
122    GraphNodeMaterialized(GraphNodeMaterializedEvent),
123    GraphRelationMaterialized(GraphRelationMaterializedEvent),
124    NodeDetailMaterialized(NodeDetailMaterializedEvent),
125}
126
127impl ProjectionEvent {
128    pub fn event_id(&self) -> &str {
129        match self {
130            Self::GraphNodeMaterialized(event) => &event.envelope.event_id,
131            Self::GraphRelationMaterialized(event) => &event.envelope.event_id,
132            Self::NodeDetailMaterialized(event) => &event.envelope.event_id,
133        }
134    }
135
136    pub fn envelope(&self) -> &ProjectionEnvelope {
137        match self {
138            Self::GraphNodeMaterialized(event) => &event.envelope,
139            Self::GraphRelationMaterialized(event) => &event.envelope,
140            Self::NodeDetailMaterialized(event) => &event.envelope,
141        }
142    }
143}
144
145#[derive(Debug, Clone, PartialEq, Eq)]
146pub struct ProjectionHandlingRequest {
147    pub consumer_name: String,
148    pub stream_name: String,
149    pub subject: String,
150    pub event: ProjectionEvent,
151}
152
153#[derive(Debug, Clone, PartialEq, Eq)]
154pub struct ProjectionHandlingResult {
155    pub event_id: String,
156    pub subject: String,
157    pub duplicate: bool,
158    pub applied_mutations: usize,
159    pub checkpoint: Option<ProjectionCheckpoint>,
160}
161
162pub trait ProjectionEventHandler {
163    fn handle_projection_event(
164        &self,
165        request: ProjectionHandlingRequest,
166    ) -> impl std::future::Future<Output = Result<ProjectionHandlingResult, PortError>> + Send;
167}