Skip to main content

harmos_domain/state/
transaction.rs

1/* ***
2 *
3 * Harmos
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 *
7 * ***
8 */
9
10/* === Modules === */
11
12/* === Re-Exports === */
13
14/* === Imports === */
15
16// Standard Library Imports
17use std::convert::Infallible;
18// Third Party Imports
19
20// Local Imports
21use crate::state::envelope::DefinitionId;
22
23/* === Constants === */
24
25/* === Macros === */
26
27/* === Type Aliases === */
28
29/* === Structs === */
30
31/* === Enums === */
32
33#[derive(Clone, Copy, Debug, PartialEq, Eq)]
34pub enum Never {}
35
36#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
37pub enum DecodeError {
38    #[error("no definition is declared for the id {id}")]
39    UnknownDefinition { id: DefinitionId },
40
41    #[error("{id} declares no implementation for version {version}")]
42    UndeclaredVersion { id: DefinitionId, version: u32 },
43
44    #[error("the payload did not decode as {id} version {version}")]
45    Malformed { id: DefinitionId, version: u32 },
46}
47
48/* === Traits === */
49
50pub trait Transaction<S> {
51    type Error;
52
53    fn check(&self, state: &S) -> Result<(), Self::Error>;
54
55    fn records(&self, _state: &S, _out: &mut super::record::Records) {}
56
57    fn apply(&self, state: &mut S);
58}
59
60pub trait TransactionCatalog<S>: Transaction<S> + super::record::CatalogCodec {
61    fn replay(&self, prepared: &super::record::Prepared, state: &mut S) -> Result<(), DecodeError> {
62        if self.identify() != (prepared.id.clone(), prepared.version) {
63            return Err(DecodeError::Malformed {
64                id: prepared.id.clone(),
65                version: prepared.version,
66            });
67        }
68        self.apply(state);
69        Ok(())
70    }
71}
72
73#[diagnostic::on_unimplemented(
74    message = "`{Self}` declares an inverse but never describes it",
75    label = "declared without a body",
76    note = "write the body beside the change: `impl harmos::state::Invertible<{S}> for {Self} {{ type \
77            Inverse = …; fn inverse(&self, before: &{S}) -> Self::Inverse {{ … }} }}`"
78)]
79pub trait Invertible<S>: Transaction<S> {
80    type Inverse: Transaction<S>;
81
82    fn inverse(&self, before: &S) -> Self::Inverse;
83}
84
85// The Attribute's `inverse = …` Declaration, Readable Without A State Type So
86// The Pair Check Runs Where The Change Is Declared Rather Than Where It Is Used.
87#[diagnostic::on_unimplemented(
88    message = "`{Self}` declares no inverse",
89    note = "a change answers the undo question where it is defined: `inverse = …` names the \
90            change that takes it back, and `inverse = Self` says it is its own"
91)]
92pub trait Paired {
93    type Inverse;
94}
95
96#[diagnostic::on_unimplemented(
97    message = "the declared inverse of `{T}` does not name `{T}` back",
98    note = "inverses come in pairs: when `{T}` declares `inverse = X`, `X` declares `inverse = \
99            {T}` — or `inverse = Self`, which is its own pair"
100)]
101pub trait Mutual<T> {}
102
103#[diagnostic::on_unimplemented(
104    message = "the declared inverse `{Self}` is not a member of `{C}`",
105    note = "add `{Self}` to `{C}` as its own variant — a catalog never invents one"
106)]
107pub trait Catalogued<C>: Sized {
108    fn catalogued(self) -> C;
109}
110
111/* === Implementations === */
112
113impl<S> TransactionCatalog<S> for Never {}
114
115impl super::record::CatalogCodec for Never {
116    fn identify(&self) -> (DefinitionId, u32) {
117        match *self {}
118    }
119
120    fn encode<S: serde::Serializer>(&self, _: S) -> Result<S::Ok, S::Error> {
121        match *self {}
122    }
123}
124
125impl super::record::RecordCatalog for Never {
126    fn restore(_: &DefinitionId, _: u32, _: &serde_json::Value) -> Result<Option<Self>, serde_json::Error> {
127        Ok(None)
128    }
129}
130
131impl<S> Transaction<S> for Never {
132    type Error = Infallible;
133
134    fn check(&self, _state: &S) -> Result<(), Self::Error> {
135        match *self {}
136    }
137
138    fn apply(&self, _state: &mut S) {
139        match *self {}
140    }
141}
142
143impl<T> Mutual<T> for T {}
144
145// The membership question is `Catalogued`, not the `Into` it is spelled with:
146// pointing an author at a missing `From` impl would send them to write one by
147// hand instead of to the catalog line that is actually missing a variant.
148#[diagnostic::do_not_recommend]
149impl<C, T: Into<C>> Catalogued<C> for T {
150    fn catalogued(self) -> C {
151        self.into()
152    }
153}
154
155/* === Public Functions === */
156
157/* === Private Functions === */
158
159/* === Tests === */