Skip to main content

rustrails_model/validations/
presence.rs

1use serde_json::Value;
2
3use super::{Validator, ValidatorOptions, value_is_blank};
4use crate::errors::{ErrorType, Errors};
5
6/// Validates that an attribute is present and not blank.
7#[derive(Debug, Clone, Default)]
8pub struct PresenceValidator {
9    /// Custom message used when the value is blank.
10    pub message: Option<String>,
11    pub(crate) options: ValidatorOptions,
12}
13
14impl PresenceValidator {
15    /// Creates a new presence validator.
16    #[must_use]
17    pub fn new() -> Self {
18        Self::default()
19    }
20
21    crate::validations::impl_common_validator_methods!();
22
23    /// Overrides the default blank message.
24    #[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}