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