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