skp_validator_rules/custom/
custom_fn.rs1use skp_validator_core::{Rule, ValidationContext, ValidationErrors, ValidationError, ValidationResult};
4use std::sync::Arc;
5
6pub struct CustomFnRule<T: ?Sized, F>
23where
24 F: Fn(&T, &ValidationContext) -> bool + Send + Sync,
25{
26 pub rule_name: String,
28 pub validator: Arc<F>,
30 pub message: Option<String>,
32 _marker: std::marker::PhantomData<fn(&T)>,
34}
35
36impl<T: ?Sized, F> CustomFnRule<T, F>
37where
38 F: Fn(&T, &ValidationContext) -> bool + Send + Sync,
39{
40 pub fn new(name: impl Into<String>, validator: F) -> Self {
42 Self {
43 rule_name: name.into(),
44 validator: Arc::new(validator),
45 message: None,
46 _marker: std::marker::PhantomData,
47 }
48 }
49
50 pub fn message(mut self, msg: impl Into<String>) -> Self {
52 self.message = Some(msg.into());
53 self
54 }
55
56 fn get_message(&self) -> String {
57 self.message.clone().unwrap_or_else(|| {
58 format!("Validation failed for rule '{}'", self.rule_name)
59 })
60 }
61}
62
63impl<T: ?Sized, F> std::fmt::Debug for CustomFnRule<T, F>
64where
65 F: Fn(&T, &ValidationContext) -> bool + Send + Sync,
66{
67 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
68 f.debug_struct("CustomFnRule")
69 .field("rule_name", &self.rule_name)
70 .field("message", &self.message)
71 .finish()
72 }
73}
74
75impl<T: ?Sized, F> Clone for CustomFnRule<T, F>
76where
77 F: Fn(&T, &ValidationContext) -> bool + Send + Sync,
78{
79 fn clone(&self) -> Self {
80 Self {
81 rule_name: self.rule_name.clone(),
82 validator: Arc::clone(&self.validator),
83 message: self.message.clone(),
84 _marker: std::marker::PhantomData,
85 }
86 }
87}
88
89impl<T: ?Sized, F> Rule<T> for CustomFnRule<T, F>
90where
91 F: Fn(&T, &ValidationContext) -> bool + Send + Sync,
92{
93 fn validate(&self, value: &T, ctx: &ValidationContext) -> ValidationResult<()> {
94 if (self.validator)(value, ctx) {
95 Ok(())
96 } else {
97 Err(ValidationErrors::from_iter([
98 ValidationError::root(&self.rule_name, self.get_message())
99 ]))
100 }
101 }
102
103 fn name(&self) -> &'static str {
104 "custom"
105 }
106
107 fn default_message(&self) -> String {
108 self.get_message()
109 }
110}
111
112pub struct CustomResultRule<T: ?Sized, F>
114where
115 F: Fn(&T, &ValidationContext) -> ValidationResult<()> + Send + Sync,
116{
117 pub rule_name: String,
119 pub validator: Arc<F>,
121 _marker: std::marker::PhantomData<fn(&T)>,
123}
124
125impl<T: ?Sized, F> CustomResultRule<T, F>
126where
127 F: Fn(&T, &ValidationContext) -> ValidationResult<()> + Send + Sync,
128{
129 pub fn new(name: impl Into<String>, validator: F) -> Self {
131 Self {
132 rule_name: name.into(),
133 validator: Arc::new(validator),
134 _marker: std::marker::PhantomData,
135 }
136 }
137}
138
139impl<T: ?Sized, F> std::fmt::Debug for CustomResultRule<T, F>
140where
141 F: Fn(&T, &ValidationContext) -> ValidationResult<()> + Send + Sync,
142{
143 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
144 f.debug_struct("CustomResultRule")
145 .field("rule_name", &self.rule_name)
146 .finish()
147 }
148}
149
150impl<T: ?Sized, F> Clone for CustomResultRule<T, F>
151where
152 F: Fn(&T, &ValidationContext) -> ValidationResult<()> + Send + Sync,
153{
154 fn clone(&self) -> Self {
155 Self {
156 rule_name: self.rule_name.clone(),
157 validator: Arc::clone(&self.validator),
158 _marker: std::marker::PhantomData,
159 }
160 }
161}
162
163impl<T: ?Sized, F> Rule<T> for CustomResultRule<T, F>
164where
165 F: Fn(&T, &ValidationContext) -> ValidationResult<()> + Send + Sync,
166{
167 fn validate(&self, value: &T, ctx: &ValidationContext) -> ValidationResult<()> {
168 (self.validator)(value, ctx)
169 }
170
171 fn name(&self) -> &'static str {
172 "custom"
173 }
174
175 fn default_message(&self) -> String {
176 format!("Validation failed for rule '{}'", self.rule_name)
177 }
178}
179
180#[cfg(test)]
181mod tests {
182 use super::*;
183
184 #[test]
185 fn test_custom_fn() {
186 let rule = CustomFnRule::new("is_positive", |value: &i32, _ctx: &ValidationContext| {
187 *value > 0
188 });
189 let ctx = ValidationContext::default();
190
191 assert!(rule.validate(&5, &ctx).is_ok());
192 assert!(rule.validate(&-1, &ctx).is_err());
193 }
194
195 #[test]
196 fn test_custom_message() {
197 let rule = CustomFnRule::new("is_even", |value: &i32, _ctx: &ValidationContext| {
198 value % 2 == 0
199 }).message("Must be an even number");
200 let ctx = ValidationContext::default();
201
202 let result = rule.validate(&3, &ctx);
203 assert!(result.is_err());
204 let errors = result.unwrap_err();
205 assert!(errors.to_string().contains("Must be an even number"));
206 }
207
208 #[test]
209 fn test_custom_result() {
210 let rule = CustomResultRule::new("complex_validation", |value: &i32, _ctx: &ValidationContext| {
211 if *value < 0 {
212 Err(ValidationErrors::from_iter([
213 ValidationError::root("positive", "Must be positive")
214 ]))
215 } else if *value > 100 {
216 Err(ValidationErrors::from_iter([
217 ValidationError::root("max", "Must be <= 100")
218 ]))
219 } else {
220 Ok(())
221 }
222 });
223 let ctx = ValidationContext::default();
224
225 assert!(rule.validate(&50, &ctx).is_ok());
226 assert!(rule.validate(&-1, &ctx).is_err());
227 assert!(rule.validate(&101, &ctx).is_err());
228 }
229}