angzarr_client/proto_ext/
pages.rs1use crate::convert::TYPE_URL_PREFIX;
6use crate::proto::page_header::SequenceType;
7use crate::proto::{
8 AngzarrDeferredSequence, CommandPage, EventPage, ExternalDeferredSequence, MergeStrategy,
9 PageHeader,
10};
11use prost::Name;
12
13pub trait PageHeaderExt {
15 fn explicit_sequence(&self) -> Option<u32>;
18
19 fn is_deferred(&self) -> bool;
21
22 fn external_deferred(&self) -> Option<&ExternalDeferredSequence>;
24
25 fn angzarr_deferred(&self) -> Option<&AngzarrDeferredSequence>;
27}
28
29impl PageHeaderExt for PageHeader {
30 fn explicit_sequence(&self) -> Option<u32> {
31 match &self.sequence_type {
32 Some(SequenceType::Sequence(seq)) => Some(*seq),
33 _ => None,
34 }
35 }
36
37 fn is_deferred(&self) -> bool {
38 matches!(
39 &self.sequence_type,
40 Some(SequenceType::ExternalDeferred(_)) | Some(SequenceType::AngzarrDeferred(_))
41 )
42 }
43
44 fn external_deferred(&self) -> Option<&ExternalDeferredSequence> {
45 match &self.sequence_type {
46 Some(SequenceType::ExternalDeferred(ext)) => Some(ext),
47 _ => None,
48 }
49 }
50
51 fn angzarr_deferred(&self) -> Option<&AngzarrDeferredSequence> {
52 match &self.sequence_type {
53 Some(SequenceType::AngzarrDeferred(ang)) => Some(ang),
54 _ => None,
55 }
56 }
57}
58
59pub trait EventPageExt {
63 fn sequence_num(&self) -> u32;
66
67 fn header(&self) -> Option<&PageHeader>;
69
70 fn is_deferred(&self) -> bool;
72
73 fn type_url(&self) -> Option<&str>;
75
76 fn payload(&self) -> Option<&[u8]>;
78
79 fn decode_typed<M: prost::Message + Default + Name>(&self) -> Option<M>;
84}
85
86impl EventPageExt for EventPage {
87 fn sequence_num(&self) -> u32 {
88 self.header
89 .as_ref()
90 .and_then(|h| h.explicit_sequence())
91 .unwrap_or(0)
92 }
93
94 fn header(&self) -> Option<&PageHeader> {
95 self.header.as_ref()
96 }
97
98 fn is_deferred(&self) -> bool {
99 self.header
100 .as_ref()
101 .map(|h| h.is_deferred())
102 .unwrap_or(false)
103 }
104
105 fn type_url(&self) -> Option<&str> {
106 match &self.payload {
107 Some(crate::proto::event_page::Payload::Event(e)) => Some(e.type_url.as_str()),
108 _ => None,
109 }
110 }
111
112 fn payload(&self) -> Option<&[u8]> {
113 match &self.payload {
114 Some(crate::proto::event_page::Payload::Event(e)) => Some(e.value.as_slice()),
115 _ => None,
116 }
117 }
118
119 fn decode_typed<M: prost::Message + Default + Name>(&self) -> Option<M> {
120 let event = match &self.payload {
121 Some(crate::proto::event_page::Payload::Event(e)) => e,
122 _ => return None,
123 };
124 let expected = format!("{}{}", TYPE_URL_PREFIX, M::full_name());
125 if event.type_url != expected {
126 return None;
127 }
128 M::decode(event.value.as_slice()).ok()
129 }
130}
131
132pub trait CommandPageExt {
136 fn sequence_num(&self) -> u32;
139
140 fn header(&self) -> Option<&PageHeader>;
142
143 fn is_deferred(&self) -> bool;
145
146 fn type_url(&self) -> Option<&str>;
148
149 fn payload(&self) -> Option<&[u8]>;
151
152 fn decode_typed<M: prost::Message + Default + Name>(&self) -> Option<M>;
157
158 fn merge_strategy(&self) -> MergeStrategy;
162}
163
164impl CommandPageExt for CommandPage {
165 fn sequence_num(&self) -> u32 {
166 self.header
167 .as_ref()
168 .and_then(|h| h.explicit_sequence())
169 .unwrap_or(0)
170 }
171
172 fn header(&self) -> Option<&PageHeader> {
173 self.header.as_ref()
174 }
175
176 fn is_deferred(&self) -> bool {
177 self.header
178 .as_ref()
179 .map(|h| h.is_deferred())
180 .unwrap_or(false)
181 }
182
183 fn type_url(&self) -> Option<&str> {
184 match &self.payload {
185 Some(crate::proto::command_page::Payload::Command(c)) => Some(c.type_url.as_str()),
186 _ => None,
187 }
188 }
189
190 fn payload(&self) -> Option<&[u8]> {
191 match &self.payload {
192 Some(crate::proto::command_page::Payload::Command(c)) => Some(c.value.as_slice()),
193 _ => None,
194 }
195 }
196
197 fn decode_typed<M: prost::Message + Default + Name>(&self) -> Option<M> {
198 let command = match &self.payload {
199 Some(crate::proto::command_page::Payload::Command(c)) => c,
200 _ => return None,
201 };
202 let expected = format!("{}{}", TYPE_URL_PREFIX, M::full_name());
203 if command.type_url != expected {
204 return None;
205 }
206 M::decode(command.value.as_slice()).ok()
207 }
208
209 fn merge_strategy(&self) -> MergeStrategy {
210 MergeStrategy::try_from(self.merge_strategy).unwrap_or(MergeStrategy::MergeCommutative)
211 }
212}
213
214pub trait AngzarrDeferredSequenceExt {
218 fn idempotency_key(&self) -> String;
224}
225
226impl AngzarrDeferredSequenceExt for AngzarrDeferredSequence {
227 fn idempotency_key(&self) -> String {
228 use super::cover::CoverExt;
229 let source = self.source.as_ref().expect("source required");
230 format!(
231 "{}:{}:{}:{}",
232 source.edition(),
233 source.domain,
234 source.root_id_hex().unwrap_or_default(),
235 self.source_seq
236 )
237 }
238}