dataprof_core/
validation.rs1use crate::errors::DataProfilerError;
2use std::collections::HashSet;
3
4pub fn validate_unique_column_names(
19 names: &[String],
20 source: &str,
21) -> Result<(), DataProfilerError> {
22 let mut seen: HashSet<&str> = HashSet::with_capacity(names.len());
23 let mut reported: HashSet<&str> = HashSet::new();
24 let mut duplicates: Vec<String> = Vec::new();
25 for name in names {
26 if !seen.insert(name.as_str()) && reported.insert(name.as_str()) {
27 duplicates.push(name.clone());
28 }
29 }
30 if duplicates.is_empty() {
31 Ok(())
32 } else {
33 Err(DataProfilerError::duplicate_column_name(
34 &duplicates,
35 source,
36 ))
37 }
38}
39
40#[cfg(test)]
41mod tests {
42 use super::*;
43
44 fn names(v: &[&str]) -> Vec<String> {
45 v.iter().map(|s| s.to_string()).collect()
46 }
47
48 #[test]
49 fn unique_column_names_pass() {
50 assert!(validate_unique_column_names(&names(&["x", "y", "z"]), "CSV header").is_ok());
51 assert!(validate_unique_column_names(&[], "CSV header").is_ok());
52 }
53
54 #[test]
55 fn duplicate_first_middle_last_rejected() {
56 for cols in [
57 names(&["x", "x", "y"]), names(&["a", "x", "x"]), names(&["a", "x", "b", "x", "c"]),
60 ] {
61 let err = validate_unique_column_names(&cols, "CSV header").unwrap_err();
62 assert!(matches!(err, DataProfilerError::DuplicateColumnName { .. }));
63 }
64 }
65
66 #[test]
67 fn duplicate_error_names_offender_and_source_only() {
68 let cols = names(&["id", "amount", "id", "amount", "note"]);
69 let err = validate_unique_column_names(&cols, "CSV bytes").unwrap_err();
70 let msg = err.to_string();
71 assert!(msg.contains("'id'"));
73 assert!(msg.contains("'amount'"));
74 assert_eq!(msg.matches("'id'").count(), 1);
75 assert!(msg.contains("CSV bytes"));
76 assert!(msg.contains("Duplicate column names:")); }
78}