sqlx_ledger/tx_template/
entity.rs1use derive_builder::Builder;
2use serde::Serialize;
3
4use cel_interpreter::CelExpression;
5
6use super::param_definition::*;
7use crate::primitives::*;
8
9#[derive(Builder)]
15pub struct NewTxTemplate {
16 #[builder(setter(into))]
17 pub(super) id: TxTemplateId,
18 #[builder(setter(into))]
19 pub(super) code: String,
20 #[builder(setter(strip_option, into), default)]
21 pub(super) description: Option<String>,
22 #[builder(setter(strip_option), default)]
23 pub(super) params: Option<Vec<ParamDefinition>>,
24 pub(super) tx_input: TxInput,
25 pub(super) entries: Vec<EntryInput>,
26 #[builder(setter(custom), default)]
27 pub(super) metadata: Option<serde_json::Value>,
28}
29
30impl NewTxTemplate {
31 pub fn builder() -> NewTxTemplateBuilder {
32 NewTxTemplateBuilder::default()
33 }
34}
35
36impl NewTxTemplateBuilder {
37 pub fn metadata<T: serde::Serialize>(
38 &mut self,
39 metadata: T,
40 ) -> Result<&mut Self, serde_json::Error> {
41 self.metadata = Some(Some(serde_json::to_value(metadata)?));
42 Ok(self)
43 }
44}
45
46#[derive(Clone, Serialize, Builder)]
48#[builder(build_fn(validate = "Self::validate"))]
49pub struct TxInput {
50 #[builder(setter(into))]
51 effective: String,
52 #[builder(setter(into))]
53 journal_id: String,
54 #[builder(setter(strip_option, into), default)]
55 correlation_id: Option<String>,
56 #[builder(setter(strip_option, into), default)]
57 external_id: Option<String>,
58 #[builder(setter(strip_option, into), default)]
59 description: Option<String>,
60 #[builder(setter(strip_option, into), default)]
61 metadata: Option<String>,
62}
63
64impl TxInput {
65 pub fn builder() -> TxInputBuilder {
66 TxInputBuilder::default()
67 }
68}
69
70impl TxInputBuilder {
71 fn validate(&self) -> Result<(), String> {
72 validate_expression(
73 self.effective
74 .as_ref()
75 .expect("Mandatory field 'effective' not set"),
76 )?;
77 validate_expression(
78 self.journal_id
79 .as_ref()
80 .expect("Mandatory field 'journal_id' not set"),
81 )?;
82 validate_optional_expression(&self.correlation_id)?;
83 validate_optional_expression(&self.external_id)?;
84 validate_optional_expression(&self.description)?;
85 validate_optional_expression(&self.metadata)
86 }
87}
88
89#[derive(Clone, Serialize, Builder)]
91#[builder(build_fn(validate = "Self::validate"))]
92pub struct EntryInput {
93 #[builder(setter(into))]
94 entry_type: String,
95 #[builder(setter(into))]
96 account_id: String,
97 #[builder(setter(into))]
98 layer: String,
99 #[builder(setter(into))]
100 direction: String,
101 #[builder(setter(into))]
102 units: String,
103 #[builder(setter(into))]
104 currency: String,
105 #[builder(setter(strip_option), default)]
106 description: Option<String>,
107}
108
109impl EntryInput {
110 pub fn builder() -> EntryInputBuilder {
111 EntryInputBuilder::default()
112 }
113}
114impl EntryInputBuilder {
115 fn validate(&self) -> Result<(), String> {
116 validate_expression(
117 self.entry_type
118 .as_ref()
119 .expect("Mandatory field 'entry_type' not set"),
120 )?;
121 validate_expression(
122 self.account_id
123 .as_ref()
124 .expect("Mandatory field 'account_id' not set"),
125 )?;
126 validate_expression(
127 self.layer
128 .as_ref()
129 .expect("Mandatory field 'layer' not set"),
130 )?;
131 validate_expression(
132 self.direction
133 .as_ref()
134 .expect("Mandatory field 'direction' not set"),
135 )?;
136 validate_expression(
137 self.units
138 .as_ref()
139 .expect("Mandatory field 'units' not set"),
140 )?;
141 validate_expression(
142 self.currency
143 .as_ref()
144 .expect("Mandatory field 'currency' not set"),
145 )?;
146 validate_optional_expression(&self.description)
147 }
148}
149
150fn validate_expression(expr: &str) -> Result<(), String> {
151 CelExpression::try_from(expr).map_err(|e| e.to_string())?;
152 Ok(())
153}
154fn validate_optional_expression(expr: &Option<Option<String>>) -> Result<(), String> {
155 if let Some(Some(expr)) = expr.as_ref() {
156 CelExpression::try_from(expr.as_str()).map_err(|e| e.to_string())?;
157 }
158 Ok(())
159}
160
161#[cfg(test)]
162mod tests {
163 use super::*;
164 use uuid::Uuid;
165
166 #[test]
167 fn it_builds() {
168 let journal_id = Uuid::new_v4();
169 let entries = vec![EntryInput::builder()
170 .entry_type("'TEST_DR'")
171 .account_id("param.recipient")
172 .layer("'Settled'")
173 .direction("'Settled'")
174 .units("1290")
175 .currency("'BTC'")
176 .build()
177 .unwrap()];
178 let new_journal = NewTxTemplate::builder()
179 .id(Uuid::new_v4())
180 .code("CODE")
181 .tx_input(
182 TxInput::builder()
183 .effective("date('2022-11-01')")
184 .journal_id(format!("'{journal_id}'"))
185 .build()
186 .unwrap(),
187 )
188 .entries(entries)
189 .build()
190 .unwrap();
191 assert_eq!(new_journal.description, None);
192 }
193
194 #[test]
195 fn fails_when_mandatory_fields_are_missing() {
196 let new_account = NewTxTemplate::builder().build();
197 assert!(new_account.is_err());
198 }
199}