1use serde::Serialize;
4use sha2::{Digest, Sha256};
5
6use crate::projection::{
7 ProjectionEventSelector, ProjectionExpression, ProjectionPartition, ProjectionProgramId,
8 ProjectionValueType,
9};
10
11use super::bind::{MutationEventBinding, MutationInputBinding};
12use super::canonical::canonical_json_bytes;
13use super::program::{MutationProgram, MutationProgramId};
14use super::MutationProgramError;
15
16#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
18#[serde(rename_all = "snake_case")]
19pub enum MutationHandlerPlacement {
20 EventualLocal,
22 EventualRemote,
24 Direct,
26}
27
28#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
30pub struct MutationHandlerRegistration {
31 owner: String,
33 epoch: String,
35 placement: MutationHandlerPlacement,
37 #[serde(skip)]
39 partition: ProjectionPartition,
40 #[serde(skip)]
42 binding: MutationEventBinding,
43 name: String,
45 version: u64,
47}
48
49impl MutationHandlerRegistration {
50 pub fn try_new(
56 name: impl Into<String>,
57 version: u64,
58 owner: impl Into<String>,
59 epoch: impl Into<String>,
60 placement: MutationHandlerPlacement,
61 partition: ProjectionPartition,
62 binding: MutationEventBinding,
63 ) -> Result<Self, MutationProgramError> {
64 let name = super::expression::non_empty(name.into(), "handler name")?;
65 let owner = super::expression::non_empty(owner.into(), "handler owner")?;
66 let epoch = super::expression::non_empty(epoch.into(), "handler epoch")?;
67 if version == 0 {
68 return Err(MutationProgramError::ZeroVersion("handler version"));
69 }
70 Ok(Self {
71 owner,
72 epoch,
73 placement,
74 partition,
75 binding,
76 name,
77 version,
78 })
79 }
80
81 pub fn name(&self) -> &str {
83 &self.name
84 }
85
86 pub fn version(&self) -> u64 {
88 self.version
89 }
90
91 pub fn owner(&self) -> &str {
93 &self.owner
94 }
95
96 pub fn epoch(&self) -> &str {
98 &self.epoch
99 }
100
101 pub fn placement(&self) -> MutationHandlerPlacement {
103 self.placement
104 }
105
106 pub fn partition(&self) -> &ProjectionPartition {
108 &self.partition
109 }
110
111 pub fn binding(&self) -> &MutationEventBinding {
113 &self.binding
114 }
115
116 pub fn uniqueness_key(&self) -> MutationHandlerUniquenessKey {
118 MutationHandlerUniquenessKey {
119 owner: self.owner.clone(),
120 event_name: self.binding.selector().event_name().to_owned(),
121 event_version: self.binding.selector().event_version(),
122 body_fingerprint: self.binding.selector().body_fingerprint().to_owned(),
123 epoch: self.epoch.clone(),
124 }
125 }
126
127 pub fn target_models(&self) -> Vec<String> {
129 let mut models = self
130 .binding
131 .program()
132 .operations()
133 .iter()
134 .map(|operation| operation.target().model().to_owned())
135 .collect::<Vec<_>>();
136 models.sort();
137 models.dedup();
138 models
139 }
140
141 pub fn digest(&self) -> Result<[u8; 32], MutationProgramError> {
147 #[derive(Serialize)]
148 struct DigestBody<'a> {
149 name: &'a str,
150 version: u64,
151 owner: &'a str,
152 epoch: &'a str,
153 placement: MutationHandlerPlacement,
154 mutation_program_id: String,
155 selector_event: &'a str,
156 selector_version: u64,
157 selector_fingerprint: &'a str,
158 }
159 let program_id = self.binding.program().id()?;
160 let body = DigestBody {
161 name: &self.name,
162 version: self.version,
163 owner: &self.owner,
164 epoch: &self.epoch,
165 placement: self.placement,
166 mutation_program_id: program_id.to_string(),
167 selector_event: self.binding.selector().event_name(),
168 selector_version: self.binding.selector().event_version(),
169 selector_fingerprint: self.binding.selector().body_fingerprint(),
170 };
171 let bytes = canonical_json_bytes(&body)?;
172 let mut digest = Sha256::new();
173 digest.update(b"distributed.mutation-handler/v1\0");
174 digest.update((bytes.len() as u64).to_be_bytes());
175 digest.update(&bytes);
176 Ok(digest.finalize().into())
177 }
178
179 pub fn to_projection_program(
185 &self,
186 ) -> Result<crate::projection::ProjectionProgram, MutationProgramError> {
187 self.binding.to_projection_program(
188 self.name.clone(),
189 self.version,
190 self.partition.clone(),
191 format!("{}-arm", self.name),
192 )
193 }
194}
195
196#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
198pub struct MutationHandlerUniquenessKey {
199 pub owner: String,
201 pub event_name: String,
203 pub event_version: u64,
205 pub body_fingerprint: String,
207 pub epoch: String,
209}
210
211#[derive(Clone, Debug, Default)]
213pub struct MutationHandlerCatalog {
214 registrations: Vec<MutationHandlerRegistration>,
215}
216
217impl MutationHandlerCatalog {
218 pub fn new() -> Self {
220 Self {
221 registrations: Vec::new(),
222 }
223 }
224
225 pub fn register(
232 &mut self,
233 registration: MutationHandlerRegistration,
234 ) -> Result<(), MutationProgramError> {
235 let key = registration.uniqueness_key();
236 if self
237 .registrations
238 .iter()
239 .any(|existing| existing.uniqueness_key() == key)
240 {
241 return Err(MutationProgramError::InvalidOperation {
242 operation: registration.name().to_owned(),
243 reason: format!(
244 "duplicate binding for owner `{}` event `{}` v{} epoch `{}`",
245 key.owner, key.event_name, key.event_version, key.epoch
246 ),
247 });
248 }
249 for model in registration.target_models() {
251 for existing in &self.registrations {
252 if existing.epoch() != registration.epoch() {
253 continue;
254 }
255 if !existing.target_models().iter().any(|item| item == &model) {
256 continue;
257 }
258 let direct_overlap = matches!(
259 (existing.placement(), registration.placement()),
260 (
261 MutationHandlerPlacement::Direct,
262 MutationHandlerPlacement::Direct
263 ) | (
264 MutationHandlerPlacement::Direct,
265 MutationHandlerPlacement::EventualLocal
266 | MutationHandlerPlacement::EventualRemote
267 ) | (
268 MutationHandlerPlacement::EventualLocal
269 | MutationHandlerPlacement::EventualRemote,
270 MutationHandlerPlacement::Direct
271 )
272 );
273 if direct_overlap
276 || (existing.owner() != registration.owner()
277 && existing.placement() == registration.placement())
278 {
279 return Err(MutationProgramError::InvalidOperation {
280 operation: registration.name().to_owned(),
281 reason: format!(
282 "dual writer for model `{model}` epoch `{}` between `{}` and `{}`",
283 registration.epoch(),
284 existing.name(),
285 registration.name()
286 ),
287 });
288 }
289 }
290 }
291 self.registrations.push(registration);
292 Ok(())
293 }
294
295 pub fn registrations(&self) -> &[MutationHandlerRegistration] {
297 &self.registrations
298 }
299
300 pub fn for_selector(
302 &self,
303 selector: &ProjectionEventSelector,
304 ) -> Vec<&MutationHandlerRegistration> {
305 self.registrations
306 .iter()
307 .filter(|registration| registration.binding().selector() == selector)
308 .collect()
309 }
310}
311
312#[derive(Clone, Debug)]
314pub struct CustomMutationHandler {
315 pub name: String,
317 pub owner: String,
319 pub epoch: String,
321 pub placement: MutationHandlerPlacement,
323 pub selector: ProjectionEventSelector,
325 pub allowed_programs: Vec<MutationProgramId>,
327}
328
329impl CustomMutationHandler {
330 pub fn new(
332 name: impl Into<String>,
333 owner: impl Into<String>,
334 epoch: impl Into<String>,
335 placement: MutationHandlerPlacement,
336 selector: ProjectionEventSelector,
337 allowed_programs: Vec<MutationProgramId>,
338 ) -> Self {
339 Self {
340 name: name.into(),
341 owner: owner.into(),
342 epoch: epoch.into(),
343 placement,
344 selector,
345 allowed_programs,
346 }
347 }
348
349 pub fn is_portable(&self) -> bool {
351 false
352 }
353}
354
355pub fn portable_binding(
357 selector: ProjectionEventSelector,
358 program: MutationProgram,
359 field_pairs: &[(&[&str], &[&str], ProjectionValueType)],
360) -> Result<MutationEventBinding, MutationProgramError> {
361 let inputs = field_pairs
362 .iter()
363 .map(|(input, body, value_type)| {
364 super::bind::body_field_binding(
365 input.iter().copied(),
366 body.iter().copied(),
367 value_type.clone(),
368 )
369 })
370 .collect::<Result<Vec<_>, _>>()?;
371 MutationEventBinding::try_new(selector, inputs, program)
372}
373
374pub fn bindings_from_expressions(
376 pairs: Vec<(Vec<String>, ProjectionExpression)>,
377) -> Result<Vec<MutationInputBinding>, MutationProgramError> {
378 pairs
379 .into_iter()
380 .map(|(path, expression)| MutationInputBinding::try_new(path, expression))
381 .collect()
382}
383
384#[allow(dead_code)]
386fn _projection_program_id_type(_: ProjectionProgramId) {}