From c318366fab92a6e3f4e7953f116dcf3573dcfd90 Mon Sep 17 00:00:00 2001 From: vivekjami Date: Thu, 13 Nov 2025 00:06:09 +0530 Subject: [PATCH] Add validation for duplicate team entries - Add explicit_leads() method to expose team leads - Add validate_duplicate_team_entries() to check for duplicates - Check leads, members, and alumni for duplicates - Collect all errors for better developer experience --- src/schema.rs | 4 ++++ src/validate.rs | 62 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/src/schema.rs b/src/schema.rs index 976eba916..feb1427d5 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -517,6 +517,10 @@ impl Team { self.people.alumni.as_ref().map_or(&[], Vec::as_slice) } + pub(crate) fn explicit_leads(&self) -> &[String] { + &self.people.leads + } + pub(crate) fn contains_person(&self, data: &Data, person: &Person) -> Result { let members = self.members(data)?; Ok(members.contains(person.github())) diff --git a/src/validate.rs b/src/validate.rs index 258568fa0..dcc9f9562 100644 --- a/src/validate.rs +++ b/src/validate.rs @@ -28,6 +28,7 @@ static CHECKS: &[Check)>] = checks![ validate_subteam_of, validate_team_leads, validate_team_members, + validate_duplicate_team_entries, validate_alumni, validate_archived_teams, validate_inactive_members, @@ -236,6 +237,67 @@ fn validate_team_members(data: &Data, errors: &mut Vec) { }); } +/// Helper for checking duplicates in a list +fn check_duplicates<'a, I>(team_name: &str, label: &str, items: I) -> Result<(), Error> +where + I: IntoIterator, +{ + let mut seen = HashSet::new(); + let mut duplicates = HashSet::new(); + + for item in items { + if !seen.insert(item) { + duplicates.insert(item); + } + } + + if !duplicates.is_empty() { + let dup_list: Vec<&str> = duplicates.into_iter().collect(); + bail!( + "team `{}` has duplicate {}: {}", + team_name, + label, + dup_list.join(", ") + ); + } + + Ok(()) +} + +/// Ensure no duplicate entries in team leads, members and alumni +fn validate_duplicate_team_entries(data: &Data, errors: &mut Vec) { + wrapper(data.teams(), errors, |team, errors| { + // Check leads for duplicates + if let Err(e) = check_duplicates( + team.name(), + "leads", + team.explicit_leads().iter().map(|s| s.as_str()), + ) { + errors.push(e.to_string()); + } + + // Check members for duplicates + if let Err(e) = check_duplicates( + team.name(), + "members", + team.explicit_members().iter().map(|m| m.github.as_str()), + ) { + errors.push(e.to_string()); + } + + // Check alumni for duplicates + if let Err(e) = check_duplicates( + team.name(), + "alumni", + team.explicit_alumni().iter().map(|a| a.github.as_str()), + ) { + errors.push(e.to_string()); + } + + Ok(()) + }); +} + /// Alumni team must consist only of automatically populated alumni from the other teams fn validate_alumni(data: &Data, errors: &mut Vec) { let Some(alumni_team) = data.team("alumni") else {