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}