rustrails_model/validations/
presence.rs1use serde_json::Value;
2
3use super::{Validator, ValidatorOptions, value_is_blank};
4use crate::errors::{ErrorType, Errors};
5
6#[derive(Debug, Clone, Default)]
8pub struct PresenceValidator {
9 pub message: Option<String>,
11 pub(crate) options: ValidatorOptions,
12}
13
14impl PresenceValidator {
15 #[must_use]
17 pub fn new() -> Self {
18 Self::default()
19 }
20
21 crate::validations::impl_common_validator_methods!();
22
23 #[must_use]
25 pub fn message(mut self, message: impl Into<String>) -> Self {
26 self.message = Some(message.into());
27 self
28 }
29
30 fn error_message(&self) -> String {
31 self.message
32 .clone()
33 .unwrap_or_else(|| "can't be blank".to_string())
34 }
35}
36
37impl Validator for PresenceValidator {
38 fn validate(&self, attribute: &str, value: Option<&Value>, errors: &mut Errors) {
39 if value_is_blank(value) {
40 errors.add(attribute, ErrorType::Blank, self.error_message());
41 }
42 }
43
44 fn name(&self) -> &str {
45 "presence"
46 }
47
48 fn options(&self) -> &ValidatorOptions {
49 &self.options
50 }
51}
52
53#[cfg(test)]
54mod tests {
55 use serde_json::json;
56
57 use super::PresenceValidator;
58 use crate::{
59 errors::{ErrorType, Errors},
60 validations::{ValidationSet, Validator},
61 };
62
63 fn validate_value(validator: PresenceValidator, value: Option<serde_json::Value>) -> Errors {
64 let mut errors = Errors::new();
65 validator.validate("field", value.as_ref(), &mut errors);
66 errors
67 }
68
69 #[test]
70 fn nil_fails() {
71 let validator = PresenceValidator::new();
72 let mut errors = Errors::new();
73
74 validator.validate("name", None, &mut errors);
75
76 assert_eq!(errors.on("name")[0].error_type, ErrorType::Blank);
77 }
78
79 #[test]
80 fn null_fails() {
81 let validator = PresenceValidator::new();
82 let mut errors = Errors::new();
83
84 validator.validate("name", Some(&json!(null)), &mut errors);
85
86 assert_eq!(errors.on("name")[0].error_type, ErrorType::Blank);
87 }
88
89 #[test]
90 fn empty_string_fails() {
91 let validator = PresenceValidator::new();
92 let mut errors = Errors::new();
93
94 validator.validate("name", Some(&json!("")), &mut errors);
95
96 assert_eq!(errors.on("name")[0].message, "can't be blank");
97 }
98
99 #[test]
100 fn whitespace_string_fails() {
101 let validator = PresenceValidator::new();
102 let mut errors = Errors::new();
103
104 validator.validate("name", Some(&json!(" ")), &mut errors);
105
106 assert_eq!(errors.on("name")[0].error_type, ErrorType::Blank);
107 }
108
109 #[test]
110 fn false_is_treated_as_blank() {
111 let validator = PresenceValidator::new();
112 let mut errors = Errors::new();
113
114 validator.validate("published", Some(&json!(false)), &mut errors);
115
116 assert_eq!(errors.on("published")[0].error_type, ErrorType::Blank);
117 }
118
119 #[test]
120 fn present_string_passes() {
121 let validator = PresenceValidator::new();
122 let mut errors = Errors::new();
123
124 validator.validate("name", Some(&json!("Alice")), &mut errors);
125
126 assert!(errors.is_empty());
127 }
128
129 #[test]
130 fn number_passes() {
131 let validator = PresenceValidator::new();
132 let mut errors = Errors::new();
133
134 validator.validate("age", Some(&json!(42)), &mut errors);
135
136 assert!(errors.is_empty());
137 }
138
139 #[test]
140 fn custom_message_is_used() {
141 let validator = PresenceValidator::new().message("must exist");
142 let mut errors = Errors::new();
143
144 validator.validate("name", None, &mut errors);
145
146 assert_eq!(errors.on("name")[0].message, "must exist");
147 }
148
149 #[test]
150 fn empty_array_fails() {
151 let errors = validate_value(PresenceValidator::new(), Some(json!([])));
152
153 assert_eq!(errors.on("field")[0].error_type, ErrorType::Blank);
154 }
155
156 #[test]
157 fn empty_object_fails() {
158 let errors = validate_value(PresenceValidator::new(), Some(json!({})));
159
160 assert_eq!(errors.on("field")[0].error_type, ErrorType::Blank);
161 }
162
163 #[test]
164 fn non_empty_array_passes() {
165 let errors = validate_value(PresenceValidator::new(), Some(json!(["tag"])));
166
167 assert!(errors.is_empty());
168 }
169
170 #[test]
171 fn non_empty_object_passes() {
172 let errors = validate_value(PresenceValidator::new(), Some(json!({ "name": "Alice" })));
173
174 assert!(errors.is_empty());
175 }
176
177 #[test]
178 fn true_is_present() {
179 let errors = validate_value(PresenceValidator::new(), Some(json!(true)));
180
181 assert!(errors.is_empty());
182 }
183
184 #[test]
185 fn tab_and_newline_only_string_fails() {
186 let errors = validate_value(PresenceValidator::new(), Some(json!("\t\n")));
187
188 assert_eq!(errors.on("field")[0].error_type, ErrorType::Blank);
189 }
190
191 #[test]
192 fn allow_nil_skips_none_in_validation_set() {
193 let mut set = ValidationSet::new();
194 set.add("nickname", PresenceValidator::new().allow_nil());
195 let mut errors = Errors::new();
196
197 let _ = set.validate(&|_| None, &mut errors);
198
199 assert!(errors.is_empty());
200 }
201
202 #[test]
203 fn allow_blank_skips_whitespace_in_validation_set() {
204 let mut set = ValidationSet::new();
205 set.add("nickname", PresenceValidator::new().allow_blank());
206 let mut errors = Errors::new();
207
208 let _ = set.validate(&|_| Some(json!(" ")), &mut errors);
209
210 assert!(errors.is_empty());
211 }
212
213 #[test]
214 fn allow_blank_skips_empty_collection_in_validation_set() {
215 let mut set = ValidationSet::new();
216 set.add("tags", PresenceValidator::new().allow_blank());
217 let mut errors = Errors::new();
218
219 let _ = set.validate(&|_| Some(json!([])), &mut errors);
220
221 assert!(errors.is_empty());
222 }
223
224 #[test]
225 fn allow_blank_skips_empty_object_in_validation_set() {
226 let mut set = ValidationSet::new();
227 set.add("profile", PresenceValidator::new().allow_blank());
228 let mut errors = Errors::new();
229
230 let _ = set.validate(&|_| Some(json!({})), &mut errors);
231
232 assert!(errors.is_empty());
233 }
234
235 #[test]
236 fn full_message_is_humanized() {
237 let errors = validate_value(PresenceValidator::new(), None);
238
239 assert_eq!(
240 errors.full_messages(),
241 vec!["Field can't be blank".to_string()]
242 );
243 }
244}