1use crate::models::{
6 AddressWithSpec, ContractSpec, EventCondition, FunctionCondition, MatchConditions, Monitor,
7 ScriptLanguage, TransactionCondition, TransactionStatus, TriggerConditions,
8};
9
10pub struct MonitorBuilder {
12 name: String,
13 networks: Vec<String>,
14 paused: bool,
15 addresses: Vec<AddressWithSpec>,
16 match_conditions: MatchConditions,
17 trigger_conditions: Vec<TriggerConditions>,
18 triggers: Vec<String>,
19}
20
21impl Default for MonitorBuilder {
22 fn default() -> Self {
23 Self {
24 name: "TestMonitor".to_string(),
25 networks: vec!["stellar_mainnet".to_string()],
26 paused: false,
27 addresses: vec![AddressWithSpec {
28 address: "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF".to_string(),
29 contract_spec: None,
30 }],
31 match_conditions: MatchConditions {
32 functions: vec![],
33 events: vec![],
34 transactions: vec![],
35 },
36 trigger_conditions: vec![],
37 triggers: vec![],
38 }
39 }
40}
41
42impl MonitorBuilder {
43 pub fn new() -> Self {
44 Self::default()
45 }
46
47 pub fn name(mut self, name: &str) -> Self {
48 self.name = name.to_string();
49 self
50 }
51
52 pub fn networks(mut self, networks: Vec<String>) -> Self {
53 self.networks = networks;
54 self
55 }
56
57 pub fn paused(mut self, paused: bool) -> Self {
58 self.paused = paused;
59 self
60 }
61
62 pub fn address(mut self, address: &str) -> Self {
63 self.addresses = vec![AddressWithSpec {
64 address: address.to_string(),
65 contract_spec: None,
66 }];
67 self
68 }
69
70 pub fn addresses(mut self, addresses: Vec<String>) -> Self {
71 self.addresses = addresses
72 .into_iter()
73 .map(|addr| AddressWithSpec {
74 address: addr,
75 contract_spec: None,
76 })
77 .collect();
78 self
79 }
80
81 pub fn add_address(mut self, address: &str) -> Self {
82 self.addresses.push(AddressWithSpec {
83 address: address.to_string(),
84 contract_spec: None,
85 });
86 self
87 }
88
89 pub fn address_with_spec(mut self, address: &str, spec: ContractSpec) -> Self {
90 self.addresses = vec![AddressWithSpec {
91 address: address.to_string(),
92 contract_spec: Some(spec),
93 }];
94 self
95 }
96
97 pub fn addresses_with_spec(mut self, addresses: Vec<(String, Option<ContractSpec>)>) -> Self {
98 self.addresses = addresses
99 .into_iter()
100 .map(|(addr, spec)| AddressWithSpec {
101 address: addr.to_string(),
102 contract_spec: spec,
103 })
104 .collect();
105 self
106 }
107
108 pub fn function(mut self, signature: &str, expression: Option<String>) -> Self {
109 self.match_conditions.functions.push(FunctionCondition {
110 signature: signature.to_string(),
111 expression,
112 });
113 self
114 }
115
116 pub fn event(mut self, signature: &str, expression: Option<String>) -> Self {
117 self.match_conditions.events.push(EventCondition {
118 signature: signature.to_string(),
119 expression,
120 });
121 self
122 }
123
124 pub fn transaction(mut self, status: TransactionStatus, expression: Option<String>) -> Self {
125 self.match_conditions
126 .transactions
127 .push(TransactionCondition { status, expression });
128 self
129 }
130
131 pub fn trigger_condition(
132 mut self,
133 script_path: &str,
134 timeout_ms: u32,
135 language: ScriptLanguage,
136 arguments: Option<Vec<String>>,
137 ) -> Self {
138 self.trigger_conditions.push(TriggerConditions {
139 script_path: script_path.to_string(),
140 timeout_ms,
141 arguments,
142 language,
143 });
144 self
145 }
146
147 pub fn triggers(mut self, triggers: Vec<String>) -> Self {
148 self.triggers = triggers;
149 self
150 }
151
152 pub fn match_conditions(mut self, match_conditions: MatchConditions) -> Self {
153 self.match_conditions = match_conditions;
154 self
155 }
156
157 pub fn build(self) -> Monitor {
158 Monitor {
159 name: self.name,
160 networks: self.networks,
161 paused: self.paused,
162 addresses: self.addresses,
163 match_conditions: self.match_conditions,
164 trigger_conditions: self.trigger_conditions,
165 triggers: self.triggers,
166 }
167 }
168}
169
170#[cfg(test)]
171mod tests {
172 use crate::models::StellarContractSpec;
173
174 use super::*;
175 use serde_json::json;
176
177 #[test]
178 fn test_default_monitor() {
179 let monitor = MonitorBuilder::new().build();
180
181 assert_eq!(monitor.name, "TestMonitor");
182 assert_eq!(monitor.networks, vec!["stellar_mainnet"]);
183 assert!(!monitor.paused);
184 assert_eq!(monitor.addresses.len(), 1);
185 assert_eq!(
186 monitor.addresses[0].address,
187 "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF"
188 );
189 assert!(monitor.addresses[0].contract_spec.is_none());
190 assert!(monitor.match_conditions.functions.is_empty());
191 assert!(monitor.match_conditions.events.is_empty());
192 assert!(monitor.match_conditions.transactions.is_empty());
193 assert!(monitor.trigger_conditions.is_empty());
194 assert!(monitor.triggers.is_empty());
195 }
196
197 #[test]
198 fn test_basic_builder_methods() {
199 let monitor = MonitorBuilder::new()
200 .name("MyMonitor")
201 .networks(vec!["stellar_testnet".to_string()])
202 .paused(true)
203 .address("GBXGQJWVLWOYHFLVTKWV5FGHA3LNYY2JQKM7OAJAUEQFU6LPCSEFVXON")
204 .build();
205
206 assert_eq!(monitor.name, "MyMonitor");
207 assert_eq!(monitor.networks, vec!["stellar_testnet"]);
208 assert!(monitor.paused);
209 assert_eq!(monitor.addresses.len(), 1);
210 assert_eq!(
211 monitor.addresses[0].address,
212 "GBXGQJWVLWOYHFLVTKWV5FGHA3LNYY2JQKM7OAJAUEQFU6LPCSEFVXON"
213 );
214 }
215
216 #[test]
217 fn test_address_methods() {
218 let monitor = MonitorBuilder::new()
219 .addresses(vec![
220 "GBXGQJWVLWOYHFLVTKWV5FGHA3LNYY2JQKM7OAJAUEQFU6LPCSEFVXON".to_string(),
221 "GCXKMKSWKQYYGTJA7KKTHG3ICTYR7VCXKCO7SBTBHADS45TVCJ4O6NA".to_string(),
222 ])
223 .add_address("GC5SXLNAM3C4NMGK2PXK4R34B5GNZ47FYQ24ZIBFDFOCU6D4KBN4POAE")
224 .build();
225
226 assert_eq!(monitor.addresses.len(), 3);
227 assert_eq!(
228 monitor.addresses[0].address,
229 "GBXGQJWVLWOYHFLVTKWV5FGHA3LNYY2JQKM7OAJAUEQFU6LPCSEFVXON"
230 );
231 assert_eq!(
232 monitor.addresses[1].address,
233 "GCXKMKSWKQYYGTJA7KKTHG3ICTYR7VCXKCO7SBTBHADS45TVCJ4O6NA"
234 );
235 assert_eq!(
236 monitor.addresses[2].address,
237 "GC5SXLNAM3C4NMGK2PXK4R34B5GNZ47FYQ24ZIBFDFOCU6D4KBN4POAE"
238 );
239 }
240
241 #[test]
242 fn test_address_with_abi() {
243 let abi = json!({"some": "abi"});
244 let monitor = MonitorBuilder::new()
245 .address_with_spec(
246 "GBXGQJWVLWOYHFLVTKWV5FGHA3LNYY2JQKM7OAJAUEQFU6LPCSEFVXON",
247 ContractSpec::Stellar(StellarContractSpec::from(abi.clone())),
248 )
249 .build();
250
251 assert_eq!(monitor.addresses.len(), 1);
252 assert_eq!(
253 monitor.addresses[0].address,
254 "GBXGQJWVLWOYHFLVTKWV5FGHA3LNYY2JQKM7OAJAUEQFU6LPCSEFVXON"
255 );
256 assert_eq!(
257 monitor.addresses[0].contract_spec,
258 Some(ContractSpec::Stellar(StellarContractSpec::from(abi)))
259 );
260 }
261
262 #[test]
263 fn test_addresses_with_abi() {
264 let abi1 = json!({"contract_spec": "1"});
265 let abi2 = json!({"contract_spec": "2"});
266 let monitor = MonitorBuilder::new()
267 .addresses_with_spec(vec![
268 (
269 "GBXGQJWVLWOYHFLVTKWV5FGHA3LNYY2JQKM7OAJAUEQFU6LPCSEFVXON".to_string(),
270 Some(ContractSpec::Stellar(StellarContractSpec::from(
271 abi1.clone(),
272 ))),
273 ),
274 (
275 "GCXKMKSWKQYYGTJA7KKTHG3ICTYR7VCXKCO7SBTBHADS45TVCJ4O6NA".to_string(),
276 None,
277 ),
278 (
279 "GC5SXLNAM3C4NMGK2PXK4R34B5GNZ47FYQ24ZIBFDFOCU6D4KBN4POAE".to_string(),
280 Some(ContractSpec::Stellar(StellarContractSpec::from(
281 abi2.clone(),
282 ))),
283 ),
284 ])
285 .build();
286
287 assert_eq!(monitor.addresses.len(), 3);
288 assert_eq!(
289 monitor.addresses[0].address,
290 "GBXGQJWVLWOYHFLVTKWV5FGHA3LNYY2JQKM7OAJAUEQFU6LPCSEFVXON"
291 );
292 assert_eq!(
293 monitor.addresses[0].contract_spec,
294 Some(ContractSpec::Stellar(StellarContractSpec::from(abi1)))
295 );
296 assert_eq!(
297 monitor.addresses[1].address,
298 "GCXKMKSWKQYYGTJA7KKTHG3ICTYR7VCXKCO7SBTBHADS45TVCJ4O6NA"
299 );
300 assert_eq!(monitor.addresses[1].contract_spec, None);
301 assert_eq!(
302 monitor.addresses[2].address,
303 "GC5SXLNAM3C4NMGK2PXK4R34B5GNZ47FYQ24ZIBFDFOCU6D4KBN4POAE"
304 );
305 assert_eq!(
306 monitor.addresses[2].contract_spec,
307 Some(ContractSpec::Stellar(StellarContractSpec::from(abi2)))
308 );
309 }
310
311 #[test]
312 fn test_match_conditions() {
313 let monitor = MonitorBuilder::new()
314 .function(
315 "transfer(to:address,amount:i128)",
316 Some("amount >= 0".to_string()),
317 )
318 .event("transfer_event(from:address,to:address,amount:i128)", None)
319 .transaction(TransactionStatus::Success, None)
320 .build();
321
322 assert_eq!(monitor.match_conditions.functions.len(), 1);
323 assert_eq!(
324 monitor.match_conditions.functions[0].signature,
325 "transfer(to:address,amount:i128)"
326 );
327 assert_eq!(
328 monitor.match_conditions.functions[0].expression,
329 Some("amount >= 0".to_string())
330 );
331 assert_eq!(monitor.match_conditions.events.len(), 1);
332 assert_eq!(
333 monitor.match_conditions.events[0].signature,
334 "transfer_event(from:address,to:address,amount:i128)"
335 );
336 assert_eq!(monitor.match_conditions.transactions.len(), 1);
337 assert_eq!(
338 monitor.match_conditions.transactions[0].status,
339 TransactionStatus::Success
340 );
341 }
342
343 #[test]
344 fn test_match_condition() {
345 let monitor = MonitorBuilder::new()
346 .match_conditions(MatchConditions {
347 functions: vec![FunctionCondition {
348 signature: "transfer(to:address,amount:i128)".to_string(),
349 expression: None,
350 }],
351 events: vec![],
352 transactions: vec![],
353 })
354 .build();
355 assert_eq!(monitor.match_conditions.functions.len(), 1);
356 assert_eq!(
357 monitor.match_conditions.functions[0].signature,
358 "transfer(to:address,amount:i128)"
359 );
360 assert!(monitor.match_conditions.events.is_empty());
361 assert!(monitor.match_conditions.transactions.is_empty());
362 }
363
364 #[test]
365 fn test_trigger_conditions() {
366 let monitor = MonitorBuilder::new()
367 .trigger_condition("script.py", 1000, ScriptLanguage::Python, None)
368 .trigger_condition(
369 "script.js",
370 2000,
371 ScriptLanguage::JavaScript,
372 Some(vec!["-verbose".to_string()]),
373 )
374 .build();
375
376 assert_eq!(monitor.trigger_conditions.len(), 2);
377 assert_eq!(monitor.trigger_conditions[0].script_path, "script.py");
378 assert_eq!(monitor.trigger_conditions[0].timeout_ms, 1000);
379 assert_eq!(
380 monitor.trigger_conditions[0].language,
381 ScriptLanguage::Python
382 );
383 assert_eq!(monitor.trigger_conditions[1].script_path, "script.js");
384 assert_eq!(monitor.trigger_conditions[1].timeout_ms, 2000);
385 assert_eq!(
386 monitor.trigger_conditions[1].language,
387 ScriptLanguage::JavaScript
388 );
389 assert_eq!(
390 monitor.trigger_conditions[1].arguments,
391 Some(vec!["-verbose".to_string()])
392 );
393 }
394
395 #[test]
396 fn test_triggers() {
397 let monitor = MonitorBuilder::new()
398 .triggers(vec!["trigger1".to_string(), "trigger2".to_string()])
399 .build();
400
401 assert_eq!(monitor.triggers.len(), 2);
402 assert_eq!(monitor.triggers[0], "trigger1");
403 assert_eq!(monitor.triggers[1], "trigger2");
404 }
405
406 #[test]
407 fn test_complex_monitor_build() {
408 let abi = json!({
409 "functions": [
410 {
411 "name": "transfer",
412 "parameters": [
413 {"name": "to", "type": "address"},
414 {"name": "amount", "type": "i128"}
415 ]
416 }
417 ]
418 });
419 let monitor = MonitorBuilder::new()
420 .name("ComplexMonitor")
421 .networks(vec![
422 "stellar_mainnet".to_string(),
423 "stellar_testnet".to_string(),
424 ])
425 .paused(true)
426 .addresses(vec![
427 "GBXGQJWVLWOYHFLVTKWV5FGHA3LNYY2JQKM7OAJAUEQFU6LPCSEFVXON".to_string(),
428 "GCXKMKSWKQYYGTJA7KKTHG3ICTYR7VCXKCO7SBTBHADS45TVCJ4O6NA".to_string(),
429 ])
430 .add_address("GC5SXLNAM3C4NMGK2PXK4R34B5GNZ47FYQ24ZIBFDFOCU6D4KBN4POAE")
431 .address_with_spec(
432 "GBXGQJWVLWOYHFLVTKWV5FGHA3LNYY2JQKM7OAJAUEQFU6LPCSEFVXON",
433 ContractSpec::Stellar(StellarContractSpec::from(abi.clone())),
434 )
435 .function(
436 "transfer(to:address,amount:i128)",
437 Some("amount >= 0".to_string()),
438 )
439 .event("transfer_event(from:address,to:address,amount:i128)", None)
440 .transaction(TransactionStatus::Success, None)
441 .trigger_condition("script.py", 1000, ScriptLanguage::Python, None)
442 .triggers(vec!["trigger1".to_string(), "trigger2".to_string()])
443 .build();
444
445 assert_eq!(monitor.name, "ComplexMonitor");
447 assert_eq!(monitor.networks, vec!["stellar_mainnet", "stellar_testnet"]);
448 assert!(monitor.paused);
449 assert_eq!(monitor.addresses.len(), 1); assert_eq!(
451 monitor.addresses[0].address,
452 "GBXGQJWVLWOYHFLVTKWV5FGHA3LNYY2JQKM7OAJAUEQFU6LPCSEFVXON"
453 );
454 assert_eq!(
455 monitor.addresses[0].contract_spec,
456 Some(ContractSpec::Stellar(StellarContractSpec::from(abi)))
457 );
458 assert_eq!(monitor.match_conditions.functions.len(), 1);
459 assert_eq!(
460 monitor.match_conditions.functions[0].expression,
461 Some("amount >= 0".to_string())
462 );
463 assert_eq!(monitor.match_conditions.events.len(), 1);
464 assert_eq!(monitor.match_conditions.transactions.len(), 1);
465 assert_eq!(monitor.trigger_conditions.len(), 1);
466 assert_eq!(monitor.triggers.len(), 2);
467 }
468}