Skip to main content

made_core/entities/ceremony_instance/
step_execution.rs

1use crate::entities::ceremony_commands::{ApplyStepResult, StartStep};
2use crate::entities::CeremonyCommand;
3use crate::value_objects::{MaxParallel, StepClaimFence};
4
5use super::{
6    CeremonyDefinition, CeremonyEvent, CeremonyInstance, DomainError, OffsetDateTime, RoleId,
7    StepAttempt, StepId, StepLease, StepResult,
8};
9
10/// The step mutators, as wrappers over [`CeremonyInstance::decide`]
11/// and [`CeremonyInstance::apply`]: each decides its command, folds
12/// the events, and returns what its callers always got.
13impl CeremonyInstance {
14    pub fn start_step_as(
15        &mut self,
16        definition: &CeremonyDefinition,
17        role_id: &RoleId,
18        step_id: &StepId,
19        lease: StepLease,
20        now: OffsetDateTime,
21    ) -> Result<StepAttempt, DomainError> {
22        self.start_step_with(
23            definition,
24            Some(role_id.clone()),
25            step_id,
26            lease,
27            now,
28            MaxParallel::SERVER_MAX,
29        )
30    }
31
32    pub fn start_step(
33        &mut self,
34        definition: &CeremonyDefinition,
35        step_id: &StepId,
36        lease: StepLease,
37        now: OffsetDateTime,
38    ) -> Result<StepAttempt, DomainError> {
39        self.start_step_with(
40            definition,
41            None,
42            step_id,
43            lease,
44            now,
45            MaxParallel::SERVER_MAX,
46        )
47    }
48
49    fn start_step_with(
50        &mut self,
51        definition: &CeremonyDefinition,
52        role_id: Option<RoleId>,
53        step_id: &StepId,
54        lease: StepLease,
55        now: OffsetDateTime,
56        max_parallel_ceiling: MaxParallel,
57    ) -> Result<StepAttempt, DomainError> {
58        let command = CeremonyCommand::StartStep(StartStep {
59            role_id,
60            step_id: step_id.clone(),
61            lease,
62            now,
63            max_parallel_ceiling,
64            budget_reservation_id: None,
65        });
66        let events = self.decide(&command, definition)?;
67        let attempt = events
68            .iter()
69            .find_map(|event| match event {
70                CeremonyEvent::StepStarted(started) => Some(started.attempt),
71                _ => None,
72            })
73            .ok_or(DomainError::InvariantViolated {
74                reason: "starting a step decides a step start",
75            })?;
76        self.apply_all(&events);
77        Ok(attempt)
78    }
79
80    /// Capture this identity from the accepted claim before starting work.
81    pub fn step_claim_fence(&self, step_id: &StepId) -> Result<StepClaimFence, DomainError> {
82        let record = self.step_record(step_id).ok_or(DomainError::NotFound {
83            what: "ceremony_instance.step_record",
84        })?;
85        StepClaimFence::for_record(self.id(), step_id, record)
86    }
87
88    pub(super) fn require_step_claim_fence(
89        &self,
90        step_id: &StepId,
91        fence: &StepClaimFence,
92    ) -> Result<(), DomainError> {
93        if &self.step_claim_fence(step_id)? != fence {
94            return Err(DomainError::InvariantViolated {
95                reason: "step completion claim fence does not match the current claim",
96            });
97        }
98        Ok(())
99    }
100
101    pub fn apply_step_result(
102        &mut self,
103        definition: &CeremonyDefinition,
104        step_id: &StepId,
105        claim_fence: StepClaimFence,
106        result: StepResult,
107        now: OffsetDateTime,
108    ) -> Result<(), DomainError> {
109        let command = CeremonyCommand::ApplyStepResult(ApplyStepResult {
110            step_id: step_id.clone(),
111            claim_fence,
112            result,
113            now,
114        });
115        let events = self.decide(&command, definition)?;
116        self.apply_all(&events);
117        Ok(())
118    }
119}