use crate::errors::DataProfilerError;
use std::collections::HashSet;
pub fn validate_unique_column_names(
names: &[String],
source: &str,
) -> Result<(), DataProfilerError> {
let mut seen: HashSet<&str> = HashSet::with_capacity(names.len());
let mut reported: HashSet<&str> = HashSet::new();
let mut duplicates: Vec<String> = Vec::new();
for name in names {
if !seen.insert(name.as_str()) && reported.insert(name.as_str()) {
duplicates.push(name.clone());
}
}
if duplicates.is_empty() {
Ok(())
} else {
Err(DataProfilerError::duplicate_column_name(
&duplicates,
source,
))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn names(v: &[&str]) -> Vec<String> {
v.iter().map(|s| s.to_string()).collect()
}
#[test]
fn unique_column_names_pass() {
assert!(validate_unique_column_names(&names(&["x", "y", "z"]), "CSV header").is_ok());
assert!(validate_unique_column_names(&[], "CSV header").is_ok());
}
#[test]
fn duplicate_first_middle_last_rejected() {
for cols in [
names(&["x", "x", "y"]), names(&["a", "x", "x"]), names(&["a", "x", "b", "x", "c"]),
] {
let err = validate_unique_column_names(&cols, "CSV header").unwrap_err();
assert!(matches!(err, DataProfilerError::DuplicateColumnName { .. }));
}
}
#[test]
fn duplicate_error_names_offender_and_source_only() {
let cols = names(&["id", "amount", "id", "amount", "note"]);
let err = validate_unique_column_names(&cols, "CSV bytes").unwrap_err();
let msg = err.to_string();
assert!(msg.contains("'id'"));
assert!(msg.contains("'amount'"));
assert_eq!(msg.matches("'id'").count(), 1);
assert!(msg.contains("CSV bytes"));
assert!(msg.contains("Duplicate column names:")); }
}