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
116
117
118
119
120
121
use serde::{Deserialize, Serialize};
use time::OffsetDateTime;
use crate::error::DomainError;
use crate::value_objects::{MemoryConfidence, RoleId};
use super::{CeremonyReasonKind, CeremonyRecordRef};
/// A reason is short. Whatever needs paragraphs belongs on the record
/// it points at, where a reader who wants it can go; an edge carrying
/// an essay makes the shape of the reasoning unreadable, which is the
/// one thing an edge is for.
const MAX_WHY: usize = 512;
/// Why one thing a session produced led to another.
///
/// Not an annotation on a record: **the reason is the edge**. A record
/// says what was decided, seen or done, and only the edges say how one
/// came from another — so a session of records alone can be read and
/// not followed, and following is how anyone later works out how and
/// why something was done.
///
/// Immutable, and not corrected. Changing your mind is asserting
/// another reason that supersedes this one, which keeps what was
/// believed at the time — the thing a later reader asking "what did we
/// think then" cannot do without.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CeremonyReason {
from: CeremonyRecordRef,
to: CeremonyRecordRef,
kind: CeremonyReasonKind,
why: String,
confidence: MemoryConfidence,
asserted_by: Option<RoleId>,
#[serde(with = "time::serde::rfc3339")]
asserted_at: OffsetDateTime,
}
impl CeremonyReason {
/// State that `from` came about from `to`, and why.
///
/// `asserted_by` is absent only for what the engine itself
/// observed. Every other kind requires a seat, and which kinds
/// those are is not this constructor's to police — the session
/// holds the definition and the records, and checks it there.
pub fn new(
from: CeremonyRecordRef,
to: CeremonyRecordRef,
kind: CeremonyReasonKind,
why: impl Into<String>,
confidence: MemoryConfidence,
asserted_by: Option<RoleId>,
asserted_at: OffsetDateTime,
) -> Result<Self, DomainError> {
if from == to {
return Err(DomainError::InvariantViolated {
reason: "nothing in a session explains itself",
});
}
let why = why.into();
let trimmed = why.trim();
if trimmed.is_empty() {
return Err(DomainError::EmptyField {
field: "ceremony_reason.why",
});
}
if trimmed.chars().count() > MAX_WHY {
return Err(DomainError::FieldTooLong {
field: "ceremony_reason.why",
max: MAX_WHY,
actual: trimmed.chars().count(),
});
}
Ok(Self {
from,
to,
kind,
why: trimmed.to_owned(),
confidence,
asserted_by,
asserted_at,
})
}
#[must_use]
pub fn from(&self) -> &CeremonyRecordRef {
&self.from
}
#[must_use]
pub fn to(&self) -> &CeremonyRecordRef {
&self.to
}
#[must_use]
pub const fn kind(&self) -> CeremonyReasonKind {
self.kind
}
/// The reason itself, in one line.
#[must_use]
pub fn why(&self) -> &str {
&self.why
}
#[must_use]
pub const fn confidence(&self) -> MemoryConfidence {
self.confidence
}
/// The seat that said so, where a seat did.
#[must_use]
pub fn asserted_by(&self) -> Option<&RoleId> {
self.asserted_by.as_ref()
}
#[must_use]
pub fn asserted_at(&self) -> OffsetDateTime {
self.asserted_at
}
}