|
77 | 77 |
|
78 | 78 | use std::collections::HashSet; |
79 | 79 | use std::fmt::Debug; |
| 80 | +use std::hash::Hash; |
80 | 81 |
|
81 | 82 | use serde::Deserialize; |
82 | 83 | use serde::Serialize; |
83 | 84 | use thiserror::Error; |
84 | 85 |
|
| 86 | +/// Finds duplicate values in a slice by extracting a key from each item. |
| 87 | +/// |
| 88 | +/// Returns a sorted, deduplicated list of keys that appear more than once. |
| 89 | +fn find_duplicates<'a, T, K, F>(items: &'a [T], key_fn: F) -> Vec<K> |
| 90 | +where |
| 91 | + K: 'a + Clone + Eq + Hash + Ord, |
| 92 | + F: Fn(&'a T) -> &'a K, |
| 93 | +{ |
| 94 | + let mut seen = HashSet::new(); |
| 95 | + let mut duplicates: Vec<_> = items |
| 96 | + .iter() |
| 97 | + .filter_map(|item| { |
| 98 | + let key = key_fn(item); |
| 99 | + if !seen.insert(key) { |
| 100 | + Some(key.clone()) |
| 101 | + } else { |
| 102 | + None |
| 103 | + } |
| 104 | + }) |
| 105 | + .collect(); |
| 106 | + duplicates.sort(); |
| 107 | + duplicates.dedup(); |
| 108 | + duplicates |
| 109 | +} |
| 110 | + |
85 | 111 | /// A strongly-typed region name. |
86 | 112 | /// |
87 | 113 | /// This newtype wrapper ensures region names cannot be confused with other string types |
@@ -552,39 +578,8 @@ impl<T: Clone + Debug + Eq + PartialEq + Serialize + for<'a> Deserialize<'a>> |
552 | 578 | let mut error = ValidationError::default(); |
553 | 579 | let all_region_names: HashSet<_> = self.regions.iter().map(|r| &r.name).collect(); |
554 | 580 |
|
555 | | - // Find duplicate region names, ensuring each duplicate is reported only once. |
556 | | - let mut seen_regions = HashSet::new(); |
557 | | - let mut duplicate_regions: Vec<_> = self |
558 | | - .regions |
559 | | - .iter() |
560 | | - .filter_map(|r| { |
561 | | - if !seen_regions.insert(&r.name) { |
562 | | - Some(r.name.clone()) |
563 | | - } else { |
564 | | - None |
565 | | - } |
566 | | - }) |
567 | | - .collect(); |
568 | | - duplicate_regions.sort(); |
569 | | - duplicate_regions.dedup(); |
570 | | - error.duplicate_region_names = duplicate_regions; |
571 | | - |
572 | | - // Find duplicate topology names, ensuring each duplicate is reported only once. |
573 | | - let mut seen_topologies = HashSet::new(); |
574 | | - let mut duplicate_topologies: Vec<_> = self |
575 | | - .topologies |
576 | | - .iter() |
577 | | - .filter_map(|t| { |
578 | | - if !seen_topologies.insert(&t.name) { |
579 | | - Some(t.name.clone()) |
580 | | - } else { |
581 | | - None |
582 | | - } |
583 | | - }) |
584 | | - .collect(); |
585 | | - duplicate_topologies.sort(); |
586 | | - duplicate_topologies.dedup(); |
587 | | - error.duplicate_topology_names = duplicate_topologies; |
| 581 | + error.duplicate_region_names = find_duplicates(&self.regions, |r| &r.name); |
| 582 | + error.duplicate_topology_names = find_duplicates(&self.topologies, |t| &t.name); |
588 | 583 |
|
589 | 584 | // Find all unique unknown regions across all topologies. |
590 | 585 | let mut unknown_regions: Vec<_> = self |
|
0 commit comments