harmos_domain/state/
transaction.rs1use std::convert::Infallible;
18use crate::state::envelope::DefinitionId;
22
23#[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
48pub 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 no inverse",
75 note = "a change answers the undo question where it is defined: `inverse = …` names the \
76 change that takes it back, `inverse = Self` says it is its own, and `irreversible` \
77 says it cannot be taken back — an irreversible change carries no invert body"
78)]
79pub trait Undoable {
80 type Inverse;
81}
82
83#[diagnostic::on_unimplemented(
84 message = "`{Self}` declares an inverse but never describes it",
85 label = "declared without a body",
86 note = "write the body beside the change: `impl harmos::state::Invert<{S}> for {Self} {{ fn \
87 invert(&self, before: &{S}) -> … }}`"
88)]
89pub trait Invert<S>: Undoable + Transaction<S> {
90 fn invert(&self, before: &S) -> Self::Inverse;
91}
92
93#[diagnostic::on_unimplemented(
94 message = "the declared inverse of `{T}` does not name `{T}` back",
95 note = "inverses come in pairs: when `{T}` declares `inverse = X`, `X` declares `inverse = \
96 {T}` — or `inverse = Self`, which is its own pair"
97)]
98pub trait Mutual<T> {}
99
100#[diagnostic::on_unimplemented(
101 message = "the declared inverse `{Self}` is not a member of `{C}`",
102 note = "add `{Self}` to `{C}` as its own variant — a catalog never invents one"
103)]
104pub trait Catalogued<C>: Sized {
105 fn catalogued(self) -> C;
106}
107
108impl<S> TransactionCatalog<S> for Never {}
111
112impl super::record::CatalogCodec for Never {
113 fn identify(&self) -> (DefinitionId, u32) {
114 match *self {}
115 }
116
117 fn encode<S: serde::Serializer>(&self, _: S) -> Result<S::Ok, S::Error> {
118 match *self {}
119 }
120}
121
122impl super::record::RecordCatalog for Never {
123 fn restore(_: &DefinitionId, _: u32, _: &serde_json::Value) -> Result<Option<Self>, serde_json::Error> {
124 Ok(None)
125 }
126}
127
128impl<S> Transaction<S> for Never {
129 type Error = Infallible;
130
131 fn check(&self, _state: &S) -> Result<(), Self::Error> {
132 match *self {}
133 }
134
135 fn apply(&self, _state: &mut S) {
136 match *self {}
137 }
138}
139
140impl<T> Mutual<T> for T {}
141
142#[diagnostic::do_not_recommend]
146impl<C, T: Into<C>> Catalogued<C> for T {
147 fn catalogued(self) -> C {
148 self.into()
149 }
150}
151
152