valitron/register/
message.rs

1use std::collections::HashMap;
2
3use serde::{Deserialize, Serialize};
4
5use crate::{rule::IntoRuleList, ser::Serializer, Validatable, Value, ValueMap};
6
7use super::{field_name, FieldNames, InnerValidator, IntoFieldName, MessageKey, ValidatorError};
8
9pub trait IntoMessage {
10    fn into_message(rule: &'static str, field: &FieldNames, value: &Value) -> Self;
11}
12
13type CoreValidator<'v> = InnerValidator<String, HashMap<FieldNames, HashMap<&'v str, &'v str>>>;
14
15/// register a string message validator
16/// ## This is an example:
17///
18/// ```rust
19/// # use serde::Serialize;
20/// # use valitron::{custom, RuleExt, Rule, ValidPhrase};
21/// #[derive(Serialize, Debug)]
22/// struct Person {
23///     introduce: &'static str,
24///     age: u8,
25///     weight: f32,
26/// }
27/// fn run() {
28///     let validator = ValidPhrase::new()
29///         .rule("introduce", Required.and(StartWith("I am")))
30///         .rule("age", custom(age_range))
31///         .message([
32///             ("introduce.required", "{field} is required"),
33///             (
34///                 "introduce.start_with",
35///                 "{field} should be starts with `I am`",
36///             ),
37///             ("age.custom", "age {value} is not in the range"),
38///         ]);
39///     let person = Person {
40///         introduce: "hi",
41///         age: 18,
42///         weight: 20.0,
43///     };
44///     let res = validator.validate(person).unwrap_err();
45///     assert!(res.len() == 2);
46///     assert_eq!(res.get("introduce").unwrap()[0], "introduce should be starts with `I am`");
47///     assert_eq!(res.get("age").unwrap()[0], "age 18 is not in the range");
48/// }
49///
50/// fn age_range(age: &mut u8) -> Result<(), String> {
51///     if *age >= 25 && *age <= 45 {
52///         Ok(())
53///     } else {
54///         Err("age should be between 25 and 45".into())
55///     }
56/// }
57///
58/// #[derive(Clone)]
59/// struct Required;
60///
61/// impl Rule for Required {
62///     type Message = String;
63///
64///     fn call(&mut self, data: &mut valitron::Value) -> bool {
65///         match data {
66///             valitron::Value::String(s) => s.len() > 0,
67///             _ => false,
68///         }
69///     }
70///     fn message(&self) -> Self::Message {
71///         "required msg".to_string()
72///     }
73///
74///     const NAME: &'static str = "required";
75/// }
76///
77/// #[derive(Clone)]
78/// struct StartWith(&'static str);
79///
80/// impl Rule for StartWith {
81///     type Message = String;
82///     
83///     fn call(&mut self, data: &mut valitron::Value) -> bool {
84///         match data {
85///             valitron::Value::String(s) => s.starts_with(self.0),
86///             _ => false,
87///         }
88///     }
89///     fn message(&self) -> Self::Message {
90///         format!("this field must start with {}", self.0)
91///     }
92///     
93///     const NAME: &'static str = "start_with";
94/// }
95/// ```
96#[derive(Default, Clone)]
97pub struct ValidPhrase<'v>(CoreValidator<'v>);
98
99impl<'v> ValidPhrase<'v> {
100    /// init a new ValidPhrase
101    pub fn new() -> Self {
102        Self::default()
103    }
104
105    /// validate given data
106    pub fn validate<T>(self, data: T) -> Result<(), ValidatorError<String>>
107    where
108        T: Serialize,
109    {
110        let value = data.serialize(Serializer).unwrap();
111
112        debug_assert!(self.0.exist_field(&value));
113
114        let mut value_map = ValueMap::new(value);
115
116        self.inner_validate(&mut value_map).ok()
117    }
118
119    /// validate given data and can modify it
120    pub fn validate_mut<'de, T>(self, data: T) -> Result<T, ValidatorError<String>>
121    where
122        T: Serialize + serde::de::Deserialize<'de>,
123    {
124        let value = data.serialize(Serializer).unwrap();
125
126        debug_assert!(self.0.exist_field(&value));
127
128        let mut value_map = ValueMap::new(value);
129
130        self.inner_validate(&mut value_map)
131            .ok()
132            .map(|_| T::deserialize(value_map.value()).unwrap())
133    }
134
135    /// custom validation message
136    pub fn message<const N: usize>(mut self, list: [(&'v str, &'v str); N]) -> Self {
137        list.map(|(key_str, v)| {
138            let MessageKey { fields, rule } =
139                crate::panic_on_err!(field_name::parse_message(key_str));
140
141            debug_assert!(
142                self.0.rule_get(&fields).is_some(),
143                "the field \"{}\" not found in validator",
144                fields.as_str()
145            );
146            debug_assert!(
147                self.0.rule_get(&fields).unwrap().contains(rule),
148                "rule \"{rule}\" is not found in rules"
149            );
150
151            self.0
152                .message
153                .entry(fields)
154                .and_modify(|field| {
155                    field
156                        .entry(rule)
157                        .and_modify(|msg| {
158                            *msg = v;
159                        })
160                        .or_insert(v);
161                })
162                .or_insert({
163                    let mut map = HashMap::new();
164                    map.insert(rule, v);
165                    map
166                });
167        });
168
169        Self(self.0)
170    }
171
172    // pub fn map<M2>(self, f: fn(message: &'v str) -> M2) -> CoreValidator<'v, M2>
173    // where
174    //     M2: 'static,
175    // {
176    //     todo!()
177    // }
178
179    /// register rules
180    pub fn rule<F, R>(self, field: F, rule: R) -> Self
181    where
182        F: IntoFieldName,
183        R: IntoRuleList<ValueMap, String>,
184    {
185        Self(self.0.rule(field, rule))
186    }
187
188    /// when first validate error is encountered, right away return Err(message).
189    pub fn bail(self) -> Self {
190        Self(self.0.bail())
191    }
192
193    fn inner_validate(self, value_map: &mut ValueMap) -> ValidatorError<String> {
194        let mut resp_message = ValidatorError::with_capacity(self.0.rules.len());
195
196        let ValidPhrase(InnerValidator {
197            rules,
198            message,
199            is_bail,
200        }) = self;
201
202        let default_map = HashMap::new();
203
204        for (mut names, mut rules) in rules.into_iter() {
205            if is_bail {
206                rules.set_bail();
207            }
208
209            let msgs = message.get(&names).unwrap_or(&default_map);
210
211            value_map.index(names);
212
213            let field_msg = rules.call_string_message(value_map, msgs);
214
215            names = value_map.take_index();
216
217            resp_message.push(names, field_msg);
218
219            if is_bail && !resp_message.is_empty() {
220                resp_message.shrink_to(1);
221                return resp_message;
222            }
223        }
224
225        resp_message.shrink_to_fit();
226
227        resp_message
228    }
229}
230
231impl<'v, T> Validatable<ValidPhrase<'v>, ValidatorError<String>> for T
232where
233    T: Serialize,
234{
235    fn validate(&self, validator: ValidPhrase<'v>) -> Result<(), ValidatorError<String>> {
236        validator.validate(self)
237    }
238
239    fn validate_mut<'de>(self, validator: ValidPhrase<'v>) -> Result<Self, ValidatorError<String>>
240    where
241        Self: Deserialize<'de>,
242    {
243        validator.validate_mut(self)
244    }
245}
246
247#[cfg(test)]
248mod tests {
249    use std::marker::PhantomData;
250
251    use crate::{Rule, RuleExt};
252
253    use super::*;
254
255    #[derive(Clone, Copy)]
256    struct Required;
257
258    impl Rule for Required {
259        type Message = String;
260
261        const NAME: &'static str = "required";
262
263        fn message(&self) -> Self::Message {
264            "{field} is default msg".to_string()
265        }
266
267        fn call(&mut self, data: &mut Value) -> bool {
268            if *data == 8_i8 {
269                true
270            } else {
271                false
272            }
273        }
274    }
275
276    #[derive(Clone)]
277    struct StartWith(&'static str);
278
279    impl Rule for StartWith {
280        type Message = String;
281
282        fn call(&mut self, data: &mut Value) -> bool {
283            match data {
284                Value::String(s) => s.starts_with(self.0),
285                _ => false,
286            }
287        }
288        fn message(&self) -> Self::Message {
289            format!("this field must start with {}", self.0)
290        }
291
292        const NAME: &'static str = "starts_with";
293    }
294
295    #[test]
296    fn original() {
297        let num = (10_i8, 11_i8);
298
299        let validator = ValidPhrase::new()
300            .rule("0", Required)
301            .message([("0.required", "foo_message")]);
302
303        let res = validator.validate(num).unwrap_err();
304
305        let (filed, msg) = res.into_iter().next().unwrap();
306
307        assert_eq!(filed.as_str(), "0");
308
309        assert_eq!(msg[0], "foo_message");
310    }
311
312    #[test]
313    fn test_trait() {
314        let num = (10_i8, 11_i8);
315
316        let validator = ValidPhrase::new()
317            .rule("0", Required)
318            .message([("0.required", "foo_message")]);
319        num.validate(validator).unwrap_err();
320    }
321
322    #[test]
323    fn field() {
324        let num = (10_i8, 11_i8);
325
326        let validator = ValidPhrase::new()
327            .rule("0", Required)
328            .message([("0.required", "{field} is required")]);
329
330        let res = validator.validate(num).unwrap_err();
331
332        let (filed, msg) = res.into_iter().next().unwrap();
333
334        assert_eq!(filed.as_str(), "0");
335
336        assert_eq!(msg[0], "0 is required");
337    }
338
339    #[test]
340    fn default_field() {
341        let num = (10_i8, 11_i8);
342
343        let validator = ValidPhrase::new().rule("0", Required);
344
345        let res = validator.validate(num).unwrap_err();
346
347        let (filed, msg) = res.into_iter().next().unwrap();
348
349        assert_eq!(filed.as_str(), "0");
350
351        assert_eq!(msg[0], "0 is default msg");
352    }
353
354    #[test]
355    fn value() {
356        let num = (10_i8, 11_i8);
357
358        let validator = ValidPhrase::new()
359            .rule("0", Required)
360            .message([("0.required", "{value} is error value, 8 is true value")]);
361
362        let res = validator.validate(num).unwrap_err();
363
364        let (filed, msg) = res.into_iter().next().unwrap();
365
366        assert_eq!(filed.as_str(), "0");
367
368        assert_eq!(msg[0], "10 is error value, 8 is true value");
369    }
370
371    #[test]
372    fn message() {
373        let validator = ValidPhrase::new()
374            .rule("field1", Required.and(StartWith("foo")))
375            .rule("field2", Required.and(StartWith("foo2")))
376            .message([("field1.required", "msg1")]);
377        let message_list = validator.0.get_message();
378        assert_eq!(
379            message_list
380                .get(&("field1".into()))
381                .unwrap()
382                .get(&"required")
383                .unwrap(),
384            &"msg1"
385        );
386        assert_eq!(message_list.len(), 1);
387        assert_eq!(message_list.get(&("field1".into())).unwrap().len(), 1);
388
389        let validator2 = validator.clone().message([("field1.starts_with", "msg2")]);
390        let message_list2 = validator2.0.get_message();
391        assert_eq!(
392            message_list2
393                .get(&("field1".into()))
394                .unwrap()
395                .get(&"required")
396                .unwrap(),
397            &"msg1"
398        );
399        assert_eq!(
400            message_list2
401                .get(&("field1".into()))
402                .unwrap()
403                .get(&"starts_with")
404                .unwrap(),
405            &"msg2"
406        );
407        assert_eq!(message_list2.len(), 1);
408        assert_eq!(message_list2.get(&("field1".into())).unwrap().len(), 2);
409
410        let validator3 = validator2.clone().message([("field1.required", "msg3")]);
411        let message_list3 = validator3.0.get_message();
412        assert_eq!(
413            message_list3
414                .get(&("field1".into()))
415                .unwrap()
416                .get(&"required")
417                .unwrap(),
418            &"msg3"
419        );
420        assert_eq!(
421            message_list3
422                .get(&("field1".into()))
423                .unwrap()
424                .get(&"starts_with")
425                .unwrap(),
426            &"msg2"
427        );
428        assert_eq!(message_list3.len(), 1);
429        assert_eq!(message_list3.get(&("field1".into())).unwrap().len(), 2);
430    }
431}