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