serde_valid/lib.rs
1//! # Serde Valid
2//!
3//! [](https://crates.io/crates/serde_valid)
4//! [](https://docs.rs/serde_valid/latest/serde_valid/)
5//! [](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//! Custom validation is suitable for handling convenience validations not defined in JSON Schema.
260//! `serde_valid::utils::*` provides convenience functions for specific types.
261//!
262//! ```rust
263//! use serde_json::json;
264//! use serde_valid::Validate;
265//! use serde_valid::utils::{duration_maximum, duration_minimum};
266//!
267//!
268//! #[derive(Validate)]
269//! struct Data {
270//! #[validate(custom = duration_maximum(std::time::Duration::from_micros(5)))]
271//! #[validate(custom = duration_minimum(std::time::Duration::from_micros(0)))]
272//! val1: std::time::Duration,
273//! }
274//!
275//! let s = Data {
276//! val1: std::time::Duration::from_micros(1),
277//! };
278//!
279//! assert!(s.validate().is_ok());
280//! ```
281//!
282//! ### Multi Errors Validation
283//! If you want to return multiple errors in the use custom validation method, you can use `#[validate(custom = ...)]` same as single error.
284//!
285//! ```rust
286//! use serde_valid::Validate;
287//!
288//! // ๐ Just change the return type from `Result<(), Error>` to `Result<(), Vec<Error>>` !!
289//! fn user_validation(_val: &i32) -> Result<(), Vec<serde_valid::validation::Error>> {
290//! Ok(())
291//! }
292//!
293//! #[derive(Validate)]
294//! struct Data {
295//! #[validate(custom = user_validation)]
296//! val: i32,
297//! }
298//!
299//! let s = Data { val: 1 };
300//!
301//! assert!(s.validate().is_ok());
302//! ```
303//!
304//! ### Multi Fields Validation
305//! Now, you can use `#[validate(custom = ...)]` for multi fields validation.
306//!
307//! ```rust
308//! use serde_json::json;
309//! use serde_valid::Validate;
310//!
311//! fn sample_validation(val1: i32, val2: &str) -> Result<(), serde_valid::validation::Error> {
312//! Ok(())
313//! }
314//!
315//! #[derive(Validate)]
316//! #[validate(custom = |s| sample_validation(s.val1, &s.val2))]
317//! struct Data {
318//! val1: i32,
319//! val2: String,
320//! }
321//!
322//! let s = Data {
323//! val1: 1,
324//! val2: "val2".to_owned(),
325//! };
326//!
327//! assert!(s.validate().is_ok());
328//! ```
329//!
330//! ## Validate Traits
331//!
332//! By implementing the validation trait, Your original type can uses Serde Valid validations.
333//!
334//! ```rust
335//! use serde_valid::Validate;
336//!
337//! struct MyType(String);
338//!
339//! impl serde_valid::ValidateMaxLength for MyType {
340//! fn validate_max_length(&self, max_length: usize) -> Result<(), serde_valid::MaxLengthError> {
341//! self.0.validate_max_length(max_length)
342//! }
343//! }
344//!
345//! #[derive(Validate)]
346//! struct Data {
347//! #[validate(max_length = 5)]
348//! val: MyType,
349//! }
350//!
351//! let s = Data {
352//! val: MyType(String::from("๐๐บ๐๐ฝ๐จโ๐ค๐จโ๐ฉโ๐งโ๐ฆ")),
353//! };
354//!
355//! assert!(s.validate().is_ok());
356//! ```
357//!
358//! ## Validation Errors Format
359//! ### Named Struct
360//! Field errors are output to `properties`.
361//!
362//! ```rust
363//! use serde_json::json;
364//! use serde_valid::Validate;
365//!
366//! #[derive(Validate)]
367//! struct Data {
368//! #[validate(maximum = 4)]
369//! val: u32,
370//! }
371//!
372//! let s = Data { val: 5 };
373//!
374//! assert_eq!(
375//! s.validate().unwrap_err().to_string(),
376//! json!({
377//! "errors": [],
378//! "properties": {
379//! "val": {
380//! "errors": ["The number must be `<= 4`."]
381//! }
382//! }
383//! })
384//! .to_string()
385//! );
386//! ```
387//!
388//! ### Unnamed Struct
389//! Field errors are output to `items`. The key for `items` is guaranteed to be a string of positive
390//! numbers.
391//!
392//! ```rust
393//! use serde_json::json;
394//! use serde_valid::Validate;
395//!
396//! #[derive(Validate)]
397//! struct Data (
398//! #[validate(maximum = 4)] u32,
399//! #[validate(maximum = 3)] u32,
400//! );
401//!
402//! let s = Data ( 5, 4 );
403//!
404//! assert_eq!(
405//! s.validate().unwrap_err().to_string(),
406//! json!({
407//! "errors": [],
408//! "items": {
409//! "0": {
410//! "errors": ["The number must be `<= 4`."]
411//! },
412//! "1": {
413//! "errors": ["The number must be `<= 3`."]
414//! }
415//! }
416//! })
417//! .to_string()
418//! );
419//! ```
420//!
421//! ### New Type
422//! Field errors are output to `errors`.
423//!
424//! ```rust
425//! use serde_json::json;
426//! use serde_valid::Validate;
427//!
428//! #[derive(Validate)]
429//! struct Data (
430//! #[validate(maximum = 4)] u32
431//! );
432//!
433//! let s = Data (5);
434//!
435//! assert_eq!(
436//! s.validate().unwrap_err().to_string(),
437//! json!({
438//! "errors": ["The number must be `<= 4`."]
439//! })
440//! .to_string()
441//! );
442//! ```
443//!
444//! ### Named Enum
445//! Variant errors are output to `properties`.
446//!
447//! ```rust
448//! use serde_json::json;
449//! use serde_valid::Validate;
450//!
451//! #[derive(Validate)]
452//! enum Data {
453//! Named {
454//! #[validate(maximum = 5)]
455//! a: i32,
456//! #[validate(maximum = 5)]
457//! b: i32,
458//! },
459//! }
460//!
461//! let s = Data::Named { a: 6, b: 6 };
462//!
463//! assert_eq!(
464//! s.validate().unwrap_err().to_string(),
465//! json!({
466//! "errors": [],
467//! "properties": {
468//! "a": {
469//! "errors": ["The number must be `<= 5`."]
470//! },
471//! "b": {
472//! "errors": ["The number must be `<= 5`."]
473//! }
474//! }
475//! })
476//! .to_string()
477//! );
478//! ```
479//!
480//! ### Unnamed Enum
481//! Variant errors are output to `items`. The key for `items` is guaranteed to be a string of
482//! positive numbers.
483//!
484//! ```rust
485//! use serde_json::json;
486//! use serde_valid::Validate;
487//!
488//! #[derive(Validate)]
489//! enum Data {
490//! Unnamed (
491//! #[validate(maximum = 5)] i32,
492//! #[validate(maximum = 5)] i32,
493//! ),
494//! }
495//!
496//! let s = Data::Unnamed ( 6, 6 );
497//!
498//! assert_eq!(
499//! s.validate().unwrap_err().to_string(),
500//! json!({
501//! "errors": [],
502//! "items": {
503//! "0": {
504//! "errors": ["The number must be `<= 5`."]
505//! },
506//! "1": {
507//! "errors": ["The number must be `<= 5`."]
508//! }
509//! }
510//! })
511//! .to_string()
512//! );
513//! ```
514//!
515//! ### New Type Enum
516//! Variant errors are output to `errors`.
517//!
518//! ```rust
519//! use serde_json::json;
520//! use serde_valid::Validate;
521//!
522//! #[derive(Validate)]
523//! enum Data {
524//! NewType (
525//! #[validate(maximum = 5)] i32,
526//! ),
527//! }
528//!
529//! let s = Data::NewType ( 6 );
530//!
531//! assert_eq!(
532//! s.validate().unwrap_err().to_string(),
533//! json!({
534//! "errors": ["The number must be `<= 5`."]
535//! })
536//! .to_string()
537//! );
538//! ```
539
540pub mod error;
541mod features;
542pub mod json;
543mod traits;
544pub mod utils;
545pub mod validation;
546
547pub use error::{
548 EnumerateError, Error, ExclusiveMaximumError, ExclusiveMinimumError, MaxItemsError,
549 MaxLengthError, MaxPropertiesError, MaximumError, MinItemsError, MinLengthError,
550 MinPropertiesError, MinimumError, MultipleOfError, PatternError, UniqueItemsError,
551};
552#[allow(unused_imports)]
553pub use features::*;
554use indexmap::IndexMap;
555use std::collections::HashMap;
556pub use validation::{
557 ValidateEnumerate, ValidateExclusiveMaximum, ValidateExclusiveMinimum, ValidateMaxItems,
558 ValidateMaxLength, ValidateMaxProperties, ValidateMaximum, ValidateMinItems, ValidateMinLength,
559 ValidateMinProperties, ValidateMinimum, ValidateMultipleOf, ValidatePattern,
560 ValidateUniqueItems,
561};
562
563pub mod export {
564 #[cfg(feature = "fluent")]
565 pub use fluent;
566 pub use once_cell;
567 pub use regex;
568}
569
570pub trait Validate {
571 fn validate(&self) -> std::result::Result<(), self::validation::Errors>;
572}
573
574impl<T> Validate for Vec<T>
575where
576 T: Validate,
577{
578 fn validate(&self) -> std::result::Result<(), self::validation::Errors> {
579 let mut items = IndexMap::new();
580
581 for (index, item) in self.iter().enumerate() {
582 if let Err(errors) = item.validate() {
583 items.insert(index, errors);
584 }
585 }
586
587 if items.is_empty() {
588 Ok(())
589 } else {
590 Err(self::validation::Errors::Array(
591 validation::error::ArrayErrors::new(vec![], items),
592 ))
593 }
594 }
595}
596
597impl<T, const N: usize> Validate for [T; N]
598where
599 T: Validate,
600{
601 fn validate(&self) -> std::result::Result<(), self::validation::Errors> {
602 let mut items = IndexMap::new();
603
604 for (index, item) in self.iter().enumerate() {
605 if let Err(errors) = item.validate() {
606 items.insert(index, errors);
607 }
608 }
609
610 if items.is_empty() {
611 Ok(())
612 } else {
613 Err(self::validation::Errors::Array(
614 validation::error::ArrayErrors::new(vec![], items),
615 ))
616 }
617 }
618}
619
620impl<K, V> Validate for HashMap<K, V>
621where
622 V: Validate,
623 for<'a> &'a K: Into<String>,
624{
625 fn validate(&self) -> std::result::Result<(), self::validation::Errors> {
626 let mut items = IndexMap::new();
627
628 for (key, value) in self.iter() {
629 if let Err(errors) = value.validate() {
630 items.insert(key.into(), errors);
631 }
632 }
633
634 if items.is_empty() {
635 Ok(())
636 } else {
637 Err(self::validation::Errors::Object(
638 validation::error::ObjectErrors::new(vec![], items),
639 ))
640 }
641 }
642}
643
644impl<K, V> Validate for IndexMap<K, V>
645where
646 V: Validate,
647 for<'a> &'a K: Into<String>,
648{
649 fn validate(&self) -> std::result::Result<(), self::validation::Errors> {
650 let mut items = IndexMap::new();
651
652 for (key, value) in self.iter() {
653 if let Err(errors) = value.validate() {
654 items.insert(key.into(), errors);
655 }
656 }
657
658 if items.is_empty() {
659 Ok(())
660 } else {
661 Err(self::validation::Errors::Object(
662 validation::ObjectErrors::new(vec![], items),
663 ))
664 }
665 }
666}
667
668impl<T> Validate for Option<T>
669where
670 T: Validate,
671{
672 fn validate(&self) -> std::result::Result<(), self::validation::Errors> {
673 match self {
674 Some(value) => value.validate(),
675 None => Ok(()),
676 }
677 }
678}
679
680pub use serde_valid_derive::Validate;
681
682#[cfg(test)]
683pub mod tests {
684 pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
685}