rustrails_model/validations/
acceptance.rs1use serde_json::Value;
2
3use super::{Validator, ValidatorOptions};
4use crate::errors::{ErrorType, Errors};
5
6#[derive(Debug, Clone)]
8pub struct AcceptanceValidator {
9 accepted_values: Vec<Value>,
10 message: Option<String>,
11 pub(crate) options: ValidatorOptions,
12}
13
14impl Default for AcceptanceValidator {
15 fn default() -> Self {
16 Self {
17 accepted_values: vec![
18 Value::Bool(true),
19 Value::String("1".to_string()),
20 Value::String("yes".to_string()),
21 ],
22 message: None,
23 options: ValidatorOptions {
24 allow_nil: true,
25 ..ValidatorOptions::default()
26 },
27 }
28 }
29}
30
31impl AcceptanceValidator {
32 #[must_use]
34 pub fn new() -> Self {
35 Self::default()
36 }
37
38 crate::validations::impl_common_validator_methods!();
39
40 #[must_use]
42 pub fn accept<T>(mut self, values: T) -> Self
43 where
44 T: Into<Vec<Value>>,
45 {
46 self.accepted_values = values.into();
47 self
48 }
49
50 #[must_use]
52 pub fn message(mut self, message: impl Into<String>) -> Self {
53 self.message = Some(message.into());
54 self
55 }
56
57 fn error_message(&self) -> String {
58 self.message
59 .clone()
60 .unwrap_or_else(|| String::from("must be accepted"))
61 }
62}
63
64impl Validator for AcceptanceValidator {
65 fn validate(&self, attribute: &str, value: Option<&Value>, errors: &mut Errors) {
66 if value.is_some_and(|candidate| {
67 !self
68 .accepted_values
69 .iter()
70 .any(|accepted| accepted == candidate)
71 }) {
72 errors.add(attribute, ErrorType::Accepted, self.error_message());
73 }
74 }
75
76 fn name(&self) -> &str {
77 "acceptance"
78 }
79
80 fn options(&self) -> &ValidatorOptions {
81 &self.options
82 }
83}
84
85#[cfg(test)]
86mod tests {
87 use std::collections::HashMap;
88
89 use serde_json::json;
90
91 use super::AcceptanceValidator;
92 use crate::{
93 errors::{ErrorType, Errors},
94 validations::{ValidationSet, Validator},
95 };
96
97 fn validate_acceptance(
98 validator: AcceptanceValidator,
99 value: Option<serde_json::Value>,
100 ) -> Errors {
101 let mut errors = Errors::new();
102 validator.validate("field", value.as_ref(), &mut errors);
103 errors
104 }
105
106 #[test]
107 fn nil_value_is_ignored() {
108 let validator = AcceptanceValidator::new();
109 let mut errors = Errors::new();
110
111 validator.validate("terms", None, &mut errors);
112
113 assert!(errors.is_empty());
114 }
115
116 #[test]
117 fn default_true_is_accepted() {
118 let validator = AcceptanceValidator::new();
119 let mut errors = Errors::new();
120
121 validator.validate("terms", Some(&json!(true)), &mut errors);
122
123 assert!(errors.is_empty());
124 }
125
126 #[test]
127 fn default_yes_is_accepted() {
128 let validator = AcceptanceValidator::new();
129 let mut errors = Errors::new();
130
131 validator.validate("terms", Some(&json!("yes")), &mut errors);
132
133 assert!(errors.is_empty());
134 }
135
136 #[test]
137 fn blank_string_is_rejected() {
138 let validator = AcceptanceValidator::new();
139 let mut errors = Errors::new();
140
141 validator.validate("terms", Some(&json!("")), &mut errors);
142
143 assert_eq!(errors.on("terms")[0].error_type, ErrorType::Accepted);
144 }
145
146 #[test]
147 fn custom_accept_values_are_used() {
148 let validator = AcceptanceValidator::new().accept(vec![json!(1), json!("I agree")]);
149 let mut errors = Errors::new();
150
151 validator.validate("terms", Some(&json!(1)), &mut errors);
152 validator.validate("terms", Some(&json!("nope")), &mut errors);
153
154 assert_eq!(errors.count(), 1);
155 }
156
157 #[test]
158 fn validation_set_skips_nil_when_default_allow_nil_applies() {
159 let mut set = ValidationSet::new();
160 set.add("terms", AcceptanceValidator::new());
161 let mut errors = Errors::new();
162
163 let _ = set.validate(&|_| None, &mut errors);
164
165 assert!(errors.is_empty());
166 }
167
168 #[test]
169 fn default_string_one_is_accepted() {
170 let errors = validate_acceptance(AcceptanceValidator::new(), Some(json!("1")));
171
172 assert!(errors.is_empty());
173 }
174
175 #[test]
176 fn false_is_rejected_by_default() {
177 let errors = validate_acceptance(AcceptanceValidator::new(), Some(json!(false)));
178
179 assert_eq!(errors.on("field")[0].error_type, ErrorType::Accepted);
180 }
181
182 #[test]
183 fn no_is_rejected_by_default() {
184 let errors = validate_acceptance(AcceptanceValidator::new(), Some(json!("no")));
185
186 assert_eq!(errors.on("field")[0].error_type, ErrorType::Accepted);
187 }
188
189 #[test]
190 fn numeric_one_is_not_equal_to_default_string_one() {
191 let errors = validate_acceptance(AcceptanceValidator::new(), Some(json!(1)));
192
193 assert_eq!(errors.on("field")[0].error_type, ErrorType::Accepted);
194 }
195
196 #[test]
197 fn custom_accept_values_replace_defaults() {
198 let errors = validate_acceptance(
199 AcceptanceValidator::new().accept(vec![json!("I agree")]),
200 Some(json!(true)),
201 );
202
203 assert_eq!(errors.on("field")[0].error_type, ErrorType::Accepted);
204 }
205
206 #[test]
207 fn custom_message_is_used() {
208 let errors = validate_acceptance(
209 AcceptanceValidator::new().message("must be accepted explicitly"),
210 Some(json!(false)),
211 );
212
213 assert_eq!(errors.on("field")[0].message, "must be accepted explicitly");
214 }
215
216 #[test]
217 fn allow_blank_skips_blank_values_in_validation_set() {
218 let mut set = ValidationSet::new();
219 set.add("terms", AcceptanceValidator::new().allow_blank());
220 let attrs = HashMap::from([("terms".to_string(), json!(" "))]);
221 let mut errors = Errors::new();
222
223 let _ = set.validate(&|name| attrs.get(name).cloned(), &mut errors);
224
225 assert!(errors.is_empty());
226 }
227
228 #[test]
229 fn custom_boolean_accept_values_are_supported() {
230 let errors = validate_acceptance(
231 AcceptanceValidator::new().accept(vec![json!(false)]),
232 Some(json!(false)),
233 );
234
235 assert!(errors.is_empty());
236 }
237
238 #[test]
239 fn validation_set_adds_error_for_rejected_value() {
240 let mut set = ValidationSet::new();
241 set.add("terms", AcceptanceValidator::new());
242 let attrs = HashMap::from([("terms".to_string(), json!("no"))]);
243 let mut errors = Errors::new();
244
245 let _ = set.validate(&|name| attrs.get(name).cloned(), &mut errors);
246
247 assert_eq!(errors.on("terms")[0].error_type, ErrorType::Accepted);
248 }
249
250 #[test]
251 fn full_message_humanizes_attribute_name() {
252 let mut errors = Errors::new();
253 AcceptanceValidator::new().validate("terms_of_service", Some(&json!(false)), &mut errors);
254
255 assert_eq!(
256 errors.full_messages(),
257 vec!["Terms of service must be accepted".to_string()],
258 );
259 }
260
261 #[test]
262 fn explicit_null_values_are_rejected_by_direct_validation() {
263 let errors = validate_acceptance(AcceptanceValidator::new(), Some(json!(null)));
264
265 assert_eq!(errors.on("field")[0].error_type, ErrorType::Accepted);
266 }
267
268 #[test]
269 fn empty_accept_list_rejects_present_values() {
270 let errors = validate_acceptance(
271 AcceptanceValidator::new().accept(Vec::new()),
272 Some(json!(true)),
273 );
274
275 assert_eq!(errors.on("field")[0].error_type, ErrorType::Accepted);
276 }
277}