thalo_schema/schema.rs
1#![allow(missing_docs)]
2
3use std::{collections::BTreeMap, fmt};
4
5use serde::{
6 de::{self, Unexpected, Visitor},
7 Deserialize, Deserializer, Serialize,
8};
9
10#[derive(Default, Debug, Clone, PartialEq, Serialize)]
11pub struct Aggregate {
12 pub name: String,
13 pub commands: BTreeMap<String, Command>,
14 pub events: BTreeMap<String, Event>,
15 pub errors: BTreeMap<String, Error>,
16}
17
18#[derive(Debug, Clone, PartialEq, Serialize)]
19pub struct Command {
20 pub args: Vec<Arg>,
21 pub event: Option<(String, Event)>,
22 pub infallible: bool,
23 pub optional: bool,
24}
25
26#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
27pub struct Arg {
28 pub name: String,
29 #[serde(rename = "type")]
30 pub type_field: String,
31}
32
33#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
34pub struct Event {
35 #[serde(default)]
36 pub fields: Vec<Field>,
37}
38
39#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
40pub struct Field {
41 pub name: String,
42 #[serde(rename = "type")]
43 pub field_type: String,
44}
45
46#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
47pub struct Error {
48 pub message: String,
49}
50
51impl<'de> Deserialize<'de> for Aggregate {
52 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
53 where
54 D: Deserializer<'de>,
55 {
56 #[derive(Default, Deserialize)]
57 #[serde(default)]
58 struct NamedCommand {
59 args: Vec<Arg>,
60 event: Option<String>,
61 infallible: bool,
62 optional: bool,
63 }
64
65 struct AggregateVisitor;
66
67 impl<'de> Visitor<'de> for AggregateVisitor {
68 type Value = Aggregate;
69
70 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
71 formatter.write_str("struct Aggregate")
72 }
73
74 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
75 where
76 A: serde::de::MapAccess<'de>,
77 {
78 let mut name = None;
79 let mut named_commands: Option<BTreeMap<String, NamedCommand>> = None;
80 let mut events = None;
81 let mut errors = None;
82
83 for _ in 0..4 {
84 match map.next_key::<String>()?.as_deref() {
85 Some("name") => {
86 name = Some(map.next_value()?);
87 }
88 Some("commands") => {
89 named_commands = Some(map.next_value()?);
90 }
91 Some("events") => {
92 events = Some(map.next_value()?);
93 }
94 Some("errors") => {
95 errors = Some(map.next_value()?);
96 }
97 _ => todo!(),
98 }
99 }
100
101 let name = name.ok_or_else(|| de::Error::missing_field("name"))?;
102 let named_commands =
103 named_commands.ok_or_else(|| de::Error::missing_field("commands"))?;
104 let events: BTreeMap<String, Event> =
105 events.ok_or_else(|| de::Error::missing_field("events"))?;
106 let errors = errors.ok_or_else(|| de::Error::missing_field("errors"))?;
107
108 let commands = named_commands
109 .into_iter()
110 .map(|(name, command)| {
111 let event = command
112 .event
113 .map(|event| {
114 let ev = events
115 .get(&event)
116 .ok_or_else(|| {
117 de::Error::invalid_value(
118 Unexpected::Str(&event),
119 &"a reference to defined event",
120 )
121 })?
122 .clone();
123
124 Ok((event, ev))
125 })
126 .transpose()?;
127
128 if command.optional && event.is_none() {
129 return Err(de::Error::invalid_value(
130 Unexpected::Bool(command.optional),
131 &"`false` when `event` is not specified",
132 ));
133 }
134
135 Ok((
136 name,
137 Command {
138 args: command.args,
139 event,
140 infallible: command.infallible,
141 optional: command.optional,
142 },
143 ))
144 })
145 .collect::<Result<_, _>>()?;
146
147 Ok(Aggregate {
148 name,
149 commands,
150 events,
151 errors,
152 })
153 }
154 }
155
156 deserializer.deserialize_struct(
157 "Aggregate",
158 &["name", "commands", "events", "errors"],
159 AggregateVisitor,
160 )
161 }
162}
163
164// #[cfg(test)]
165// mod tests {
166
167// use std::collections::BTreeMap;
168
169// use crate::schema::{Aggregate, Arg, Command, Error, Event, Field};
170
171// #[test]
172// fn it_deserializes_correctly() -> Result<(), serde_yaml::Error> {
173// let config = r#"
174// # The name of the aggregate
175// name: BankAccount
176
177// # The events resulted resulting from commands
178// events:
179// AccountOpened:
180// fields:
181// - name: initial_balance
182// type: f64
183
184// DepositedFunds:
185// fields:
186// - name: amount
187// type: f64
188
189// WithdrewFunds:
190// fields:
191// - name: amount
192// type: f64
193
194// # The commands availble for the aggregate
195// commands:
196// open_account:
197// event: AccountOpened # resulting event when command is successful
198// args:
199// - name: initial_balance
200// type: f64
201
202// deposit_funds:
203// event: DepositedFunds
204// args:
205// - name: amount
206// type: f64
207
208// withdraw_funds:
209// event: WithdrewFunds
210// args:
211// - name: amount
212// type: f64
213
214// # Errors that can occur when executing a command
215// errors:
216// NegativeAmount:
217// message: "amount cannot be negative"
218
219// InsufficientFunds:
220// message: "insufficient funds for transaction"
221// "#;
222
223// let aggregate = serde_yaml::from_str::<Aggregate>(config)?;
224
225// let name = "BankAccount".to_string();
226
227// let mut events = BTreeMap::new();
228// let account_opened = Event {
229// fields: vec![Field {
230// name: "initial_balance".to_string(),
231// field_type: "f64".to_string(),
232// }],
233// };
234// let deposited_funds = Event {
235// fields: vec![Field {
236// name: "amount".to_string(),
237// field_type: "f64".to_string(),
238// }],
239// };
240// let withdrew_funds = Event {
241// fields: vec![Field {
242// name: "amount".to_string(),
243// field_type: "f64".to_string(),
244// }],
245// };
246// events.insert("AccountOpened".to_string(), account_opened.clone());
247// events.insert("DepositedFunds".to_string(), deposited_funds.clone());
248// events.insert("WithdrewFunds".to_string(), withdrew_funds.clone());
249
250// let mut commands = BTreeMap::new();
251// commands.insert(
252// "open_account".to_string(),
253// Command {
254// event: ("AccountOpened".to_string(), account_opened),
255// args: vec![Arg {
256// name: "initial_balance".to_string(),
257// type_field: "f64".to_string(),
258// }],
259// },
260// );
261// commands.insert(
262// "deposit_funds".to_string(),
263// Command {
264// event: ("DepositedFunds".to_string(), deposited_funds),
265// args: vec![Arg {
266// name: "amount".to_string(),
267// type_field: "f64".to_string(),
268// }],
269// },
270// );
271// commands.insert(
272// "withdraw_funds".to_string(),
273// Command {
274// event: ("WithdrewFunds".to_string(), withdrew_funds),
275// args: vec![Arg {
276// name: "amount".to_string(),
277// type_field: "f64".to_string(),
278// }],
279// },
280// );
281
282// let mut errors = BTreeMap::new();
283// errors.insert(
284// "NegativeAmount".to_string(),
285// Error {
286// message: "amount cannot be negative".to_string(),
287// },
288// );
289// errors.insert(
290// "InsufficientFunds".to_string(),
291// Error {
292// message: "insufficient funds for transaction".to_string(),
293// },
294// );
295
296// assert_eq!(
297// aggregate,
298// Aggregate {
299// name,
300// events,
301// commands,
302// errors,
303// }
304// );
305
306// Ok(())
307// }
308// }