serde_valid/
lib.rs

1//! # Serde Valid
2//!
3//! [![Latest Version](https://img.shields.io/crates/v/serde_valid.svg?color=green&style=flat-square)](https://crates.io/crates/serde_valid)
4//! [![crate docs](https://docs.rs/serde_valid/badge.svg)](https://docs.rs/serde_valid/latest/serde_valid/)
5//! [![GitHub license](https://badgen.net/github/license/Naereen/Strapdown.js?style=flat-square)](https://github.com/Naereen/StrapDown.js/blob/master/LICENSE)
6//!
7//! This is [JSON Schema](https://json-schema.org/) based validation tool using [serde](https://github.com/serde-rs/serde).
8//!
9//! ## Usage
10//!
11//! You derive `Validate` trait, and write validations.
12//!
13//! ```rust
14//! use serde_valid::Validate;
15//!
16//! #[derive(Validate)]
17//! struct Data {
18//!     #[validate(minimum = 0)]
19//!     #[validate(maximum = 10)]
20//!     val: i32,
21//! }
22//!
23//! #[derive(Validate)]
24//! enum DataEnum {
25//!     Named {
26//!         #[validate]
27//!         a: Data,
28//!     },
29//! }
30//!
31//! let s = DataEnum::Named {
32//!     a: Data { val: 5 },
33//! };
34//!
35//! assert!(s.validate().is_ok());
36//! ```
37//!
38//! ## Feature Flags
39//!
40//! - `toml` - provide serialization/deserialization in `toml` format.
41//! - `yaml` - provide serialization/deserialization in `yaml` format.
42//! - `i128` - support `i128`/`u128` type (default).
43//! - `fluent` - provide localization using [fluent](https://projectfluent.org/).
44//!
45//! ## Validations
46//!
47//! Serde Valid support standard validation based JSON Schema.
48//!
49//! | Type    | Serde Valid (validate derive)          | Serde Valid (validate trait) | JSON Schema                                                                                   |
50//! | :-----: | :------------------------------------- | :--------------------------- | :-------------------------------------------------------------------------------------------- |
51//! | String  | `#[validate(max_length = 5)]`          | [`ValidateMaxLength`]        | [maxLength](https://json-schema.org/understanding-json-schema/reference/string#length)        |
52//! | String  | `#[validate(min_length = 5)]`          | [`ValidateMinLength`]        | [minLength](https://json-schema.org/understanding-json-schema/reference/string#length)        |
53//! | String  | `#[validate(pattern = r"^\d{5}$")]`    | [`ValidatePattern`]          | [pattern](https://json-schema.org/understanding-json-schema/reference/string#regexp)          |
54//! | Numeric | `#[validate(maximum = 5)]`             | [`ValidateMaximum`]          | [maximum](https://json-schema.org/understanding-json-schema/reference/numeric#range)          |
55//! | Numeric | `#[validate(minimum = 5)]`             | [`ValidateMinimum`]          | [minimum](https://json-schema.org/understanding-json-schema/reference/numeric#range)          |
56//! | Numeric | `#[validate(exclusive_maximum = 5)]`   | [`ValidateExclusiveMaximum`] | [exclusiveMaximum](https://json-schema.org/understanding-json-schema/reference/numeric#range) |
57//! | Numeric | `#[validate(exclusive_minimum = 5)]`   | [`ValidateExclusiveMinimum`] | [exclusiveMinimum](https://json-schema.org/understanding-json-schema/reference/numeric#range) |
58//! | Numeric | `#[validate(multiple_of = 5)]`         | [`ValidateMultipleOf`]       | [multipleOf](https://json-schema.org/understanding-json-schema/reference/numeric#multiples)   |
59//! | Object  | `#[validate(max_properties = 5)]`      | [`ValidateMaxProperties`]    | [maxProperties](https://json-schema.org/understanding-json-schema/reference/object#size)      |
60//! | Object  | `#[validate(min_properties = 5)]`      | [`ValidateMinProperties`]    | [minProperties](https://json-schema.org/understanding-json-schema/reference/object#size)      |
61//! | Array   | `#[validate(max_items = 5)]`           | [`ValidateMaxItems`]         | [maxItems](https://json-schema.org/understanding-json-schema/reference/array#length)          |
62//! | Array   | `#[validate(min_items = 5)]`           | [`ValidateMinItems`]         | [minItems](https://json-schema.org/understanding-json-schema/reference/array#length)          |
63//! | Array   | `#[validate(unique_items)]`            | [`ValidateUniqueItems`]      | [uniqueItems](https://json-schema.org/understanding-json-schema/reference/array#uniqueItems)  |
64//! | Generic | `#[validate(enumerate = [5, 10, 15])]` | [`ValidateEnumerate`]        | [enum](https://json-schema.org/understanding-json-schema/reference/enum)                      |
65//!
66//! In addition, [serde_valid::utils][module@crate::utils] provides a type of validation not described in the JSON schema specification.
67//!
68//! | Type                                                                 | Serde Valid (validate derive)                              | Serde Valid (validation function)                                        |
69//! | :------------------------------------------------------------------: | :--------------------------------------------------------- | :----------------------------------------------------------------------- |
70//! | [Duration](https://doc.rust-lang.org/core/time/struct.Duration.html) | `#[validate(custom = duration_maximum(SECOND))]`           | [duration_maximum][`crate::utils::duration_maximum`]                     |
71//! | [Duration](https://doc.rust-lang.org/core/time/struct.Duration.html) | `#[validate(custom = duration_minimum(ZERO))]`             | [duration_minimum][`crate::utils::duration_minimum`]                     |
72//! | [Duration](https://doc.rust-lang.org/core/time/struct.Duration.html) | `#[validate(custom = duration_exclusive_maximum(SECOND))]` | [duration_exclusive_maximum][`crate::utils::duration_exclusive_maximum`] |
73//! | [Duration](https://doc.rust-lang.org/core/time/struct.Duration.html) | `#[validate(custom = duration_exclusive_minimum(ZERO))]`   | [duration_exclusive_minimum][`crate::utils::duration_exclusive_minimum`] |
74//!
75//! ## Complete Constructor (Deserialization)
76//!
77//! Serde Valid support complete constructor method using by
78//! [`serde_valid::json::FromJsonValue`](json::FromJsonValue) trait.
79//!
80//! ```rust
81//! use serde::Deserialize;
82//! use serde_valid::Validate;
83//! use serde_valid::json::{json, FromJsonValue};
84//!
85//! #[derive(Debug, Deserialize, Validate)]
86//! struct Data {
87//!     #[validate(maximum = 100)]
88//!     val: i32,
89//! }
90//!
91//! // Deserialization and Validation!! ๐Ÿš€
92//! let err = Data::from_json_value(json!({ "val": 123 })).unwrap_err();
93//!
94//! assert_eq!(
95//!     err.to_string(),
96//!     json!({
97//!         "errors": [],
98//!         "properties": {
99//!             "val": {
100//!                 "errors": ["The number must be `<= 100`."]
101//!             }
102//!         }
103//!     })
104//!     .to_string()
105//! );
106//! ```
107//!
108//! You can force validation by only deserialization through `serde_valid`, and removing
109//! `serde_json` from `Cargo.toml` of your project.
110//!
111//! ## Serialization
112//!
113//! For serialization, provides [`serde_valid::json::ToJsonString`](json::ToJsonString) trait.
114//!
115//! ```rust
116//! use serde::Serialize;
117//! use serde_valid::Validate;
118//! use serde_valid::json::{json, ToJsonString};
119//!
120//! #[derive(Debug, Serialize, Validate)]
121//! struct Data {
122//!     #[validate(maximum = 100)]
123//!     val: i32,
124//! }
125//!
126//! assert_eq!(
127//!     Data{ val: 12i32 }.to_json_string().unwrap(),
128//!     json!({ "val": 12i32 }).to_json_string().unwrap()
129//! );
130//! ```
131//!
132//! ## Custom Message
133//!
134//! For user custom message, Serde Valid provides `message_fn` or `message`.
135//!
136//! ```rust
137//! use serde_json::json;
138//! use serde_valid::Validate;
139//!
140//! #[inline]
141//! fn min_error_message(_params: &serde_valid::MinItemsError) -> String {
142//!     "this is custom message_fn.".to_string()
143//! }
144//!
145//! #[derive(Validate)]
146//! struct Data {
147//!     #[validate(min_items = 4, message_fn = min_error_message)]
148//!     #[validate(max_items = 2, message = "this is custom message.")]
149//!     val: Vec<i32>,
150//! }
151//!
152//! let s = Data { val: vec![1, 2, 3] };
153//!
154//! assert_eq!(
155//!     s.validate().unwrap_err().to_string(),
156//!     json!({
157//!         "errors": [],
158//!         "properties": {
159//!             "val": {
160//!                 "errors": [
161//!                     "this is custom message_fn.",
162//!                     "this is custom message."
163//!                 ]
164//!             }
165//!         }
166//!     })
167//!     .to_string()
168//! );
169//! ```
170//!
171//! ### Fluent localization
172//!
173//! <section class="warning">
174//! <code>fluent</code> feature is required.
175//! </section>
176//!
177//! You can also use [fluent](https://projectfluent.org/) localization by using `fluent` feature.
178//!
179//! Allow the following attributes:
180//! - `#[validate(..., fluent("message-id", key1 = value1, ...))]`
181//! - `#[validate(..., message_l10n = fluent("message-id", key1 = value1, ...))]`
182//!
183//! ```rust
184//! # #[cfg(feature = "fluent")] {
185//! # use fluent::{FluentBundle, FluentResource};
186//! use unic_langid::LanguageIdentifier;
187//! use serde_json::json;
188//! use serde_valid::{fluent::Localize, Validate};
189//!
190//! # fn get_bundle(source: impl Into<String>) -> FluentBundle<FluentResource> {
191//! #     let res = FluentResource::try_new(source.into()).expect("Failed to parse an FTL string.");
192//! #     let langid_en: LanguageIdentifier = "ja_JP".parse().expect("Parsing failed");
193//! #     let mut bundle = FluentBundle::new(vec![langid_en]);
194//! #     bundle.add_resource(res).unwrap();
195//! #     bundle
196//! # }
197//!
198//! #[derive(Validate)]
199//! struct Data (
200//!     #[validate(min_length = 3, fluent("name-min-length", min_length = 3))]
201//!     String,
202//! );
203//!
204//! assert_eq!(
205//!     Data("็”ฐไธญ".to_string()).validate()
206//!         .unwrap_err()
207//!         .localize(&get_bundle("name-min-length = ๅๅ‰ใฎ้•ทใ•ใฏ { $min_length } ๆ–‡ๅญ—ไปฅไธŠใงใชใ„ใจใ„ใ‘ใพใ›ใ‚“ใ€‚"))
208//!         .to_string(),
209//!     json!({
210//!         "errors": ["ๅๅ‰ใฎ้•ทใ•ใฏ \u{2068}3\u{2069} ๆ–‡ๅญ—ไปฅไธŠใงใชใ„ใจใ„ใ‘ใพใ›ใ‚“ใ€‚"]
211//!     })
212//!     .to_string()
213//! );
214//! # }
215//! ```
216//!
217//! ## Custom Validation
218//! ### Single Error Validation
219//! You can use your custom validation using by `#[validate(custom = ...)]`.
220//!
221//! ```rust
222//! use serde_valid::Validate;
223//!
224//! fn user_validation(_val: &i32) -> Result<(), serde_valid::validation::Error> {
225//!     Ok(())
226//! }
227//!
228//! #[derive(Validate)]
229//! struct Data {
230//!     #[validate(custom = user_validation)]
231//!     val: i32,
232//! }
233//!
234//! let s = Data { val: 1 };
235//!
236//! assert!(s.validate().is_ok());
237//! ```
238//!
239//! And you can also use closure.
240//!
241//! ```rust
242//! use serde_valid::Validate;
243//!
244//! fn user_validation(_val: &i32, param1: bool) -> Result<(), serde_valid::validation::Error> {
245//!     Ok(())
246//! }
247//!
248//! #[derive(Validate)]
249//! struct Data {
250//!     #[validate(custom = |v| user_validation(v, true))]
251//!     val: i32,
252//! }
253//!
254//! let s = Data { val: 1 };
255//!
256//! assert!(s.validate().is_ok());
257//! ```
258//!
259//! You can use **closure**, so you can access other fields of the struct by using the `self` keyword.
260//!
261//! ```rust
262//! use serde_valid::Validate;
263//!
264//! #[derive(Validate)]
265//! struct Data {
266//!     val1: i32,
267//!     #[validate(custom = |val2: &i32| {
268//!         if self.val1 < *val2 {
269//!             Ok(())
270//!         } else {
271//!             Err(serde_valid::validation::Error::Custom("val2 must be greater than val1".to_owned()))
272//!         }
273//!     })]
274//!     val2: i32,
275//! }
276//!
277//! let s = Data { val1: 2, val2: 1 };
278//!
279//! assert_eq!(
280//!     s.validate().unwrap_err().to_string(),
281//!     serde_json::json!({
282//!         "errors": [],
283//!         "properties": {
284//!             "val2": {
285//!                 "errors": ["val2 must be greater than val1"]
286//!             }
287//!         }
288//!     })
289//!     .to_string()
290//! );
291//! ```
292//!
293//! Custom validation is suitable for handling convenience validations not defined in JSON Schema.
294//! `serde_valid::utils::*` provides convenience functions for specific types.
295//!
296//! ```rust
297//! use serde_json::json;
298//! use serde_valid::Validate;
299//! use serde_valid::utils::{duration_maximum, duration_minimum};
300//!
301//!
302//! #[derive(Validate)]
303//! struct Data {
304//!     #[validate(custom = duration_maximum(std::time::Duration::from_micros(5)))]
305//!     #[validate(custom = duration_minimum(std::time::Duration::from_micros(0)))]
306//!     val1: std::time::Duration,
307//! }
308//!
309//! let s = Data {
310//!     val1: std::time::Duration::from_micros(1),
311//! };
312//!
313//! assert!(s.validate().is_ok());
314//! ```
315//!
316//! ### Multi Errors Validation
317//! If you want to return multiple errors in the use custom validation method, you can use `#[validate(custom = ...)]` same as single error.
318//!
319//! ```rust
320//! use serde_valid::Validate;
321//!
322//! // ๐Ÿš€ Just change the return type from `Result<(), Error>` to `Result<(), Vec<Error>>` !!
323//! fn user_validation(_val: &i32) -> Result<(), Vec<serde_valid::validation::Error>> {
324//!     Ok(())
325//! }
326//!
327//! #[derive(Validate)]
328//! struct Data {
329//!     #[validate(custom = user_validation)]
330//!     val: i32,
331//! }
332//!
333//! let s = Data { val: 1 };
334//!
335//! assert!(s.validate().is_ok());
336//! ```
337//!
338//! ### Multi Fields Validation
339//! Now, you can use `#[validate(custom = ...)]` for multi fields validation.
340//!
341//! ```rust
342//! use serde_json::json;
343//! use serde_valid::Validate;
344//!
345//! fn sample_validation(val1: i32, val2: &str) -> Result<(), serde_valid::validation::Error> {
346//!     Ok(())
347//! }
348//!
349//! #[derive(Validate)]
350//! #[validate(custom = |s| sample_validation(s.val1, &s.val2))]
351//! struct Data {
352//!     val1: i32,
353//!     val2: String,
354//! }
355//!
356//! let s = Data {
357//!     val1: 1,
358//!     val2: "val2".to_owned(),
359//! };
360//!
361//! assert!(s.validate().is_ok());
362//! ```
363//!
364//! ## Validate Traits
365//!
366//! By implementing the validation trait, Your original type can uses Serde Valid validations.
367//!
368//! ```rust
369//! use serde_valid::Validate;
370//!
371//! struct MyType(String);
372//!
373//! impl serde_valid::ValidateMaxLength for MyType {
374//!     fn validate_max_length(&self, max_length: usize) -> Result<(), serde_valid::MaxLengthError> {
375//!         self.0.validate_max_length(max_length)
376//!     }
377//! }
378//!
379//! #[derive(Validate)]
380//! struct Data {
381//!     #[validate(max_length = 5)]
382//!     val: MyType,
383//! }
384//!
385//! let s = Data {
386//!     val: MyType(String::from("๐Ÿ˜๐Ÿ‘บ๐Ÿ™‹๐Ÿฝ๐Ÿ‘จโ€๐ŸŽค๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ")),
387//! };
388//!
389//! assert!(s.validate().is_ok());
390//! ```
391//!
392//! ## Validation Errors Format
393//! ### Named Struct
394//! Field errors are output to `properties`.
395//!
396//! ```rust
397//! use serde_json::json;
398//! use serde_valid::Validate;
399//!
400//! #[derive(Validate)]
401//! struct Data {
402//!     #[validate(maximum = 4)]
403//!     val: u32,
404//! }
405//!
406//! let s = Data { val: 5 };
407//!
408//! assert_eq!(
409//!     s.validate().unwrap_err().to_string(),
410//!     json!({
411//!         "errors": [],
412//!         "properties": {
413//!             "val": {
414//!                 "errors": ["The number must be `<= 4`."]
415//!             }
416//!         }
417//!     })
418//!     .to_string()
419//! );
420//! ```
421//!
422//! ### Unnamed Struct
423//! Field errors are output to `items`. The key for `items` is guaranteed to be a string of positive
424//! numbers.
425//!
426//! ```rust
427//! use serde_json::json;
428//! use serde_valid::Validate;
429//!
430//! #[derive(Validate)]
431//! struct Data (
432//!     #[validate(maximum = 4)] u32,
433//!     #[validate(maximum = 3)] u32,
434//! );
435//!
436//! let s = Data ( 5, 4 );
437//!
438//! assert_eq!(
439//!     s.validate().unwrap_err().to_string(),
440//!     json!({
441//!         "errors": [],
442//!         "items": {
443//!             "0": {
444//!                 "errors": ["The number must be `<= 4`."]
445//!             },
446//!             "1": {
447//!                 "errors": ["The number must be `<= 3`."]
448//!             }
449//!         }
450//!     })
451//!     .to_string()
452//! );
453//! ```
454//!
455//! ### New Type
456//! Field errors are output to `errors`.
457//!
458//! ```rust
459//! use serde_json::json;
460//! use serde_valid::Validate;
461//!
462//! #[derive(Validate)]
463//! struct Data (
464//!     #[validate(maximum = 4)] u32
465//! );
466//!
467//! let s = Data (5);
468//!
469//! assert_eq!(
470//!     s.validate().unwrap_err().to_string(),
471//!     json!({
472//!         "errors": ["The number must be `<= 4`."]
473//!     })
474//!     .to_string()
475//! );
476//! ```
477//!
478//! ### Named Enum
479//! Variant errors are output to `properties`.
480//!
481//! ```rust
482//! use serde_json::json;
483//! use serde_valid::Validate;
484//!
485//! #[derive(Validate)]
486//! enum Data {
487//!     Named {
488//!         #[validate(maximum = 5)]
489//!         a: i32,
490//!         #[validate(maximum = 5)]
491//!         b: i32,
492//!     },
493//! }
494//!
495//! let s = Data::Named { a: 6, b: 6 };
496//!
497//! assert_eq!(
498//!     s.validate().unwrap_err().to_string(),
499//!     json!({
500//!         "errors": [],
501//!         "properties": {
502//!             "a": {
503//!                 "errors": ["The number must be `<= 5`."]
504//!             },
505//!             "b": {
506//!                 "errors": ["The number must be `<= 5`."]
507//!             }
508//!         }
509//!     })
510//!     .to_string()
511//! );
512//! ```
513//!
514//! ### Unnamed Enum
515//! Variant errors are output to `items`. The key for `items` is guaranteed to be a string of
516//! positive numbers.
517//!
518//! ```rust
519//! use serde_json::json;
520//! use serde_valid::Validate;
521//!
522//! #[derive(Validate)]
523//! enum Data {
524//!     Unnamed (
525//!         #[validate(maximum = 5)] i32,
526//!         #[validate(maximum = 5)] i32,
527//!     ),
528//! }
529//!
530//! let s = Data::Unnamed ( 6, 6 );
531//!
532//! assert_eq!(
533//!     s.validate().unwrap_err().to_string(),
534//!     json!({
535//!         "errors": [],
536//!         "items": {
537//!             "0": {
538//!                 "errors": ["The number must be `<= 5`."]
539//!             },
540//!             "1": {
541//!                 "errors": ["The number must be `<= 5`."]
542//!             }
543//!         }
544//!     })
545//!     .to_string()
546//! );
547//! ```
548//!
549//! ### New Type Enum
550//! Variant errors are output to `errors`.
551//!
552//! ```rust
553//! use serde_json::json;
554//! use serde_valid::Validate;
555//!
556//! #[derive(Validate)]
557//! enum Data {
558//!     NewType (
559//!         #[validate(maximum = 5)] i32,
560//!     ),
561//! }
562//!
563//! let s = Data::NewType ( 6 );
564//!
565//! assert_eq!(
566//!     s.validate().unwrap_err().to_string(),
567//!     json!({
568//!         "errors": ["The number must be `<= 5`."]
569//!     })
570//!     .to_string()
571//! );
572//! ```
573
574pub mod error;
575mod features;
576pub mod json;
577mod traits;
578pub mod utils;
579pub mod validation;
580
581pub use error::{
582    EnumerateError, Error, ExclusiveMaximumError, ExclusiveMinimumError, MaxItemsError,
583    MaxLengthError, MaxPropertiesError, MaximumError, MinItemsError, MinLengthError,
584    MinPropertiesError, MinimumError, MultipleOfError, PatternError, UniqueItemsError,
585};
586#[allow(unused_imports)]
587pub use features::*;
588use indexmap::IndexMap;
589use std::{borrow::Cow, collections::HashMap};
590pub use validation::{
591    ValidateEnumerate, ValidateExclusiveMaximum, ValidateExclusiveMinimum, ValidateMaxItems,
592    ValidateMaxLength, ValidateMaxProperties, ValidateMaximum, ValidateMinItems, ValidateMinLength,
593    ValidateMinProperties, ValidateMinimum, ValidateMultipleOf, ValidatePattern,
594    ValidateUniqueItems,
595};
596
597pub mod export {
598    #[cfg(feature = "fluent")]
599    pub use fluent;
600    pub use once_cell;
601    pub use regex;
602}
603
604pub trait Validate {
605    fn validate(&self) -> std::result::Result<(), self::validation::Errors>;
606}
607
608impl<T> Validate for Vec<T>
609where
610    T: Validate,
611{
612    fn validate(&self) -> std::result::Result<(), self::validation::Errors> {
613        let mut items = IndexMap::new();
614
615        for (index, item) in self.iter().enumerate() {
616            if let Err(errors) = item.validate() {
617                items.insert(index, errors);
618            }
619        }
620
621        if items.is_empty() {
622            Ok(())
623        } else {
624            Err(self::validation::Errors::Array(
625                validation::error::ArrayErrors::new(vec![], items),
626            ))
627        }
628    }
629}
630
631impl<T, const N: usize> Validate for [T; N]
632where
633    T: Validate,
634{
635    fn validate(&self) -> std::result::Result<(), self::validation::Errors> {
636        let mut items = IndexMap::new();
637
638        for (index, item) in self.iter().enumerate() {
639            if let Err(errors) = item.validate() {
640                items.insert(index, errors);
641            }
642        }
643
644        if items.is_empty() {
645            Ok(())
646        } else {
647            Err(self::validation::Errors::Array(
648                validation::error::ArrayErrors::new(vec![], items),
649            ))
650        }
651    }
652}
653
654impl<K, V> Validate for HashMap<K, V>
655where
656    V: Validate,
657    for<'a> &'a K: Into<String>,
658{
659    fn validate(&self) -> std::result::Result<(), self::validation::Errors> {
660        let mut items = IndexMap::new();
661
662        for (key, value) in self.iter() {
663            if let Err(errors) = value.validate() {
664                items.insert(Cow::from(key.into()), errors);
665            }
666        }
667
668        if items.is_empty() {
669            Ok(())
670        } else {
671            Err(self::validation::Errors::Object(
672                validation::error::ObjectErrors::new(vec![], items),
673            ))
674        }
675    }
676}
677
678impl<K, V> Validate for IndexMap<K, V>
679where
680    V: Validate,
681    for<'a> &'a K: Into<String>,
682{
683    fn validate(&self) -> std::result::Result<(), self::validation::Errors> {
684        let mut items = IndexMap::new();
685
686        for (key, value) in self.iter() {
687            if let Err(errors) = value.validate() {
688                items.insert(Cow::from(key.into()), errors);
689            }
690        }
691
692        if items.is_empty() {
693            Ok(())
694        } else {
695            Err(self::validation::Errors::Object(
696                validation::ObjectErrors::new(vec![], items),
697            ))
698        }
699    }
700}
701
702impl<T> Validate for Option<T>
703where
704    T: Validate,
705{
706    fn validate(&self) -> std::result::Result<(), self::validation::Errors> {
707        match self {
708            Some(value) => value.validate(),
709            None => Ok(()),
710        }
711    }
712}
713
714pub use serde_valid_derive::Validate;
715
716#[cfg(test)]
717pub mod tests {
718    pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
719}