made_core/ports/
append_outcome.rs1use crate::entities::AuditRecord;
2use crate::ports::PositionedRecord;
3use crate::value_objects::{GlobalPosition, StreamVersion};
4
5#[derive(Debug, Clone, PartialEq, Eq)]
11pub enum AppendOutcome {
12 Appended {
17 version: StreamVersion,
18 records: Vec<AuditRecord>,
19 first_position: GlobalPosition,
20 },
21 Conflict {
24 expected: StreamVersion,
25 actual: StreamVersion,
26 },
27}
28
29impl AppendOutcome {
30 #[must_use]
31 pub fn is_conflict(&self) -> bool {
32 matches!(self, Self::Conflict { .. })
33 }
34
35 #[must_use]
37 pub fn records(&self) -> &[AuditRecord] {
38 match self {
39 Self::Appended { records, .. } => records,
40 Self::Conflict { .. } => &[],
41 }
42 }
43
44 #[must_use]
46 pub fn appended_version(&self) -> Option<StreamVersion> {
47 match self {
48 Self::Appended { version, .. } => Some(*version),
49 Self::Conflict { .. } => None,
50 }
51 }
52
53 #[must_use]
63 pub fn positioned(&self) -> Vec<PositionedRecord> {
64 let Self::Appended {
65 records,
66 first_position,
67 ..
68 } = self
69 else {
70 return Vec::new();
71 };
72 let mut position = *first_position;
73 records
74 .iter()
75 .map(|record| {
76 let at = position;
77 position = position.next();
78 PositionedRecord {
79 position: at,
80 record: record.clone(),
81 }
82 })
83 .collect()
84 }
85}
86
87#[cfg(test)]
88mod tests {
89 use time::OffsetDateTime;
90
91 use super::*;
92 use crate::entities::ceremony_events::CeremonyCompleted;
93 use crate::entities::{AuditFact, CeremonyEvent};
94 use crate::value_objects::{
95 AuditActor, AuditActorKind, CeremonyId, CeremonyName, CeremonyVersion, EventId, StateId,
96 };
97
98 fn record(event_id: &str) -> AuditRecord {
99 AuditRecord::first(AuditFact {
100 event_id: EventId::new(event_id).unwrap(),
101 event: CeremonyEvent::CeremonyCompleted(CeremonyCompleted {
102 final_state: StateId::new("DONE").unwrap(),
103 completed_at: OffsetDateTime::UNIX_EPOCH,
104 }),
105 ceremony_id: CeremonyId::new("c1").unwrap(),
106 definition_name: CeremonyName::new("positions").unwrap(),
107 definition_version: CeremonyVersion::v1(),
108 occurred_at: OffsetDateTime::UNIX_EPOCH,
109 actor: AuditActor::new("test", AuditActorKind::Engine, None).unwrap(),
110 correlation_id: None,
111 causation_id: None,
112 trace: None,
113 })
114 .unwrap()
115 }
116
117 #[test]
118 fn positions_of_an_append_run_on_from_the_first() {
119 let outcome = AppendOutcome::Appended {
120 version: StreamVersion::new(3),
121 records: vec![record("e1"), record("e2"), record("e3")],
122 first_position: GlobalPosition::new(7).unwrap(),
123 };
124
125 let positioned = outcome.positioned();
126
127 assert_eq!(
128 positioned
129 .iter()
130 .map(|entry| entry.position.value())
131 .collect::<Vec<_>>(),
132 [7, 8, 9]
133 );
134 assert_eq!(
135 positioned
136 .iter()
137 .map(|entry| entry.record.clone())
138 .collect::<Vec<_>>(),
139 outcome.records()
140 );
141 }
142
143 #[test]
144 fn a_conflict_positions_nothing() {
145 let outcome = AppendOutcome::Conflict {
146 expected: StreamVersion::EMPTY,
147 actual: StreamVersion::new(2),
148 };
149
150 assert!(outcome.positioned().is_empty());
151 assert!(outcome.records().is_empty());
152 assert!(outcome.appended_version().is_none());
153 assert!(outcome.is_conflict());
154 }
155}