Skip to main content

made_core/entities/ceremony_instance/decisions/
start.rs

1use time::OffsetDateTime;
2
3use crate::entities::ceremony_events::{CeremonyInstanceStarted, MemoryRecalled};
4use crate::entities::{
5    CeremonyDefinition, CeremonyEvent, CeremonyInstance, PublishedCeremonyDefinition,
6};
7use crate::error::DomainError;
8use crate::value_objects::{
9    CeremonyContext, CeremonyDefinitionDigest, CeremonyId, SessionRecollection,
10};
11
12impl CeremonyInstance {
13    /// The opening of a ceremony run from a definition supplied for it.
14    ///
15    /// A constructor rather than a command: there is no instance yet
16    /// to decide against. Required inputs are checked against this run's
17    /// context before any opening event is built. The event carries everything
18    /// [`Self::from_started`] needs to open the same instance without
19    /// the definition in hand.
20    ///
21    /// A batch rather than one event, because an opening is sometimes
22    /// two facts: what was started, and what it was told. See
23    /// [`Self::opening_batch`].
24    pub fn decide_start(
25        id: CeremonyId,
26        definition: &CeremonyDefinition,
27        context: CeremonyContext,
28        recollection: Option<SessionRecollection>,
29        now: OffsetDateTime,
30    ) -> Result<Vec<CeremonyEvent>, DomainError> {
31        Ok(Self::opening_batch(
32            Self::opening(id, definition, context, now, None)?,
33            recollection,
34            now,
35        ))
36    }
37
38    /// The opening of a ceremony bound to a published definition, its
39    /// digest recorded so a later reader can check which one ran.
40    pub fn decide_start_bound(
41        id: CeremonyId,
42        published: &PublishedCeremonyDefinition,
43        context: CeremonyContext,
44        recollection: Option<SessionRecollection>,
45        now: OffsetDateTime,
46    ) -> Result<Vec<CeremonyEvent>, DomainError> {
47        Ok(Self::opening_batch(
48            Self::opening(
49                id,
50                published.definition(),
51                context,
52                now,
53                Some(published.digest()),
54            )?,
55            recollection,
56            now,
57        ))
58    }
59
60    /// The opening, and the recollection when there was one.
61    ///
62    /// The recollection is decided outside — reading memory is IO, and
63    /// `decide` stays pure — and handed in. What is decided here is
64    /// whether it becomes a fact: a recollection that came back empty
65    /// appends nothing, so a session with nothing to recall, which is
66    /// every session that declares no scope, has exactly the stream it
67    /// had before memory could be read at all.
68    fn opening_batch(
69        opening: CeremonyInstanceStarted,
70        recollection: Option<SessionRecollection>,
71        now: OffsetDateTime,
72    ) -> Vec<CeremonyEvent> {
73        let mut events = vec![CeremonyEvent::CeremonyInstanceStarted(opening)];
74        if let Some(recollection) = recollection.filter(|recalled| !recalled.is_empty()) {
75            events.push(CeremonyEvent::MemoryRecalled(MemoryRecalled {
76                recollection,
77                recalled_at: now,
78            }));
79        }
80        events
81    }
82
83    /// What starting derives from the definition: the initial state
84    /// and the steps that get a pending record.
85    pub(in crate::entities::ceremony_instance) fn opening(
86        id: CeremonyId,
87        definition: &CeremonyDefinition,
88        context: CeremonyContext,
89        now: OffsetDateTime,
90        bound_definition: Option<CeremonyDefinitionDigest>,
91    ) -> Result<CeremonyInstanceStarted, DomainError> {
92        let missing = definition
93            .inputs()
94            .values()
95            .filter(|input| input.requirement().is_required())
96            .filter(|input| context.attributes().get(input.name().as_str()).is_none())
97            .map(|input| input.name().as_str())
98            .collect::<Vec<_>>();
99        if !missing.is_empty() {
100            return Err(DomainError::InvalidDocument {
101                reason: format!("missing required ceremony inputs: {}", missing.join(", ")),
102            });
103        }
104        Ok(CeremonyInstanceStarted {
105            ceremony_id: id,
106            definition_name: definition.name().clone(),
107            definition_version: definition.version().clone(),
108            initial_state: definition.initial_state_id().clone(),
109            step_ids: definition.steps().keys().cloned().collect(),
110            context,
111            bound_definition,
112            created_at: now,
113        })
114    }
115}