skp_validator_rules/collection/
unique_items.rs1use skp_validator_core::{Rule, ValidationContext, ValidationErrors, ValidationError, ValidationResult};
4use std::collections::HashSet;
5use std::hash::Hash;
6
7#[derive(Debug, Clone, Default)]
22pub struct UniqueItemsRule {
23 pub message: Option<String>,
25}
26
27impl UniqueItemsRule {
28 pub fn new() -> Self {
30 Self::default()
31 }
32
33 pub fn message(mut self, msg: impl Into<String>) -> Self {
35 self.message = Some(msg.into());
36 self
37 }
38
39 fn get_message(&self) -> String {
40 self.message.clone().unwrap_or_else(|| "All items must be unique".to_string())
41 }
42}
43
44impl<T: Eq + Hash> Rule<Vec<T>> for UniqueItemsRule {
45 fn validate(&self, value: &Vec<T>, _ctx: &ValidationContext) -> ValidationResult<()> {
46 let mut seen = HashSet::new();
47 let mut duplicates = 0;
48
49 for item in value {
50 if !seen.insert(item) {
51 duplicates += 1;
52 }
53 }
54
55 if duplicates == 0 {
56 Ok(())
57 } else {
58 Err(ValidationErrors::from_iter([
59 ValidationError::root("unique_items", self.get_message())
60 .with_param("duplicate_count", duplicates as i64)
61 ]))
62 }
63 }
64
65 fn name(&self) -> &'static str {
66 "unique_items"
67 }
68
69 fn default_message(&self) -> String {
70 self.get_message()
71 }
72}
73
74impl<T: Eq + Hash> Rule<[T]> for UniqueItemsRule {
75 fn validate(&self, value: &[T], _ctx: &ValidationContext) -> ValidationResult<()> {
76 let mut seen = HashSet::new();
77 let mut duplicates = 0;
78
79 for item in value {
80 if !seen.insert(item) {
81 duplicates += 1;
82 }
83 }
84
85 if duplicates == 0 {
86 Ok(())
87 } else {
88 Err(ValidationErrors::from_iter([
89 ValidationError::root("unique_items", self.get_message())
90 .with_param("duplicate_count", duplicates as i64)
91 ]))
92 }
93 }
94
95 fn name(&self) -> &'static str {
96 "unique_items"
97 }
98
99 fn default_message(&self) -> String {
100 self.get_message()
101 }
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107
108 #[test]
109 fn test_unique_strings() {
110 let rule = UniqueItemsRule::new();
111 let ctx = ValidationContext::default();
112
113 assert!(rule.validate(&vec!["a", "b", "c"], &ctx).is_ok());
114 assert!(rule.validate(&vec!["a", "b", "a"], &ctx).is_err());
115 }
116
117 #[test]
118 fn test_unique_numbers() {
119 let rule = UniqueItemsRule::new();
120 let ctx = ValidationContext::default();
121
122 assert!(rule.validate(&vec![1, 2, 3, 4, 5], &ctx).is_ok());
123 assert!(rule.validate(&vec![1, 2, 3, 2, 5], &ctx).is_err());
124 }
125
126 #[test]
127 fn test_empty_is_valid() {
128 let rule = UniqueItemsRule::new();
129 let ctx = ValidationContext::default();
130
131 assert!(rule.validate(&Vec::<i32>::new(), &ctx).is_ok());
132 }
133
134 #[test]
135 fn test_single_item() {
136 let rule = UniqueItemsRule::new();
137 let ctx = ValidationContext::default();
138
139 assert!(rule.validate(&vec![1], &ctx).is_ok());
140 }
141}