@@ -28,6 +28,7 @@ static CHECKS: &[Check<fn(&Data, &mut Vec<String>)>] = checks![
2828 validate_subteam_of,
2929 validate_team_leads,
3030 validate_team_members,
31+ validate_duplicate_team_entries,
3132 validate_alumni,
3233 validate_archived_teams,
3334 validate_inactive_members,
@@ -240,6 +241,67 @@ fn validate_team_members(data: &Data, errors: &mut Vec<String>) {
240241 } ) ;
241242}
242243
244+ /// Helper for checking duplicates in a list
245+ fn check_duplicates < ' a , I > ( team_name : & str , label : & str , items : I ) -> Result < ( ) , Error >
246+ where
247+ I : IntoIterator < Item = & ' a str > ,
248+ {
249+ let mut seen = HashSet :: new ( ) ;
250+ let mut duplicates = HashSet :: new ( ) ;
251+
252+ for item in items {
253+ if !seen. insert ( item) {
254+ duplicates. insert ( item) ;
255+ }
256+ }
257+
258+ if !duplicates. is_empty ( ) {
259+ let dup_list: Vec < & str > = duplicates. into_iter ( ) . collect ( ) ;
260+ bail ! (
261+ "team `{}` has duplicate {}: {}" ,
262+ team_name,
263+ label,
264+ dup_list. join( ", " )
265+ ) ;
266+ }
267+
268+ Ok ( ( ) )
269+ }
270+
271+ /// Ensure no duplicate entries in team leads, members and alumni
272+ fn validate_duplicate_team_entries ( data : & Data , errors : & mut Vec < String > ) {
273+ wrapper ( data. teams ( ) , errors, |team, errors| {
274+ // Check leads for duplicates
275+ if let Err ( e) = check_duplicates (
276+ team. name ( ) ,
277+ "leads" ,
278+ team. explicit_leads ( ) . iter ( ) . map ( |s| s. as_str ( ) ) ,
279+ ) {
280+ errors. push ( e. to_string ( ) ) ;
281+ }
282+
283+ // Check members for duplicates
284+ if let Err ( e) = check_duplicates (
285+ team. name ( ) ,
286+ "members" ,
287+ team. explicit_members ( ) . iter ( ) . map ( |m| m. github . as_str ( ) ) ,
288+ ) {
289+ errors. push ( e. to_string ( ) ) ;
290+ }
291+
292+ // Check alumni for duplicates
293+ if let Err ( e) = check_duplicates (
294+ team. name ( ) ,
295+ "alumni" ,
296+ team. explicit_alumni ( ) . iter ( ) . map ( |a| a. github . as_str ( ) ) ,
297+ ) {
298+ errors. push ( e. to_string ( ) ) ;
299+ }
300+
301+ Ok ( ( ) )
302+ } ) ;
303+ }
304+
243305/// Alumni team must consist only of automatically populated alumni from the other teams
244306fn validate_alumni ( data : & Data , errors : & mut Vec < String > ) {
245307 let Some ( alumni_team) = data. team ( "alumni" ) else {
0 commit comments