Skip to main content

karbon_framework/validation/constraints/collection/
unique.rs

1use crate::validation::constraints::{ConstraintResult, ConstraintViolation};
2use std::collections::HashSet;
3use std::hash::Hash;
4
5/// Validates that all elements in a collection are unique.
6///
7/// Equivalent to Symfony's `Unique` constraint.
8pub struct Unique {
9    pub message: String,
10}
11
12impl Default for Unique {
13    fn default() -> Self {
14        Self {
15            message: "This collection should contain only unique elements.".to_string(),
16        }
17    }
18}
19
20impl Unique {
21    pub fn new() -> Self {
22        Self::default()
23    }
24
25    pub fn with_message(mut self, message: impl Into<String>) -> Self {
26        self.message = message.into();
27        self
28    }
29
30    pub fn validate_slice<T: Eq + Hash>(&self, value: &[T]) -> ConstraintResult {
31        let mut seen = HashSet::new();
32        for item in value {
33            if !seen.insert(item) {
34                return Err(ConstraintViolation::new(
35                    "Unique",
36                    &self.message,
37                    format!("count: {}", value.len()),
38                ));
39            }
40        }
41        Ok(())
42    }
43}
44
45#[cfg(test)]
46mod tests {
47    use super::*;
48
49    #[test]
50    fn test_unique_elements() {
51        let constraint = Unique::new();
52        assert!(constraint.validate_slice(&[1, 2, 3, 4]).is_ok());
53        assert!(constraint.validate_slice(&["a", "b", "c"]).is_ok());
54        assert!(constraint.validate_slice::<i32>(&[]).is_ok());
55    }
56
57    #[test]
58    fn test_duplicate_elements() {
59        let constraint = Unique::new();
60        assert!(constraint.validate_slice(&[1, 2, 3, 2]).is_err());
61        assert!(constraint.validate_slice(&["a", "b", "a"]).is_err());
62        assert!(constraint.validate_slice(&[1, 1]).is_err());
63    }
64
65    #[test]
66    fn test_single_element() {
67        let constraint = Unique::new();
68        assert!(constraint.validate_slice(&[42]).is_ok());
69    }
70}