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// }