1use std::collections::BTreeMap;
2
3use serde::Serialize;
4use serde_json::Value;
5
6use crate::projection_protocol::{MAX_PROJECTION_PARTITION_BYTES, MAX_PROJECTION_RECORD_KEY_BYTES};
7use crate::DomainEventOccurrence;
8
9use super::canonical::{bounded_key_bytes, bounded_partition_bytes, canonical_json_bytes};
10use super::{
11 ProjectionArm, ProjectionInvalidation, ProjectionKeyField, ProjectionMutationKind,
12 ProjectionOperation, ProjectionPartition, ProjectionProgram, ProjectionProgramError,
13 ProjectionProgramId, ProjectionRelationship, ProjectionRelationshipEffectKind,
14 ProjectionTarget, ProjectionValue, ResolvedProjectionValue,
15};
16
17#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
19pub struct ResolvedProjectionPartition {
20 partition: ResolvedProjectionPartitionValue,
21 canonical_bytes: Vec<u8>,
22}
23
24#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
25#[serde(tag = "kind", content = "value", rename_all = "snake_case")]
26enum ResolvedProjectionPartitionValue {
27 Unit,
28 Value(ProjectionValue),
29}
30
31#[derive(Clone, Copy, Debug, PartialEq, Eq)]
33pub enum ResolvedProjectionPartitionRef<'a> {
34 Unit,
36 Value(&'a ProjectionValue),
38}
39
40impl ResolvedProjectionPartition {
41 pub fn as_ref(&self) -> ResolvedProjectionPartitionRef<'_> {
43 match &self.partition {
44 ResolvedProjectionPartitionValue::Unit => ResolvedProjectionPartitionRef::Unit,
45 ResolvedProjectionPartitionValue::Value(value) => {
46 ResolvedProjectionPartitionRef::Value(value)
47 }
48 }
49 }
50
51 pub fn canonical_bytes(&self) -> &[u8] {
57 &self.canonical_bytes
58 }
59}
60
61#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
63pub struct ResolvedProjectionKeyField {
64 ordinal: u32,
65 name: String,
66 value: ProjectionValue,
67}
68
69impl ResolvedProjectionKeyField {
70 pub fn ordinal(&self) -> u32 {
72 self.ordinal
73 }
74
75 pub fn name(&self) -> &str {
77 &self.name
78 }
79
80 pub fn value(&self) -> &ProjectionValue {
82 &self.value
83 }
84}
85
86#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
88pub struct ResolvedProjectionKey {
89 fields: Vec<ResolvedProjectionKeyField>,
90 canonical_bytes: Vec<u8>,
91}
92
93impl ResolvedProjectionKey {
94 pub fn fields(&self) -> &[ResolvedProjectionKeyField] {
96 &self.fields
97 }
98
99 pub fn canonical_bytes(&self) -> &[u8] {
103 &self.canonical_bytes
104 }
105}
106
107#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
109pub struct ResolvedProjectionMutationScope {
110 partition: ResolvedProjectionPartition,
111 model: String,
112 storage: String,
113 key: ResolvedProjectionKey,
114}
115
116impl ResolvedProjectionMutationScope {
117 pub fn partition(&self) -> &ResolvedProjectionPartition {
119 &self.partition
120 }
121
122 pub fn model(&self) -> &str {
124 &self.model
125 }
126
127 pub fn storage(&self) -> &str {
129 &self.storage
130 }
131
132 pub fn key(&self) -> &ResolvedProjectionKey {
134 &self.key
135 }
136}
137
138#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
140pub struct ResolvedProjectionField {
141 operation_staging_ordinal: u32,
142 field_ordinal: u32,
143 name: String,
144 value: ResolvedProjectionValue,
145}
146
147impl ResolvedProjectionField {
148 pub fn operation_staging_ordinal(&self) -> u32 {
150 self.operation_staging_ordinal
151 }
152
153 pub fn field_ordinal(&self) -> u32 {
155 self.field_ordinal
156 }
157
158 pub fn name(&self) -> &str {
160 &self.name
161 }
162
163 pub fn value(&self) -> &ResolvedProjectionValue {
165 &self.value
166 }
167}
168
169#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
171pub struct ProjectionMutationProvenance {
172 occurrence: ProjectionOccurrenceProvenance,
173 program_id: ProjectionProgramId,
174 arm_id: String,
175 operation_ids: Vec<String>,
176 staging_ordinals: Vec<u32>,
177 relationship_effects: Vec<ResolvedProjectionRelationshipEffect>,
178 invalidations: Vec<ProjectionInvalidation>,
179}
180
181#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
183pub struct ResolvedProjectionRelationshipEffect {
184 ordinal: u32,
185 kind: ProjectionRelationshipEffectKind,
186 relationship: ProjectionRelationship,
187 source_key: Option<ResolvedProjectionKey>,
188 target_key: Option<ResolvedProjectionKey>,
189}
190
191impl ResolvedProjectionRelationshipEffect {
192 pub fn ordinal(&self) -> u32 {
194 self.ordinal
195 }
196
197 pub fn kind(&self) -> ProjectionRelationshipEffectKind {
199 self.kind
200 }
201
202 pub fn relationship(&self) -> &ProjectionRelationship {
204 &self.relationship
205 }
206
207 pub fn source_key(&self) -> Option<&ResolvedProjectionKey> {
209 self.source_key.as_ref()
210 }
211
212 pub fn target_key(&self) -> Option<&ResolvedProjectionKey> {
214 self.target_key.as_ref()
215 }
216}
217
218#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
223pub struct ProjectionOccurrenceProvenance {
224 occurrence_version: u16,
225 occurrence_id: String,
226 event_name: String,
227 event_version: u64,
228 body_fingerprint: String,
229 aggregate_type: String,
230 aggregate_id: String,
231 aggregate_sequence: u64,
232 publication_ordinal: u32,
233}
234
235impl ProjectionOccurrenceProvenance {
236 pub fn occurrence_version(&self) -> u16 {
238 self.occurrence_version
239 }
240
241 pub fn occurrence_id(&self) -> &str {
243 &self.occurrence_id
244 }
245
246 pub fn event_name(&self) -> &str {
248 &self.event_name
249 }
250
251 pub fn event_version(&self) -> u64 {
253 self.event_version
254 }
255
256 pub fn body_fingerprint(&self) -> &str {
258 &self.body_fingerprint
259 }
260
261 pub fn aggregate_type(&self) -> &str {
263 &self.aggregate_type
264 }
265
266 pub fn aggregate_id(&self) -> &str {
268 &self.aggregate_id
269 }
270
271 pub fn aggregate_sequence(&self) -> u64 {
273 self.aggregate_sequence
274 }
275
276 pub fn publication_ordinal(&self) -> u32 {
278 self.publication_ordinal
279 }
280}
281
282impl ProjectionMutationProvenance {
283 pub fn occurrence(&self) -> &ProjectionOccurrenceProvenance {
285 &self.occurrence
286 }
287
288 pub fn program_id(&self) -> ProjectionProgramId {
290 self.program_id
291 }
292
293 pub fn arm_id(&self) -> &str {
295 &self.arm_id
296 }
297
298 pub fn operation_ids(&self) -> &[String] {
300 &self.operation_ids
301 }
302
303 pub fn staging_ordinals(&self) -> &[u32] {
305 &self.staging_ordinals
306 }
307
308 pub fn relationship_effects(&self) -> &[ResolvedProjectionRelationshipEffect] {
310 &self.relationship_effects
311 }
312
313 pub fn invalidations(&self) -> &[ProjectionInvalidation] {
315 &self.invalidations
316 }
317}
318
319#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
321pub struct ResolvedProjectionMutation {
322 kind: ProjectionMutationKind,
323 target: ProjectionTarget,
324 scope: ResolvedProjectionMutationScope,
325 fields: Vec<ResolvedProjectionField>,
326 provenance: ProjectionMutationProvenance,
327}
328
329impl ResolvedProjectionMutation {
330 pub fn kind(&self) -> ProjectionMutationKind {
332 self.kind
333 }
334
335 pub fn target(&self) -> &ProjectionTarget {
337 &self.target
338 }
339
340 pub fn key(&self) -> &ResolvedProjectionKey {
342 &self.scope.key
343 }
344
345 pub fn scope(&self) -> &ResolvedProjectionMutationScope {
347 &self.scope
348 }
349
350 pub fn fields(&self) -> &[ResolvedProjectionField] {
352 &self.fields
353 }
354
355 pub fn provenance(&self) -> &ProjectionMutationProvenance {
357 &self.provenance
358 }
359}
360
361#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
366pub struct ResolvedProjectionPlan {
367 program_id: ProjectionProgramId,
368 occurrence: DomainEventOccurrence,
369 arm_id: String,
370 partition: ResolvedProjectionPartition,
371 mutations: Vec<ResolvedProjectionMutation>,
372}
373
374impl ResolvedProjectionPlan {
375 pub fn resolve(
385 program: &ProjectionProgram,
386 occurrence: &DomainEventOccurrence,
387 ) -> Result<Self, ProjectionProgramError> {
388 let matches = program
389 .arms()
390 .iter()
391 .filter(|arm| arm.selector().matches(occurrence))
392 .collect::<Vec<_>>();
393 let arm = match matches.as_slice() {
394 [] => return Err(ProjectionProgramError::NoMatchingArm),
395 [arm] => *arm,
396 _ => return Err(ProjectionProgramError::MultipleMatchingArms),
397 };
398 let body: Value = occurrence
399 .decode_body()
400 .map_err(|error| ProjectionProgramError::CanonicalJson(error.to_string()))?;
401 let program_id = program.id()?;
402 let partition = resolve_partition(program.partition(), occurrence, &body)?;
403 let mut mutations = arm
404 .operations()
405 .iter()
406 .map(|operation| {
407 resolve_operation(program_id, arm, operation, occurrence, &body, &partition)
408 })
409 .collect::<Result<Vec<_>, _>>()?;
410 mutations = coalesce_mutations(mutations)?;
411 mutations.sort_by(|left, right| {
412 left.kind
413 .cmp(&right.kind)
414 .then_with(|| left.target.model().cmp(right.target.model()))
415 .then_with(|| left.target.storage().cmp(right.target.storage()))
416 .then_with(|| {
417 left.scope
418 .key
419 .canonical_bytes
420 .cmp(&right.scope.key.canonical_bytes)
421 })
422 .then_with(|| {
423 left.provenance.staging_ordinals[0].cmp(&right.provenance.staging_ordinals[0])
424 })
425 });
426 Ok(Self {
427 program_id,
428 occurrence: occurrence.clone(),
429 arm_id: arm.arm_id().to_owned(),
430 partition,
431 mutations,
432 })
433 }
434
435 pub fn program_id(&self) -> ProjectionProgramId {
437 self.program_id
438 }
439
440 pub fn occurrence(&self) -> &DomainEventOccurrence {
442 &self.occurrence
443 }
444
445 pub fn arm_id(&self) -> &str {
447 &self.arm_id
448 }
449
450 pub fn partition(&self) -> &ResolvedProjectionPartition {
452 &self.partition
453 }
454
455 pub fn mutations(&self) -> &[ResolvedProjectionMutation] {
457 &self.mutations
458 }
459
460 pub fn canonical_bytes(&self) -> Result<Vec<u8>, ProjectionProgramError> {
466 canonical_json_bytes(self)
467 }
468}
469
470fn resolve_partition(
471 partition: &ProjectionPartition,
472 occurrence: &DomainEventOccurrence,
473 body: &Value,
474) -> Result<ResolvedProjectionPartition, ProjectionProgramError> {
475 let partition = match partition {
476 ProjectionPartition::Unit => ResolvedProjectionPartitionValue::Unit,
477 ProjectionPartition::Expression(expression) => {
478 let value = match expression.resolve(occurrence, body)? {
479 ResolvedProjectionValue::Value(value) => value,
480 ResolvedProjectionValue::Absent => {
481 return Err(ProjectionProgramError::RequiredValueAbsent {
482 path: "projection partition".to_owned(),
483 });
484 }
485 ResolvedProjectionValue::Unset => {
486 return Err(ProjectionProgramError::UnsetNotAllowed {
487 field: "projection partition".to_owned(),
488 });
489 }
490 };
491 ResolvedProjectionPartitionValue::Value(value)
492 }
493 };
494 let canonical_bytes = bounded_partition_bytes(&partition, MAX_PROJECTION_PARTITION_BYTES)?;
495 Ok(ResolvedProjectionPartition {
496 partition,
497 canonical_bytes,
498 })
499}
500
501fn resolve_operation(
502 program_id: ProjectionProgramId,
503 arm: &ProjectionArm,
504 operation: &ProjectionOperation,
505 occurrence: &DomainEventOccurrence,
506 body: &Value,
507 partition: &ResolvedProjectionPartition,
508) -> Result<ResolvedProjectionMutation, ProjectionProgramError> {
509 let key = resolve_key(operation.key(), occurrence, body)?;
510 let mut fields = Vec::with_capacity(operation.fields().len());
511 for field in operation.fields() {
512 let value = field.assignment().resolve(occurrence, body)?;
513 if operation.kind().is_complete_write() {
514 match &value {
515 ResolvedProjectionValue::Absent => {
516 return Err(ProjectionProgramError::RequiredValueAbsent {
517 path: field.name().to_owned(),
518 });
519 }
520 ResolvedProjectionValue::Unset => {
521 return Err(ProjectionProgramError::UnsetNotAllowed {
522 field: field.name().to_owned(),
523 });
524 }
525 ResolvedProjectionValue::Value(_) => {}
526 }
527 }
528 fields.push(ResolvedProjectionField {
529 operation_staging_ordinal: operation.staging_ordinal(),
530 field_ordinal: field.ordinal(),
531 name: field.name().to_owned(),
532 value,
533 });
534 }
535 let relationship_effects = operation
536 .relationship_effects()
537 .iter()
538 .map(|effect| {
539 let (source_key, target_key) = match effect.kind() {
540 ProjectionRelationshipEffectKind::Link
541 | ProjectionRelationshipEffectKind::Unlink => (
542 Some(resolve_key(effect.source_key(), occurrence, body)?),
543 Some(resolve_key(effect.target_key(), occurrence, body)?),
544 ),
545 ProjectionRelationshipEffectKind::Invalidate => (
546 Some(resolve_key(effect.source_key(), occurrence, body)?),
547 None,
548 ),
549 };
550 Ok(ResolvedProjectionRelationshipEffect {
551 ordinal: effect.ordinal(),
552 kind: effect.kind(),
553 relationship: effect.relationship().clone(),
554 source_key,
555 target_key,
556 })
557 })
558 .collect::<Result<Vec<_>, ProjectionProgramError>>()?;
559 Ok(ResolvedProjectionMutation {
560 kind: operation.kind(),
561 target: operation.target().clone(),
562 scope: ResolvedProjectionMutationScope {
563 partition: partition.clone(),
564 model: operation.target().model().to_owned(),
565 storage: operation.target().storage().to_owned(),
566 key,
567 },
568 fields,
569 provenance: ProjectionMutationProvenance {
570 occurrence: ProjectionOccurrenceProvenance {
571 occurrence_version: occurrence.occurrence_version(),
572 occurrence_id: occurrence.id().to_owned(),
573 event_name: occurrence.descriptor().name.to_string(),
574 event_version: occurrence.descriptor().version,
575 body_fingerprint: occurrence.descriptor().body.fingerprint.to_string(),
576 aggregate_type: occurrence.aggregate_type().to_owned(),
577 aggregate_id: occurrence.aggregate_id().to_owned(),
578 aggregate_sequence: occurrence.aggregate_sequence(),
579 publication_ordinal: occurrence.publication_ordinal(),
580 },
581 program_id,
582 arm_id: arm.arm_id().to_owned(),
583 operation_ids: vec![operation.operation_id().to_owned()],
584 staging_ordinals: vec![operation.staging_ordinal()],
585 relationship_effects,
586 invalidations: operation.invalidations().to_vec(),
587 },
588 })
589}
590
591fn resolve_key(
592 key: &[ProjectionKeyField],
593 occurrence: &DomainEventOccurrence,
594 body: &Value,
595) -> Result<ResolvedProjectionKey, ProjectionProgramError> {
596 let mut key_fields = Vec::with_capacity(key.len());
597 for field in key {
598 let value = match field.expression().resolve(occurrence, body)? {
599 ResolvedProjectionValue::Value(value) if value.valid_key_component() => value,
600 _ => {
601 return Err(ProjectionProgramError::InvalidKeyValue {
602 field: field.name().to_owned(),
603 });
604 }
605 };
606 key_fields.push(ResolvedProjectionKeyField {
607 ordinal: field.ordinal(),
608 name: field.name().to_owned(),
609 value,
610 });
611 }
612 let canonical_bytes = bounded_key_bytes(&key_fields, MAX_PROJECTION_RECORD_KEY_BYTES)?;
613 Ok(ResolvedProjectionKey {
614 fields: key_fields,
615 canonical_bytes,
616 })
617}
618
619fn coalesce_mutations(
620 mutations: Vec<ResolvedProjectionMutation>,
621) -> Result<Vec<ResolvedProjectionMutation>, ProjectionProgramError> {
622 let mut by_scope: BTreeMap<(String, String, Vec<u8>), ResolvedProjectionMutation> =
623 BTreeMap::new();
624 for mutation in mutations {
625 let scope = (
626 mutation.target.model().to_owned(),
627 mutation.target.storage().to_owned(),
628 mutation.scope.key.canonical_bytes.clone(),
629 );
630 if let Some(existing) = by_scope.get_mut(&scope) {
631 merge_mutation(existing, mutation)?;
632 } else {
633 by_scope.insert(scope, mutation);
634 }
635 }
636 Ok(by_scope.into_values().collect())
637}
638
639fn merge_mutation(
640 existing: &mut ResolvedProjectionMutation,
641 incoming: ResolvedProjectionMutation,
642) -> Result<(), ProjectionProgramError> {
643 if existing.kind != incoming.kind
644 || existing.target != incoming.target
645 || existing.scope != incoming.scope
646 || existing.provenance.relationship_effects != incoming.provenance.relationship_effects
647 {
648 return Err(ProjectionProgramError::AmbiguousMutation {
649 model: existing.target.model().to_owned(),
650 reason: "same record scope resolves to incompatible logical mutations".to_owned(),
651 });
652 }
653
654 if existing.kind.is_patch() {
655 for field in incoming.fields {
656 if let Some(prior) = existing
657 .fields
658 .iter()
659 .find(|candidate| candidate.name == field.name)
660 {
661 if prior.value != field.value {
662 return Err(ProjectionProgramError::AmbiguousMutation {
663 model: existing.target.model().to_owned(),
664 reason: format!(
665 "field `{}` receives conflicting values in one occurrence",
666 field.name
667 ),
668 });
669 }
670 } else {
671 existing.fields.push(field);
672 }
673 }
674 existing.fields.sort_by(|left, right| {
675 left.operation_staging_ordinal
676 .cmp(&right.operation_staging_ordinal)
677 .then_with(|| left.field_ordinal.cmp(&right.field_ordinal))
678 .then_with(|| left.name.cmp(&right.name))
679 });
680 } else if !same_resolved_fields_ignoring_staging(&existing.fields, &incoming.fields) {
681 return Err(ProjectionProgramError::AmbiguousMutation {
682 model: existing.target.model().to_owned(),
683 reason: "duplicate complete mutations are not byte-identical".to_owned(),
684 });
685 }
686
687 existing
688 .provenance
689 .operation_ids
690 .extend(incoming.provenance.operation_ids);
691 existing
692 .provenance
693 .staging_ordinals
694 .extend(incoming.provenance.staging_ordinals);
695 existing
696 .provenance
697 .invalidations
698 .extend(incoming.provenance.invalidations);
699 existing.provenance.invalidations.sort();
700 existing.provenance.invalidations.dedup();
701 Ok(())
702}
703
704fn same_resolved_fields_ignoring_staging(
705 left: &[ResolvedProjectionField],
706 right: &[ResolvedProjectionField],
707) -> bool {
708 left.len() == right.len()
709 && left.iter().zip(right).all(|(left, right)| {
710 left.field_ordinal == right.field_ordinal
711 && left.name == right.name
712 && left.value == right.value
713 })
714}