1#![allow(unused_must_use)]
2
3use std::{collections::BTreeMap, env, fmt::Write, fs, path::Path};
4
5use crate::{
6 schema::{self, Aggregate, Command, Event},
7 Error,
8};
9
10#[derive(Default)]
26pub struct Compiler {
27 schemas: Vec<Aggregate>,
28}
29
30impl Compiler {
31 pub fn new() -> Self {
33 Compiler {
34 schemas: Vec::new(),
35 }
36 }
37
38 pub fn add_schema(mut self, schema: Aggregate) -> Self {
40 self.schemas.push(schema);
41 self
42 }
43
44 pub fn add_schema_file<P: AsRef<Path>>(self, path: P) -> Result<Self, Error> {
46 let content = fs::read(path)?;
47 let aggregate: Aggregate = serde_yaml::from_slice(&content)?;
48 Ok(self.add_schema(aggregate))
49 }
50
51 pub fn add_schema_str(self, content: &str) -> Result<Self, Error> {
53 let aggregate: Aggregate = serde_yaml::from_str(content)?;
54 Ok(self.add_schema(aggregate))
55 }
56
57 pub fn compile(self) -> Result<(), Error> {
59 let out_dir = env::var("OUT_DIR").unwrap();
60
61 for schema in self.schemas {
62 let code = Self::compile_schema(&schema);
63 fs::write(format!("{}/{}.rs", out_dir, schema.name), code)?;
64 }
65
66 Ok(())
67 }
68
69 fn compile_schema(schema: &Aggregate) -> String {
70 let mut code = String::new();
71
72 Self::compile_schema_events(&mut code, &schema.name, &schema.events);
73
74 Self::compile_schema_errors(&mut code, &schema.name, &schema.errors);
75
76 Self::compile_schema_commands(&mut code, &schema.name, &schema.commands);
77
78 code
79 }
80
81 fn compile_schema_events(code: &mut String, name: &str, events: &BTreeMap<String, Event>) {
82 writeln!(
83 code,
84 "#[derive(Clone, Debug, serde::Deserialize, thalo::event::EventType, PartialEq, serde::Serialize)]"
85 );
86 writeln!(code, "pub enum {}Event {{", name);
87
88 for event_name in events.keys() {
89 writeln!(code, " {}({0}Event),", event_name);
90 }
91
92 writeln!(code, "}}\n");
93
94 for (event_name, event) in events {
95 writeln!(code, "#[derive(Clone, Debug, serde::Deserialize, thalo::event::Event, PartialEq, serde::Serialize)]");
96 writeln!(
97 code,
98 r#"#[thalo(parent = "{}Event", variant = "{}")]"#,
99 name, event_name
100 );
101 writeln!(code, "pub struct {}Event {{", event_name);
102 for field in &event.fields {
103 writeln!(code, " pub {}: {},", field.name, field.field_type);
104 }
105 writeln!(code, "}}\n");
106 }
107 }
108
109 fn compile_schema_errors(
110 code: &mut String,
111 name: &str,
112 errors: &BTreeMap<String, schema::Error>,
113 ) {
114 writeln!(code, "#[derive(Clone, Debug, thiserror::Error, PartialEq)]");
115 writeln!(code, "pub enum {}Error {{", name);
116
117 for (error_name, error) in errors {
118 writeln!(code, r#" #[error("{}")]"#, error.message);
119 writeln!(code, " {},", error_name);
120 }
121
122 writeln!(code, "}}");
123 }
124
125 fn compile_schema_commands(
126 code: &mut String,
127 name: &str,
128 commands: &BTreeMap<String, Command>,
129 ) {
130 writeln!(code, "pub trait {}Command {{", name);
131
132 for (command_name, command) in commands {
133 write!(code, " fn {}(&self", command_name);
134
135 for arg in &command.args {
136 write!(code, ", {}: {}", arg.name, arg.type_field);
137 }
138
139 write!(code, ") -> ");
140
141 if !command.infallible {
142 write!(code, "std::result::Result<");
143 }
144
145 if command.event.is_some() && command.optional {
146 write!(code, "std::option::Option<");
147 }
148
149 match &command.event {
150 Some((event_name, _)) => {
151 write!(code, "{}Event", event_name);
152 }
153 None => {
154 write!(code, "Vec<{}Event>", name);
155 }
156 }
157
158 if command.event.is_some() && command.optional {
159 write!(code, ">");
160 }
161
162 if !command.infallible {
163 write!(code, ", {}Error>", name);
164 }
165
166 writeln!(code, ";");
167 }
168
169 writeln!(code, "}}");
170 }
171}
172
173#[cfg(test)]
174mod tests {
175 use std::{collections::BTreeMap, fs};
176
177 use crate::{
178 schema::{Aggregate, Event, Field},
179 Compiler,
180 };
181
182 #[test]
183 fn it_compile_schema_events() {
184 let mut events = BTreeMap::new();
185 events.insert(
186 "AccountOpened".to_string(),
187 Event {
188 fields: vec![Field {
189 name: "initial_balance".to_string(),
190 field_type: "f64".to_string(),
191 }],
192 },
193 );
194 events.insert(
195 "DepositedFunds".to_string(),
196 Event {
197 fields: vec![Field {
198 name: "amount".to_string(),
199 field_type: "f64".to_string(),
200 }],
201 },
202 );
203 events.insert(
204 "WithdrewFunds".to_string(),
205 Event {
206 fields: vec![Field {
207 name: "amount".to_string(),
208 field_type: "f64".to_string(),
209 }],
210 },
211 );
212
213 let mut code = String::new();
214 Compiler::compile_schema_events(&mut code, "BankAccount", &events);
215
216 assert_eq!(
217 code,
218 r#"#[derive(Clone, Debug, serde::Deserialize, thalo::event::EventType, PartialEq, serde::Serialize)]
219pub enum BankAccountEvent {
220 AccountOpened(AccountOpenedEvent),
221 DepositedFunds(DepositedFundsEvent),
222 WithdrewFunds(WithdrewFundsEvent),
223}
224
225#[derive(Clone, Debug, serde::Deserialize, thalo::event::Event, PartialEq, serde::Serialize)]
226#[thalo(parent = "BankAccountEvent", variant = "AccountOpened")]
227pub struct AccountOpenedEvent {
228 pub initial_balance: f64,
229}
230
231#[derive(Clone, Debug, serde::Deserialize, thalo::event::Event, PartialEq, serde::Serialize)]
232#[thalo(parent = "BankAccountEvent", variant = "DepositedFunds")]
233pub struct DepositedFundsEvent {
234 pub amount: f64,
235}
236
237#[derive(Clone, Debug, serde::Deserialize, thalo::event::Event, PartialEq, serde::Serialize)]
238#[thalo(parent = "BankAccountEvent", variant = "WithdrewFunds")]
239pub struct WithdrewFundsEvent {
240 pub amount: f64,
241}
242
243"#
244 )
245 }
246
247 #[test]
248 fn it_compile_schema_commands() {
249 let config = fs::read_to_string("./bank-account.yaml").unwrap();
250 let schema: Aggregate = serde_yaml::from_str(&config).unwrap();
251
252 let mut code = String::new();
253 Compiler::compile_schema_commands(&mut code, &schema.name, &schema.commands);
254
255 println!("{}", code);
256 }
257}