1use crate::config::ConfigFileError;
13use serde::{Deserialize, Serialize};
14
15#[derive(Debug, Serialize, Deserialize, Clone)]
16pub struct NetworkConfigCommon {
17 pub network: String,
19 pub from: Option<String>,
23 pub rpc_urls: Option<Vec<String>>,
25 pub explorer_urls: Option<Vec<String>>,
27 pub average_blocktime_ms: Option<u64>,
29 pub is_testnet: Option<bool>,
31 pub tags: Option<Vec<String>>,
33}
34
35impl NetworkConfigCommon {
36 pub fn validate(&self) -> Result<(), ConfigFileError> {
42 if self.network.is_empty() {
44 return Err(ConfigFileError::MissingField("network name".into()));
45 }
46
47 if self.from.is_none() {
49 if self.rpc_urls.is_none() || self.rpc_urls.as_ref().unwrap().is_empty() {
51 return Err(ConfigFileError::MissingField("rpc_urls".into()));
52 }
53 }
54
55 if let Some(urls) = &self.rpc_urls {
57 for url in urls {
58 reqwest::Url::parse(url).map_err(|_| {
59 ConfigFileError::InvalidFormat(format!("Invalid RPC URL: {}", url))
60 })?;
61 }
62 }
63
64 if let Some(urls) = &self.explorer_urls {
65 for url in urls {
66 reqwest::Url::parse(url).map_err(|_| {
67 ConfigFileError::InvalidFormat(format!("Invalid Explorer URL: {}", url))
68 })?;
69 }
70 }
71
72 Ok(())
73 }
74
75 pub fn merge_with_parent(&self, parent: &Self) -> Self {
83 Self {
84 network: self.network.clone(),
85 from: self.from.clone(),
86 rpc_urls: self.rpc_urls.clone().or_else(|| parent.rpc_urls.clone()),
87 explorer_urls: self
88 .explorer_urls
89 .clone()
90 .or_else(|| parent.explorer_urls.clone()),
91 average_blocktime_ms: self.average_blocktime_ms.or(parent.average_blocktime_ms),
92 is_testnet: self.is_testnet.or(parent.is_testnet),
93 tags: merge_tags(&self.tags, &parent.tags),
94 }
95 }
96}
97
98pub fn merge_optional_string_vecs(
107 child: &Option<Vec<String>>,
108 parent: &Option<Vec<String>>,
109) -> Option<Vec<String>> {
110 match (child, parent) {
111 (Some(child), Some(parent)) => {
112 let mut merged = parent.clone();
113 for item in child {
114 if !merged.contains(item) {
115 merged.push(item.clone());
116 }
117 }
118 Some(merged)
119 }
120 (Some(items), None) => Some(items.clone()),
121 (None, Some(items)) => Some(items.clone()),
122 (None, None) => None,
123 }
124}
125
126fn merge_tags(
135 child_tags: &Option<Vec<String>>,
136 parent_tags: &Option<Vec<String>>,
137) -> Option<Vec<String>> {
138 merge_optional_string_vecs(child_tags, parent_tags)
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144 use crate::config::config_file::network::test_utils::*;
145
146 #[test]
147 fn test_validate_success_base_network() {
148 let config = create_network_common("test-network");
149 let result = config.validate();
150 assert!(result.is_ok());
151 }
152
153 #[test]
154 fn test_validate_success_inheriting_network() {
155 let config = create_network_common_with_parent("child-network", "parent-network");
156 let result = config.validate();
157 assert!(result.is_ok());
158 }
159
160 #[test]
161 fn test_validate_empty_network_name() {
162 let mut config = create_network_common("test-network");
163 config.network = String::new();
164
165 let result = config.validate();
166 assert!(result.is_err());
167 assert!(matches!(
168 result.unwrap_err(),
169 ConfigFileError::MissingField(_)
170 ));
171 }
172
173 #[test]
174 fn test_validate_base_network_missing_rpc_urls() {
175 let mut config = create_network_common("test-network");
176 config.rpc_urls = None;
177
178 let result = config.validate();
179 assert!(result.is_err());
180 assert!(matches!(
181 result.unwrap_err(),
182 ConfigFileError::MissingField(_)
183 ));
184 }
185
186 #[test]
187 fn test_validate_base_network_empty_rpc_urls() {
188 let mut config = create_network_common("test-network");
189 config.rpc_urls = Some(vec![]);
190
191 let result = config.validate();
192 assert!(result.is_err());
193 assert!(matches!(
194 result.unwrap_err(),
195 ConfigFileError::MissingField(_)
196 ));
197 }
198
199 #[test]
200 fn test_validate_invalid_rpc_url_format() {
201 let mut config = create_network_common("test-network");
202 config.rpc_urls = Some(vec!["invalid-url".to_string()]);
203
204 let result = config.validate();
205 assert!(result.is_err());
206 assert!(matches!(
207 result.unwrap_err(),
208 ConfigFileError::InvalidFormat(_)
209 ));
210 }
211
212 #[test]
213 fn test_validate_multiple_invalid_rpc_urls() {
214 let mut config = create_network_common("test-network");
215 config.rpc_urls = Some(vec![
216 "https://valid.example.com".to_string(),
217 "invalid-url".to_string(),
218 "also-invalid".to_string(),
219 ]);
220
221 let result = config.validate();
222 assert!(result.is_err());
223 assert!(matches!(
224 result.unwrap_err(),
225 ConfigFileError::InvalidFormat(_)
226 ));
227 }
228
229 #[test]
230 fn test_validate_various_valid_rpc_url_formats() {
231 let mut config = create_network_common("test-network");
232 config.rpc_urls = Some(vec![
233 "https://mainnet.infura.io/v3/key".to_string(),
234 "http://localhost:8545".to_string(),
235 "wss://ws.example.com".to_string(),
236 "https://rpc.example.com:8080/path".to_string(),
237 ]);
238
239 let result = config.validate();
240 assert!(result.is_ok());
241 }
242
243 #[test]
244 fn test_validate_inheriting_network_with_rpc_urls() {
245 let mut config = create_network_common_with_parent("child-network", "parent-network");
246 config.rpc_urls = Some(vec!["https://override.example.com".to_string()]);
247
248 let result = config.validate();
249 assert!(result.is_ok());
250 }
251
252 #[test]
253 fn test_validate_inheriting_network_with_invalid_rpc_urls() {
254 let mut config = create_network_common_with_parent("child-network", "parent-network");
255 config.rpc_urls = Some(vec!["invalid-url".to_string()]);
256
257 let result = config.validate();
258 assert!(result.is_err());
259 assert!(matches!(
260 result.unwrap_err(),
261 ConfigFileError::InvalidFormat(_)
262 ));
263 }
264
265 #[test]
266 fn test_merge_with_parent_child_overrides() {
267 let parent = NetworkConfigCommon {
268 network: "parent".to_string(),
269 from: None,
270 rpc_urls: Some(vec!["https://parent-rpc.example.com".to_string()]),
271 explorer_urls: Some(vec!["https://parent-explorer.example.com".to_string()]),
272 average_blocktime_ms: Some(10000),
273 is_testnet: Some(true),
274 tags: Some(vec!["parent-tag".to_string()]),
275 };
276
277 let child = NetworkConfigCommon {
278 network: "child".to_string(),
279 from: Some("parent".to_string()),
280 rpc_urls: Some(vec!["https://child-rpc.example.com".to_string()]),
281 explorer_urls: Some(vec!["https://child-explorer.example.com".to_string()]),
282 average_blocktime_ms: Some(15000),
283 is_testnet: Some(false),
284 tags: Some(vec!["child-tag".to_string()]),
285 };
286
287 let result = child.merge_with_parent(&parent);
288
289 assert_eq!(result.network, "child");
290 assert_eq!(result.from, Some("parent".to_string()));
291 assert_eq!(
292 result.rpc_urls,
293 Some(vec!["https://child-rpc.example.com".to_string()])
294 );
295 assert_eq!(result.average_blocktime_ms, Some(15000));
296 assert_eq!(result.is_testnet, Some(false));
297 assert_eq!(
298 result.tags,
299 Some(vec!["parent-tag".to_string(), "child-tag".to_string()])
300 );
301 }
302
303 #[test]
304 fn test_merge_with_parent_child_inherits() {
305 let parent = NetworkConfigCommon {
306 network: "parent".to_string(),
307 from: None,
308 rpc_urls: Some(vec!["https://parent-rpc.example.com".to_string()]),
309 explorer_urls: Some(vec!["https://parent-explorer.example.com".to_string()]),
310 average_blocktime_ms: Some(10000),
311 is_testnet: Some(true),
312 tags: Some(vec!["parent-tag".to_string()]),
313 };
314
315 let child = NetworkConfigCommon {
316 network: "child".to_string(),
317 from: Some("parent".to_string()),
318 rpc_urls: None, explorer_urls: None, average_blocktime_ms: None, is_testnet: None, tags: None, };
324
325 let result = child.merge_with_parent(&parent);
326
327 assert_eq!(result.network, "child");
328 assert_eq!(result.from, Some("parent".to_string()));
329 assert_eq!(
330 result.rpc_urls,
331 Some(vec!["https://parent-rpc.example.com".to_string()])
332 );
333 assert_eq!(
334 result.explorer_urls,
335 Some(vec!["https://parent-explorer.example.com".to_string()])
336 );
337 assert_eq!(result.average_blocktime_ms, Some(10000));
338 assert_eq!(result.is_testnet, Some(true));
339 assert_eq!(result.tags, Some(vec!["parent-tag".to_string()]));
340 }
341
342 #[test]
343 fn test_merge_with_parent_mixed_inheritance() {
344 let parent = NetworkConfigCommon {
345 network: "parent".to_string(),
346 from: None,
347 rpc_urls: Some(vec!["https://parent-rpc.example.com".to_string()]),
348 explorer_urls: Some(vec!["https://parent-explorer.example.com".to_string()]),
349 average_blocktime_ms: Some(10000),
350 is_testnet: Some(true),
351 tags: Some(vec!["parent-tag1".to_string(), "parent-tag2".to_string()]),
352 };
353
354 let child = NetworkConfigCommon {
355 network: "child".to_string(),
356 from: Some("parent".to_string()),
357 rpc_urls: Some(vec!["https://child-rpc.example.com".to_string()]), explorer_urls: Some(vec!["https://child-explorer.example.com".to_string()]), average_blocktime_ms: None, is_testnet: Some(false), tags: Some(vec!["child-tag".to_string()]), };
363
364 let result = child.merge_with_parent(&parent);
365
366 assert_eq!(result.network, "child");
367 assert_eq!(
368 result.rpc_urls,
369 Some(vec!["https://child-rpc.example.com".to_string()])
370 );
371 assert_eq!(
372 result.explorer_urls,
373 Some(vec!["https://child-explorer.example.com".to_string()])
374 );
375 assert_eq!(result.average_blocktime_ms, Some(10000)); assert_eq!(result.is_testnet, Some(false)); assert_eq!(
378 result.tags,
379 Some(vec![
380 "parent-tag1".to_string(),
381 "parent-tag2".to_string(),
382 "child-tag".to_string()
383 ])
384 );
385 }
386
387 #[test]
388 fn test_merge_with_parent_both_empty() {
389 let parent = NetworkConfigCommon {
390 network: "parent".to_string(),
391 from: None,
392 rpc_urls: None,
393 explorer_urls: None,
394 average_blocktime_ms: None,
395 is_testnet: None,
396 tags: None,
397 };
398
399 let child = NetworkConfigCommon {
400 network: "child".to_string(),
401 from: Some("parent".to_string()),
402 rpc_urls: None,
403 explorer_urls: None,
404 average_blocktime_ms: None,
405 is_testnet: None,
406 tags: None,
407 };
408
409 let result = child.merge_with_parent(&parent);
410
411 assert_eq!(result.network, "child");
412 assert_eq!(result.from, Some("parent".to_string()));
413 assert_eq!(result.rpc_urls, None);
414 assert_eq!(result.explorer_urls, None);
415 assert_eq!(result.average_blocktime_ms, None);
416 assert_eq!(result.is_testnet, None);
417 assert_eq!(result.tags, None);
418 }
419
420 #[test]
421 fn test_merge_with_parent_complex_tag_merging() {
422 let parent = NetworkConfigCommon {
423 network: "parent".to_string(),
424 from: None,
425 rpc_urls: Some(vec!["https://rpc.example.com".to_string()]),
426 explorer_urls: Some(vec!["https://explorer.example.com".to_string()]),
427 average_blocktime_ms: Some(12000),
428 is_testnet: Some(true),
429 tags: Some(vec![
430 "production".to_string(),
431 "mainnet".to_string(),
432 "shared".to_string(),
433 ]),
434 };
435
436 let child = NetworkConfigCommon {
437 network: "child".to_string(),
438 from: Some("parent".to_string()),
439 rpc_urls: None,
440 explorer_urls: None,
441 average_blocktime_ms: None,
442 is_testnet: None,
443 tags: Some(vec![
444 "shared".to_string(),
445 "custom".to_string(),
446 "override".to_string(),
447 ]),
448 };
449
450 let result = child.merge_with_parent(&parent);
451
452 let expected_tags = vec![
454 "production".to_string(),
455 "mainnet".to_string(),
456 "shared".to_string(), "custom".to_string(),
458 "override".to_string(),
459 ];
460 assert_eq!(result.tags, Some(expected_tags));
461 }
462
463 #[test]
464 fn test_merge_optional_string_vecs_both_some() {
465 let child = Some(vec!["child1".to_string(), "child2".to_string()]);
466 let parent = Some(vec!["parent1".to_string(), "parent2".to_string()]);
467 let result = merge_optional_string_vecs(&child, &parent);
468 assert_eq!(
469 result,
470 Some(vec![
471 "parent1".to_string(),
472 "parent2".to_string(),
473 "child1".to_string(),
474 "child2".to_string()
475 ])
476 );
477 }
478
479 #[test]
480 fn test_merge_optional_string_vecs_child_some_parent_none() {
481 let child = Some(vec!["child1".to_string()]);
482 let parent = None;
483 let result = merge_optional_string_vecs(&child, &parent);
484 assert_eq!(result, Some(vec!["child1".to_string()]));
485 }
486
487 #[test]
488 fn test_merge_optional_string_vecs_child_none_parent_some() {
489 let child = None;
490 let parent = Some(vec!["parent1".to_string()]);
491 let result = merge_optional_string_vecs(&child, &parent);
492 assert_eq!(result, Some(vec!["parent1".to_string()]));
493 }
494
495 #[test]
496 fn test_merge_optional_string_vecs_both_none() {
497 let child = None;
498 let parent = None;
499 let result = merge_optional_string_vecs(&child, &parent);
500 assert_eq!(result, None);
501 }
502
503 #[test]
504 fn test_merge_optional_string_vecs_duplicate_handling() {
505 let child = Some(vec!["duplicate".to_string(), "child1".to_string()]);
507 let parent = Some(vec!["duplicate".to_string(), "parent1".to_string()]);
508 let result = merge_optional_string_vecs(&child, &parent);
509 assert_eq!(
510 result,
511 Some(vec![
512 "duplicate".to_string(),
513 "parent1".to_string(),
514 "child1".to_string()
515 ])
516 );
517 }
518
519 #[test]
520 fn test_merge_optional_string_vecs_empty_vectors() {
521 let child = Some(vec![]);
523 let parent = Some(vec!["parent1".to_string()]);
524 let result = merge_optional_string_vecs(&child, &parent);
525 assert_eq!(result, Some(vec!["parent1".to_string()]));
526
527 let child = Some(vec!["child1".to_string()]);
529 let parent = Some(vec![]);
530 let result = merge_optional_string_vecs(&child, &parent);
531 assert_eq!(result, Some(vec!["child1".to_string()]));
532
533 let child = Some(vec![]);
535 let parent = Some(vec![]);
536 let result = merge_optional_string_vecs(&child, &parent);
537 assert_eq!(result, Some(vec![]));
538 }
539
540 #[test]
541 fn test_merge_optional_string_vecs_multiple_duplicates() {
542 let child = Some(vec![
543 "a".to_string(),
544 "b".to_string(),
545 "c".to_string(),
546 "a".to_string(),
547 ]);
548 let parent = Some(vec!["b".to_string(), "d".to_string(), "a".to_string()]);
549 let result = merge_optional_string_vecs(&child, &parent);
550
551 let expected = vec![
553 "b".to_string(),
554 "d".to_string(),
555 "a".to_string(),
556 "c".to_string(),
557 ];
558 assert_eq!(result, Some(expected));
559 }
560
561 #[test]
562 fn test_merge_optional_string_vecs_single_item_vectors() {
563 let child = Some(vec!["child".to_string()]);
564 let parent = Some(vec!["parent".to_string()]);
565 let result = merge_optional_string_vecs(&child, &parent);
566 assert_eq!(
567 result,
568 Some(vec!["parent".to_string(), "child".to_string()])
569 );
570 }
571
572 #[test]
573 fn test_merge_optional_string_vecs_identical_vectors() {
574 let child = Some(vec!["same1".to_string(), "same2".to_string()]);
575 let parent = Some(vec!["same1".to_string(), "same2".to_string()]);
576 let result = merge_optional_string_vecs(&child, &parent);
577 assert_eq!(result, Some(vec!["same1".to_string(), "same2".to_string()]));
578 }
579
580 #[test]
582 fn test_network_config_common_clone() {
583 let config = create_network_common("test-network");
584 let cloned = config.clone();
585
586 assert_eq!(config.network, cloned.network);
587 assert_eq!(config.from, cloned.from);
588 assert_eq!(config.rpc_urls, cloned.rpc_urls);
589 assert_eq!(config.average_blocktime_ms, cloned.average_blocktime_ms);
590 assert_eq!(config.is_testnet, cloned.is_testnet);
591 assert_eq!(config.tags, cloned.tags);
592 }
593
594 #[test]
595 fn test_network_config_common_debug() {
596 let config = create_network_common("test-network");
597 let debug_str = format!("{:?}", config);
598
599 assert!(debug_str.contains("NetworkConfigCommon"));
600 assert!(debug_str.contains("test-network"));
601 }
602
603 #[test]
604 fn test_validate_with_unicode_network_name() {
605 let mut config = create_network_common("test-network");
606 config.network = "测试网络".to_string();
607
608 let result = config.validate();
609 assert!(result.is_ok());
610 }
611
612 #[test]
613 fn test_validate_with_unicode_rpc_urls() {
614 let mut config = create_network_common("test-network");
615 config.rpc_urls = Some(vec!["https://测试.example.com".to_string()]);
616
617 let result = config.validate();
618 assert!(result.is_ok());
619 }
620
621 #[test]
622 fn test_merge_with_parent_preserves_child_network_name() {
623 let parent = NetworkConfigCommon {
624 network: "parent-name".to_string(),
625 from: None,
626 rpc_urls: Some(vec!["https://parent.example.com".to_string()]),
627 explorer_urls: Some(vec!["https://parent.example.com".to_string()]),
628 average_blocktime_ms: Some(10000),
629 is_testnet: Some(true),
630 tags: None,
631 };
632
633 let child = NetworkConfigCommon {
634 network: "child-name".to_string(),
635 from: Some("parent-name".to_string()),
636 rpc_urls: None,
637 explorer_urls: None,
638 average_blocktime_ms: None,
639 is_testnet: None,
640 tags: None,
641 };
642
643 let result = child.merge_with_parent(&parent);
644
645 assert_eq!(result.network, "child-name");
647 assert_eq!(result.from, Some("parent-name".to_string()));
648 }
649
650 #[test]
651 fn test_merge_with_parent_preserves_child_from_field() {
652 let parent = NetworkConfigCommon {
653 network: "parent".to_string(),
654 from: Some("grandparent".to_string()),
655 rpc_urls: Some(vec!["https://parent.example.com".to_string()]),
656 explorer_urls: Some(vec!["https://parent.example.com".to_string()]),
657 average_blocktime_ms: Some(10000),
658 is_testnet: Some(true),
659 tags: None,
660 };
661
662 let child = NetworkConfigCommon {
663 network: "child".to_string(),
664 from: Some("parent".to_string()),
665 rpc_urls: None,
666 explorer_urls: None,
667 average_blocktime_ms: None,
668 is_testnet: None,
669 tags: None,
670 };
671
672 let result = child.merge_with_parent(&parent);
673
674 assert_eq!(result.from, Some("parent".to_string()));
676 }
677}