Skip to main content

made_core/value_objects/ceremony/
ceremony_reason.rs

1use serde::{Deserialize, Serialize};
2use time::OffsetDateTime;
3
4use crate::error::DomainError;
5use crate::value_objects::{MemoryConfidence, RoleId};
6
7use super::{CeremonyReasonKind, CeremonyRecordRef};
8
9/// A reason is short. Whatever needs paragraphs belongs on the record
10/// it points at, where a reader who wants it can go; an edge carrying
11/// an essay makes the shape of the reasoning unreadable, which is the
12/// one thing an edge is for.
13const MAX_WHY: usize = 512;
14
15/// Why one thing a session produced led to another.
16///
17/// Not an annotation on a record: **the reason is the edge**. A record
18/// says what was decided, seen or done, and only the edges say how one
19/// came from another — so a session of records alone can be read and
20/// not followed, and following is how anyone later works out how and
21/// why something was done.
22///
23/// Immutable, and not corrected. Changing your mind is asserting
24/// another reason that supersedes this one, which keeps what was
25/// believed at the time — the thing a later reader asking "what did we
26/// think then" cannot do without.
27#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
28pub struct CeremonyReason {
29    from: CeremonyRecordRef,
30    to: CeremonyRecordRef,
31    kind: CeremonyReasonKind,
32    why: String,
33    confidence: MemoryConfidence,
34    asserted_by: Option<RoleId>,
35    #[serde(with = "time::serde::rfc3339")]
36    asserted_at: OffsetDateTime,
37}
38
39impl CeremonyReason {
40    /// State that `from` came about from `to`, and why.
41    ///
42    /// `asserted_by` is absent only for what the engine itself
43    /// observed. Every other kind requires a seat, and which kinds
44    /// those are is not this constructor's to police — the session
45    /// holds the definition and the records, and checks it there.
46    pub fn new(
47        from: CeremonyRecordRef,
48        to: CeremonyRecordRef,
49        kind: CeremonyReasonKind,
50        why: impl Into<String>,
51        confidence: MemoryConfidence,
52        asserted_by: Option<RoleId>,
53        asserted_at: OffsetDateTime,
54    ) -> Result<Self, DomainError> {
55        if from == to {
56            return Err(DomainError::InvariantViolated {
57                reason: "nothing in a session explains itself",
58            });
59        }
60        let why = why.into();
61        let trimmed = why.trim();
62        if trimmed.is_empty() {
63            return Err(DomainError::EmptyField {
64                field: "ceremony_reason.why",
65            });
66        }
67        if trimmed.chars().count() > MAX_WHY {
68            return Err(DomainError::FieldTooLong {
69                field: "ceremony_reason.why",
70                max: MAX_WHY,
71                actual: trimmed.chars().count(),
72            });
73        }
74        Ok(Self {
75            from,
76            to,
77            kind,
78            why: trimmed.to_owned(),
79            confidence,
80            asserted_by,
81            asserted_at,
82        })
83    }
84
85    #[must_use]
86    pub fn from(&self) -> &CeremonyRecordRef {
87        &self.from
88    }
89
90    #[must_use]
91    pub fn to(&self) -> &CeremonyRecordRef {
92        &self.to
93    }
94
95    #[must_use]
96    pub const fn kind(&self) -> CeremonyReasonKind {
97        self.kind
98    }
99
100    /// The reason itself, in one line.
101    #[must_use]
102    pub fn why(&self) -> &str {
103        &self.why
104    }
105
106    #[must_use]
107    pub const fn confidence(&self) -> MemoryConfidence {
108        self.confidence
109    }
110
111    /// The seat that said so, where a seat did.
112    #[must_use]
113    pub fn asserted_by(&self) -> Option<&RoleId> {
114        self.asserted_by.as_ref()
115    }
116
117    #[must_use]
118    pub fn asserted_at(&self) -> OffsetDateTime {
119        self.asserted_at
120    }
121}