├── .cargo └── config.toml ├── .github └── workflows │ ├── CI.yml │ └── Publish.yml ├── .gitignore ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── Cargo.toml ├── LICENSE ├── README.md ├── crates ├── serde_valid │ ├── Cargo.toml │ ├── README.md │ ├── expands │ │ └── .gitignore │ ├── src │ │ ├── error.rs │ │ ├── features.rs │ │ ├── features │ │ │ ├── flatten.rs │ │ │ ├── fluent.rs │ │ │ ├── fluent │ │ │ │ ├── error.rs │ │ │ │ ├── localize.rs │ │ │ │ ├── message.rs │ │ │ │ └── try_localize.rs │ │ │ ├── toml.rs │ │ │ ├── toml │ │ │ │ ├── from_toml_reader.rs │ │ │ │ ├── from_toml_slice.rs │ │ │ │ ├── from_toml_str.rs │ │ │ │ ├── from_toml_value.rs │ │ │ │ ├── to_toml_string.rs │ │ │ │ ├── to_toml_value.rs │ │ │ │ └── to_toml_writer.rs │ │ │ ├── yaml.rs │ │ │ └── yaml │ │ │ │ ├── from_yaml_reader.rs │ │ │ │ ├── from_yaml_slice.rs │ │ │ │ ├── from_yaml_str.rs │ │ │ │ ├── from_yaml_value.rs │ │ │ │ ├── to_yaml_string.rs │ │ │ │ ├── to_yaml_value.rs │ │ │ │ └── to_yaml_writer.rs │ │ ├── json.rs │ │ ├── json │ │ │ ├── from_json_reader.rs │ │ │ ├── from_json_slice.rs │ │ │ ├── from_json_str.rs │ │ │ ├── from_json_value.rs │ │ │ ├── to_json_string.rs │ │ │ ├── to_json_value.rs │ │ │ └── to_json_writer.rs │ │ ├── lib.rs │ │ ├── traits.rs │ │ ├── traits │ │ │ ├── is_match.rs │ │ │ ├── is_unique.rs │ │ │ ├── length.rs │ │ │ └── size.rs │ │ ├── utils.rs │ │ ├── utils │ │ │ └── duration.rs │ │ ├── validation.rs │ │ └── validation │ │ │ ├── array.rs │ │ │ ├── array │ │ │ ├── max_items.rs │ │ │ ├── min_items.rs │ │ │ └── unique_items.rs │ │ │ ├── composited.rs │ │ │ ├── custom.rs │ │ │ ├── error.rs │ │ │ ├── error │ │ │ ├── array_erros.rs │ │ │ ├── errors.rs │ │ │ ├── format.rs │ │ │ ├── into_error.rs │ │ │ ├── message.rs │ │ │ └── object_errors.rs │ │ │ ├── generic.rs │ │ │ ├── generic │ │ │ └── enumerate.rs │ │ │ ├── numeric.rs │ │ │ ├── numeric │ │ │ ├── exclusive_maximum.rs │ │ │ ├── exclusive_minimum.rs │ │ │ ├── maximum.rs │ │ │ ├── minimum.rs │ │ │ └── multiple_of.rs │ │ │ ├── object.rs │ │ │ ├── object │ │ │ ├── max_properties.rs │ │ │ └── min_properties.rs │ │ │ ├── string.rs │ │ │ └── string │ │ │ ├── max_length.rs │ │ │ ├── min_length.rs │ │ │ └── pattern.rs │ └── tests │ │ ├── array_test.rs │ │ ├── complex_test.rs │ │ ├── custom_duration_test.rs │ │ ├── custom_test.rs │ │ ├── deserialize_test.rs │ │ ├── empty_test.rs │ │ ├── enum_test.rs │ │ ├── enumerate_test.rs │ │ ├── fluent_test.rs │ │ ├── hashmap_test.rs │ │ ├── issues_test.rs │ │ ├── items_test.rs │ │ ├── length_test.rs │ │ ├── message_l10n_fluent_test.rs │ │ ├── multiple_of_test.rs │ │ ├── nested_struct_test.rs │ │ ├── pattern_test.rs │ │ ├── properties_test.rs │ │ ├── range_test.rs │ │ ├── serde_rename_test.rs │ │ ├── serialize_test.rs │ │ ├── specific_test.rs │ │ ├── struct_unnamed_fields_test.rs │ │ └── unique_items_test.rs ├── serde_valid_derive │ ├── Cargo.toml │ └── src │ │ ├── attribute.rs │ │ ├── attribute │ │ ├── common.rs │ │ ├── common │ │ │ ├── lit.rs │ │ │ └── message_format.rs │ │ ├── field_validate.rs │ │ ├── field_validate │ │ │ ├── array.rs │ │ │ ├── array │ │ │ │ ├── length_items.rs │ │ │ │ └── unique_items.rs │ │ │ ├── field.rs │ │ │ ├── generic.rs │ │ │ ├── generic │ │ │ │ ├── custom.rs │ │ │ │ ├── enumerate.rs │ │ │ │ └── validate.rs │ │ │ ├── meta.rs │ │ │ ├── meta │ │ │ │ ├── meta_list.rs │ │ │ │ ├── meta_name_value.rs │ │ │ │ └── meta_path.rs │ │ │ ├── numeric.rs │ │ │ ├── numeric │ │ │ │ ├── multiple_of.rs │ │ │ │ └── range.rs │ │ │ ├── object.rs │ │ │ ├── object │ │ │ │ └── size_properties.rs │ │ │ ├── string.rs │ │ │ └── string │ │ │ │ ├── length.rs │ │ │ │ └── pattern.rs │ │ ├── struct_validate.rs │ │ ├── struct_validate │ │ │ ├── generic.rs │ │ │ ├── generic │ │ │ │ └── custom.rs │ │ │ ├── meta.rs │ │ │ └── meta │ │ │ │ ├── meta_list.rs │ │ │ │ ├── meta_name_value.rs │ │ │ │ └── meta_path.rs │ │ ├── variant_validate.rs │ │ └── variant_validate │ │ │ ├── meta.rs │ │ │ └── meta │ │ │ ├── meta_list.rs │ │ │ ├── meta_name_value.rs │ │ │ └── meta_path.rs │ │ ├── derive.rs │ │ ├── derive │ │ ├── enum_derive.rs │ │ ├── named_struct_derive.rs │ │ └── unnamed_struct_derive.rs │ │ ├── error.rs │ │ ├── lib.rs │ │ ├── serde.rs │ │ ├── serde │ │ └── rename.rs │ │ ├── types.rs │ │ ├── types │ │ ├── field.rs │ │ ├── field │ │ │ ├── named.rs │ │ │ └── unnamed.rs │ │ ├── nested_meta.rs │ │ └── single_ident_path.rs │ │ └── warning.rs └── serde_valid_literal │ ├── Cargo.toml │ └── src │ ├── lib.rs │ ├── literal.rs │ ├── number.rs │ └── pattern.rs ├── docs ├── .gitignore ├── book.toml └── src │ ├── Attributes │ ├── array_max_items.md │ ├── array_min_items.md │ ├── custom_message.md │ ├── custom_validation.md │ ├── index.md │ ├── nested.md │ ├── numeric_exclusive_maximum.md │ ├── numeric_exclusive_minimum.md │ ├── numeric_maximum.md │ ├── numeric_minimum.md │ ├── numeric_multiple_of.md │ ├── object_max_properties.md │ ├── object_min_properties.md │ ├── string_max_length.md │ ├── string_min_length.md │ └── string_pattern.md │ ├── Features │ ├── fluent.md │ ├── index.md │ ├── json.md │ ├── toml.md │ └── yaml.md │ ├── Overview.md │ ├── SUMMARY.md │ ├── img │ ├── github.svg │ ├── run.png │ ├── runtab.png │ └── rustdoc.svg │ └── trait.md ├── scripts ├── doctest.sh ├── generate_readme.sh ├── publish.sh └── test.sh ├── serde_valid.code-workspace └── xtask ├── Cargo.toml └── src ├── commands.rs ├── commands └── update_tags.rs ├── main.rs └── utils.rs /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [alias] 2 | xtask = "run --package xtask --bin xtask --" 3 | -------------------------------------------------------------------------------- /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | on: [push] 2 | 3 | name: CI 4 | 5 | jobs: 6 | check: 7 | name: Check 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout sources 11 | uses: actions/checkout@v2 12 | 13 | - name: Install stable toolchain 14 | uses: actions-rs/toolchain@v1 15 | with: 16 | profile: minimal 17 | toolchain: stable 18 | override: true 19 | 20 | - name: Run cargo check 21 | uses: actions-rs/cargo@v1 22 | with: 23 | command: check 24 | 25 | test: 26 | name: Test Suite 27 | runs-on: ubuntu-latest 28 | steps: 29 | - name: Checkout sources 30 | uses: actions/checkout@v2 31 | 32 | - name: Install stable toolchain 33 | uses: actions-rs/toolchain@v1 34 | with: 35 | profile: minimal 36 | toolchain: stable 37 | override: true 38 | 39 | - name: Run cargo test 40 | uses: actions-rs/cargo@v1 41 | with: 42 | command: test 43 | args: --all-features 44 | 45 | lints: 46 | name: Lints 47 | runs-on: ubuntu-latest 48 | steps: 49 | - name: Checkout sources 50 | uses: actions/checkout@v2 51 | 52 | - name: Install stable toolchain 53 | uses: actions-rs/toolchain@v1 54 | with: 55 | profile: minimal 56 | toolchain: stable 57 | override: true 58 | components: rustfmt, clippy 59 | 60 | - name: Run cargo fmt 61 | uses: actions-rs/cargo@v1 62 | with: 63 | command: fmt 64 | args: --all -- --check 65 | 66 | - name: Run cargo clippy 67 | uses: actions-rs/cargo@v1 68 | with: 69 | command: clippy 70 | args: -- -D warnings 71 | -------------------------------------------------------------------------------- /.github/workflows/Publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" 7 | workflow_dispatch: 8 | 9 | jobs: 10 | publish: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | - run: ./scripts/publish.sh 15 | env: 16 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "tombi-toml.tombi" 4 | ] 5 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "lldb", 9 | "request": "launch", 10 | "name": "cargo test", 11 | "cargo": { 12 | "args": [ 13 | "test", 14 | "--all-features" 15 | ] 16 | }, 17 | "args": [], 18 | "cwd": "${workspaceFolder}", 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.cargo.features": "all", 3 | "rust-analyzer.checkOnSave": true, 4 | "rust-analyzer.check.command": "clippy", 5 | } -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = ["crates/*", "xtask"] 4 | 5 | [workspace.package] 6 | version = "1.0.5" 7 | authors = ["yassun7010 "] 8 | edition = "2021" 9 | repository = "https://github.com/yassun7010/serde_valid.git" 10 | license = "MIT" 11 | keywords = ["json_schema", "serde", "validation"] 12 | 13 | [workspace.dependencies] 14 | itertools = "0.13.0" 15 | paste = "^1.0" 16 | regex = "^1.6" 17 | serde = "^1.0" 18 | serde_json = "^1.0" 19 | serde_valid_derive = { path = "crates/serde_valid_derive" } 20 | serde_valid_literal = { path = "crates/serde_valid_literal" } 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 yassun7010 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | crates/serde_valid/README.md -------------------------------------------------------------------------------- /crates/serde_valid/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "serde_valid" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | description = "JSON Schema based validation tool using serde." 7 | repository.workspace = true 8 | license.workspace = true 9 | keywords.workspace = true 10 | categories = ["encoding"] 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | [dependencies] 14 | fluent = { version = "^0.16.0", optional = true, package = "fluent" } 15 | indexmap = { version = "^2.0", features = ["serde"] } 16 | itertools.workspace = true 17 | num-traits = "^0.2" 18 | once_cell = "^1.7" 19 | paste.workspace = true 20 | regex.workspace = true 21 | serde = { workspace = true, features = ["derive"] } 22 | serde_json.workspace = true 23 | serde_toml = { version = "^0.8", optional = true, package = "toml" } 24 | serde_valid_derive = { workspace = true } 25 | serde_valid_literal = { workspace = true } 26 | serde_yaml = { version = "^0.9", optional = true } 27 | thiserror = "^1.0" 28 | unicode-segmentation = "^1.7" 29 | 30 | [dev-dependencies] 31 | intl-memoizer = "0.5" 32 | unic-langid = "0.9" 33 | 34 | [features] 35 | default = ["i128"] 36 | fluent = ["dep:fluent", "serde_valid_derive/fluent"] 37 | i128 = ["indexmap/std", "num-traits/i128", "serde_valid_literal/i128"] 38 | toml = ["serde_toml"] 39 | yaml = ["serde_yaml"] 40 | -------------------------------------------------------------------------------- /crates/serde_valid/expands/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /crates/serde_valid/src/features.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "fluent")] 2 | pub mod fluent; 3 | 4 | #[cfg(feature = "toml")] 5 | pub mod toml; 6 | 7 | #[cfg(feature = "yaml")] 8 | pub mod yaml; 9 | -------------------------------------------------------------------------------- /crates/serde_valid/src/features/flatten.rs: -------------------------------------------------------------------------------- 1 | mod flat_error; 2 | mod flat_errors; 3 | mod into_flat; 4 | 5 | pub use flat_error::FlatError; 6 | pub use flat_errors::FlatErrors; 7 | pub use into_flat::IntoFlat; 8 | -------------------------------------------------------------------------------- /crates/serde_valid/src/features/fluent.rs: -------------------------------------------------------------------------------- 1 | mod error; 2 | mod localize; 3 | mod message; 4 | mod try_localize; 5 | 6 | pub use error::LocalizedError; 7 | pub use localize::Localize; 8 | pub use message::Message; 9 | pub use try_localize::TryLocalize; 10 | -------------------------------------------------------------------------------- /crates/serde_valid/src/features/fluent/error.rs: -------------------------------------------------------------------------------- 1 | use crate::validation::{ArrayErrors, ObjectErrors}; 2 | 3 | #[derive(Debug, Clone, serde::Serialize)] 4 | #[serde(untagged)] 5 | pub enum LocalizedError { 6 | String(String), 7 | Items(ArrayErrors), 8 | Properties(ObjectErrors), 9 | } 10 | 11 | impl std::fmt::Display for LocalizedError { 12 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 13 | match self { 14 | LocalizedError::String(string) => write!(f, "{}", string), 15 | LocalizedError::Items(items) => write!(f, "{}", items), 16 | LocalizedError::Properties(properties) => write!(f, "{}", properties), 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /crates/serde_valid/src/features/fluent/message.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Clone)] 2 | pub struct Message { 3 | pub id: &'static str, 4 | pub args: Vec<(&'static str, fluent::FluentValue<'static>)>, 5 | } 6 | 7 | impl std::fmt::Display for Message { 8 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 9 | self.id.fmt(f) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /crates/serde_valid/src/features/toml.rs: -------------------------------------------------------------------------------- 1 | mod from_toml_reader; 2 | mod from_toml_slice; 3 | mod from_toml_str; 4 | mod from_toml_value; 5 | mod to_toml_string; 6 | mod to_toml_value; 7 | mod to_toml_writer; 8 | 9 | pub use serde_toml::{toml, Value}; 10 | 11 | pub use from_toml_reader::FromTomlReader; 12 | pub use from_toml_slice::FromTomlSlice; 13 | pub use from_toml_str::FromTomlStr; 14 | pub use from_toml_value::FromTomlValue; 15 | pub use to_toml_string::ToTomlString; 16 | pub use to_toml_value::ToTomlValue; 17 | pub use to_toml_writer::ToTomlWriter; 18 | -------------------------------------------------------------------------------- /crates/serde_valid/src/features/toml/from_toml_reader.rs: -------------------------------------------------------------------------------- 1 | pub trait FromTomlReader 2 | where 3 | Self: Sized, 4 | { 5 | /// Convert from toml reader. 6 | /// 7 | /// ```should_panic 8 | /// use std::fs::File; 9 | /// use serde::Deserialize; 10 | /// use serde_valid::Validate; 11 | /// use serde_valid::toml::FromTomlReader; 12 | /// 13 | /// #[derive(Debug, Validate, Deserialize)] 14 | /// struct TestStruct { 15 | /// #[validate(maximum = 2000)] 16 | /// val: i32, 17 | /// } 18 | /// 19 | /// let s = TestStruct::from_toml_reader(File::open("foo.txt").unwrap()); 20 | /// 21 | /// assert!(s.is_ok()) 22 | /// ``` 23 | fn from_toml_reader(reader: R) -> Result> 24 | where 25 | R: std::io::Read; 26 | } 27 | 28 | impl FromTomlReader for T 29 | where 30 | T: serde::de::DeserializeOwned + crate::Validate, 31 | { 32 | fn from_toml_reader(reader: R) -> Result> 33 | where 34 | R: std::io::Read, 35 | { 36 | use serde::de::Error; 37 | 38 | let mut buffer = String::new(); 39 | let mut reader = reader; 40 | reader 41 | .read_to_string(&mut buffer) 42 | .map_err(serde_toml::de::Error::custom)?; 43 | 44 | let model: T = serde_toml::from_str(&buffer)?; 45 | model.validate().map_err(crate::Error::ValidationError)?; 46 | Ok(model) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /crates/serde_valid/src/features/toml/from_toml_slice.rs: -------------------------------------------------------------------------------- 1 | pub trait FromTomlSlice<'de> 2 | where 3 | Self: Sized, 4 | { 5 | /// Convert from toml slice. 6 | /// 7 | /// ```rust 8 | /// use serde::Deserialize; 9 | /// use serde_valid::Validate; 10 | /// use serde_valid::toml::FromTomlSlice; 11 | /// 12 | /// #[derive(Debug, Validate, Deserialize)] 13 | /// struct TestStruct { 14 | /// #[validate(min_length = 1)] 15 | /// val: String, 16 | /// } 17 | /// 18 | /// let s = TestStruct::from_toml_slice(br#"val= "abcde""#); 19 | /// 20 | /// s.unwrap(); 21 | /// ``` 22 | fn from_toml_slice(slice: &'de [u8]) -> Result>; 23 | } 24 | 25 | impl<'de, T> FromTomlSlice<'de> for T 26 | where 27 | T: serde::de::Deserialize<'de> + crate::Validate, 28 | { 29 | fn from_toml_slice(slice: &'de [u8]) -> Result> { 30 | let model = T::deserialize(serde_toml::Deserializer::new( 31 | // unwrap for backward compatibility. 32 | // `toml` crate no longer provides `from_slice`. 33 | std::str::from_utf8(slice).unwrap(), 34 | ))?; 35 | model.validate().map_err(crate::Error::ValidationError)?; 36 | Ok(model) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /crates/serde_valid/src/features/toml/from_toml_str.rs: -------------------------------------------------------------------------------- 1 | pub trait FromTomlStr<'de> 2 | where 3 | Self: Sized, 4 | { 5 | /// Convert from toml str. 6 | /// 7 | /// ```rust 8 | /// use serde::Deserialize; 9 | /// use serde_valid::Validate; 10 | /// use serde_valid::toml::FromTomlStr; 11 | /// 12 | /// #[derive(Debug, Validate, Deserialize)] 13 | /// struct TestStruct { 14 | /// #[validate(min_length = 1)] 15 | /// val: String, 16 | /// } 17 | /// 18 | /// let s = TestStruct::from_toml_str(r#"val = "abcde""#); 19 | /// 20 | /// assert!(s.is_ok()) 21 | /// ``` 22 | fn from_toml_str(str: &'de str) -> Result>; 23 | } 24 | 25 | impl<'de, T> FromTomlStr<'de> for T 26 | where 27 | T: serde::de::Deserialize<'de> + crate::Validate, 28 | { 29 | fn from_toml_str(str: &'de str) -> Result> { 30 | let model = T::deserialize(serde_toml::Deserializer::new(str))?; 31 | model.validate().map_err(crate::Error::ValidationError)?; 32 | Ok(model) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /crates/serde_valid/src/features/toml/from_toml_value.rs: -------------------------------------------------------------------------------- 1 | pub trait FromTomlValue 2 | where 3 | Self: Sized, 4 | { 5 | /// Convert from [`serde_toml::Value`](serde_toml::Value). 6 | /// 7 | /// ```rust 8 | /// use serde::Deserialize; 9 | /// use serde_valid::Validate; 10 | /// use serde_valid::toml::{FromTomlValue, Value}; 11 | /// 12 | /// #[derive(Debug, Validate, Deserialize)] 13 | /// struct TestStruct { 14 | /// #[validate(maximum = 2000)] 15 | /// val: i32, 16 | /// } 17 | /// 18 | /// let s = TestStruct::from_toml_value(serde_toml::from_str("val = 5").unwrap()); 19 | /// 20 | /// assert!(s.is_ok()) 21 | /// ``` 22 | fn from_toml_value( 23 | value: serde_toml::Value, 24 | ) -> Result>; 25 | } 26 | 27 | impl FromTomlValue for T 28 | where 29 | T: serde::de::DeserializeOwned + crate::Validate, 30 | { 31 | fn from_toml_value( 32 | value: serde_toml::Value, 33 | ) -> Result> { 34 | let model: T = serde::Deserialize::deserialize(value)?; 35 | model.validate().map_err(crate::Error::ValidationError)?; 36 | Ok(model) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /crates/serde_valid/src/features/toml/to_toml_string.rs: -------------------------------------------------------------------------------- 1 | pub trait ToTomlString { 2 | /// Convert to toml string. 3 | /// 4 | /// ```rust 5 | /// use serde::Serialize; 6 | /// use serde_valid::toml::ToTomlString; 7 | /// use serde_valid::Validate; 8 | /// 9 | /// #[derive(Debug, Validate, Serialize)] 10 | /// struct TestStruct { 11 | /// #[validate(maximum = 100)] 12 | /// val: i32, 13 | /// } 14 | /// let s = TestStruct { val: 10 }; 15 | /// 16 | /// assert!(s.to_toml_string().is_ok()); 17 | /// ``` 18 | fn to_toml_string(&self) -> Result; 19 | 20 | /// Convert to toml pretty string. 21 | /// 22 | /// ```rust 23 | /// use serde::Serialize; 24 | /// use serde_valid::toml::ToTomlString; 25 | /// use serde_valid::Validate; 26 | /// 27 | /// #[derive(Debug, Validate, Serialize)] 28 | /// struct TestStruct { 29 | /// #[validate(maximum = 100)] 30 | /// val: i32, 31 | /// } 32 | /// let s = TestStruct { val: 10 }; 33 | /// 34 | /// assert!(s.to_toml_string_pretty().is_ok()); 35 | /// ``` 36 | fn to_toml_string_pretty(&self) -> Result; 37 | } 38 | 39 | impl ToTomlString for T 40 | where 41 | T: serde::Serialize + crate::Validate, 42 | { 43 | fn to_toml_string(&self) -> Result { 44 | serde_toml::to_string(self) 45 | } 46 | 47 | fn to_toml_string_pretty(&self) -> Result { 48 | serde_toml::to_string_pretty(self) 49 | } 50 | } 51 | 52 | impl ToTomlString for serde_toml::Value { 53 | fn to_toml_string(&self) -> Result { 54 | serde_toml::to_string(self) 55 | } 56 | fn to_toml_string_pretty(&self) -> Result { 57 | serde_toml::to_string_pretty(self) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /crates/serde_valid/src/features/toml/to_toml_value.rs: -------------------------------------------------------------------------------- 1 | pub trait ToTomlValue { 2 | /// Convert to toml string. 3 | /// 4 | /// ```rust 5 | /// use serde::Serialize; 6 | /// use serde_valid::toml::ToTomlValue; 7 | /// use serde_valid::Validate; 8 | /// 9 | /// #[derive(Debug, Validate, Serialize)] 10 | /// struct TestStruct { 11 | /// #[validate(maximum = 100)] 12 | /// val: i32, 13 | /// } 14 | /// let s = TestStruct { val: 10 }; 15 | /// 16 | /// assert!(s.to_toml_value().is_ok()); 17 | /// ``` 18 | fn to_toml_value(&self) -> Result; 19 | } 20 | 21 | impl ToTomlValue for T 22 | where 23 | T: serde::Serialize + crate::Validate, 24 | { 25 | fn to_toml_value(&self) -> Result { 26 | serde_toml::Value::try_from(self) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /crates/serde_valid/src/features/toml/to_toml_writer.rs: -------------------------------------------------------------------------------- 1 | use serde::ser::Error; 2 | 3 | use super::ToTomlString; 4 | 5 | pub trait ToTomlWriter { 6 | /// Convert to toml writer. 7 | /// 8 | /// ```should_panic 9 | /// use std::fs::File; 10 | /// use serde::Serialize; 11 | /// use serde_valid::toml::ToTomlWriter; 12 | /// use serde_valid::Validate; 13 | /// 14 | /// #[derive(Debug, Validate, Serialize)] 15 | /// struct TestStruct { 16 | /// #[validate(maximum = 100)] 17 | /// val: i32, 18 | /// } 19 | /// let s = TestStruct { val: 10 }; 20 | /// 21 | /// assert!(s.to_toml_writer(File::open("foo.txt").unwrap()).is_ok()); 22 | /// ``` 23 | fn to_toml_writer(&self, writer: W) -> Result<(), serde_toml::ser::Error> 24 | where 25 | W: std::io::Write; 26 | 27 | /// Convert to pretty toml writer. 28 | /// 29 | /// ```should_panic 30 | /// use std::fs::File; 31 | /// use serde::Serialize; 32 | /// use serde_valid::toml::ToTomlWriter; 33 | /// use serde_valid::Validate; 34 | /// 35 | /// #[derive(Debug, Validate, Serialize)] 36 | /// struct TestStruct { 37 | /// #[validate(maximum = 100)] 38 | /// val: i32, 39 | /// } 40 | /// let s = TestStruct { val: 10 }; 41 | /// 42 | /// assert!(s.to_toml_writer_pretty(File::open("foo.txt").unwrap()).is_ok()); 43 | /// ``` 44 | fn to_toml_writer_pretty(&self, writer: W) -> Result<(), serde_toml::ser::Error> 45 | where 46 | W: std::io::Write; 47 | } 48 | 49 | impl ToTomlWriter for T 50 | where 51 | T: serde::Serialize + crate::Validate, 52 | { 53 | fn to_toml_writer(&self, writer: W) -> Result<(), serde_toml::ser::Error> 54 | where 55 | W: std::io::Write, 56 | { 57 | let mut writer = writer; 58 | match writer.write_all(&self.to_toml_string()?.into_bytes()) { 59 | Ok(_) => Ok(()), 60 | Err(err) => Err(serde_toml::ser::Error::custom(err.to_string())), 61 | } 62 | } 63 | 64 | fn to_toml_writer_pretty(&self, writer: W) -> Result<(), serde_toml::ser::Error> 65 | where 66 | W: std::io::Write, 67 | { 68 | let mut writer = writer; 69 | match writer.write_all(&self.to_toml_string_pretty()?.into_bytes()) { 70 | Ok(_) => Ok(()), 71 | Err(err) => Err(serde_toml::ser::Error::custom(err.to_string())), 72 | } 73 | } 74 | } 75 | 76 | impl ToTomlWriter for serde_toml::Value { 77 | fn to_toml_writer(&self, writer: W) -> Result<(), serde_toml::ser::Error> 78 | where 79 | W: std::io::Write, 80 | { 81 | let mut writer = writer; 82 | match writer.write_all(&self.to_toml_string()?.into_bytes()) { 83 | Ok(_) => Ok(()), 84 | Err(err) => Err(serde_toml::ser::Error::custom(err.to_string())), 85 | } 86 | } 87 | 88 | fn to_toml_writer_pretty(&self, writer: W) -> Result<(), serde_toml::ser::Error> 89 | where 90 | W: std::io::Write, 91 | { 92 | let mut writer = writer; 93 | match writer.write_all(&self.to_toml_string_pretty()?.into_bytes()) { 94 | Ok(_) => Ok(()), 95 | Err(err) => Err(serde_toml::ser::Error::custom(err.to_string())), 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /crates/serde_valid/src/features/yaml.rs: -------------------------------------------------------------------------------- 1 | mod from_yaml_reader; 2 | mod from_yaml_slice; 3 | mod from_yaml_str; 4 | mod from_yaml_value; 5 | mod to_yaml_string; 6 | mod to_yaml_value; 7 | mod to_yaml_writer; 8 | 9 | pub use serde_yaml::{Error, Index, Location, Mapping, Number, Sequence, Value}; 10 | 11 | pub use from_yaml_reader::FromYamlReader; 12 | pub use from_yaml_slice::FromYamlSlice; 13 | pub use from_yaml_str::FromYamlStr; 14 | pub use from_yaml_value::FromYamlValue; 15 | pub use to_yaml_string::ToYamlString; 16 | pub use to_yaml_value::ToYamlValue; 17 | pub use to_yaml_writer::ToYamlWriter; 18 | -------------------------------------------------------------------------------- /crates/serde_valid/src/features/yaml/from_yaml_reader.rs: -------------------------------------------------------------------------------- 1 | pub trait FromYamlReader 2 | where 3 | Self: Sized, 4 | { 5 | /// Convert from yaml reader. 6 | /// 7 | /// ```should_panic 8 | /// use std::fs::File; 9 | /// use serde::Deserialize; 10 | /// use serde_valid::Validate; 11 | /// use serde_valid::yaml::FromYamlReader; 12 | /// 13 | /// #[derive(Debug, Validate, Deserialize)] 14 | /// struct TestStruct { 15 | /// #[validate(maximum = 2000)] 16 | /// val: i32, 17 | /// } 18 | /// 19 | /// let s = TestStruct::from_yaml_reader(File::open("foo.txt").unwrap()); 20 | /// 21 | /// assert!(s.is_ok()) 22 | /// ``` 23 | fn from_yaml_reader(reader: R) -> Result> 24 | where 25 | R: std::io::Read; 26 | } 27 | 28 | impl FromYamlReader for T 29 | where 30 | for<'de> T: serde::de::Deserialize<'de>, 31 | T: crate::Validate, 32 | { 33 | fn from_yaml_reader(reader: R) -> Result> 34 | where 35 | R: std::io::Read, 36 | { 37 | let model: T = serde_yaml::from_reader(reader)?; 38 | model.validate().map_err(crate::Error::ValidationError)?; 39 | Ok(model) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /crates/serde_valid/src/features/yaml/from_yaml_slice.rs: -------------------------------------------------------------------------------- 1 | pub trait FromYamlSlice<'de> 2 | where 3 | Self: Sized, 4 | { 5 | /// Convert from yaml slice. 6 | /// 7 | /// ```rust 8 | /// use serde::Deserialize; 9 | /// use serde_valid::Validate; 10 | /// use serde_valid::yaml::FromYamlSlice; 11 | /// 12 | /// #[derive(Debug, Validate, Deserialize)] 13 | /// struct TestStruct<'a> { 14 | /// #[validate(min_length = 1)] 15 | /// val: &'a str, 16 | /// } 17 | /// 18 | /// let s = TestStruct::from_yaml_slice(b"---\nval: abcde\n"); 19 | /// 20 | /// assert!(s.is_ok()) 21 | /// ``` 22 | fn from_yaml_slice(slice: &'de [u8]) -> Result>; 23 | } 24 | 25 | impl<'de, T> FromYamlSlice<'de> for T 26 | where 27 | T: serde::de::Deserialize<'de> + crate::Validate, 28 | { 29 | fn from_yaml_slice(slice: &'de [u8]) -> Result> { 30 | let model: T = serde_yaml::from_slice(slice)?; 31 | model.validate().map_err(crate::Error::ValidationError)?; 32 | Ok(model) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /crates/serde_valid/src/features/yaml/from_yaml_str.rs: -------------------------------------------------------------------------------- 1 | pub trait FromYamlStr<'de> 2 | where 3 | Self: Sized, 4 | { 5 | /// Convert from yaml str. 6 | /// 7 | /// ```rust 8 | /// use serde::Deserialize; 9 | /// use serde_valid::Validate; 10 | /// use serde_valid::yaml::FromYamlStr; 11 | /// 12 | /// #[derive(Debug, Validate, Deserialize)] 13 | /// struct TestStruct<'a> { 14 | /// #[validate(min_length = 1)] 15 | /// val: &'a str, 16 | /// } 17 | /// 18 | /// let s = TestStruct::from_yaml_str("---\nval: abcde\n"); 19 | /// 20 | /// assert!(s.is_ok()) 21 | /// ``` 22 | fn from_yaml_str(str: &'de str) -> Result>; 23 | } 24 | 25 | impl<'de, T> FromYamlStr<'de> for T 26 | where 27 | T: serde::de::Deserialize<'de> + crate::Validate, 28 | { 29 | fn from_yaml_str(str: &'de str) -> Result> { 30 | let model: T = serde_yaml::from_str(str)?; 31 | model.validate().map_err(crate::Error::ValidationError)?; 32 | Ok(model) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /crates/serde_valid/src/features/yaml/from_yaml_value.rs: -------------------------------------------------------------------------------- 1 | pub trait FromYamlValue 2 | where 3 | Self: Sized, 4 | { 5 | /// Convert from [`serde_yaml::Value`](serde_yaml::Value). 6 | /// 7 | /// ```rust 8 | /// use serde::Deserialize; 9 | /// use serde_valid::Validate; 10 | /// use serde_valid::yaml::{FromYamlValue, Value}; 11 | /// 12 | /// #[derive(Debug, Validate, Deserialize)] 13 | /// struct TestStruct { 14 | /// #[validate(maximum = 2000)] 15 | /// val: i32, 16 | /// } 17 | /// 18 | /// let s = TestStruct::from_yaml_value(serde_yaml::from_str("val: 5").unwrap()); 19 | /// 20 | /// assert!(s.is_ok()) 21 | /// ``` 22 | fn from_yaml_value(value: serde_yaml::Value) -> Result>; 23 | } 24 | 25 | impl FromYamlValue for T 26 | where 27 | T: serde::de::DeserializeOwned + crate::Validate, 28 | { 29 | fn from_yaml_value(value: serde_yaml::Value) -> Result> { 30 | let model: T = serde_yaml::from_value(value)?; 31 | model.validate().map_err(crate::Error::ValidationError)?; 32 | Ok(model) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /crates/serde_valid/src/features/yaml/to_yaml_string.rs: -------------------------------------------------------------------------------- 1 | pub trait ToYamlString { 2 | /// Convert to yaml string. 3 | /// 4 | /// ```rust 5 | /// use serde::Serialize; 6 | /// use serde_valid::yaml::ToYamlString; 7 | /// use serde_valid::Validate; 8 | /// 9 | /// #[derive(Debug, Validate, Serialize)] 10 | /// struct TestStruct { 11 | /// #[validate(maximum = 100)] 12 | /// val: i32, 13 | /// } 14 | /// let s = TestStruct { val: 10 }; 15 | /// 16 | /// assert!(s.to_yaml_string().is_ok()); 17 | /// ``` 18 | fn to_yaml_string(&self) -> Result; 19 | } 20 | 21 | impl ToYamlString for T 22 | where 23 | T: serde::Serialize + crate::Validate, 24 | { 25 | fn to_yaml_string(&self) -> Result { 26 | serde_yaml::to_string(self) 27 | } 28 | } 29 | 30 | impl ToYamlString for serde_yaml::Value { 31 | fn to_yaml_string(&self) -> Result { 32 | serde_yaml::to_string(self) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /crates/serde_valid/src/features/yaml/to_yaml_value.rs: -------------------------------------------------------------------------------- 1 | pub trait ToYamlValue { 2 | /// Convert to yaml string. 3 | /// 4 | /// ```rust 5 | /// use serde::Serialize; 6 | /// use serde_valid::yaml::ToYamlValue; 7 | /// use serde_valid::Validate; 8 | /// 9 | /// #[derive(Debug, Validate, Serialize)] 10 | /// struct TestStruct { 11 | /// #[validate(maximum = 100)] 12 | /// val: i32, 13 | /// } 14 | /// let s = TestStruct { val: 10 }; 15 | /// 16 | /// assert!(s.to_yaml_value().is_ok()); 17 | /// ``` 18 | fn to_yaml_value(&self) -> Result; 19 | } 20 | 21 | impl ToYamlValue for T 22 | where 23 | T: serde::Serialize + crate::Validate, 24 | { 25 | fn to_yaml_value(&self) -> Result { 26 | serde_yaml::to_value(self) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /crates/serde_valid/src/features/yaml/to_yaml_writer.rs: -------------------------------------------------------------------------------- 1 | pub trait ToYamlWriter { 2 | /// Convert to yaml writer. 3 | /// 4 | /// ```should_panic 5 | /// use std::fs::File; 6 | /// use serde::Serialize; 7 | /// use serde_valid::yaml::ToYamlWriter; 8 | /// use serde_valid::Validate; 9 | /// 10 | /// #[derive(Debug, Validate, Serialize)] 11 | /// struct TestStruct { 12 | /// #[validate(maximum = 100)] 13 | /// val: i32, 14 | /// } 15 | /// let s = TestStruct { val: 10 }; 16 | /// 17 | /// assert!(s.to_yaml_writer(File::open("foo.txt").unwrap()).is_ok()); 18 | /// ``` 19 | fn to_yaml_writer(&self, writer: W) -> Result<(), serde_yaml::Error> 20 | where 21 | W: std::io::Write; 22 | } 23 | 24 | impl ToYamlWriter for T 25 | where 26 | T: serde::Serialize + crate::Validate, 27 | { 28 | fn to_yaml_writer(&self, writer: W) -> Result<(), serde_yaml::Error> 29 | where 30 | W: std::io::Write, 31 | { 32 | serde_yaml::to_writer(writer, self) 33 | } 34 | } 35 | 36 | impl ToYamlWriter for serde_yaml::Value { 37 | fn to_yaml_writer(&self, writer: W) -> Result<(), serde_yaml::Error> 38 | where 39 | W: std::io::Write, 40 | { 41 | serde_yaml::to_writer(writer, self) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /crates/serde_valid/src/json.rs: -------------------------------------------------------------------------------- 1 | mod from_json_reader; 2 | mod from_json_slice; 3 | mod from_json_str; 4 | mod from_json_value; 5 | mod to_json_string; 6 | mod to_json_value; 7 | mod to_json_writer; 8 | 9 | pub use serde_json::{json, Map, Value}; 10 | 11 | pub use from_json_reader::FromJsonReader; 12 | pub use from_json_slice::FromJsonSlice; 13 | pub use from_json_str::FromJsonStr; 14 | pub use from_json_value::FromJsonValue; 15 | pub use to_json_string::ToJsonString; 16 | pub use to_json_value::ToJsonValue; 17 | pub use to_json_writer::ToJsonWriter; 18 | -------------------------------------------------------------------------------- /crates/serde_valid/src/json/from_json_reader.rs: -------------------------------------------------------------------------------- 1 | pub trait FromJsonReader 2 | where 3 | Self: Sized, 4 | { 5 | /// Convert from json reader. 6 | /// 7 | /// ```should_panic 8 | /// use std::fs::File; 9 | /// use serde::Deserialize; 10 | /// use serde_valid::Validate; 11 | /// use serde_valid::json::FromJsonReader; 12 | /// 13 | /// #[derive(Debug, Validate, Deserialize)] 14 | /// struct TestStruct { 15 | /// #[validate(maximum = 2000)] 16 | /// val: i32, 17 | /// } 18 | /// 19 | /// let s = TestStruct::from_json_reader(File::open("foo.txt").unwrap()); 20 | /// 21 | /// assert!(s.is_ok()) 22 | /// ``` 23 | fn from_json_reader(reader: R) -> Result> 24 | where 25 | R: std::io::Read; 26 | } 27 | 28 | impl FromJsonReader for T 29 | where 30 | T: serde::de::DeserializeOwned + crate::Validate, 31 | { 32 | fn from_json_reader(reader: R) -> Result> 33 | where 34 | R: std::io::Read, 35 | { 36 | let model: T = serde_json::from_reader(reader)?; 37 | model.validate().map_err(crate::Error::ValidationError)?; 38 | Ok(model) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /crates/serde_valid/src/json/from_json_slice.rs: -------------------------------------------------------------------------------- 1 | pub trait FromJsonSlice<'de> 2 | where 3 | Self: Sized, 4 | { 5 | /// Convert from json slice. 6 | /// 7 | /// ```rust 8 | /// use serde::Deserialize; 9 | /// use serde_valid::Validate; 10 | /// use serde_valid::json::FromJsonSlice; 11 | /// 12 | /// #[derive(Debug, Validate, Deserialize)] 13 | /// struct TestStruct<'a> { 14 | /// #[validate(min_length = 1)] 15 | /// val: &'a str, 16 | /// } 17 | /// 18 | /// let s = TestStruct::from_json_slice(br#"{ "val": "abcde" }"#); 19 | /// 20 | /// assert!(s.is_ok()) 21 | /// ``` 22 | fn from_json_slice(slice: &'de [u8]) -> Result>; 23 | } 24 | 25 | impl<'de, T> FromJsonSlice<'de> for T 26 | where 27 | T: serde::de::Deserialize<'de> + crate::Validate, 28 | { 29 | fn from_json_slice(slice: &'de [u8]) -> Result> { 30 | let model: T = serde_json::from_slice(slice)?; 31 | model.validate().map_err(crate::Error::ValidationError)?; 32 | Ok(model) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /crates/serde_valid/src/json/from_json_str.rs: -------------------------------------------------------------------------------- 1 | pub trait FromJsonStr<'de> 2 | where 3 | Self: Sized, 4 | { 5 | /// Convert from json str. 6 | /// 7 | /// ```rust 8 | /// use serde::Deserialize; 9 | /// use serde_valid::Validate; 10 | /// use serde_valid::json::{json, FromJsonStr}; 11 | /// 12 | /// #[derive(Debug, Validate, Deserialize)] 13 | /// struct TestStruct<'a> { 14 | /// #[validate(min_length = 1)] 15 | /// val: &'a str, 16 | /// } 17 | /// 18 | /// let s = TestStruct::from_json_str(r#"{ "val": "abcde" }"#); 19 | /// 20 | /// assert!(s.is_ok()) 21 | /// ``` 22 | fn from_json_str(str: &'de str) -> Result>; 23 | } 24 | 25 | impl<'de, T> FromJsonStr<'de> for T 26 | where 27 | T: serde::de::Deserialize<'de> + crate::Validate, 28 | { 29 | fn from_json_str(str: &'de str) -> Result> { 30 | let model: Self = serde_json::from_str(str)?; 31 | model.validate().map_err(crate::Error::ValidationError)?; 32 | Ok(model) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /crates/serde_valid/src/json/from_json_value.rs: -------------------------------------------------------------------------------- 1 | pub trait FromJsonValue 2 | where 3 | Self: Sized, 4 | { 5 | /// Convert from [`serde_json::Value`](serde_json::Value). 6 | /// 7 | /// ```rust 8 | /// use serde::Deserialize; 9 | /// use serde_valid::Validate; 10 | /// use serde_valid::json::{json, FromJsonValue}; 11 | /// 12 | /// #[derive(Debug, Validate, Deserialize)] 13 | /// struct TestStruct { 14 | /// #[validate(maximum = 2000)] 15 | /// val: i32, 16 | /// } 17 | /// 18 | /// let s = TestStruct::from_json_value(json!({ "val": 1234 })); 19 | /// 20 | /// assert!(s.is_ok()) 21 | /// ``` 22 | fn from_json_value(value: serde_json::Value) -> Result>; 23 | } 24 | 25 | impl FromJsonValue for T 26 | where 27 | T: serde::de::DeserializeOwned + crate::Validate, 28 | { 29 | fn from_json_value(value: serde_json::Value) -> Result> { 30 | let model: T = serde_json::from_value(value)?; 31 | model.validate().map_err(crate::Error::ValidationError)?; 32 | Ok(model) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /crates/serde_valid/src/json/to_json_string.rs: -------------------------------------------------------------------------------- 1 | pub trait ToJsonString { 2 | /// Convert to json string. 3 | /// 4 | /// ```rust 5 | /// use serde::Serialize; 6 | /// use serde_valid::json::ToJsonString; 7 | /// use serde_valid::Validate; 8 | /// 9 | /// #[derive(Debug, Validate, Serialize)] 10 | /// struct TestStruct { 11 | /// #[validate(maximum = 100)] 12 | /// val: i32, 13 | /// } 14 | /// let s = TestStruct { val: 10 }; 15 | /// 16 | /// assert!(s.to_json_string().is_ok()); 17 | /// ``` 18 | fn to_json_string(&self) -> Result; 19 | 20 | /// Convert to json pretty string. 21 | /// 22 | /// ```rust 23 | /// use serde::Serialize; 24 | /// use serde_valid::json::ToJsonString; 25 | /// use serde_valid::Validate; 26 | /// 27 | /// #[derive(Debug, Validate, Serialize)] 28 | /// struct TestStruct { 29 | /// #[validate(maximum = 100)] 30 | /// val: i32, 31 | /// } 32 | /// let s = TestStruct { val: 10 }; 33 | /// 34 | /// assert!(s.to_json_string_pretty().is_ok()); 35 | /// ``` 36 | fn to_json_string_pretty(&self) -> Result; 37 | } 38 | 39 | impl ToJsonString for T 40 | where 41 | T: serde::Serialize + crate::Validate, 42 | { 43 | fn to_json_string(&self) -> Result { 44 | serde_json::to_string(self) 45 | } 46 | 47 | fn to_json_string_pretty(&self) -> Result { 48 | serde_json::to_string_pretty(self) 49 | } 50 | } 51 | 52 | impl ToJsonString for serde_json::Value { 53 | fn to_json_string(&self) -> Result { 54 | serde_json::to_string(self) 55 | } 56 | 57 | fn to_json_string_pretty(&self) -> Result { 58 | serde_json::to_string_pretty(self) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /crates/serde_valid/src/json/to_json_value.rs: -------------------------------------------------------------------------------- 1 | pub trait ToJsonValue { 2 | /// Convert to json string. 3 | /// 4 | /// ```rust 5 | /// use serde::Serialize; 6 | /// use serde_valid::json::ToJsonValue; 7 | /// use serde_valid::Validate; 8 | /// 9 | /// #[derive(Debug, Validate, Serialize)] 10 | /// struct TestStruct { 11 | /// #[validate(maximum = 100)] 12 | /// val: i32, 13 | /// } 14 | /// let s = TestStruct { val: 10 }; 15 | /// 16 | /// assert!(s.to_json_value().is_ok()); 17 | /// ``` 18 | fn to_json_value(&self) -> Result; 19 | } 20 | 21 | impl ToJsonValue for T 22 | where 23 | T: serde::Serialize + crate::Validate, 24 | { 25 | fn to_json_value(&self) -> Result { 26 | serde_json::to_value(self) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /crates/serde_valid/src/json/to_json_writer.rs: -------------------------------------------------------------------------------- 1 | pub trait ToJsonWriter { 2 | /// Convert to json writer. 3 | /// 4 | /// ```should_panic 5 | /// use std::fs::File; 6 | /// use serde::Serialize; 7 | /// use serde_valid::json::ToJsonWriter; 8 | /// use serde_valid::Validate; 9 | /// 10 | /// #[derive(Debug, Validate, Serialize)] 11 | /// struct TestStruct { 12 | /// #[validate(maximum = 100)] 13 | /// val: i32, 14 | /// } 15 | /// let s = TestStruct { val: 10 }; 16 | /// 17 | /// assert!(s.to_json_writer(File::open("foo.txt").unwrap()).is_ok()); 18 | /// ``` 19 | fn to_json_writer(&self, writer: W) -> Result<(), serde_json::Error> 20 | where 21 | W: std::io::Write; 22 | 23 | /// Convert to pretty json writer. 24 | /// 25 | /// ```should_panic 26 | /// use std::fs::File; 27 | /// use serde::Serialize; 28 | /// use serde_valid::json::ToJsonWriter; 29 | /// use serde_valid::Validate; 30 | /// 31 | /// #[derive(Debug, Validate, Serialize)] 32 | /// struct TestStruct { 33 | /// #[validate(maximum = 100)] 34 | /// val: i32, 35 | /// } 36 | /// let s = TestStruct { val: 10 }; 37 | /// 38 | /// assert!(s.to_json_writer_pretty(File::open("foo.txt").unwrap()).is_ok()); 39 | /// ``` 40 | fn to_json_writer_pretty(&self, writer: W) -> Result<(), serde_json::Error> 41 | where 42 | W: std::io::Write; 43 | } 44 | 45 | impl ToJsonWriter for T 46 | where 47 | T: serde::Serialize + crate::Validate, 48 | { 49 | fn to_json_writer(&self, writer: W) -> Result<(), serde_json::Error> 50 | where 51 | W: std::io::Write, 52 | { 53 | serde_json::to_writer(writer, self) 54 | } 55 | 56 | fn to_json_writer_pretty(&self, writer: W) -> Result<(), serde_json::Error> 57 | where 58 | W: std::io::Write, 59 | { 60 | serde_json::to_writer_pretty(writer, self) 61 | } 62 | } 63 | 64 | impl ToJsonWriter for serde_json::Value { 65 | fn to_json_writer(&self, writer: W) -> Result<(), serde_json::Error> 66 | where 67 | W: std::io::Write, 68 | { 69 | serde_json::to_writer(writer, self) 70 | } 71 | 72 | fn to_json_writer_pretty(&self, writer: W) -> Result<(), serde_json::Error> 73 | where 74 | W: std::io::Write, 75 | { 76 | serde_json::to_writer_pretty(writer, self) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /crates/serde_valid/src/traits.rs: -------------------------------------------------------------------------------- 1 | mod is_match; 2 | mod is_unique; 3 | mod length; 4 | mod size; 5 | 6 | pub use is_match::IsMatch; 7 | pub use is_unique::IsUnique; 8 | pub use length::Length; 9 | pub use size::Size; 10 | -------------------------------------------------------------------------------- /crates/serde_valid/src/traits/is_match.rs: -------------------------------------------------------------------------------- 1 | pub trait IsMatch { 2 | fn is_match(&self, pattern: ®ex::Regex) -> bool; 3 | } 4 | 5 | macro_rules! impl_for_str { 6 | ($ty:ty) => { 7 | impl IsMatch for $ty { 8 | fn is_match(&self, pattern: ®ex::Regex) -> bool { 9 | pattern.is_match(self) 10 | } 11 | } 12 | }; 13 | } 14 | 15 | impl_for_str!(str); 16 | impl_for_str!(&str); 17 | impl_for_str!(String); 18 | impl_for_str!(std::borrow::Cow<'_, str>); 19 | 20 | macro_rules! impl_for_os_str { 21 | ($ty:ty) => { 22 | impl IsMatch for $ty { 23 | fn is_match(&self, pattern: ®ex::Regex) -> bool { 24 | pattern.is_match(&self.to_string_lossy()) 25 | } 26 | } 27 | }; 28 | } 29 | 30 | impl_for_os_str!(std::ffi::OsStr); 31 | impl_for_os_str!(&std::ffi::OsStr); 32 | impl_for_os_str!(std::ffi::OsString); 33 | impl_for_os_str!(std::borrow::Cow<'_, std::ffi::OsStr>); 34 | 35 | macro_rules! impl_for_path { 36 | ($ty:ty) => { 37 | impl IsMatch for $ty { 38 | fn is_match(&self, pattern: ®ex::Regex) -> bool { 39 | self.as_os_str().is_match(pattern) 40 | } 41 | } 42 | }; 43 | } 44 | 45 | impl_for_path!(std::path::Path); 46 | impl_for_path!(&std::path::Path); 47 | impl_for_path!(std::path::PathBuf); 48 | impl_for_path!(std::borrow::Cow<'_, std::path::Path>); 49 | -------------------------------------------------------------------------------- /crates/serde_valid/src/traits/is_unique.rs: -------------------------------------------------------------------------------- 1 | use itertools::Itertools; 2 | 3 | pub trait IsUnique { 4 | fn is_unique(&self) -> bool; 5 | } 6 | 7 | impl IsUnique for [T] 8 | where 9 | T: std::cmp::Eq + std::hash::Hash, 10 | { 11 | fn is_unique(&self) -> bool { 12 | let len = self.len(); 13 | let unique = self.iter().unique(); 14 | let (lower, upper) = unique.size_hint(); 15 | if let Some(upper) = upper { 16 | if lower == len && upper == len { 17 | return true; 18 | } 19 | } 20 | unique.count() == len 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /crates/serde_valid/src/traits/length.rs: -------------------------------------------------------------------------------- 1 | use unicode_segmentation::UnicodeSegmentation; 2 | 3 | pub trait Length { 4 | fn length(&self) -> usize; 5 | } 6 | 7 | macro_rules! impl_for_str { 8 | ($ty:ty) => { 9 | impl Length for $ty { 10 | fn length(&self) -> usize { 11 | self.graphemes(true).count() 12 | } 13 | } 14 | }; 15 | } 16 | 17 | impl_for_str!(str); 18 | impl_for_str!(&str); 19 | impl_for_str!(String); 20 | impl_for_str!(std::borrow::Cow<'_, str>); 21 | 22 | macro_rules! impl_for_os_str { 23 | ($ty:ty) => { 24 | impl Length for $ty { 25 | fn length(&self) -> usize { 26 | self.to_string_lossy().length() 27 | } 28 | } 29 | }; 30 | } 31 | 32 | impl_for_os_str!(std::ffi::OsStr); 33 | impl_for_os_str!(&std::ffi::OsStr); 34 | impl_for_os_str!(std::ffi::OsString); 35 | impl_for_os_str!(std::borrow::Cow<'_, std::ffi::OsStr>); 36 | 37 | macro_rules! impl_for_path { 38 | ($ty:ty) => { 39 | impl Length for $ty { 40 | fn length(&self) -> usize { 41 | self.as_os_str().length() 42 | } 43 | } 44 | }; 45 | } 46 | 47 | impl_for_path!(std::path::Path); 48 | impl_for_path!(&std::path::Path); 49 | impl_for_path!(std::path::PathBuf); 50 | impl_for_os_str!(std::borrow::Cow<'_, std::path::Path>); 51 | -------------------------------------------------------------------------------- /crates/serde_valid/src/traits/size.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeMap; 2 | use std::collections::HashMap; 3 | 4 | pub trait Size { 5 | fn size(&self) -> usize; 6 | } 7 | 8 | impl Size for HashMap { 9 | fn size(&self) -> usize { 10 | self.len() 11 | } 12 | } 13 | 14 | impl Size for BTreeMap { 15 | fn size(&self) -> usize { 16 | self.len() 17 | } 18 | } 19 | 20 | impl Size for serde_json::Map { 21 | fn size(&self) -> usize { 22 | self.len() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /crates/serde_valid/src/utils.rs: -------------------------------------------------------------------------------- 1 | mod duration; 2 | 3 | pub use duration::duration_exclusive_maximum; 4 | pub use duration::duration_exclusive_minimum; 5 | pub use duration::duration_maximum; 6 | pub use duration::duration_minimum; 7 | -------------------------------------------------------------------------------- /crates/serde_valid/src/utils/duration.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | /// Validate that the duration is less than or equal to the maximum. 4 | /// 5 | /// # Example 6 | /// 7 | /// ```rust 8 | /// use std::time::Duration; 9 | /// 10 | /// use serde_valid::utils::duration_maximum; 11 | /// use serde_valid::Validate; 12 | /// 13 | /// #[derive(Validate)] 14 | /// struct TestStruct { 15 | /// #[validate(custom = duration_maximum(Duration::from_micros(5)))] 16 | /// val: Duration, 17 | /// } 18 | /// 19 | /// let s = TestStruct { 20 | /// val: Duration::from_micros(5), 21 | /// }; 22 | /// 23 | /// assert!(s.validate().is_ok()); 24 | /// ``` 25 | #[allow(dead_code)] 26 | pub fn duration_maximum( 27 | maximum: Duration, 28 | ) -> impl FnOnce(&Duration) -> Result<(), crate::validation::Error> { 29 | move |val: &Duration| { 30 | if *val <= maximum { 31 | Ok(()) 32 | } else { 33 | Err(crate::validation::Error::Custom(format!( 34 | "Duration {val:?} is greater than maximum {maximum:?}.", 35 | ))) 36 | } 37 | } 38 | } 39 | 40 | /// Validate that the duration is greater than or equal to the minimum. 41 | /// 42 | /// # Example 43 | /// 44 | /// ```rust 45 | /// use std::time::Duration; 46 | /// 47 | /// use serde_valid::utils::duration_minimum; 48 | /// use serde_valid::Validate; 49 | /// 50 | /// #[derive(Validate)] 51 | /// struct TestStruct { 52 | /// #[validate(custom = duration_minimum(Duration::from_micros(5)))] 53 | /// val: Duration, 54 | /// } 55 | /// 56 | /// let s = TestStruct { 57 | /// val: Duration::from_secs(5), 58 | /// }; 59 | /// 60 | /// assert!(s.validate().is_ok()); 61 | /// ``` 62 | #[allow(dead_code)] 63 | pub fn duration_minimum( 64 | minimum: Duration, 65 | ) -> impl FnOnce(&Duration) -> Result<(), crate::validation::Error> { 66 | move |val: &Duration| { 67 | if *val >= minimum { 68 | Ok(()) 69 | } else { 70 | Err(crate::validation::Error::Custom(format!( 71 | "Duration {val:?} is less than minimum {minimum:?}.", 72 | ))) 73 | } 74 | } 75 | } 76 | 77 | /// Validate that the duration is less than the exclusive maximum. 78 | /// 79 | /// # Example 80 | /// 81 | /// ```rust 82 | /// use std::time::Duration; 83 | /// 84 | /// use serde_valid::utils::duration_exclusive_maximum; 85 | /// use serde_valid::Validate; 86 | /// 87 | /// #[derive(Validate)] 88 | /// struct TestStruct { 89 | /// #[validate(custom = duration_exclusive_maximum(Duration::from_micros(5)))] 90 | /// val: Duration, 91 | /// } 92 | /// 93 | /// let s = TestStruct { 94 | /// val: Duration::from_micros(4), 95 | /// }; 96 | /// 97 | /// assert!(s.validate().is_ok()); 98 | /// ``` 99 | #[allow(dead_code)] 100 | pub fn duration_exclusive_maximum( 101 | maximum: Duration, 102 | ) -> impl FnOnce(&Duration) -> Result<(), crate::validation::Error> { 103 | move |val: &Duration| { 104 | if *val < maximum { 105 | Ok(()) 106 | } else { 107 | Err(crate::validation::Error::Custom(format!( 108 | "Duration {val:?} is greater than or equal to exclusive maximum {maximum:?}.", 109 | ))) 110 | } 111 | } 112 | } 113 | 114 | /// Validate that the duration is greater than the exclusive minimum. 115 | /// 116 | /// # Example 117 | /// 118 | /// ```rust 119 | /// use std::time::Duration; 120 | /// 121 | /// use serde_valid::utils::duration_exclusive_minimum; 122 | /// use serde_valid::Validate; 123 | /// 124 | /// #[derive(Validate)] 125 | /// struct TestStruct { 126 | /// #[validate(custom = duration_exclusive_minimum(Duration::from_micros(5)))] 127 | /// val: Duration, 128 | /// } 129 | /// 130 | /// let s = TestStruct { 131 | /// val: Duration::from_micros(6), 132 | /// }; 133 | /// 134 | /// assert!(s.validate().is_ok()); 135 | /// ``` 136 | #[allow(dead_code)] 137 | pub fn duration_exclusive_minimum( 138 | minimum: Duration, 139 | ) -> impl FnOnce(&Duration) -> Result<(), crate::validation::Error> { 140 | move |val: &Duration| { 141 | if *val > minimum { 142 | Ok(()) 143 | } else { 144 | Err(crate::validation::Error::Custom(format!( 145 | "Duration {val:?} is less than or equal to exclusive minimum {minimum:?}.", 146 | ))) 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /crates/serde_valid/src/validation/array.rs: -------------------------------------------------------------------------------- 1 | mod max_items; 2 | mod min_items; 3 | mod unique_items; 4 | 5 | pub use max_items::ValidateMaxItems; 6 | pub use min_items::ValidateMinItems; 7 | pub use unique_items::ValidateUniqueItems; 8 | 9 | use crate::{MaxItemsError, MinItemsError}; 10 | 11 | macro_rules! impl_validate_array_length_items { 12 | ($ErrorType:ident) => { 13 | paste::paste! { 14 | impl [] for Option 15 | where 16 | T: [], 17 | { 18 | fn [] (&self, limit: usize) -> Result<(), [<$ErrorType Error>]> { 19 | match self { 20 | Some(value) => value.[](limit), 21 | None => Ok(()), 22 | } 23 | } 24 | } 25 | } 26 | }; 27 | } 28 | 29 | impl_validate_array_length_items!(MaxItems); 30 | impl_validate_array_length_items!(MinItems); 31 | -------------------------------------------------------------------------------- /crates/serde_valid/src/validation/array/max_items.rs: -------------------------------------------------------------------------------- 1 | /// Max length validation of the array items. 2 | /// 3 | /// See 4 | /// 5 | /// ```rust 6 | /// use serde_json::json; 7 | /// use serde_valid::{Validate, ValidateMaxItems}; 8 | /// 9 | /// struct MyType(Vec); 10 | /// 11 | /// impl ValidateMaxItems for MyType { 12 | /// fn validate_max_items( 13 | /// &self, 14 | /// max_items: usize, 15 | /// ) -> Result<(), serde_valid::MaxItemsError> { 16 | /// self.0.validate_max_items(max_items) 17 | /// } 18 | /// } 19 | /// 20 | /// #[derive(Validate)] 21 | /// struct TestStruct { 22 | /// #[validate(max_items = 2)] 23 | /// val: MyType, 24 | /// } 25 | /// 26 | /// let s = TestStruct { 27 | /// val: MyType(vec![1, 2, 3]), 28 | /// }; 29 | /// 30 | /// assert_eq!( 31 | /// s.validate().unwrap_err().to_string(), 32 | /// json!({ 33 | /// "errors": [], 34 | /// "properties": { 35 | /// "val": { 36 | /// "errors": ["The length of the items must be `<= 2`."] 37 | /// } 38 | /// } 39 | /// }) 40 | /// .to_string() 41 | /// ); 42 | /// ``` 43 | pub trait ValidateMaxItems { 44 | fn validate_max_items(&self, max_items: usize) -> Result<(), crate::MaxItemsError>; 45 | } 46 | 47 | impl ValidateMaxItems for Vec { 48 | fn validate_max_items(&self, max_items: usize) -> Result<(), crate::MaxItemsError> { 49 | if max_items >= self.len() { 50 | Ok(()) 51 | } else { 52 | Err(crate::MaxItemsError::new(max_items)) 53 | } 54 | } 55 | } 56 | 57 | impl ValidateMaxItems for [T; N] { 58 | fn validate_max_items(&self, max_items: usize) -> Result<(), crate::MaxItemsError> { 59 | if max_items >= self.len() { 60 | Ok(()) 61 | } else { 62 | Err(crate::MaxItemsError::new(max_items)) 63 | } 64 | } 65 | } 66 | 67 | #[cfg(test)] 68 | mod tests { 69 | use super::*; 70 | 71 | #[test] 72 | fn test_validate_array_vec_type() { 73 | assert!(ValidateMaxItems::validate_max_items(&vec!['a', 'b', 'c'], 3).is_ok()); 74 | } 75 | 76 | #[test] 77 | fn test_validate_array_max_items_array_type() { 78 | assert!(ValidateMaxItems::validate_max_items(&['a', 'b', 'c'], 3).is_ok()); 79 | } 80 | 81 | #[test] 82 | fn test_validate_array_max_items_is_true() { 83 | assert!(ValidateMaxItems::validate_max_items(&[1, 2, 3], 3).is_ok()); 84 | } 85 | 86 | #[test] 87 | fn test_validate_array_max_items_is_false() { 88 | assert!(ValidateMaxItems::validate_max_items(&[1, 2, 3], 2).is_err()); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /crates/serde_valid/src/validation/array/min_items.rs: -------------------------------------------------------------------------------- 1 | /// Min length validation of the array items. 2 | /// 3 | /// See 4 | /// 5 | /// ```rust 6 | /// use serde_json::json; 7 | /// use serde_valid::{Validate, ValidateMinItems}; 8 | /// 9 | /// struct MyType(Vec); 10 | /// 11 | /// impl ValidateMinItems for MyType { 12 | /// fn validate_min_items( 13 | /// &self, 14 | /// min_items: usize, 15 | /// ) -> Result<(), serde_valid::MinItemsError> { 16 | /// self.0.validate_min_items(min_items) 17 | /// } 18 | /// } 19 | /// 20 | /// #[derive(Validate)] 21 | /// struct TestStruct { 22 | /// #[validate(min_items = 2)] 23 | /// val: MyType, 24 | /// } 25 | /// 26 | /// let s = TestStruct { 27 | /// val: MyType(vec![1]), 28 | /// }; 29 | /// 30 | /// assert_eq!( 31 | /// s.validate().unwrap_err().to_string(), 32 | /// json!({ 33 | /// "errors": [], 34 | /// "properties": { 35 | /// "val": { 36 | /// "errors": ["The length of the items must be `>= 2`."] 37 | /// } 38 | /// } 39 | /// }) 40 | /// .to_string() 41 | /// ); 42 | /// ``` 43 | pub trait ValidateMinItems { 44 | fn validate_min_items(&self, min_items: usize) -> Result<(), crate::MinItemsError>; 45 | } 46 | 47 | impl ValidateMinItems for Vec { 48 | fn validate_min_items(&self, min_items: usize) -> Result<(), crate::MinItemsError> { 49 | if min_items <= self.len() { 50 | Ok(()) 51 | } else { 52 | Err(crate::MinItemsError::new(min_items)) 53 | } 54 | } 55 | } 56 | 57 | impl ValidateMinItems for [T; N] { 58 | fn validate_min_items(&self, min_items: usize) -> Result<(), crate::MinItemsError> { 59 | if min_items <= self.len() { 60 | Ok(()) 61 | } else { 62 | Err(crate::MinItemsError::new(min_items)) 63 | } 64 | } 65 | } 66 | 67 | #[cfg(test)] 68 | mod tests { 69 | use super::*; 70 | 71 | #[test] 72 | fn test_validate_array_min_items_is_true() { 73 | assert!(ValidateMinItems::validate_min_items(&[1, 2, 3], 3).is_ok()); 74 | } 75 | 76 | #[test] 77 | fn test_validate_array_min_items_is_false() { 78 | assert!(ValidateMinItems::validate_min_items(&[1, 2, 3], 4).is_err()); 79 | } 80 | 81 | #[test] 82 | fn test_validate_array_min_items_vec_is_true() { 83 | assert!(ValidateMinItems::validate_min_items(&vec!['a', 'b', 'c'], 3).is_ok()); 84 | } 85 | 86 | #[test] 87 | fn test_validate_array_min_items_array_is_true() { 88 | assert!(ValidateMinItems::validate_min_items(&['a', 'b', 'c'], 3).is_ok()); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /crates/serde_valid/src/validation/array/unique_items.rs: -------------------------------------------------------------------------------- 1 | use crate::traits::IsUnique; 2 | 3 | /// Uniqueness validation of the array items. 4 | /// 5 | /// See 6 | /// 7 | /// ```rust 8 | /// use serde_json::json; 9 | /// use serde_valid::{Validate, ValidateUniqueItems}; 10 | /// 11 | /// struct MyType(Vec); 12 | /// 13 | /// impl ValidateUniqueItems for MyType { 14 | /// fn validate_unique_items(&self) -> Result<(), serde_valid::UniqueItemsError> { 15 | /// self.0.validate_unique_items() 16 | /// } 17 | /// } 18 | /// 19 | /// #[derive(Validate)] 20 | /// struct TestStruct { 21 | /// #[validate(unique_items)] 22 | /// val: MyType, 23 | /// } 24 | /// 25 | /// let s = TestStruct { 26 | /// val: MyType(vec![1, 2, 1]), 27 | /// }; 28 | /// 29 | /// assert_eq!( 30 | /// s.validate().unwrap_err().to_string(), 31 | /// json!({ 32 | /// "errors": [], 33 | /// "properties": { 34 | /// "val": { 35 | /// "errors": ["The items must be unique."] 36 | /// } 37 | /// } 38 | /// }) 39 | /// .to_string() 40 | /// ); 41 | /// ``` 42 | pub trait ValidateUniqueItems { 43 | fn validate_unique_items(&self) -> Result<(), crate::UniqueItemsError>; 44 | } 45 | 46 | impl ValidateUniqueItems for Vec 47 | where 48 | T: std::cmp::Eq + std::hash::Hash + std::fmt::Debug, 49 | { 50 | fn validate_unique_items(&self) -> Result<(), crate::UniqueItemsError> { 51 | if self.is_unique() { 52 | Ok(()) 53 | } else { 54 | Err(crate::UniqueItemsError {}) 55 | } 56 | } 57 | } 58 | 59 | impl ValidateUniqueItems for [T; N] 60 | where 61 | T: std::cmp::Eq + std::hash::Hash + std::fmt::Debug, 62 | { 63 | fn validate_unique_items(&self) -> Result<(), crate::UniqueItemsError> { 64 | if self.is_unique() { 65 | Ok(()) 66 | } else { 67 | Err(crate::UniqueItemsError {}) 68 | } 69 | } 70 | } 71 | 72 | impl ValidateUniqueItems for Option 73 | where 74 | T: ValidateUniqueItems, 75 | { 76 | fn validate_unique_items(&self) -> Result<(), crate::UniqueItemsError> { 77 | match self { 78 | Some(value) => value.validate_unique_items(), 79 | None => Ok(()), 80 | } 81 | } 82 | } 83 | 84 | #[cfg(test)] 85 | mod tests { 86 | use super::*; 87 | 88 | #[test] 89 | fn test_validate_array_unique_items_array_type_is_true() { 90 | assert!(ValidateUniqueItems::validate_unique_items(&[1, 2, 3, 4]).is_ok()); 91 | } 92 | 93 | #[test] 94 | fn test_validate_array_unique_items_vec_type_is_true() { 95 | assert!(ValidateUniqueItems::validate_unique_items(&vec![1, 2, 3, 4]).is_ok()); 96 | } 97 | 98 | #[test] 99 | fn test_validate_array_unique_items_is_false() { 100 | assert!(ValidateUniqueItems::validate_unique_items(&[1, 2, 3, 3]).is_err()); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /crates/serde_valid/src/validation/composited.rs: -------------------------------------------------------------------------------- 1 | use crate::validation::error::IntoError; 2 | 3 | use crate::error::{ 4 | EnumerateError, ExclusiveMaximumError, ExclusiveMinimumError, MaxItemsError, MaxLengthError, 5 | MaxPropertiesError, MaximumError, MinItemsError, MinLengthError, MinPropertiesError, 6 | MinimumError, MultipleOfError, PatternError, UniqueItemsError, 7 | }; 8 | use indexmap::IndexMap; 9 | 10 | /// Composited use Vec or Map error. 11 | /// 12 | /// Composited elevates field validation errors to per-element error in the array. 13 | /// 14 | /// # Examples 15 | /// ```rust 16 | /// use serde_valid::Validate; 17 | /// 18 | /// #[derive(Validate)] 19 | /// pub struct Data { 20 | /// #[validate(minimum = 0)] 21 | /// #[validate(maximum = 10)] 22 | /// pub val: Vec, // <-- Here 23 | /// } 24 | /// ``` 25 | #[derive(Debug)] 26 | pub enum Composited { 27 | Single(Error), 28 | Array(IndexMap>), 29 | } 30 | 31 | macro_rules! impl_into_error { 32 | ($ErrorType:ident) => { 33 | paste::paste! { 34 | impl IntoError<[<$ErrorType Error>]> for Composited<[<$ErrorType Error>]> { 35 | fn into_error_by(self, format: crate::validation::error::Format<[<$ErrorType Error>]>) -> crate::validation::error::Error { 36 | match self { 37 | Composited::Single(single) => { 38 | crate::validation::error::Error::$ErrorType(format.into_message(single)) 39 | }, 40 | Composited::Array(array) =>{ 41 | crate::validation::error::Error::Items(crate::validation::error::ArrayErrors::new( 42 | Vec::with_capacity(0), 43 | array 44 | .into_iter() 45 | .map(|(index, params)| { 46 | (index, crate::validation::Errors::NewType(vec![params.into_error_by(format.clone())])) 47 | }) 48 | .collect::>(), 49 | ))}, 50 | } 51 | } 52 | } 53 | } 54 | }; 55 | } 56 | 57 | // Global 58 | impl_into_error!(Enumerate); 59 | 60 | // Numeric 61 | impl_into_error!(Maximum); 62 | impl_into_error!(Minimum); 63 | impl_into_error!(ExclusiveMaximum); 64 | impl_into_error!(ExclusiveMinimum); 65 | impl_into_error!(MultipleOf); 66 | 67 | // String 68 | impl_into_error!(MaxLength); 69 | impl_into_error!(MinLength); 70 | impl_into_error!(Pattern); 71 | 72 | // Array 73 | impl_into_error!(MaxItems); 74 | impl_into_error!(MinItems); 75 | impl_into_error!(UniqueItems); 76 | 77 | // Object 78 | impl_into_error!(MaxProperties); 79 | impl_into_error!(MinProperties); 80 | -------------------------------------------------------------------------------- /crates/serde_valid/src/validation/custom.rs: -------------------------------------------------------------------------------- 1 | /// This function is used to avoid [rustc(E0282)](https://doc.rust-lang.org/error_codes/E0282.html) error in `#[validate(custom = ...)]` validator on the struct. 2 | #[inline] 3 | pub fn wrap_closure_validation( 4 | data: &T, 5 | f: impl FnOnce(&T) -> Result<(), M>, 6 | ) -> Result<(), Vec> { 7 | f(data).map_err(|e| e.into_vec_errors()) 8 | } 9 | 10 | #[inline] 11 | pub fn wrap_into_vec_errors( 12 | result: Result<(), M>, 13 | ) -> Result<(), Vec> { 14 | result.map_err(|e| e.into_vec_errors()) 15 | } 16 | 17 | pub trait IntoVecErrors { 18 | fn into_vec_errors(self) -> Vec; 19 | } 20 | 21 | impl IntoVecErrors for Vec { 22 | fn into_vec_errors(self) -> Vec { 23 | self 24 | } 25 | } 26 | 27 | impl IntoVecErrors for crate::validation::Error { 28 | fn into_vec_errors(self) -> Vec { 29 | vec![self] 30 | } 31 | } 32 | 33 | #[cfg(test)] 34 | mod test { 35 | use super::*; 36 | 37 | #[test] 38 | fn test_custom_fn_single_error() { 39 | fn single_error(data: &i32) -> Result<(), crate::validation::Error> { 40 | if *data > 0 { 41 | Ok(()) 42 | } else { 43 | Err(crate::validation::Error::Custom( 44 | "Value must be greater than 0".to_string(), 45 | )) 46 | } 47 | } 48 | 49 | assert!(wrap_closure_validation(&1i32, single_error).is_ok()); 50 | assert!(wrap_closure_validation(&0i32, single_error).is_err()); 51 | assert!(wrap_closure_validation(&-1i32, single_error).is_err()); 52 | } 53 | 54 | #[test] 55 | fn test_custom_fn_multiple_errors() { 56 | fn multiple_errors(data: &i32) -> Result<(), Vec> { 57 | let mut errors = Vec::new(); 58 | if *data < 1 { 59 | errors.push(crate::validation::Error::Custom( 60 | "Value must be greater than 0".to_string(), 61 | )); 62 | } 63 | 64 | if *data >= 10 { 65 | errors.push(crate::validation::Error::Custom( 66 | "Value must be less than 10".to_string(), 67 | )); 68 | } 69 | 70 | if errors.is_empty() { 71 | Ok(()) 72 | } else { 73 | Err(errors) 74 | } 75 | } 76 | 77 | assert!(wrap_closure_validation(&1i32, multiple_errors).is_ok()); 78 | assert!(wrap_closure_validation(&10i32, multiple_errors).is_err()); 79 | assert!(wrap_closure_validation(&11i32, multiple_errors).is_err()); 80 | assert!(wrap_closure_validation(&0i32, multiple_errors).is_err()); 81 | assert!(wrap_closure_validation(&-1i32, multiple_errors).is_err()); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /crates/serde_valid/src/validation/error.rs: -------------------------------------------------------------------------------- 1 | mod array_erros; 2 | mod errors; 3 | mod format; 4 | mod into_error; 5 | mod message; 6 | mod object_errors; 7 | 8 | use std::borrow::Cow; 9 | 10 | pub use crate::error::{ 11 | EnumerateError, ExclusiveMaximumError, ExclusiveMinimumError, MaxItemsError, MaxLengthError, 12 | MaxPropertiesError, MaximumError, MinItemsError, MinLengthError, MinPropertiesError, 13 | MinimumError, MultipleOfError, PatternError, UniqueItemsError, 14 | }; 15 | pub use array_erros::ArrayErrors; 16 | pub use errors::Errors; 17 | pub use format::{Format, FormatDefault}; 18 | use indexmap::IndexMap; 19 | pub use into_error::IntoError; 20 | pub use message::Message; 21 | pub use object_errors::ObjectErrors; 22 | 23 | #[derive(Debug, Clone, serde::Serialize, thiserror::Error)] 24 | #[serde(untagged)] 25 | pub enum Error { 26 | #[error("{0}")] 27 | #[serde(serialize_with = "serialize_error_message")] 28 | Minimum(Message), 29 | 30 | #[error("{0}")] 31 | #[serde(serialize_with = "serialize_error_message")] 32 | Maximum(Message), 33 | 34 | #[error("{0}")] 35 | #[serde(serialize_with = "serialize_error_message")] 36 | ExclusiveMinimum(Message), 37 | 38 | #[error("{0}")] 39 | #[serde(serialize_with = "serialize_error_message")] 40 | ExclusiveMaximum(Message), 41 | 42 | #[error("{0}")] 43 | #[serde(serialize_with = "serialize_error_message")] 44 | MultipleOf(Message), 45 | 46 | #[error("{0}")] 47 | #[serde(serialize_with = "serialize_error_message")] 48 | MinLength(Message), 49 | 50 | #[error("{0}")] 51 | #[serde(serialize_with = "serialize_error_message")] 52 | MaxLength(Message), 53 | 54 | #[error("{0}")] 55 | #[serde(serialize_with = "serialize_error_message")] 56 | Pattern(Message), 57 | 58 | #[error("{0}")] 59 | #[serde(serialize_with = "serialize_error_message")] 60 | MinItems(Message), 61 | 62 | #[error("{0}")] 63 | #[serde(serialize_with = "serialize_error_message")] 64 | MaxItems(Message), 65 | 66 | #[error("{0}")] 67 | #[serde(serialize_with = "serialize_error_message")] 68 | UniqueItems(Message), 69 | 70 | #[error("{0}")] 71 | #[serde(serialize_with = "serialize_error_message")] 72 | MinProperties(Message), 73 | 74 | #[error("{0}")] 75 | #[serde(serialize_with = "serialize_error_message")] 76 | MaxProperties(Message), 77 | 78 | #[error("{0}")] 79 | #[serde(serialize_with = "serialize_error_message")] 80 | Enumerate(Message), 81 | 82 | #[error("{0}")] 83 | #[serde(serialize_with = "serialize_error_message")] 84 | Custom(String), 85 | 86 | #[error(transparent)] 87 | Items(ArrayErrors), 88 | 89 | #[error(transparent)] 90 | Properties(ObjectErrors), 91 | 92 | #[cfg(feature = "fluent")] 93 | #[error("{0}")] 94 | #[serde(serialize_with = "serialize_error_message")] 95 | Fluent(crate::fluent::Message), 96 | } 97 | 98 | fn serialize_error_message(message: &T, serializer: S) -> Result 99 | where 100 | T: std::fmt::Display, 101 | S: serde::Serializer, 102 | { 103 | serializer.serialize_str(&message.to_string()) 104 | } 105 | 106 | pub type VecErrors = Vec; 107 | pub type ItemErrorsMap = IndexMap>; 108 | pub type ItemVecErrorsMap = IndexMap>; 109 | pub type PropertyErrorsMap = IndexMap, Errors>; 110 | pub type PropertyVecErrorsMap = IndexMap, VecErrors>; 111 | -------------------------------------------------------------------------------- /crates/serde_valid/src/validation/error/array_erros.rs: -------------------------------------------------------------------------------- 1 | use serde::ser::SerializeStruct; 2 | 3 | use super::{ItemErrorsMap, VecErrors}; 4 | 5 | #[derive(Debug, Clone, thiserror::Error)] 6 | pub struct ArrayErrors { 7 | pub errors: VecErrors, 8 | pub items: ItemErrorsMap, 9 | } 10 | 11 | impl serde::Serialize for ArrayErrors 12 | where 13 | E: serde::Serialize, 14 | { 15 | fn serialize(&self, serializer: S) -> Result 16 | where 17 | S: serde::Serializer, 18 | { 19 | let mut array_errors = serializer.serialize_struct("ArrayErrors", 2)?; 20 | array_errors.serialize_field("errors", &self.errors)?; 21 | array_errors.serialize_field("items", &self.items)?; 22 | array_errors.end() 23 | } 24 | } 25 | 26 | impl ArrayErrors { 27 | pub fn new(errors: VecErrors, items: ItemErrorsMap) -> Self { 28 | Self { errors, items } 29 | } 30 | } 31 | 32 | impl ArrayErrors 33 | where 34 | E: Clone, 35 | { 36 | pub fn merge(mut self, other: ArrayErrors) -> Self { 37 | self.errors.extend(other.errors); 38 | 39 | for (index, item) in other.items { 40 | match self.items.get_mut(&index) { 41 | Some(errors) => errors.merge(item), 42 | None => { 43 | self.items.insert(index, item); 44 | } 45 | }; 46 | } 47 | self 48 | } 49 | } 50 | 51 | impl std::fmt::Display for ArrayErrors 52 | where 53 | E: serde::Serialize, 54 | { 55 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 56 | match serde_json::to_string(&self) { 57 | Ok(json_string) => { 58 | write!(f, "{}", json_string) 59 | } 60 | Err(_) => Err(std::fmt::Error), 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /crates/serde_valid/src/validation/error/errors.rs: -------------------------------------------------------------------------------- 1 | use super::{ArrayErrors, ObjectErrors, VecErrors}; 2 | 3 | #[derive(Debug, Clone, thiserror::Error)] 4 | pub enum Errors { 5 | Array(ArrayErrors), 6 | Object(ObjectErrors), 7 | NewType(VecErrors), 8 | } 9 | 10 | impl serde::Serialize for Errors 11 | where 12 | E: serde::Serialize, 13 | { 14 | fn serialize(&self, serializer: S) -> Result 15 | where 16 | S: serde::Serializer, 17 | { 18 | match self { 19 | Self::Array(a) => serde::Serialize::serialize(a, serializer), 20 | Self::Object(o) => serde::Serialize::serialize(o, serializer), 21 | Self::NewType(n) => { 22 | #[derive(Debug, Clone, serde::Serialize)] 23 | struct NewTypeErrors<'a, E> { 24 | errors: &'a VecErrors, 25 | } 26 | 27 | serde::Serialize::serialize(&NewTypeErrors { errors: n }, serializer) 28 | } 29 | } 30 | } 31 | } 32 | 33 | impl Errors 34 | where 35 | E: Clone, 36 | { 37 | pub fn merge(&mut self, other: Errors) { 38 | match self { 39 | Errors::Array(a) => match other { 40 | Errors::Array(b) => { 41 | a.errors.extend(b.errors); 42 | 43 | for (index, item) in b.items { 44 | match a.items.get_mut(&index) { 45 | Some(errors) => errors.merge(item), 46 | None => { 47 | a.items.insert(index, item); 48 | } 49 | }; 50 | } 51 | } 52 | Errors::Object(_) => { 53 | unreachable!("conflict Array and Object in serde_valid::validation::Errors") 54 | } 55 | Errors::NewType(errors) => { 56 | a.errors.extend(errors); 57 | } 58 | }, 59 | Errors::NewType(a) => match other { 60 | Errors::Array(b) => { 61 | a.extend(b.errors); 62 | *self = Errors::Array(ArrayErrors::new(a.to_vec(), b.items)); 63 | } 64 | Errors::Object(_) => { 65 | unreachable!("conflict Array and Object in serde_valid::validation::Errors") 66 | } 67 | Errors::NewType(b) => { 68 | a.extend(b); 69 | } 70 | }, 71 | Errors::Object(_) => { 72 | unimplemented!("Object does not support yet.") 73 | } 74 | } 75 | } 76 | } 77 | 78 | impl std::fmt::Display for Errors 79 | where 80 | E: serde::Serialize + std::fmt::Display, 81 | { 82 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 83 | match self { 84 | Self::Array(errors) => std::fmt::Display::fmt(errors, f), 85 | Self::Object(errors) => std::fmt::Display::fmt(errors, f), 86 | Self::NewType(vec_errors) => { 87 | let errors = &vec_errors 88 | .iter() 89 | .map(ToString::to_string) 90 | .collect::>(); 91 | let value = serde_json::json!({ "errors": errors }); 92 | std::fmt::Display::fmt(&value, f) 93 | } 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /crates/serde_valid/src/validation/error/format.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Clone, Default)] 2 | pub enum Format { 3 | #[default] 4 | Default, 5 | Message(String), 6 | MessageFn(fn(&E) -> String), 7 | #[cfg(feature = "fluent")] 8 | Fluent(crate::fluent::Message), 9 | } 10 | 11 | impl Format { 12 | pub fn into_message(self, error: E) -> crate::validation::error::Message { 13 | crate::validation::error::Message::new(error, self) 14 | } 15 | } 16 | 17 | pub trait FormatDefault { 18 | fn format_default(&self) -> String; 19 | } 20 | -------------------------------------------------------------------------------- /crates/serde_valid/src/validation/error/into_error.rs: -------------------------------------------------------------------------------- 1 | pub trait IntoError: Sized { 2 | fn into_error(self) -> crate::validation::Error { 3 | self.into_error_by(crate::validation::error::Format::Default) 4 | } 5 | 6 | fn into_error_by(self, format: crate::validation::error::Format) 7 | -> crate::validation::Error; 8 | } 9 | -------------------------------------------------------------------------------- /crates/serde_valid/src/validation/error/message.rs: -------------------------------------------------------------------------------- 1 | use super::{Format, FormatDefault}; 2 | 3 | #[derive(Debug, Clone)] 4 | pub struct Message { 5 | error: E, 6 | format: Format, 7 | } 8 | 9 | impl Message { 10 | pub fn new(error: E, format: Format) -> Self { 11 | Self { error, format } 12 | } 13 | 14 | #[cfg(feature = "fluent")] 15 | pub fn fluent_message(&self) -> Option<&crate::features::fluent::Message> { 16 | match self.format { 17 | Format::Fluent(ref message) => Some(message), 18 | _ => None, 19 | } 20 | } 21 | } 22 | 23 | impl FormatDefault for Message 24 | where 25 | E: FormatDefault, 26 | { 27 | fn format_default(&self) -> String { 28 | match &self.format { 29 | Format::Default => self.error.format_default(), 30 | Format::Message(ref message) => message.to_string(), 31 | Format::MessageFn(ref format_fn) => format_fn(&self.error), 32 | #[cfg(feature = "fluent")] 33 | Format::Fluent(message) => format!("{message}"), 34 | } 35 | } 36 | } 37 | 38 | impl std::fmt::Display for Message 39 | where 40 | E: FormatDefault, 41 | { 42 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 43 | write!(f, "{}", self.format_default()) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /crates/serde_valid/src/validation/error/object_errors.rs: -------------------------------------------------------------------------------- 1 | use serde::ser::SerializeStruct; 2 | 3 | use super::{PropertyErrorsMap, VecErrors}; 4 | 5 | #[derive(Debug, Clone, thiserror::Error)] 6 | pub struct ObjectErrors { 7 | pub errors: VecErrors, 8 | pub properties: PropertyErrorsMap, 9 | } 10 | 11 | impl serde::Serialize for ObjectErrors 12 | where 13 | E: serde::Serialize, 14 | { 15 | fn serialize(&self, serializer: S) -> Result 16 | where 17 | S: serde::Serializer, 18 | { 19 | let mut object_errors = serializer.serialize_struct("ObjectErrors", 2)?; 20 | object_errors.serialize_field("errors", &self.errors)?; 21 | object_errors.serialize_field("properties", &self.properties)?; 22 | object_errors.end() 23 | } 24 | } 25 | 26 | impl ObjectErrors { 27 | pub fn new(errors: VecErrors, properties: PropertyErrorsMap) -> Self { 28 | Self { errors, properties } 29 | } 30 | } 31 | 32 | impl std::fmt::Display for ObjectErrors 33 | where 34 | E: std::fmt::Display + serde::Serialize, 35 | { 36 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 37 | match serde_json::to_string(&self) { 38 | Ok(json_string) => { 39 | write!(f, "{}", json_string) 40 | } 41 | Err(_) => Err(std::fmt::Error), 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /crates/serde_valid/src/validation/generic.rs: -------------------------------------------------------------------------------- 1 | mod enumerate; 2 | pub use enumerate::ValidateEnumerate; 3 | -------------------------------------------------------------------------------- /crates/serde_valid/src/validation/numeric.rs: -------------------------------------------------------------------------------- 1 | mod exclusive_maximum; 2 | mod exclusive_minimum; 3 | mod maximum; 4 | mod minimum; 5 | mod multiple_of; 6 | 7 | pub use exclusive_maximum::ValidateExclusiveMaximum; 8 | pub use exclusive_minimum::ValidateExclusiveMinimum; 9 | pub use maximum::ValidateMaximum; 10 | pub use minimum::ValidateMinimum; 11 | pub use multiple_of::ValidateMultipleOf; 12 | -------------------------------------------------------------------------------- /crates/serde_valid/src/validation/object.rs: -------------------------------------------------------------------------------- 1 | mod max_properties; 2 | mod min_properties; 3 | 4 | pub use max_properties::ValidateMaxProperties; 5 | pub use min_properties::ValidateMinProperties; 6 | -------------------------------------------------------------------------------- /crates/serde_valid/src/validation/object/max_properties.rs: -------------------------------------------------------------------------------- 1 | use crate::{traits::Size, MaxPropertiesError}; 2 | 3 | /// Max size validation of the object properties. 4 | /// 5 | /// See 6 | /// 7 | /// ```rust 8 | /// use std::collections::HashMap; 9 | /// 10 | /// use serde_json::json; 11 | /// use serde_valid::{Validate, ValidateMaxProperties}; 12 | /// 13 | /// struct MyType(HashMap); 14 | /// 15 | /// impl ValidateMaxProperties for MyType { 16 | /// fn validate_max_properties( 17 | /// &self, 18 | /// max_properties: usize, 19 | /// ) -> Result<(), serde_valid::MaxPropertiesError> { 20 | /// self.0.validate_max_properties(max_properties) 21 | /// } 22 | /// } 23 | /// 24 | /// #[derive(Validate)] 25 | /// struct TestStruct { 26 | /// #[validate(max_properties = 2)] 27 | /// val: MyType, 28 | /// } 29 | /// 30 | /// let mut map = HashMap::new(); 31 | /// map.insert("key1".to_string(), "value1".to_string()); 32 | /// map.insert("key2".to_string(), "value2".to_string()); 33 | /// map.insert("key3".to_string(), "value3".to_string()); 34 | /// 35 | /// let s = TestStruct { val: MyType(map) }; 36 | /// 37 | /// assert_eq!( 38 | /// s.validate().unwrap_err().to_string(), 39 | /// json!({ 40 | /// "errors": [], 41 | /// "properties": { 42 | /// "val": { 43 | /// "errors": ["The size of the properties must be `<= 2`."] 44 | /// } 45 | /// } 46 | /// }) 47 | /// .to_string() 48 | /// ); 49 | /// ``` 50 | pub trait ValidateMaxProperties { 51 | fn validate_max_properties(&self, max_properties: usize) -> Result<(), MaxPropertiesError>; 52 | } 53 | 54 | impl ValidateMaxProperties for T 55 | where 56 | T: Size, 57 | { 58 | fn validate_max_properties(&self, max_properties: usize) -> Result<(), MaxPropertiesError> { 59 | if max_properties >= self.size() { 60 | Ok(()) 61 | } else { 62 | Err(MaxPropertiesError::new(max_properties)) 63 | } 64 | } 65 | } 66 | 67 | #[cfg(test)] 68 | mod tests { 69 | use super::*; 70 | use serde_json::json; 71 | use std::collections::BTreeMap; 72 | use std::collections::HashMap; 73 | 74 | #[test] 75 | fn test_validate_object_max_properties_hash_map_type() { 76 | let mut map = HashMap::new(); 77 | map.insert("key1".to_string(), "value1".to_string()); 78 | map.insert("key2".to_string(), "value2".to_string()); 79 | map.insert("key3".to_string(), "value3".to_string()); 80 | assert!(ValidateMaxProperties::validate_max_properties(&map, 3).is_ok()); 81 | } 82 | 83 | #[test] 84 | fn test_validate_object_max_properties_btree_map_type() { 85 | let mut map = BTreeMap::new(); 86 | map.insert("key1".to_string(), "value1".to_string()); 87 | map.insert("key2".to_string(), "value2".to_string()); 88 | map.insert("key3".to_string(), "value3".to_string()); 89 | assert!(ValidateMaxProperties::validate_max_properties(&map, 3).is_ok()); 90 | } 91 | 92 | #[test] 93 | fn test_validate_object_max_properties_json_map_type() { 94 | let value = json!({ 95 | "key1": "value1", 96 | "key2": "value2", 97 | "key3": "value3", 98 | }); 99 | let map = value.as_object().unwrap(); 100 | 101 | assert!(ValidateMaxProperties::validate_max_properties(map, 4).is_ok()); 102 | assert!(ValidateMaxProperties::validate_max_properties(map, 3).is_ok()); 103 | } 104 | 105 | #[test] 106 | fn test_validate_object_max_properties_is_false() { 107 | let value = json!({ 108 | "key1": "value1", 109 | "key2": "value2", 110 | "key3": "value3", 111 | }); 112 | let map = value.as_object().unwrap(); 113 | 114 | assert!(ValidateMaxProperties::validate_max_properties(map, 2).is_err()); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /crates/serde_valid/src/validation/object/min_properties.rs: -------------------------------------------------------------------------------- 1 | use crate::{traits::Size, MinPropertiesError}; 2 | 3 | /// Min size validation of the object properties. 4 | /// 5 | /// See 6 | /// 7 | /// ```rust 8 | /// use std::collections::HashMap; 9 | /// 10 | /// use serde_json::json; 11 | /// use serde_valid::{Validate, ValidateMinProperties}; 12 | /// 13 | /// struct MyType(HashMap); 14 | /// 15 | /// impl ValidateMinProperties for MyType { 16 | /// fn validate_min_properties( 17 | /// &self, 18 | /// min_properties: usize, 19 | /// ) -> Result<(), serde_valid::MinPropertiesError> { 20 | /// self.0.validate_min_properties(min_properties) 21 | /// } 22 | /// } 23 | /// 24 | /// #[derive(Validate)] 25 | /// struct TestStruct { 26 | /// #[validate(min_properties = 2)] 27 | /// val: MyType, 28 | /// } 29 | /// 30 | /// let mut map = HashMap::new(); 31 | /// map.insert("key1".to_string(), "value1".to_string()); 32 | /// 33 | /// let s = TestStruct { val: MyType(map) }; 34 | /// 35 | /// assert_eq!( 36 | /// s.validate().unwrap_err().to_string(), 37 | /// json!({ 38 | /// "errors": [], 39 | /// "properties": { 40 | /// "val": { 41 | /// "errors": ["The size of the properties must be `>= 2`."] 42 | /// } 43 | /// } 44 | /// }) 45 | /// .to_string() 46 | /// ); 47 | /// ``` 48 | pub trait ValidateMinProperties { 49 | fn validate_min_properties(&self, min_properties: usize) -> Result<(), MinPropertiesError>; 50 | } 51 | 52 | impl ValidateMinProperties for T 53 | where 54 | T: Size, 55 | { 56 | fn validate_min_properties(&self, min_properties: usize) -> Result<(), MinPropertiesError> { 57 | if min_properties <= self.size() { 58 | Ok(()) 59 | } else { 60 | Err(MinPropertiesError::new(min_properties)) 61 | } 62 | } 63 | } 64 | 65 | #[cfg(test)] 66 | mod tests { 67 | use super::*; 68 | use serde_json::json; 69 | use std::collections::BTreeMap; 70 | use std::collections::HashMap; 71 | 72 | #[test] 73 | fn test_validate_object_min_properties_hash_map_type() { 74 | let mut map = HashMap::new(); 75 | map.insert("key1".to_string(), "value1".to_string()); 76 | map.insert("key2".to_string(), "value2".to_string()); 77 | map.insert("key3".to_string(), "value3".to_string()); 78 | assert!(ValidateMinProperties::validate_min_properties(&map, 3).is_ok()); 79 | } 80 | 81 | #[test] 82 | fn test_validate_object_min_properties_btree_map_type() { 83 | let mut map = BTreeMap::new(); 84 | map.insert("key1".to_string(), "value1".to_string()); 85 | map.insert("key2".to_string(), "value2".to_string()); 86 | map.insert("key3".to_string(), "value3".to_string()); 87 | assert!(ValidateMinProperties::validate_min_properties(&map, 3).is_ok()); 88 | } 89 | 90 | #[test] 91 | fn test_validate_object_min_properties_json_map_type() { 92 | let value = json!({ 93 | "key1": "value1", 94 | "key2": "value2", 95 | "key3": "value3", 96 | }); 97 | let map = value.as_object().unwrap(); 98 | 99 | assert!(ValidateMinProperties::validate_min_properties(map, 2).is_ok()); 100 | assert!(ValidateMinProperties::validate_min_properties(map, 3).is_ok()); 101 | } 102 | 103 | #[test] 104 | fn test_validate_object_min_properties_is_false() { 105 | let value = json!({ 106 | "key1": "value1", 107 | "key2": "value2", 108 | "key3": "value3", 109 | }); 110 | let map = value.as_object().unwrap(); 111 | 112 | assert!(ValidateMinProperties::validate_min_properties(map, 4).is_err()); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /crates/serde_valid/src/validation/string.rs: -------------------------------------------------------------------------------- 1 | mod max_length; 2 | mod min_length; 3 | mod pattern; 4 | pub use max_length::ValidateMaxLength; 5 | pub use min_length::ValidateMinLength; 6 | pub use pattern::ValidatePattern; 7 | -------------------------------------------------------------------------------- /crates/serde_valid/src/validation/string/max_length.rs: -------------------------------------------------------------------------------- 1 | use crate::{traits::Length, MaxLengthError}; 2 | 3 | /// Max length validation of the string. 4 | /// 5 | /// See 6 | /// 7 | /// ```rust 8 | /// use serde_json::json; 9 | /// use serde_valid::{Validate, ValidateMaxLength}; 10 | /// 11 | /// struct MyType(String); 12 | /// 13 | /// impl ValidateMaxLength for MyType { 14 | /// fn validate_max_length( 15 | /// &self, 16 | /// max_length: usize, 17 | /// ) -> Result<(), serde_valid::MaxLengthError> { 18 | /// self.0.validate_max_length(max_length) 19 | /// } 20 | /// } 21 | /// 22 | /// #[derive(Validate)] 23 | /// struct TestStruct { 24 | /// #[validate(max_length = 5)] 25 | /// val: MyType, 26 | /// } 27 | /// 28 | /// let s = TestStruct { 29 | /// val: MyType(String::from("abcdef")), 30 | /// }; 31 | /// 32 | /// assert_eq!( 33 | /// s.validate().unwrap_err().to_string(), 34 | /// json!({ 35 | /// "errors": [], 36 | /// "properties": { 37 | /// "val": { 38 | /// "errors": ["The length of the value must be `<= 5`."] 39 | /// } 40 | /// } 41 | /// }) 42 | /// .to_string() 43 | /// ); 44 | /// ``` 45 | pub trait ValidateMaxLength { 46 | fn validate_max_length(&self, max_length: usize) -> Result<(), MaxLengthError>; 47 | } 48 | 49 | impl ValidateMaxLength for T 50 | where 51 | T: Length + ?Sized, 52 | { 53 | fn validate_max_length(&self, max_length: usize) -> Result<(), MaxLengthError> { 54 | if max_length >= self.length() { 55 | Ok(()) 56 | } else { 57 | Err(MaxLengthError::new(max_length)) 58 | } 59 | } 60 | } 61 | 62 | #[cfg(test)] 63 | mod tests { 64 | use super::*; 65 | use std::borrow::Cow; 66 | use std::ffi::{OsStr, OsString}; 67 | use std::path::{Path, PathBuf}; 68 | 69 | #[test] 70 | fn test_validate_string_max_length_ascii_is_true() { 71 | assert!(ValidateMaxLength::validate_max_length("abcde", 5).is_ok()); 72 | assert!(ValidateMaxLength::validate_max_length("abcde", 6).is_ok()); 73 | } 74 | 75 | #[test] 76 | fn test_validate_string_max_length_unicode_is_true() { 77 | assert!(ValidateMaxLength::validate_max_length("a̐éö̲", 3).is_ok()); 78 | } 79 | 80 | #[test] 81 | fn test_validate_string_max_length_japanese_is_true() { 82 | assert!(ValidateMaxLength::validate_max_length("あ堯", 2).is_ok()); 83 | } 84 | 85 | #[test] 86 | fn test_validate_string_max_length_emoji_is_true() { 87 | assert!(ValidateMaxLength::validate_max_length("😍👺🙋🏽👨‍🎤👨‍👩‍👧‍👦", 5).is_ok()); 88 | } 89 | 90 | #[test] 91 | fn test_validate_string_max_length_string_type() { 92 | assert!(ValidateMaxLength::validate_max_length(&String::from("abcde"), 5).is_ok()); 93 | } 94 | 95 | #[test] 96 | fn test_validate_string_max_length_cow_str_type() { 97 | assert!(ValidateMaxLength::validate_max_length(&Cow::from("abcde"), 5).is_ok()); 98 | } 99 | 100 | #[test] 101 | fn test_validate_string_max_length_os_str_type() { 102 | assert!(ValidateMaxLength::validate_max_length(OsStr::new("fo�o"), 4).is_ok()); 103 | } 104 | 105 | #[test] 106 | fn test_validate_string_max_length_os_string_type() { 107 | assert!(ValidateMaxLength::validate_max_length(&OsString::from("fo�o"), 4).is_ok()); 108 | } 109 | 110 | #[test] 111 | fn test_validate_string_max_length_path_type() { 112 | assert!(ValidateMaxLength::validate_max_length(&Path::new("./foo/bar.txt"), 13).is_ok()); 113 | } 114 | 115 | #[test] 116 | fn test_validate_string_max_length_path_buf_type() { 117 | assert!( 118 | ValidateMaxLength::validate_max_length(&PathBuf::from("./foo/bar.txt"), 13).is_ok() 119 | ); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /crates/serde_valid/src/validation/string/min_length.rs: -------------------------------------------------------------------------------- 1 | use crate::{traits::Length, MinLengthError}; 2 | 3 | /// Min length validation of the string. 4 | /// 5 | /// See 6 | /// 7 | /// ```rust 8 | /// use serde_json::json; 9 | /// use serde_valid::{Validate, ValidateMinLength}; 10 | /// 11 | /// struct MyType(String); 12 | /// 13 | /// impl ValidateMinLength for MyType { 14 | /// fn validate_min_length( 15 | /// &self, 16 | /// min_length: usize, 17 | /// ) -> Result<(), serde_valid::MinLengthError> { 18 | /// self.0.validate_min_length(min_length) 19 | /// } 20 | /// } 21 | /// 22 | /// #[derive(Validate)] 23 | /// struct TestStruct { 24 | /// #[validate(min_length = 5)] 25 | /// val: MyType, 26 | /// } 27 | /// 28 | /// let s = TestStruct { 29 | /// val: MyType(String::from("abc")), 30 | /// }; 31 | /// 32 | /// assert_eq!( 33 | /// s.validate().unwrap_err().to_string(), 34 | /// json!({ 35 | /// "errors": [], 36 | /// "properties": { 37 | /// "val": { 38 | /// "errors": ["The length of the value must be `>= 5`."] 39 | /// } 40 | /// } 41 | /// }) 42 | /// .to_string() 43 | /// ); 44 | /// ``` 45 | pub trait ValidateMinLength { 46 | fn validate_min_length(&self, min_length: usize) -> Result<(), MinLengthError>; 47 | } 48 | 49 | impl ValidateMinLength for T 50 | where 51 | T: Length + ?Sized, 52 | { 53 | fn validate_min_length(&self, min_length: usize) -> Result<(), MinLengthError> { 54 | if min_length <= self.length() { 55 | Ok(()) 56 | } else { 57 | Err(MinLengthError::new(min_length)) 58 | } 59 | } 60 | } 61 | 62 | #[cfg(test)] 63 | mod tests { 64 | use super::*; 65 | use std::borrow::Cow; 66 | use std::ffi::{OsStr, OsString}; 67 | use std::path::{Path, PathBuf}; 68 | 69 | #[test] 70 | fn test_validate_string_min_length_ascii_is_true() { 71 | assert!(ValidateMinLength::validate_min_length(&"abcde", 5).is_ok()); 72 | assert!(ValidateMinLength::validate_min_length(&"abcde", 4).is_ok()); 73 | } 74 | 75 | #[test] 76 | fn test_validate_string_min_length_unicode_is_true() { 77 | assert!(ValidateMinLength::validate_min_length(&"a̐éö̲", 3).is_ok()); 78 | } 79 | 80 | #[test] 81 | fn test_validate_string_min_length_japanese_is_true() { 82 | assert!(ValidateMinLength::validate_min_length(&"あ堯", 2).is_ok()); 83 | } 84 | 85 | #[test] 86 | fn test_validate_string_min_length_emoji_is_true() { 87 | assert!(ValidateMinLength::validate_min_length(&"😍👺🙋🏽👨‍🎤👨‍👩‍👧‍👦", 5).is_ok()); 88 | } 89 | 90 | #[test] 91 | fn test_validate_string_min_length_string_type() { 92 | assert!(ValidateMinLength::validate_min_length(&String::from("abcde"), 5).is_ok()); 93 | } 94 | 95 | #[test] 96 | fn test_validate_string_min_length_cow_str_type() { 97 | assert!(ValidateMinLength::validate_min_length(&Cow::from("abcde"), 5).is_ok()); 98 | } 99 | 100 | #[test] 101 | fn test_validate_string_min_length_os_str_type() { 102 | assert!(ValidateMinLength::validate_min_length(&OsStr::new("fo�o"), 4).is_ok()); 103 | } 104 | 105 | #[test] 106 | fn test_validate_string_min_length_os_string_type() { 107 | assert!(ValidateMinLength::validate_min_length(&OsString::from("fo�o"), 4).is_ok()); 108 | } 109 | 110 | #[test] 111 | fn test_validate_string_min_length_path_type() { 112 | assert!(ValidateMinLength::validate_min_length(&Path::new("./foo/bar.txt"), 13).is_ok()); 113 | } 114 | 115 | #[test] 116 | fn test_validate_string_min_length_path_buf_type() { 117 | assert!( 118 | ValidateMinLength::validate_min_length(&PathBuf::from("./foo/bar.txt"), 13).is_ok() 119 | ); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /crates/serde_valid/src/validation/string/pattern.rs: -------------------------------------------------------------------------------- 1 | use crate::{traits::IsMatch, PatternError}; 2 | use regex::Regex; 3 | 4 | /// Pattern validation of the string. 5 | /// 6 | /// See 7 | /// 8 | /// ```rust 9 | /// use serde_json::json; 10 | /// use serde_valid::{Validate, ValidatePattern}; 11 | /// 12 | /// struct MyType(String); 13 | /// 14 | /// impl ValidatePattern for MyType { 15 | /// fn validate_pattern( 16 | /// &self, 17 | /// pattern: ®ex::Regex, 18 | /// ) -> Result<(), serde_valid::PatternError> { 19 | /// self.0.validate_pattern(pattern) 20 | /// } 21 | /// } 22 | /// 23 | /// #[derive(Validate)] 24 | /// struct TestStruct { 25 | /// #[validate(pattern = r"^\d{4}-\d{2}-\d{2}$")] 26 | /// val: MyType, 27 | /// } 28 | /// 29 | /// let s = TestStruct { 30 | /// val: MyType(String::from("2020/09/10")), 31 | /// }; 32 | /// 33 | /// assert_eq!( 34 | /// s.validate().unwrap_err().to_string(), 35 | /// json!({ 36 | /// "errors": [], 37 | /// "properties": { 38 | /// "val": { 39 | /// "errors": [r#"The value must match the pattern of "^\d{4}-\d{2}-\d{2}$"."#] 40 | /// } 41 | /// } 42 | /// }) 43 | /// .to_string() 44 | /// ); 45 | /// ``` 46 | pub trait ValidatePattern { 47 | fn validate_pattern(&self, pattern: &Regex) -> Result<(), PatternError>; 48 | } 49 | 50 | impl ValidatePattern for T 51 | where 52 | T: IsMatch + ?Sized, 53 | { 54 | fn validate_pattern(&self, pattern: &Regex) -> Result<(), PatternError> { 55 | if self.is_match(pattern) { 56 | Ok(()) 57 | } else { 58 | Err(PatternError::new(pattern.to_string())) 59 | } 60 | } 61 | } 62 | 63 | #[cfg(test)] 64 | mod tests { 65 | use super::*; 66 | use std::borrow::Cow; 67 | use std::ffi::{OsStr, OsString}; 68 | use std::path::{Path, PathBuf}; 69 | 70 | #[test] 71 | fn test_validate_string_pattern_str_type() { 72 | assert!(ValidatePattern::validate_pattern( 73 | "2020-09-10", 74 | &Regex::new(r"^\d{4}-\d{2}-\d{2}$").unwrap() 75 | ) 76 | .is_ok()); 77 | } 78 | 79 | #[test] 80 | fn test_validate_string_pattern_string_type() { 81 | assert!(ValidatePattern::validate_pattern( 82 | &String::from("2020-09-10"), 83 | &Regex::new(r"^\d{4}-\d{2}-\d{2}$").unwrap() 84 | ) 85 | .is_ok()); 86 | } 87 | 88 | #[test] 89 | fn test_validate_string_pattern_cow_str_type() { 90 | assert!(ValidatePattern::validate_pattern( 91 | &Cow::from("2020-09-10"), 92 | &Regex::new(r"^\d{4}-\d{2}-\d{2}$").unwrap() 93 | ) 94 | .is_ok()); 95 | } 96 | 97 | #[test] 98 | fn test_validate_string_pattern_os_str_type() { 99 | assert!(ValidatePattern::validate_pattern( 100 | OsStr::new("2020-09-10"), 101 | &Regex::new(r"^\d{4}-\d{2}-\d{2}$").unwrap() 102 | ) 103 | .is_ok()); 104 | } 105 | 106 | #[test] 107 | fn test_validate_string_pattern_os_string_type() { 108 | assert!(ValidatePattern::validate_pattern( 109 | &OsString::from("2020-09-10"), 110 | &Regex::new(r"^\d{4}-\d{2}-\d{2}$").unwrap() 111 | ) 112 | .is_ok()); 113 | } 114 | 115 | #[test] 116 | fn test_validate_string_pattern_path_type() { 117 | assert!(ValidatePattern::validate_pattern( 118 | Path::new("./foo/bar.txt"), 119 | &Regex::new(r"^*.txt$").unwrap() 120 | ) 121 | .is_ok()); 122 | } 123 | 124 | #[test] 125 | fn test_validate_string_pattern_path_buf_type() { 126 | assert!(ValidatePattern::validate_pattern( 127 | &PathBuf::from("./foo/bar.txt"), 128 | &Regex::new(r"^*.txt$").unwrap() 129 | ) 130 | .is_ok()); 131 | } 132 | 133 | #[test] 134 | fn test_validate_string_pattern_is_false() { 135 | assert!(ValidatePattern::validate_pattern( 136 | "2020/09/10", 137 | &Regex::new(r"^\d{4}-\d{2}-\d{2}$").unwrap() 138 | ) 139 | .is_err()); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /crates/serde_valid/tests/array_test.rs: -------------------------------------------------------------------------------- 1 | use serde_json::json; 2 | use serde_valid::Validate; 3 | 4 | #[test] 5 | fn items_err_message() { 6 | fn rule_sample(_a: i32) -> Result<(), serde_valid::validation::Error> { 7 | Err(serde_valid::validation::Error::Custom( 8 | "Rule error.".to_owned(), 9 | )) 10 | } 11 | 12 | #[derive(Validate)] 13 | struct TestStruct { 14 | #[validate(min_items = 5)] 15 | #[validate(max_items = 2)] 16 | #[validate] 17 | val: Vec, 18 | } 19 | 20 | #[derive(Validate)] 21 | #[validate(custom = |s| rule_sample(s.val))] 22 | struct TestChildStruct { 23 | #[validate(minimum = 1)] 24 | #[validate(maximum = 10)] 25 | val: i32, 26 | } 27 | 28 | let s = TestStruct { 29 | val: vec![ 30 | TestChildStruct { val: 0 }, 31 | TestChildStruct { val: 5 }, 32 | TestChildStruct { val: 15 }, 33 | ], 34 | }; 35 | 36 | assert_eq!( 37 | s.validate().unwrap_err().to_string(), 38 | json!({ 39 | "errors": [], 40 | "properties": { 41 | "val": { 42 | "errors": [ 43 | "The length of the items must be `>= 5`.", 44 | "The length of the items must be `<= 2`.", 45 | ], 46 | "items": { 47 | "0": { 48 | "errors": ["Rule error."], 49 | "properties": { 50 | "val": { 51 | "errors": ["The number must be `>= 1`."] 52 | } 53 | } 54 | }, 55 | "1": { 56 | "errors": ["Rule error."], 57 | "properties": {} 58 | }, 59 | "2": { 60 | "errors": ["Rule error."], 61 | "properties": { 62 | "val": { 63 | "errors": ["The number must be `<= 10`."] 64 | } 65 | } 66 | } 67 | } 68 | } 69 | } 70 | }) 71 | .to_string() 72 | ); 73 | } 74 | -------------------------------------------------------------------------------- /crates/serde_valid/tests/custom_duration_test.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use serde_json::json; 4 | use serde_valid::utils::{duration_maximum, duration_minimum}; 5 | use serde_valid::Validate; 6 | 7 | #[test] 8 | fn duration_maximum_is_ok() { 9 | #[derive(Validate)] 10 | struct TestStruct { 11 | #[validate(custom = duration_maximum(Duration::from_micros(5)))] 12 | val: Duration, 13 | } 14 | 15 | let s = TestStruct { 16 | val: Duration::from_micros(5), 17 | }; 18 | 19 | assert!(s.validate().is_ok()); 20 | } 21 | 22 | #[test] 23 | fn duration_minimum_is_ok() { 24 | #[derive(Validate)] 25 | struct TestStruct { 26 | #[validate(custom = duration_minimum(Duration::from_micros(5)))] 27 | val: Duration, 28 | } 29 | 30 | let s = TestStruct { 31 | val: Duration::from_secs(5), 32 | }; 33 | 34 | assert!(s.validate().is_ok()); 35 | } 36 | 37 | #[test] 38 | fn duration_maximum_is_err() { 39 | #[derive(Validate)] 40 | struct TestStruct { 41 | #[validate(custom = duration_maximum(Duration::from_micros(5)))] 42 | val: Duration, 43 | } 44 | 45 | let s = TestStruct { 46 | val: Duration::from_micros(10), 47 | }; 48 | 49 | assert_eq!( 50 | s.validate().unwrap_err().to_string(), 51 | json!({ 52 | "errors": [], 53 | "properties": { 54 | "val": { 55 | "errors": [ 56 | "Duration 10µs is greater than maximum 5µs." 57 | ] 58 | } 59 | } 60 | }) 61 | .to_string() 62 | ); 63 | } 64 | 65 | #[test] 66 | fn duration_minimum_is_err() { 67 | #[derive(Validate)] 68 | struct TestStruct { 69 | #[validate(custom = duration_minimum(Duration::from_micros(5)))] 70 | val: Duration, 71 | } 72 | 73 | let s = TestStruct { 74 | val: Duration::from_micros(1), 75 | }; 76 | 77 | assert_eq!( 78 | s.validate().unwrap_err().to_string(), 79 | json!({ 80 | "errors": [], 81 | "properties": { 82 | "val": { 83 | "errors": [ 84 | "Duration 1µs is less than minimum 5µs." 85 | ] 86 | } 87 | } 88 | }) 89 | .to_string() 90 | ); 91 | } 92 | -------------------------------------------------------------------------------- /crates/serde_valid/tests/deserialize_test.rs: -------------------------------------------------------------------------------- 1 | use serde::Deserialize; 2 | use serde_json::json; 3 | use serde_valid::json::FromJsonValue; 4 | use serde_valid::Validate; 5 | 6 | #[test] 7 | fn json_error_to_string() { 8 | #[derive(Debug, Validate, Deserialize)] 9 | struct TestStruct { 10 | #[validate(minimum = 0)] 11 | #[validate(maximum = 1000)] 12 | val: i32, 13 | } 14 | 15 | let err = TestStruct::from_json_value(json!({ "val": 1234 })).unwrap_err(); 16 | 17 | assert_eq!( 18 | serde_json::from_str::(&err.to_string()).unwrap(), 19 | json!({ 20 | "errors": [], 21 | "properties": { 22 | "val": { 23 | "errors": ["The number must be `<= 1000`."] 24 | } 25 | } 26 | }) 27 | ); 28 | } 29 | 30 | #[test] 31 | fn json_error_as_validation_errors() { 32 | #[derive(Debug, Validate, Deserialize)] 33 | struct TestStruct { 34 | #[validate(minimum = 0)] 35 | #[validate(maximum = 1000)] 36 | val: i32, 37 | } 38 | 39 | let err = TestStruct::from_json_value(json!({ "val": 1234 })).unwrap_err(); 40 | 41 | assert_eq!( 42 | serde_json::to_value(err.as_validation_errors().unwrap()).unwrap(), 43 | json!({ 44 | "errors": [], 45 | "properties": { 46 | "val": { 47 | "errors": ["The number must be `<= 1000`."] 48 | } 49 | } 50 | }) 51 | ); 52 | } 53 | 54 | #[cfg(feature = "yaml")] 55 | #[test] 56 | fn yaml_error_as_validation_errors() { 57 | use serde::Deserialize; 58 | use serde_valid::yaml::FromYamlValue; 59 | use serde_valid::Validate; 60 | 61 | #[derive(Debug, Validate, Deserialize)] 62 | struct TestStruct { 63 | #[validate(maximum = 10)] 64 | val: i32, 65 | } 66 | 67 | let err = TestStruct::from_yaml_value(serde_yaml::from_str("val: 15").unwrap()).unwrap_err(); 68 | 69 | assert_eq!( 70 | serde_json::to_value(err.as_validation_errors().unwrap()).unwrap(), 71 | json!({ 72 | "errors": [], 73 | "properties": { 74 | "val": { 75 | "errors": ["The number must be `<= 10`."] 76 | } 77 | } 78 | }) 79 | ); 80 | } 81 | 82 | #[cfg(feature = "toml")] 83 | #[test] 84 | fn toml_error_as_validation_errors() { 85 | use serde::Deserialize; 86 | use serde_valid::toml::FromTomlValue; 87 | use serde_valid::Validate; 88 | 89 | #[derive(Debug, Validate, Deserialize)] 90 | struct TestStruct { 91 | #[validate(maximum = 10)] 92 | val: i32, 93 | } 94 | 95 | let err = TestStruct::from_toml_value(serde_toml::from_str("val = 15").unwrap()).unwrap_err(); 96 | 97 | assert_eq!( 98 | serde_json::to_value(err.as_validation_errors().unwrap()).unwrap(), 99 | json!({ 100 | "errors": [], 101 | "properties": { 102 | "val": { 103 | "errors": ["The number must be `<= 10`."] 104 | } 105 | } 106 | }) 107 | ); 108 | } 109 | -------------------------------------------------------------------------------- /crates/serde_valid/tests/empty_test.rs: -------------------------------------------------------------------------------- 1 | use serde_valid::Validate; 2 | 3 | #[test] 4 | fn empty_struct_with_braces_is_ok() { 5 | #[derive(Validate)] 6 | struct TestStruct {} 7 | 8 | let s = TestStruct {}; 9 | assert!(s.validate().is_ok()); 10 | } 11 | -------------------------------------------------------------------------------- /crates/serde_valid/tests/fluent_test.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "fluent")] 2 | mod tests { 3 | use fluent::{FluentBundle, FluentResource}; 4 | use serde::Deserialize; 5 | use serde_json::json; 6 | use serde_valid::{fluent::Localize, Validate}; 7 | use unic_langid::LanguageIdentifier; 8 | 9 | fn get_bundle(source: impl Into) -> FluentBundle { 10 | let res = FluentResource::try_new(source.into()).expect("Failed to parse an FTL string."); 11 | 12 | let langid_en: LanguageIdentifier = "en-US".parse().expect("Parsing failed"); 13 | let mut bundle = FluentBundle::new(vec![langid_en]); 14 | bundle.add_resource(res).unwrap(); 15 | 16 | bundle 17 | } 18 | 19 | #[test] 20 | fn fluent_error() { 21 | #[derive(Debug, Deserialize, Validate)] 22 | struct Test { 23 | #[validate(minimum = 5, fluent("hello-world"))] 24 | a: u32, 25 | #[validate(maximum = 10, fluent("intro", name = "taro"))] 26 | b: u32, 27 | } 28 | 29 | let test = Test { a: 1, b: 11 }; 30 | let a = test.validate().unwrap_err().localize(&get_bundle( 31 | ["hello-world = Hello, world!", "intro = Welcome, { $name }."].join("\n"), 32 | )); 33 | 34 | assert_eq!( 35 | a.to_string(), 36 | json!({ 37 | "errors": [], 38 | "properties": { 39 | "a": { 40 | "errors": [ 41 | "Hello, world!" 42 | ] 43 | }, 44 | "b": { 45 | "errors": [ 46 | "Welcome, \u{2068}taro\u{2069}." 47 | ] 48 | } 49 | } 50 | }) 51 | .to_string() 52 | ); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /crates/serde_valid/tests/issues_test.rs: -------------------------------------------------------------------------------- 1 | use serde_valid::Validate; 2 | 3 | mod issue54 { 4 | use super::*; 5 | 6 | #[test] 7 | fn test_enum_valians_works() { 8 | #[derive(Validate)] 9 | enum Works { 10 | VariantB(), 11 | VariantA, 12 | } 13 | 14 | assert!(Works::VariantA.validate().is_ok()); 15 | assert!(Works::VariantB().validate().is_ok()); 16 | } 17 | 18 | #[test] 19 | fn test_enum_valiant_fied_case() { 20 | #[derive(Validate)] 21 | enum Fails { 22 | VariantA, 23 | VariantB(), 24 | } 25 | 26 | assert!(Fails::VariantA.validate().is_ok()); 27 | assert!(Fails::VariantB().validate().is_ok()); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /crates/serde_valid/tests/message_l10n_fluent_test.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "fluent")] 2 | mod tests { 3 | use fluent::{FluentBundle, FluentResource}; 4 | use serde::Deserialize; 5 | use serde_json::json; 6 | use serde_valid::{fluent::Localize, Validate}; 7 | use unic_langid::LanguageIdentifier; 8 | 9 | fn get_bundle(source: impl Into) -> FluentBundle { 10 | let res = FluentResource::try_new(source.into()).expect("Failed to parse an FTL string."); 11 | 12 | let langid_en: LanguageIdentifier = "en-US".parse().expect("Parsing failed"); 13 | let mut bundle = FluentBundle::new(vec![langid_en]); 14 | bundle.add_resource(res).unwrap(); 15 | 16 | bundle 17 | } 18 | 19 | #[test] 20 | fn fluent_error() { 21 | #[derive(Debug, Deserialize, Validate)] 22 | struct Test { 23 | #[validate(minimum = 5, message_l10n = fluent("hello-world"))] 24 | a: u32, 25 | #[validate(maximum = 10, message_l10n = fluent("intro", name = "taro"))] 26 | b: u32, 27 | } 28 | 29 | let test = Test { a: 1, b: 11 }; 30 | let a = test.validate().unwrap_err().localize(&get_bundle( 31 | ["hello-world = Hello, world!", "intro = Welcome, { $name }."].join("\n"), 32 | )); 33 | 34 | assert_eq!( 35 | a.to_string(), 36 | json!({ 37 | "errors": [], 38 | "properties": { 39 | "a": { 40 | "errors": [ 41 | "Hello, world!" 42 | ] 43 | }, 44 | "b": { 45 | "errors": [ 46 | "Welcome, \u{2068}taro\u{2069}." 47 | ] 48 | } 49 | } 50 | }) 51 | .to_string() 52 | ); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /crates/serde_valid/tests/serde_rename_test.rs: -------------------------------------------------------------------------------- 1 | use serde::Deserialize; 2 | use serde_json::json; 3 | use serde_valid::json::FromJsonValue; 4 | use serde_valid::Validate; 5 | 6 | #[test] 7 | fn serde_rename_is_ok() { 8 | #[derive(Debug, Validate, Deserialize)] 9 | struct TestStruct { 10 | #[validate(minimum = 100)] 11 | #[serde(rename = "value")] 12 | val: i32, 13 | } 14 | 15 | let s = TestStruct::from_json_value(json!({ "value": 123 })); 16 | 17 | assert!(s.is_ok()) 18 | } 19 | 20 | #[test] 21 | fn serde_rename_is_err() { 22 | #[derive(Debug, Validate, Deserialize)] 23 | struct TestStruct { 24 | #[validate(maximum = 100)] 25 | #[serde(rename = "value")] 26 | val: i32, 27 | } 28 | 29 | let err = TestStruct::from_json_value(json!({ "value": 123 })).unwrap_err(); 30 | 31 | assert_eq!( 32 | serde_json::from_str::(&err.to_string()).unwrap(), 33 | json!({ 34 | "errors": [], 35 | "properties": { 36 | "value": { 37 | "errors": [ 38 | "The number must be `<= 100`." 39 | ] 40 | } 41 | } 42 | }) 43 | ); 44 | } 45 | 46 | #[test] 47 | fn serde_rename_deserialize_is_ok() { 48 | #[derive(Debug, Validate, Deserialize)] 49 | struct TestStruct { 50 | #[validate(minimum = 100)] 51 | #[serde(rename(deserialize = "value"))] 52 | val: i32, 53 | } 54 | 55 | let s = TestStruct::from_json_value(json!({ "value": 123 })); 56 | 57 | assert!(s.is_ok()) 58 | } 59 | 60 | #[test] 61 | fn serde_rename_deserialize_is_err() { 62 | #[derive(Debug, Validate, Deserialize)] 63 | struct TestStruct { 64 | #[validate(maximum = 100)] 65 | #[serde(rename(deserialize = "value"))] 66 | val: i32, 67 | } 68 | 69 | let err = TestStruct::from_json_value(json!({ "value": 123 })).unwrap_err(); 70 | 71 | assert_eq!( 72 | serde_json::from_str::(&err.to_string()).unwrap(), 73 | json!({ 74 | "errors": [], 75 | "properties": { 76 | "value": { 77 | "errors": ["The number must be `<= 100`."] 78 | } 79 | } 80 | }) 81 | ); 82 | } 83 | 84 | #[test] 85 | fn serde_rename_enume_is_ok() { 86 | #[derive(Debug, Validate, Deserialize)] 87 | enum TestEnum { 88 | Struct { 89 | #[validate(minimum = 100)] 90 | #[serde(rename = "value")] 91 | val: i32, 92 | }, 93 | } 94 | 95 | let s = TestEnum::from_json_value(json!({ "Struct": { "value": 123 } })); 96 | 97 | assert!(s.is_ok()) 98 | } 99 | 100 | #[test] 101 | fn serde_rename_enume_is_err() { 102 | #[derive(Debug, Validate, Deserialize)] 103 | enum TestEnum { 104 | Struct { 105 | #[validate(maximum = 100)] 106 | #[serde(rename = "value")] 107 | val: i32, 108 | }, 109 | } 110 | 111 | let err = TestEnum::from_json_value(json!({ "Struct": { "value": 123 } })).unwrap_err(); 112 | 113 | assert_eq!( 114 | serde_json::from_str::(&err.to_string()).unwrap(), 115 | json!({ 116 | "errors": [], 117 | "properties": { 118 | "value": { 119 | "errors": ["The number must be `<= 100`."] 120 | } 121 | } 122 | }) 123 | ); 124 | } 125 | -------------------------------------------------------------------------------- /crates/serde_valid/tests/serialize_test.rs: -------------------------------------------------------------------------------- 1 | use serde_json::json; 2 | use serde_valid::json::ToJsonString; 3 | 4 | #[test] 5 | fn to_json_string_is_ok() { 6 | assert!(json!({"val": 10}).to_json_string().is_ok()) 7 | } 8 | -------------------------------------------------------------------------------- /crates/serde_valid/tests/specific_test.rs: -------------------------------------------------------------------------------- 1 | use serde_valid::Validate; 2 | 3 | #[test] 4 | fn test_raw_type_field() { 5 | #[derive(Validate)] 6 | #[validate(custom = |s| sample_rule(s.r#type))] 7 | struct MyStruct { 8 | #[validate(maximum = 10)] 9 | pub r#type: i32, 10 | } 11 | 12 | fn sample_rule(_type: i32) -> Result<(), serde_valid::validation::Error> { 13 | Ok(()) 14 | } 15 | 16 | let my_struct = MyStruct { r#type: 1 }; 17 | assert!(my_struct.validate().is_ok()); 18 | 19 | let my_struct = MyStruct { r#type: 11 }; 20 | assert!(my_struct.validate().is_err()); 21 | } 22 | -------------------------------------------------------------------------------- /crates/serde_valid/tests/struct_unnamed_fields_test.rs: -------------------------------------------------------------------------------- 1 | use serde_json::json; 2 | use serde_valid::Validate; 3 | 4 | #[test] 5 | fn struct_unnamed_fields_newtype_is_ok() { 6 | #[derive(Validate)] 7 | struct TestStruct(#[validate(multiple_of = 5)] i32); 8 | 9 | let s = TestStruct(15); 10 | assert!(s.validate().is_ok()); 11 | } 12 | 13 | #[test] 14 | fn struct_unnamed_fields_newtype_is_err() { 15 | #[derive(Validate)] 16 | struct TestStruct(#[validate(multiple_of = 5)] i32); 17 | 18 | let s = TestStruct(13); 19 | let err = s.validate().unwrap_err(); 20 | 21 | assert_eq!( 22 | serde_json::from_str::(&err.to_string()).unwrap(), 23 | json!({ 24 | "errors": ["The value must be multiple of `5`."] 25 | }) 26 | ); 27 | } 28 | 29 | #[test] 30 | fn struct_unnamed_fields_is_ok() { 31 | #[derive(Validate)] 32 | struct TestStruct( 33 | #[validate(multiple_of = 5)] i32, 34 | #[validate(min_length = 1)] &'static str, 35 | ); 36 | 37 | let s = TestStruct(15, "ababa"); 38 | assert!(s.validate().is_ok()); 39 | } 40 | 41 | #[test] 42 | fn struct_unnamed_fields_is_err() { 43 | #[derive(Validate)] 44 | struct TestStruct( 45 | #[validate(multiple_of = 5)] i32, 46 | #[validate(min_length = 8)] &'static str, 47 | ); 48 | 49 | let s = TestStruct(15, "abcde"); 50 | let err = s.validate().unwrap_err(); 51 | 52 | assert_eq!( 53 | serde_json::from_str::(&err.to_string()).unwrap(), 54 | json!({ 55 | "errors": [], 56 | "items": { 57 | "1": { 58 | "errors": ["The length of the value must be `>= 8`."] 59 | } 60 | } 61 | }) 62 | ); 63 | } 64 | -------------------------------------------------------------------------------- /crates/serde_valid/tests/unique_items_test.rs: -------------------------------------------------------------------------------- 1 | use serde_json::json; 2 | use serde_valid::Validate; 3 | 4 | #[test] 5 | fn unique_items_vec_type() { 6 | #[derive(Validate)] 7 | struct TestStruct { 8 | #[validate(unique_items)] 9 | val: Vec, 10 | } 11 | 12 | let s = TestStruct { 13 | val: vec![1, 2, 3, 4], 14 | }; 15 | assert!(s.validate().is_ok()); 16 | } 17 | 18 | #[test] 19 | fn unique_items_slice_type() { 20 | #[derive(Validate)] 21 | struct TestStruct { 22 | #[validate(unique_items)] 23 | val: [i32; 4], 24 | } 25 | 26 | let s = TestStruct { val: [1, 2, 3, 4] }; 27 | assert!(s.validate().is_ok()); 28 | } 29 | 30 | #[test] 31 | fn unique_items_is_err() { 32 | #[derive(Validate)] 33 | struct TestStruct { 34 | #[validate(unique_items)] 35 | val: Vec, 36 | } 37 | 38 | let s = TestStruct { val: vec![1, 2, 2] }; 39 | assert!(s.validate().is_err()); 40 | } 41 | 42 | #[test] 43 | fn unique_items_err_message() { 44 | #[derive(Validate)] 45 | struct TestStruct { 46 | #[validate(unique_items)] 47 | val: Vec, 48 | } 49 | 50 | let s = TestStruct { 51 | val: vec![1, 2, 3, 2], 52 | }; 53 | 54 | assert_eq!( 55 | s.validate().unwrap_err().to_string(), 56 | json!({ 57 | "errors": [], 58 | "properties": { 59 | "val": { 60 | "errors": [ 61 | "The items must be unique." 62 | ] 63 | } 64 | } 65 | }) 66 | .to_string() 67 | ); 68 | } 69 | 70 | #[test] 71 | fn unique_items_custom_err_message_fn() { 72 | fn error_message(_params: &serde_valid::UniqueItemsError) -> String { 73 | "this is custom message.".to_string() 74 | } 75 | 76 | #[derive(Validate)] 77 | struct TestStruct { 78 | #[validate(unique_items, message_fn = error_message)] 79 | val: Vec, 80 | } 81 | 82 | let s = TestStruct { 83 | val: vec![1, 2, 3, 2], 84 | }; 85 | 86 | assert_eq!( 87 | s.validate().unwrap_err().to_string(), 88 | json!({ 89 | "errors": [], 90 | "properties": { 91 | "val": { 92 | "errors": [ 93 | "this is custom message." 94 | ] 95 | } 96 | } 97 | }) 98 | .to_string() 99 | ); 100 | } 101 | 102 | #[test] 103 | fn unique_items_custom_err_message() { 104 | #[derive(Validate)] 105 | struct TestStruct { 106 | #[validate(unique_items, message = "this is custom message.")] 107 | val: Vec, 108 | } 109 | 110 | let s = TestStruct { 111 | val: vec![1, 2, 3, 2], 112 | }; 113 | 114 | assert_eq!( 115 | s.validate().unwrap_err().to_string(), 116 | json!({ 117 | "errors": [], 118 | "properties": { 119 | "val": { 120 | "errors": [ 121 | "this is custom message." 122 | ] 123 | } 124 | } 125 | }) 126 | .to_string() 127 | ); 128 | } 129 | -------------------------------------------------------------------------------- /crates/serde_valid_derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "serde_valid_derive" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | description = "JSON Schema based validation tool using serde." 7 | repository.workspace = true 8 | license.workspace = true 9 | keywords.workspace = true 10 | categories = ["encoding"] 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [lib] 15 | proc-macro = true 16 | 17 | [dependencies] 18 | itertools.workspace = true 19 | paste.workspace = true 20 | proc-macro-error2 = { version = "2.0.0", default-features = false } 21 | proc-macro2 = "^1.0" 22 | quote = "^1.0" 23 | strsim = "0.11.0" 24 | syn = { version = "^2.0", features = ["extra-traits", "full"] } 25 | 26 | [features] 27 | default = [] 28 | fluent = [] 29 | -------------------------------------------------------------------------------- /crates/serde_valid_derive/src/attribute.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | 3 | pub mod common; 4 | pub mod field_validate; 5 | pub mod struct_validate; 6 | pub mod variant_validate; 7 | 8 | pub type Validator = TokenStream; 9 | 10 | macro_rules! count { 11 | () => (0usize); 12 | ( $x:literal $($xs:literal)* ) => (1usize + count!($($xs)*)); 13 | } 14 | 15 | macro_rules! enum_str { 16 | (pub enum $name:ident {}) => { 17 | pub enum $name { 18 | } 19 | 20 | impl $name { 21 | #[allow(dead_code)] 22 | pub fn name(&self) -> &'static str { 23 | unimplemented!() 24 | } 25 | 26 | #[allow(dead_code)] 27 | pub fn iter() -> std::array::IntoIter { 28 | [].into_iter() 29 | } 30 | } 31 | 32 | impl std::str::FromStr for $name { 33 | type Err = String; 34 | 35 | fn from_str(s: &str) -> Result { 36 | Err(s.to_owned()) 37 | } 38 | } 39 | }; 40 | 41 | (pub enum $name:ident { 42 | $($variant:ident = $val:literal),*, 43 | }) => { 44 | pub enum $name { 45 | $($variant,)* 46 | } 47 | 48 | impl $name { 49 | #[allow(dead_code)] 50 | pub fn name(&self) -> &'static str { 51 | match *self { 52 | $($name::$variant => $val),* 53 | } 54 | } 55 | 56 | #[allow(dead_code)] 57 | pub fn iter() -> std::array::IntoIter { 58 | [ 59 | $($name::$variant),* 60 | ].into_iter() 61 | } 62 | } 63 | 64 | impl std::str::FromStr for $name { 65 | type Err = String; 66 | 67 | fn from_str(s: &str) -> Result { 68 | match s { 69 | $($val => Ok($name::$variant) ),*, 70 | _ => Err(s.to_owned()) 71 | } 72 | } 73 | } 74 | }; 75 | } 76 | 77 | enum_str! { 78 | pub enum MetaPathStructValidation { 79 | } 80 | } 81 | 82 | enum_str! { 83 | pub enum MetaListStructValidation { 84 | } 85 | } 86 | 87 | enum_str! { 88 | pub enum MetaNameValueStructValidation { 89 | Custom = "custom", 90 | } 91 | } 92 | 93 | enum_str! { 94 | pub enum MetaPathFieldValidation { 95 | UniqueItems = "unique_items", 96 | } 97 | } 98 | 99 | enum_str! { 100 | pub enum MetaListFieldValidation { 101 | Custom = "custom", 102 | } 103 | } 104 | 105 | enum_str! { 106 | pub enum MetaNameValueFieldValidation { 107 | Minimum = "minimum", 108 | Maximum = "maximum", 109 | ExclusiveMinimum = "exclusive_minimum", 110 | ExclusiveMaximum = "exclusive_maximum", 111 | MinLength = "min_length", 112 | MaxLength = "max_length", 113 | MinItems = "min_items", 114 | MaxItems = "max_items", 115 | MinProperties = "min_properties", 116 | MaxProperties = "max_properties", 117 | MultipleOf = "multiple_of", 118 | Pattern = "pattern", 119 | Enumerate = "enumerate", 120 | Custom = "custom", 121 | } 122 | } 123 | 124 | enum_str! { 125 | pub enum MetaPathCustomMessage { 126 | } 127 | } 128 | 129 | #[cfg(not(feature = "fluent"))] 130 | enum_str! { 131 | pub enum MetaListCustomMessage { 132 | } 133 | } 134 | 135 | #[cfg(feature = "fluent")] 136 | enum_str! { 137 | pub enum MetaListCustomMessage { 138 | I18n = "i18n", 139 | Fluent = "fluent", 140 | } 141 | } 142 | 143 | #[cfg(not(feature = "fluent"))] 144 | enum_str! { 145 | pub enum MetaNameValueCustomMessage { 146 | Message = "message", 147 | MessageFn = "message_fn", 148 | } 149 | } 150 | 151 | #[cfg(feature = "fluent")] 152 | enum_str! { 153 | pub enum MetaNameValueCustomMessage { 154 | Message = "message", 155 | MessageFn = "message_fn", 156 | MessageL10n = "message_l10n", 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /crates/serde_valid_derive/src/attribute/common.rs: -------------------------------------------------------------------------------- 1 | pub mod lit; 2 | pub mod message_format; 3 | -------------------------------------------------------------------------------- /crates/serde_valid_derive/src/attribute/common/lit.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::ToTokens; 3 | 4 | pub enum LitNumeric<'a> { 5 | Int(&'a syn::LitInt), 6 | Float(&'a syn::LitFloat), 7 | } 8 | 9 | impl ToTokens for LitNumeric<'_> { 10 | fn to_tokens(&self, tokens: &mut TokenStream) { 11 | match self { 12 | LitNumeric::Int(lin) => lin.to_tokens(tokens), 13 | LitNumeric::Float(lin) => lin.to_tokens(tokens), 14 | } 15 | } 16 | } 17 | 18 | pub fn get_lit(expr: &syn::Expr) -> Result<&syn::Lit, crate::Errors> { 19 | match expr { 20 | syn::Expr::Lit(expr_lit) => Ok(&expr_lit.lit), 21 | _ => Err(vec![crate::Error::literal_only(expr)]), 22 | } 23 | } 24 | 25 | pub fn get_numeric(lit: &syn::Lit) -> Result { 26 | match lit { 27 | syn::Lit::Int(int) => Ok(LitNumeric::Int(int)), 28 | syn::Lit::Float(float) => Ok(LitNumeric::Float(float)), 29 | _ => Err(vec![crate::Error::numeric_literal_only(lit)]), 30 | } 31 | } 32 | 33 | pub fn get_str(lit: &syn::Lit) -> Result<&syn::LitStr, crate::Errors> { 34 | match lit { 35 | syn::Lit::Str(lit_str) => Ok(lit_str), 36 | _ => Err(vec![crate::Error::str_literal_only(lit)]), 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /crates/serde_valid_derive/src/attribute/field_validate.rs: -------------------------------------------------------------------------------- 1 | mod array; 2 | mod field; 3 | mod generic; 4 | mod meta; 5 | mod numeric; 6 | mod object; 7 | mod string; 8 | 9 | pub use field::FieldValidators; 10 | pub use meta::extract_field_validator; 11 | -------------------------------------------------------------------------------- /crates/serde_valid_derive/src/attribute/field_validate/array.rs: -------------------------------------------------------------------------------- 1 | mod length_items; 2 | mod unique_items; 3 | pub use length_items::{extract_array_max_items_validator, extract_array_min_items_validator}; 4 | pub use unique_items::extract_array_unique_items_validator; 5 | -------------------------------------------------------------------------------- /crates/serde_valid_derive/src/attribute/field_validate/array/length_items.rs: -------------------------------------------------------------------------------- 1 | use crate::attribute::common::lit::get_numeric; 2 | use crate::attribute::common::message_format::MessageFormat; 3 | use crate::attribute::Validator; 4 | use crate::serde::rename::RenameMap; 5 | use crate::types::Field; 6 | use proc_macro2::TokenStream; 7 | use quote::quote; 8 | 9 | /// Length validation. 10 | /// 11 | /// See 12 | macro_rules! extract_array_length_validator{ 13 | ($ErrorType:ident) => { 14 | paste::paste! { 15 | pub fn []( 16 | field: &impl Field, 17 | validation_value: &syn::Lit, 18 | message_format: MessageFormat, 19 | rename_map: &RenameMap, 20 | ) -> Result { 21 | [](field, validation_value, message_format, rename_map) 22 | } 23 | 24 | fn []( 25 | field: &impl Field, 26 | validation_value: &syn::Lit, 27 | message_format: MessageFormat, 28 | rename_map: &RenameMap, 29 | ) -> Result { 30 | let field_name = field.name(); 31 | let field_ident = field.ident(); 32 | let field_key = field.key(); 33 | let rename = rename_map.get(field_name).unwrap_or(&field_key); 34 | let [<$ErrorType:snake>] = get_numeric(validation_value)?; 35 | let errors = field.errors_variable(); 36 | 37 | Ok(quote!( 38 | if let Err(error_params) = ::serde_valid::[]::[]( 39 | #field_ident, 40 | #[<$ErrorType:snake>], 41 | ) { 42 | use ::serde_valid::validation::error::FormatDefault; 43 | 44 | #errors 45 | .entry(#rename) 46 | .or_default() 47 | .push(::serde_valid::validation::Error::$ErrorType( 48 | ::serde_valid::validation::error::Message::new( 49 | error_params, 50 | #message_format, 51 | ) 52 | )); 53 | } 54 | )) 55 | } 56 | } 57 | } 58 | } 59 | 60 | extract_array_length_validator!(MaxItems); 61 | extract_array_length_validator!(MinItems); 62 | -------------------------------------------------------------------------------- /crates/serde_valid_derive/src/attribute/field_validate/array/unique_items.rs: -------------------------------------------------------------------------------- 1 | use crate::attribute::common::message_format::MessageFormat; 2 | use crate::attribute::Validator; 3 | use crate::serde::rename::RenameMap; 4 | use crate::types::Field; 5 | use proc_macro2::TokenStream; 6 | use quote::quote; 7 | 8 | pub fn extract_array_unique_items_validator( 9 | field: &impl Field, 10 | message_format: MessageFormat, 11 | rename_map: &RenameMap, 12 | ) -> Validator { 13 | inner_extract_array_unique_items_validator(field, message_format, rename_map) 14 | } 15 | 16 | fn inner_extract_array_unique_items_validator( 17 | field: &impl Field, 18 | message_format: MessageFormat, 19 | rename_map: &RenameMap, 20 | ) -> TokenStream { 21 | let field_name = field.name(); 22 | let field_ident = field.ident(); 23 | let field_key = field.key(); 24 | let rename = rename_map.get(field_name).unwrap_or(&field_key); 25 | let errors = field.errors_variable(); 26 | 27 | quote!( 28 | if let Err(error_params) = ::serde_valid::ValidateUniqueItems::validate_unique_items( 29 | #field_ident 30 | ) { 31 | use ::serde_valid::validation::error::FormatDefault; 32 | 33 | #errors 34 | .entry(#rename) 35 | .or_default() 36 | .push(::serde_valid::validation::Error::UniqueItems( 37 | ::serde_valid::validation::error::Message::new( 38 | error_params, 39 | #message_format, 40 | ) 41 | )); 42 | } 43 | ) 44 | } 45 | -------------------------------------------------------------------------------- /crates/serde_valid_derive/src/attribute/field_validate/field.rs: -------------------------------------------------------------------------------- 1 | use crate::attribute::Validator; 2 | use crate::types::Field; 3 | use crate::warning::WithWarnings; 4 | use quote::quote; 5 | use std::borrow::Cow; 6 | use std::iter::FromIterator; 7 | 8 | pub struct FieldValidators<'a, F: Field + Clone + 'a> { 9 | field: Cow<'a, F>, 10 | validators: Vec, 11 | pub warnings: Vec, 12 | } 13 | 14 | impl<'a, F: Field + Clone> FieldValidators<'a, F> { 15 | pub fn new(field: Cow<'a, F>, validators: Vec>) -> Self { 16 | Self { 17 | field, 18 | validators: validators.iter().map(|v| v.data.clone()).collect(), 19 | warnings: validators.into_iter().flat_map(|v| v.warnings).collect(), 20 | } 21 | } 22 | 23 | pub fn ident(&self) -> &syn::Ident { 24 | self.field.ident() 25 | } 26 | 27 | pub fn is_empty(&self) -> bool { 28 | self.validators.is_empty() 29 | } 30 | 31 | pub fn get_tokens(&self) -> Option { 32 | if !self.validators.is_empty() { 33 | let validators = Validator::from_iter(self.validators.clone()); 34 | Some(quote! (#validators)) 35 | } else { 36 | None 37 | } 38 | } 39 | 40 | pub fn get_field_variable_token(&self) -> Validator { 41 | let field_ident = self.field.ident(); 42 | let field_getter = self.field.getter_token(); 43 | quote!( 44 | let #field_ident = &self.#field_getter; 45 | ) 46 | } 47 | 48 | pub fn generate_tokens(&self) -> Validator { 49 | let normal_tokens = self.get_tokens(); 50 | 51 | if normal_tokens.is_some() { 52 | let field_variable_token = self.get_field_variable_token(); 53 | quote!( 54 | #field_variable_token 55 | #normal_tokens 56 | ) 57 | } else { 58 | quote!() 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /crates/serde_valid_derive/src/attribute/field_validate/generic.rs: -------------------------------------------------------------------------------- 1 | mod custom; 2 | mod enumerate; 3 | mod validate; 4 | 5 | pub use custom::{ 6 | extract_generic_custom_validator_from_meta_list, 7 | extract_generic_custom_validator_from_meta_name_value, 8 | }; 9 | pub use enumerate::extract_generic_enumerate_validator_from_name_value; 10 | pub use validate::extract_generic_validate_validator; 11 | -------------------------------------------------------------------------------- /crates/serde_valid_derive/src/attribute/field_validate/generic/custom.rs: -------------------------------------------------------------------------------- 1 | use crate::attribute::common::message_format::MessageFormat; 2 | use crate::attribute::Validator; 3 | use crate::serde::rename::RenameMap; 4 | use crate::types::{CommaSeparatedNestedMetas, Field, SingleIdentPath}; 5 | use proc_macro2::TokenStream; 6 | use quote::quote; 7 | 8 | pub fn extract_generic_custom_validator_from_meta_list( 9 | field: &impl Field, 10 | meta_list: &syn::MetaList, 11 | _message_format: MessageFormat, 12 | rename_map: &RenameMap, 13 | ) -> Result { 14 | let path = &meta_list.path; 15 | let path_ident = SingleIdentPath::new(path).ident(); 16 | let field_name = field.name(); 17 | let field_key = field.key(); 18 | let nested = meta_list 19 | .parse_args_with(CommaSeparatedNestedMetas::parse_terminated) 20 | .map_err(|error| vec![crate::Error::custom_message_parse_error(path_ident, &error)])?; 21 | 22 | let field_ident = field.ident(); 23 | let rename = rename_map.get(field_name).unwrap_or(&field_key); 24 | let custom_fn_name = match nested.len() { 25 | 0 => Err(vec![ 26 | crate::Error::validate_custom_meta_list_need_function_or_closure(path), 27 | ]), 28 | 1 => extract_custom_fn_name(&nested[0]), 29 | _ => Err(nested 30 | .iter() 31 | .skip(1) 32 | .map(crate::Error::validate_custom_tail_error) 33 | .collect()), 34 | }?; 35 | inner_extract_generic_custom_validator( 36 | field_ident, 37 | rename, 38 | &custom_fn_name, 39 | &field.errors_variable(), 40 | ) 41 | } 42 | 43 | pub fn extract_generic_custom_validator_from_meta_name_value( 44 | field: &impl Field, 45 | meta_name_value: &syn::MetaNameValue, 46 | _message_format: MessageFormat, 47 | rename_map: &RenameMap, 48 | ) -> Result { 49 | let field_name = field.name(); 50 | let field_key = field.key(); 51 | 52 | let field_ident = field.ident(); 53 | let rename = rename_map.get(field_name).unwrap_or(&field_key); 54 | let fn_name = match &meta_name_value.value { 55 | syn::Expr::Path(path) => Ok(quote!(#path)), 56 | syn::Expr::Call(func_call) => Ok(quote!(#func_call)), 57 | syn::Expr::Closure(closure) => Ok(quote!((#closure))), 58 | _ => Err(vec![ 59 | crate::Error::validate_custom_meta_name_value_need_function_or_closure(meta_name_value), 60 | ]), 61 | }?; 62 | 63 | inner_extract_generic_custom_validator(field_ident, rename, &fn_name, &field.errors_variable()) 64 | } 65 | 66 | fn inner_extract_generic_custom_validator( 67 | field_ident: &syn::Ident, 68 | rename: &TokenStream, 69 | custom_fn_name: &TokenStream, 70 | errors: &TokenStream, 71 | ) -> Result { 72 | Ok(quote!( 73 | if let Err(__errors) = serde_valid::validation::custom::wrap_into_vec_errors(#custom_fn_name(#field_ident)) { 74 | #errors 75 | .entry(#rename) 76 | .or_default() 77 | .extend(__errors); 78 | }; 79 | )) 80 | } 81 | 82 | fn extract_custom_fn_name( 83 | nested_meta: &crate::types::NestedMeta, 84 | ) -> Result { 85 | match nested_meta { 86 | crate::types::NestedMeta::Meta(syn::Meta::Path(fn_name)) => Ok(quote!(#fn_name)), 87 | crate::types::NestedMeta::Meta(syn::Meta::List(closure)) => Ok(quote!(#closure)), 88 | crate::types::NestedMeta::Closure(closure) => Ok(quote!((#closure))), 89 | _ => Err(vec![ 90 | crate::Error::validate_custom_meta_list_need_function_or_closure(nested_meta), 91 | ]), 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /crates/serde_valid_derive/src/attribute/field_validate/generic/enumerate.rs: -------------------------------------------------------------------------------- 1 | use crate::attribute::common::message_format::MessageFormat; 2 | use crate::attribute::Validator; 3 | use crate::serde::rename::RenameMap; 4 | use crate::types::Field; 5 | use quote::quote; 6 | 7 | type Lits<'a> = syn::punctuated::Punctuated; 8 | 9 | pub fn extract_generic_enumerate_validator_from_name_value( 10 | field: &impl Field, 11 | name_value: &syn::MetaNameValue, 12 | message_format: MessageFormat, 13 | rename_map: &RenameMap, 14 | ) -> Result { 15 | let lits = get_enumerate_from_name_value(name_value)?; 16 | inner_extract_generic_enumerate_validator(field, &lits, message_format, rename_map) 17 | } 18 | 19 | fn inner_extract_generic_enumerate_validator( 20 | field: &impl Field, 21 | lits: &Lits, 22 | message_format: MessageFormat, 23 | rename_map: &RenameMap, 24 | ) -> Result { 25 | let field_name = field.name(); 26 | let field_ident = field.ident(); 27 | let field_key = field.key(); 28 | let rename = rename_map.get(field_name).unwrap_or(&field_key); 29 | let errors = field.errors_variable(); 30 | 31 | Ok(quote!( 32 | if let Err(__composited_error_params) = ::serde_valid::validation::ValidateCompositedEnumerate::validate_composited_enumerate( 33 | #field_ident, 34 | &[#lits], 35 | ) { 36 | use ::serde_valid::validation::IntoError; 37 | use ::serde_valid::validation::error::FormatDefault; 38 | 39 | #errors 40 | .entry(#rename) 41 | .or_default() 42 | .push(__composited_error_params.into_error_by(#message_format)); 43 | } 44 | )) 45 | } 46 | 47 | fn get_enumerate_from_name_value(name_value: &syn::MetaNameValue) -> Result { 48 | if let syn::Expr::Array(array) = &name_value.value { 49 | let mut enumerate = Lits::new(); 50 | for item in &array.elems { 51 | match item { 52 | syn::Expr::Lit(lit) => enumerate.push(lit.lit.clone()), 53 | _ => return Err(vec![crate::Error::literal_only(item)]), 54 | } 55 | } 56 | Ok(enumerate) 57 | } else { 58 | Err(vec![crate::Error::validate_enumerate_need_array( 59 | &name_value.value, 60 | )]) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /crates/serde_valid_derive/src/attribute/field_validate/generic/validate.rs: -------------------------------------------------------------------------------- 1 | use crate::attribute::Validator; 2 | use crate::serde::rename::RenameMap; 3 | use crate::types::Field; 4 | use crate::warning::WithWarnings; 5 | use quote::quote; 6 | 7 | pub fn extract_generic_validate_validator( 8 | field: &impl Field, 9 | rename_map: &RenameMap, 10 | ) -> Result, crate::Errors> { 11 | let field_ident = field.ident(); 12 | let field_name = field.name(); 13 | let field_key = field.key(); 14 | let rename = rename_map.get(field_name).unwrap_or(&field_key); 15 | let errors = field.errors_variable(); 16 | 17 | Ok(WithWarnings::new(quote!( 18 | if let Err(__inner_errors) = #field_ident.validate() { 19 | match __inner_errors { 20 | ::serde_valid::validation::Errors::Object(__object_errors) => { 21 | #errors.entry(#rename).or_default().push( 22 | ::serde_valid::validation::Error::Properties(__object_errors) 23 | ); 24 | } 25 | ::serde_valid::validation::Errors::Array(__array_errors) => { 26 | #errors.entry(#rename).or_default().push( 27 | ::serde_valid::validation::Error::Items(__array_errors) 28 | ); 29 | } 30 | ::serde_valid::validation::Errors::NewType(__new_type_errors) => { 31 | #errors.entry(#rename).or_default().extend(__new_type_errors); 32 | } 33 | } 34 | } 35 | ))) 36 | } 37 | -------------------------------------------------------------------------------- /crates/serde_valid_derive/src/attribute/field_validate/meta/meta_list.rs: -------------------------------------------------------------------------------- 1 | use crate::attribute::common::message_format::MessageFormat; 2 | use crate::attribute::field_validate::generic::extract_generic_custom_validator_from_meta_list; 3 | use crate::attribute::{MetaListFieldValidation, Validator}; 4 | use crate::serde::rename::RenameMap; 5 | use crate::types::Field; 6 | use crate::warning::WithWarnings; 7 | 8 | pub fn extract_field_validator_from_meta_list( 9 | field: &impl Field, 10 | validation_type: MetaListFieldValidation, 11 | validation: &syn::MetaList, 12 | message_format: MessageFormat, 13 | rename_map: &RenameMap, 14 | ) -> Result, crate::Errors> { 15 | match validation_type { 16 | MetaListFieldValidation::Custom => extract_generic_custom_validator_from_meta_list( 17 | field, 18 | validation, 19 | message_format, 20 | rename_map, 21 | ) 22 | .map(WithWarnings::new), 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /crates/serde_valid_derive/src/attribute/field_validate/meta/meta_path.rs: -------------------------------------------------------------------------------- 1 | use crate::attribute::common::message_format::MessageFormat; 2 | use crate::attribute::field_validate::array::extract_array_unique_items_validator; 3 | use crate::attribute::{MetaPathFieldValidation, Validator}; 4 | use crate::serde::rename::RenameMap; 5 | use crate::types::Field; 6 | use crate::warning::WithWarnings; 7 | 8 | pub fn extract_field_validator_from_meta_path( 9 | field: &impl Field, 10 | validation_type: MetaPathFieldValidation, 11 | _validation: &syn::Path, 12 | message_format: MessageFormat, 13 | rename_map: &RenameMap, 14 | ) -> Result, crate::Errors> { 15 | match validation_type { 16 | MetaPathFieldValidation::UniqueItems => Ok(WithWarnings::new( 17 | extract_array_unique_items_validator(field, message_format, rename_map), 18 | )), 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /crates/serde_valid_derive/src/attribute/field_validate/numeric.rs: -------------------------------------------------------------------------------- 1 | mod multiple_of; 2 | mod range; 3 | 4 | pub use multiple_of::extract_numeric_multiple_of_validator; 5 | pub use range::{ 6 | extract_numeric_exclusive_maximum_validator, extract_numeric_exclusive_minimum_validator, 7 | extract_numeric_maximum_validator, extract_numeric_minimum_validator, 8 | }; 9 | -------------------------------------------------------------------------------- /crates/serde_valid_derive/src/attribute/field_validate/numeric/multiple_of.rs: -------------------------------------------------------------------------------- 1 | use crate::attribute::common::lit::get_numeric; 2 | use crate::attribute::common::message_format::MessageFormat; 3 | use crate::attribute::Validator; 4 | use crate::serde::rename::RenameMap; 5 | use crate::types::Field; 6 | use proc_macro2::TokenStream; 7 | use quote::quote; 8 | 9 | pub fn extract_numeric_multiple_of_validator( 10 | field: &impl Field, 11 | validation_value: &syn::Lit, 12 | message_format: MessageFormat, 13 | rename_map: &RenameMap, 14 | ) -> Result { 15 | inner_extract_numeric_multiple_of_validator(field, validation_value, message_format, rename_map) 16 | } 17 | 18 | fn inner_extract_numeric_multiple_of_validator( 19 | field: &impl Field, 20 | validation_value: &syn::Lit, 21 | message_format: MessageFormat, 22 | rename_map: &RenameMap, 23 | ) -> Result { 24 | let field_name = field.name(); 25 | let field_ident = field.ident(); 26 | let field_key = field.key(); 27 | let rename = rename_map.get(field_name).unwrap_or(&field_key); 28 | let errors = field.errors_variable(); 29 | let multiple_of = get_numeric(validation_value)?; 30 | 31 | Ok(quote!( 32 | if let Err(__composited_error_params) = ::serde_valid::validation::ValidateCompositedMultipleOf::validate_composited_multiple_of( 33 | #field_ident, 34 | #multiple_of, 35 | ) { 36 | use ::serde_valid::validation::IntoError; 37 | use ::serde_valid::validation::error::FormatDefault; 38 | 39 | #errors 40 | .entry(#rename) 41 | .or_default() 42 | .push(__composited_error_params.into_error_by(#message_format)); 43 | } 44 | )) 45 | } 46 | -------------------------------------------------------------------------------- /crates/serde_valid_derive/src/attribute/field_validate/numeric/range.rs: -------------------------------------------------------------------------------- 1 | use crate::attribute::common::lit::get_numeric; 2 | use crate::attribute::common::message_format::MessageFormat; 3 | use crate::attribute::Validator; 4 | use crate::serde::rename::RenameMap; 5 | use crate::types::Field; 6 | use proc_macro2::TokenStream; 7 | use quote::quote; 8 | 9 | /// Range validation. 10 | /// 11 | /// See 12 | macro_rules! extract_numeric_range_validator{ 13 | ($ErrorType:ident) => { 14 | paste::paste! { 15 | pub fn []( 16 | field: &impl Field, 17 | validation_value: &syn::Lit, 18 | message_format: MessageFormat, 19 | rename_map: &RenameMap, 20 | ) -> Result { 21 | [](field, validation_value, message_format, rename_map) 22 | } 23 | 24 | fn []( 25 | field: &impl Field, 26 | validation_value: &syn::Lit, 27 | message_format: MessageFormat, 28 | rename_map: &RenameMap, 29 | ) -> Result { 30 | let field_name = field.name(); 31 | let field_ident = field.ident(); 32 | let field_key = field.key(); 33 | let rename = rename_map.get(field_name).unwrap_or(&field_key); 34 | let errors = field.errors_variable(); 35 | let [<$ErrorType:snake>] = get_numeric(validation_value)?; 36 | 37 | Ok(quote!( 38 | if let Err(__composited_error_params) = ::serde_valid::validation::[]::[]( 39 | #field_ident, 40 | #[<$ErrorType:snake>], 41 | ) { 42 | use ::serde_valid::validation::IntoError; 43 | use ::serde_valid::validation::error::FormatDefault; 44 | 45 | #errors 46 | .entry(#rename) 47 | .or_default() 48 | .push(__composited_error_params.into_error_by(#message_format)); 49 | } 50 | )) 51 | } 52 | } 53 | } 54 | } 55 | 56 | extract_numeric_range_validator!(Maximum); 57 | extract_numeric_range_validator!(Minimum); 58 | extract_numeric_range_validator!(ExclusiveMaximum); 59 | extract_numeric_range_validator!(ExclusiveMinimum); 60 | -------------------------------------------------------------------------------- /crates/serde_valid_derive/src/attribute/field_validate/object.rs: -------------------------------------------------------------------------------- 1 | mod size_properties; 2 | pub use size_properties::{ 3 | extract_object_max_properties_validator, extract_object_min_properties_validator, 4 | }; 5 | -------------------------------------------------------------------------------- /crates/serde_valid_derive/src/attribute/field_validate/object/size_properties.rs: -------------------------------------------------------------------------------- 1 | use crate::attribute::common::lit::get_numeric; 2 | use crate::attribute::common::message_format::MessageFormat; 3 | use crate::attribute::Validator; 4 | use crate::serde::rename::RenameMap; 5 | use crate::types::Field; 6 | use proc_macro2::TokenStream; 7 | use quote::quote; 8 | 9 | /// Length validation. 10 | /// 11 | /// See 12 | macro_rules! extract_object_size_validator { 13 | ($ErrorType:ident) => { 14 | paste::paste! { 15 | pub fn []( 16 | field: &impl Field, 17 | validation_value: &syn::Lit, 18 | message_format: MessageFormat, 19 | rename_map: &RenameMap, 20 | ) -> Result { 21 | [](field, validation_value, message_format, rename_map) 22 | } 23 | 24 | fn []( 25 | field: &impl Field, 26 | validation_value: &syn::Lit, 27 | message_format: MessageFormat, 28 | rename_map: &RenameMap, 29 | ) -> Result { 30 | let field_name = field.name(); 31 | let field_ident = field.ident(); 32 | let field_key = field.key(); 33 | let rename = rename_map.get(field_name).unwrap_or(&field_key); 34 | let errors = field.errors_variable(); 35 | let [<$ErrorType:snake>] = get_numeric(validation_value)?; 36 | 37 | Ok(quote!( 38 | if let Err(__composited_error_params) = ::serde_valid::validation::[]::[]( 39 | #field_ident, 40 | #[<$ErrorType:snake>] 41 | ) { 42 | use ::serde_valid::validation::IntoError; 43 | use ::serde_valid::validation::error::FormatDefault; 44 | 45 | #errors 46 | .entry(#rename) 47 | .or_default() 48 | .push(__composited_error_params.into_error_by(#message_format)); 49 | } 50 | )) 51 | } 52 | } 53 | } 54 | } 55 | 56 | extract_object_size_validator!(MaxProperties); 57 | extract_object_size_validator!(MinProperties); 58 | -------------------------------------------------------------------------------- /crates/serde_valid_derive/src/attribute/field_validate/string.rs: -------------------------------------------------------------------------------- 1 | mod length; 2 | mod pattern; 3 | pub use length::{extract_string_max_length_validator, extract_string_min_length_validator}; 4 | pub use pattern::extract_string_pattern_validator; 5 | -------------------------------------------------------------------------------- /crates/serde_valid_derive/src/attribute/field_validate/string/length.rs: -------------------------------------------------------------------------------- 1 | use crate::attribute::common::lit::get_numeric; 2 | use crate::attribute::common::message_format::MessageFormat; 3 | use crate::attribute::Validator; 4 | use crate::serde::rename::RenameMap; 5 | use crate::types::Field; 6 | use proc_macro2::TokenStream; 7 | use quote::quote; 8 | 9 | /// Length validation. 10 | /// 11 | /// See 12 | macro_rules! extract_string_length_validator{ 13 | ($ErrorType:ident) => { 14 | paste::paste! { 15 | pub fn []( 16 | field: &impl Field, 17 | validation_value: &syn::Lit, 18 | message_format: MessageFormat, 19 | rename_map: &RenameMap, 20 | ) -> Result { 21 | [](field, validation_value, message_format, rename_map) 22 | } 23 | 24 | fn []( 25 | field: &impl Field, 26 | validation_value: &syn::Lit, 27 | message_format: MessageFormat, 28 | rename_map: &RenameMap, 29 | ) -> Result { 30 | let field_name = field.name(); 31 | let field_ident = field.ident(); 32 | let field_key = field.key(); 33 | let rename = rename_map.get(field_name).unwrap_or(&field_key); 34 | let errors = field.errors_variable(); 35 | let [<$ErrorType:snake>] = get_numeric(validation_value)?; 36 | 37 | Ok(quote!( 38 | if let Err(__composited_error_params) = ::serde_valid::validation::[]::[]( 39 | #field_ident, 40 | #[<$ErrorType:snake>], 41 | ) { 42 | use ::serde_valid::validation::IntoError; 43 | use ::serde_valid::validation::error::FormatDefault; 44 | 45 | #errors 46 | .entry(#rename) 47 | .or_default() 48 | .push(__composited_error_params.into_error_by(#message_format)); 49 | } 50 | )) 51 | } 52 | } 53 | } 54 | } 55 | 56 | extract_string_length_validator!(MaxLength); 57 | extract_string_length_validator!(MinLength); 58 | -------------------------------------------------------------------------------- /crates/serde_valid_derive/src/attribute/field_validate/string/pattern.rs: -------------------------------------------------------------------------------- 1 | use crate::attribute::common::lit::get_str; 2 | use crate::attribute::common::message_format::MessageFormat; 3 | use crate::attribute::Validator; 4 | use crate::serde::rename::RenameMap; 5 | use crate::types::Field; 6 | use proc_macro2::TokenStream; 7 | use quote::quote; 8 | 9 | pub fn extract_string_pattern_validator( 10 | field: &impl Field, 11 | validation_value: &syn::Lit, 12 | message_format: MessageFormat, 13 | rename_map: &RenameMap, 14 | ) -> Result { 15 | inner_extract_string_pattern_validator(field, validation_value, message_format, rename_map) 16 | } 17 | 18 | fn inner_extract_string_pattern_validator( 19 | field: &impl Field, 20 | validation_value: &syn::Lit, 21 | message_format: MessageFormat, 22 | rename_map: &RenameMap, 23 | ) -> Result { 24 | let field_name = field.name(); 25 | let field_ident = field.ident(); 26 | let field_key = field.key(); 27 | let rename = rename_map.get(field_name).unwrap_or(&field_key); 28 | let errors = field.errors_variable(); 29 | let pattern = get_str(validation_value)?; 30 | let pattern_ident = syn::Ident::new( 31 | &format!("{}_PATTERN", &field_ident).to_uppercase(), 32 | field_ident.span(), 33 | ); 34 | 35 | Ok(quote!( 36 | static #pattern_ident : ::serde_valid::export::once_cell::sync::OnceCell<::serde_valid::export::regex::Regex> = ::serde_valid::export::once_cell::sync::OnceCell::new(); 37 | let __pattern = #pattern_ident.get_or_init(|| ::serde_valid::export::regex::Regex::new(#pattern).unwrap()); 38 | if let Err(__composited_error_params) = ::serde_valid::validation::ValidateCompositedPattern::validate_composited_pattern( 39 | #field_ident, 40 | __pattern, 41 | ) { 42 | use ::serde_valid::validation::IntoError; 43 | use ::serde_valid::validation::error::FormatDefault; 44 | 45 | #errors 46 | .entry(#rename) 47 | .or_default() 48 | .push(__composited_error_params.into_error_by(#message_format)); 49 | } 50 | )) 51 | } 52 | -------------------------------------------------------------------------------- /crates/serde_valid_derive/src/attribute/struct_validate.rs: -------------------------------------------------------------------------------- 1 | pub mod generic; 2 | mod meta; 3 | 4 | use crate::{attribute::Validator, warning::WithWarnings}; 5 | 6 | use self::meta::extract_struct_validator; 7 | 8 | pub fn collect_struct_custom_from_named_struct( 9 | attributes: &[syn::Attribute], 10 | ) -> Result>, crate::Errors> { 11 | let mut errors = vec![]; 12 | 13 | let validations = attributes 14 | .iter() 15 | .filter_map(|attribute| { 16 | if attribute.path().is_ident("validate") { 17 | match extract_struct_validator(attribute) { 18 | Ok(validator) => Some(validator), 19 | Err(validator_error) => { 20 | errors.extend(validator_error); 21 | None 22 | } 23 | } 24 | } else { 25 | None 26 | } 27 | }) 28 | .collect::>(); 29 | 30 | if errors.is_empty() { 31 | Ok(WithWarnings::from_iter(validations)) 32 | } else { 33 | Err(errors) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /crates/serde_valid_derive/src/attribute/struct_validate/generic.rs: -------------------------------------------------------------------------------- 1 | mod custom; 2 | 3 | pub use custom::extract_generic_struct_custom_validator_from_meta_name_value; 4 | -------------------------------------------------------------------------------- /crates/serde_valid_derive/src/attribute/struct_validate/generic/custom.rs: -------------------------------------------------------------------------------- 1 | use crate::attribute::common::message_format::MessageFormat; 2 | use crate::attribute::Validator; 3 | use quote::quote; 4 | 5 | pub fn extract_generic_struct_custom_validator_from_meta_name_value( 6 | meta_name_value: &syn::MetaNameValue, 7 | _message_format: MessageFormat, 8 | ) -> Result { 9 | match &meta_name_value.value { 10 | syn::Expr::Path(syn::ExprPath { path, .. }) => extract_struct_custom_from_meta_path(path), 11 | syn::Expr::Call(call) => extract_struct_custom_from_call(call), 12 | syn::Expr::Closure(closure) => extract_struct_custom_from_closure(closure), 13 | _ => Err(vec![ 14 | crate::Error::validate_custom_meta_name_value_need_function_or_closure(meta_name_value), 15 | ]), 16 | } 17 | } 18 | 19 | fn extract_struct_custom_from_meta_path(meta_path: &syn::Path) -> Result { 20 | let rule_fn_name = &meta_path; 21 | 22 | Ok(quote!( 23 | if let Err(__errors) = serde_valid::validation::custom::wrap_into_vec_errors(#rule_fn_name(self)) { 24 | __rule_vec_errors.extend(__errors); 25 | }; 26 | )) 27 | } 28 | 29 | fn extract_struct_custom_from_call(call: &syn::ExprCall) -> Result { 30 | Ok(quote!( 31 | if let Err(__errors) = serde_valid::validation::custom::wrap_call_validation(self, #call) { 32 | __rule_vec_errors.extend(__errors); 33 | }; 34 | )) 35 | } 36 | 37 | fn extract_struct_custom_from_closure( 38 | closure: &syn::ExprClosure, 39 | ) -> Result { 40 | Ok(quote!( 41 | if let Err(__errors) = serde_valid::validation::custom::wrap_closure_validation(self, #closure) { 42 | __rule_vec_errors.extend(__errors); 43 | }; 44 | )) 45 | } 46 | -------------------------------------------------------------------------------- /crates/serde_valid_derive/src/attribute/struct_validate/meta/meta_list.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | attribute::{common::message_format::MessageFormat, MetaListStructValidation, Validator}, 3 | warning::WithWarnings, 4 | }; 5 | 6 | pub fn extract_struct_validator_from_meta_list( 7 | validation_type: MetaListStructValidation, 8 | _validation: &syn::MetaList, 9 | _message_format: MessageFormat, 10 | ) -> Result, crate::Errors> { 11 | match validation_type {} 12 | } 13 | -------------------------------------------------------------------------------- /crates/serde_valid_derive/src/attribute/struct_validate/meta/meta_name_value.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | attribute::{ 3 | common::message_format::MessageFormat, 4 | struct_validate::generic::extract_generic_struct_custom_validator_from_meta_name_value, 5 | MetaNameValueStructValidation, Validator, 6 | }, 7 | warning::WithWarnings, 8 | }; 9 | 10 | #[inline] 11 | pub fn extract_struct_validator_from_meta_name_value( 12 | validation_type: MetaNameValueStructValidation, 13 | validation: &syn::MetaNameValue, 14 | message_format: MessageFormat, 15 | ) -> Result, crate::Errors> { 16 | match validation_type { 17 | MetaNameValueStructValidation::Custom => { 18 | extract_generic_struct_custom_validator_from_meta_name_value(validation, message_format) 19 | } 20 | } 21 | .map(WithWarnings::new) 22 | } 23 | -------------------------------------------------------------------------------- /crates/serde_valid_derive/src/attribute/struct_validate/meta/meta_path.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | attribute::{common::message_format::MessageFormat, MetaPathStructValidation, Validator}, 3 | warning::WithWarnings, 4 | }; 5 | 6 | #[inline] 7 | pub fn extract_struct_validator_from_meta_path( 8 | validation_type: MetaPathStructValidation, 9 | _validation: &syn::Path, 10 | _message_format: MessageFormat, 11 | ) -> Result, crate::Errors> { 12 | match validation_type {} 13 | } 14 | -------------------------------------------------------------------------------- /crates/serde_valid_derive/src/attribute/variant_validate.rs: -------------------------------------------------------------------------------- 1 | mod meta; 2 | 3 | use crate::{attribute::Validator, warning::WithWarnings}; 4 | 5 | use self::meta::extract_variant_validator; 6 | 7 | pub fn collect_variant_custom_from_variant( 8 | attributes: &[syn::Attribute], 9 | ) -> Result, crate::Errors> { 10 | let mut errors = vec![]; 11 | let mut warnings = vec![]; 12 | 13 | let validations = attributes 14 | .iter() 15 | .filter_map(|attribute| { 16 | if attribute.path().is_ident("validate") { 17 | match extract_variant_validator(attribute) { 18 | Ok(validator) => { 19 | warnings.extend(validator.warnings); 20 | Some(validator.data) 21 | } 22 | Err(validator_error) => { 23 | errors.extend(validator_error); 24 | None 25 | } 26 | } 27 | } else { 28 | None 29 | } 30 | }) 31 | .collect::>(); 32 | 33 | if errors.is_empty() { 34 | Ok(WithWarnings::new_with_warnings( 35 | Validator::from_iter(validations), 36 | warnings, 37 | )) 38 | } else { 39 | Err(errors) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /crates/serde_valid_derive/src/attribute/variant_validate/meta/meta_list.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | attribute::{common::message_format::MessageFormat, MetaListStructValidation, Validator}, 3 | warning::WithWarnings, 4 | }; 5 | 6 | pub fn extract_variant_validator_from_meta_list( 7 | validation_type: MetaListStructValidation, 8 | _validation: &syn::MetaList, 9 | _message_format: MessageFormat, 10 | ) -> Result, crate::Errors> { 11 | match validation_type {} 12 | } 13 | -------------------------------------------------------------------------------- /crates/serde_valid_derive/src/attribute/variant_validate/meta/meta_name_value.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | attribute::{ 3 | common::message_format::MessageFormat, 4 | struct_validate::generic::extract_generic_struct_custom_validator_from_meta_name_value, 5 | MetaNameValueStructValidation, Validator, 6 | }, 7 | warning::WithWarnings, 8 | }; 9 | 10 | #[inline] 11 | pub fn extract_variant_validator_from_meta_name_value( 12 | validation_type: MetaNameValueStructValidation, 13 | validation: &syn::MetaNameValue, 14 | message_format: MessageFormat, 15 | ) -> Result, crate::Errors> { 16 | match validation_type { 17 | MetaNameValueStructValidation::Custom => { 18 | extract_generic_struct_custom_validator_from_meta_name_value(validation, message_format) 19 | } 20 | } 21 | .map(WithWarnings::new) 22 | } 23 | -------------------------------------------------------------------------------- /crates/serde_valid_derive/src/attribute/variant_validate/meta/meta_path.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | attribute::{common::message_format::MessageFormat, MetaPathStructValidation, Validator}, 3 | warning::WithWarnings, 4 | }; 5 | 6 | #[inline] 7 | pub fn extract_variant_validator_from_meta_path( 8 | validation_type: MetaPathStructValidation, 9 | _validation: &syn::Path, 10 | _message_format: MessageFormat, 11 | ) -> Result, crate::Errors> { 12 | match validation_type {} 13 | } 14 | -------------------------------------------------------------------------------- /crates/serde_valid_derive/src/derive.rs: -------------------------------------------------------------------------------- 1 | mod enum_derive; 2 | mod named_struct_derive; 3 | mod unnamed_struct_derive; 4 | 5 | use enum_derive::expand_enum_validate_derive; 6 | use named_struct_derive::expand_named_struct_derive; 7 | use proc_macro2::TokenStream; 8 | use unnamed_struct_derive::expand_unnamed_struct_derive; 9 | 10 | pub fn expand_derive(input: &syn::DeriveInput) -> Result { 11 | match &input.data { 12 | syn::Data::Struct(syn::DataStruct { ref fields, .. }) => match fields { 13 | syn::Fields::Named(fields) => expand_named_struct_derive(input, fields), 14 | syn::Fields::Unnamed(fields) => expand_unnamed_struct_derive(input, fields), 15 | syn::Fields::Unit => Err(vec![crate::Error::unit_struct_not_supported(input)]), 16 | }, 17 | syn::Data::Enum(syn::DataEnum { variants, .. }) => { 18 | expand_enum_validate_derive(input, variants) 19 | } 20 | syn::Data::Union(_) => Err(vec![crate::Error::union_not_supported(input)]), 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /crates/serde_valid_derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[warn(clippy::needless_collect)] 2 | mod attribute; 3 | mod derive; 4 | mod error; 5 | mod serde; 6 | mod types; 7 | mod warning; 8 | 9 | use derive::expand_derive; 10 | use error::to_compile_errors; 11 | use error::{Error, Errors}; 12 | use proc_macro::TokenStream; 13 | use proc_macro_error2::proc_macro_error; 14 | use syn::{parse_macro_input, DeriveInput}; 15 | 16 | #[proc_macro_derive(Validate, attributes(rule, validate, serde_valid))] 17 | #[proc_macro_error] 18 | pub fn derive_validate(tokens: TokenStream) -> TokenStream { 19 | let input = parse_macro_input!(tokens as DeriveInput); 20 | 21 | expand_derive(&input) 22 | .unwrap_or_else(to_compile_errors) 23 | .into() 24 | } 25 | -------------------------------------------------------------------------------- /crates/serde_valid_derive/src/serde.rs: -------------------------------------------------------------------------------- 1 | pub mod rename; 2 | -------------------------------------------------------------------------------- /crates/serde_valid_derive/src/serde/rename.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use crate::types::{CommaSeparatedMetas, Field, NamedField}; 4 | use proc_macro2::TokenStream; 5 | use quote::{quote, ToTokens}; 6 | 7 | pub type RenameMap = HashMap; 8 | 9 | pub fn collect_serde_rename_map(fields: &syn::FieldsNamed) -> RenameMap { 10 | let mut renames = RenameMap::new(); 11 | for field in fields.named.iter() { 12 | let named_field = NamedField::new(field); 13 | for attribute in named_field.attrs() { 14 | if attribute.path().is_ident("serde") { 15 | if let Some(rename) = find_rename_from_serde_attributes(attribute) { 16 | renames.insert( 17 | field.ident.to_token_stream().to_string(), 18 | quote!(std::borrow::Cow::from(#rename)), 19 | ); 20 | } 21 | } 22 | } 23 | } 24 | renames 25 | } 26 | 27 | fn find_rename_from_serde_attributes(attribute: &syn::Attribute) -> Option { 28 | if let syn::Meta::List(serde_list) = &attribute.meta { 29 | if let Ok(serde_nested_meta) = 30 | serde_list.parse_args_with(CommaSeparatedMetas::parse_terminated) 31 | { 32 | for serde_meta in serde_nested_meta { 33 | if let Some(rename) = find_rename_from_serde_rename_attributes(&serde_meta) { 34 | return Some(rename); 35 | } 36 | } 37 | } 38 | } 39 | None 40 | } 41 | 42 | fn find_rename_from_serde_rename_attributes(serde_meta: &syn::Meta) -> Option { 43 | match serde_meta { 44 | syn::Meta::NameValue(rename_name_value) => { 45 | if let syn::Expr::Lit(syn::ExprLit { 46 | lit: syn::Lit::Str(lit_str), 47 | .. 48 | }) = &rename_name_value.value 49 | { 50 | Some(lit_str.to_token_stream()) 51 | } else { 52 | None 53 | } 54 | } 55 | syn::Meta::List(rename_list) => { 56 | if let Ok(nested) = rename_list.parse_args_with(CommaSeparatedMetas::parse_terminated) { 57 | for rename_meta in nested { 58 | if !rename_meta.path().is_ident("deserialize") { 59 | continue; 60 | } 61 | if let syn::Meta::NameValue(deserialize_name_value) = rename_meta { 62 | if let syn::Expr::Lit(syn::ExprLit { 63 | lit: syn::Lit::Str(lit_str), 64 | .. 65 | }) = &deserialize_name_value.value 66 | { 67 | return Some(lit_str.to_token_stream()); 68 | } 69 | } 70 | } 71 | } 72 | 73 | None 74 | } 75 | _ => None, 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /crates/serde_valid_derive/src/types.rs: -------------------------------------------------------------------------------- 1 | mod field; 2 | mod nested_meta; 3 | mod single_ident_path; 4 | 5 | pub use field::{Field, NamedField, UnnamedField}; 6 | pub use nested_meta::NestedMeta; 7 | use proc_macro2::TokenStream; 8 | pub use single_ident_path::SingleIdentPath; 9 | 10 | pub type CommaSeparatedTokenStreams = syn::punctuated::Punctuated; 11 | pub type CommaSeparatedNestedMetas = syn::punctuated::Punctuated; 12 | pub type CommaSeparatedMetas = syn::punctuated::Punctuated; 13 | -------------------------------------------------------------------------------- /crates/serde_valid_derive/src/types/field.rs: -------------------------------------------------------------------------------- 1 | mod named; 2 | mod unnamed; 3 | 4 | pub use named::NamedField; 5 | pub use unnamed::UnnamedField; 6 | 7 | pub trait Field { 8 | fn name(&self) -> &String; 9 | 10 | fn ident(&self) -> &syn::Ident; 11 | 12 | fn key(&self) -> proc_macro2::TokenStream; 13 | 14 | fn errors_variable(&self) -> proc_macro2::TokenStream; 15 | 16 | fn getter_token(&self) -> proc_macro2::TokenStream; 17 | 18 | fn attrs(&self) -> &Vec; 19 | 20 | #[allow(dead_code)] 21 | fn vis(&self) -> &syn::Visibility; 22 | 23 | #[allow(dead_code)] 24 | fn ty(&self) -> &syn::Type; 25 | } 26 | -------------------------------------------------------------------------------- /crates/serde_valid_derive/src/types/field/named.rs: -------------------------------------------------------------------------------- 1 | use super::Field; 2 | use proc_macro_error2::abort; 3 | use quote::quote; 4 | use std::borrow::Cow; 5 | use syn::spanned::Spanned; 6 | 7 | #[derive(Debug, Clone)] 8 | pub struct NamedField<'a> { 9 | name: String, 10 | field: Cow<'a, syn::Field>, 11 | } 12 | 13 | impl<'a> NamedField<'a> { 14 | pub fn new(field: &'a syn::Field) -> Self { 15 | if field.ident.is_none() { 16 | abort!(field.span(), "struct must be named fields struct.") 17 | } 18 | Self { 19 | name: field.ident.as_ref().unwrap().to_string(), 20 | field: Cow::Borrowed(field), 21 | } 22 | } 23 | } 24 | 25 | impl Field for NamedField<'_> { 26 | fn name(&self) -> &String { 27 | &self.name 28 | } 29 | 30 | fn ident(&self) -> &syn::Ident { 31 | self.field.ident.as_ref().unwrap() 32 | } 33 | 34 | fn key(&self) -> proc_macro2::TokenStream { 35 | let name = &self.name; 36 | quote!(std::borrow::Cow::from(#name)) 37 | } 38 | 39 | fn errors_variable(&self) -> proc_macro2::TokenStream { 40 | quote!(__property_vec_errors_map) 41 | } 42 | 43 | fn getter_token(&self) -> proc_macro2::TokenStream { 44 | let ident = self.ident(); 45 | quote!(#ident) 46 | } 47 | 48 | fn attrs(&self) -> &Vec { 49 | self.field.attrs.as_ref() 50 | } 51 | 52 | fn vis(&self) -> &syn::Visibility { 53 | &self.field.vis 54 | } 55 | 56 | fn ty(&self) -> &syn::Type { 57 | &self.field.ty 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /crates/serde_valid_derive/src/types/field/unnamed.rs: -------------------------------------------------------------------------------- 1 | use super::Field; 2 | use proc_macro_error2::abort; 3 | use quote::quote; 4 | use std::borrow::Cow; 5 | use std::convert::AsRef; 6 | use syn::spanned::Spanned; 7 | 8 | #[derive(Debug, Clone)] 9 | pub struct UnnamedField<'a> { 10 | name: String, 11 | index: usize, 12 | ident: syn::Ident, 13 | field: Cow<'a, syn::Field>, 14 | } 15 | 16 | impl<'a> UnnamedField<'a> { 17 | pub fn new(index: usize, field: &'a syn::Field) -> Self { 18 | if field.ident.is_some() { 19 | abort!(field.span(), "struct must be unnamed fields struct.") 20 | } 21 | Self { 22 | name: index.to_string(), 23 | index, 24 | ident: syn::Ident::new(&format!("__{}", index), field.span()), 25 | field: Cow::Borrowed(field), 26 | } 27 | } 28 | } 29 | 30 | impl Field for UnnamedField<'_> { 31 | fn name(&self) -> &String { 32 | &self.name 33 | } 34 | 35 | fn ident(&self) -> &syn::Ident { 36 | &self.ident 37 | } 38 | 39 | fn key(&self) -> proc_macro2::TokenStream { 40 | let index = self.index; 41 | quote!(#index) 42 | } 43 | 44 | fn errors_variable(&self) -> proc_macro2::TokenStream { 45 | quote!(__item_vec_errors_map) 46 | } 47 | 48 | fn getter_token(&self) -> proc_macro2::TokenStream { 49 | let index = syn::Index::from(self.index); 50 | quote!(#index) 51 | } 52 | 53 | fn attrs(&self) -> &Vec { 54 | self.field.attrs.as_ref() 55 | } 56 | 57 | fn vis(&self) -> &syn::Visibility { 58 | &self.field.vis 59 | } 60 | 61 | fn ty(&self) -> &syn::Type { 62 | &self.field.ty 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /crates/serde_valid_derive/src/types/nested_meta.rs: -------------------------------------------------------------------------------- 1 | pub enum NestedMeta { 2 | Lit(syn::Lit), 3 | Meta(syn::Meta), 4 | Closure(syn::ExprClosure), 5 | } 6 | 7 | impl quote::ToTokens for NestedMeta { 8 | fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { 9 | match self { 10 | NestedMeta::Lit(lit) => lit.to_tokens(tokens), 11 | NestedMeta::Meta(meta) => meta.to_tokens(tokens), 12 | NestedMeta::Closure(closure) => closure.to_tokens(tokens), 13 | } 14 | } 15 | } 16 | 17 | impl syn::parse::Parse for NestedMeta { 18 | fn parse(input: syn::parse::ParseStream) -> syn::Result { 19 | let lookahead = input.lookahead1(); 20 | if lookahead.peek(syn::Lit) { 21 | Ok(NestedMeta::Lit(input.parse()?)) 22 | } else if lookahead.peek(syn::Ident) { 23 | Ok(NestedMeta::Meta(input.parse()?)) 24 | } else if lookahead.peek(syn::token::Or) || lookahead.peek(syn::token::OrOr) { 25 | Ok(NestedMeta::Closure(input.parse()?)) 26 | } else { 27 | Err(lookahead.error()) 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /crates/serde_valid_derive/src/types/single_ident_path.rs: -------------------------------------------------------------------------------- 1 | use proc_macro_error2::abort; 2 | use syn::spanned::Spanned; 3 | 4 | pub struct SingleIdentPath<'a>(&'a syn::Path); 5 | 6 | impl<'a> SingleIdentPath<'a> { 7 | pub fn new(path: &'a syn::Path) -> Self { 8 | if path.get_ident().is_none() { 9 | abort!( 10 | path.span(), 11 | "Path(='{}') must be single ident path.", 12 | path_to_string(path) 13 | ) 14 | } 15 | Self(path) 16 | } 17 | 18 | pub fn ident(&self) -> &'a syn::Ident { 19 | self.0.get_ident().unwrap() 20 | } 21 | } 22 | 23 | fn path_to_string(path: &syn::Path) -> String { 24 | path.segments 25 | .pairs() 26 | .map(|pair| match pair { 27 | syn::punctuated::Pair::Punctuated(seg, ..) => { 28 | format!("{}::", seg.ident) 29 | } 30 | syn::punctuated::Pair::End(seg) => seg.ident.to_string(), 31 | }) 32 | .collect::>() 33 | .join("") 34 | } 35 | -------------------------------------------------------------------------------- /crates/serde_valid_derive/src/warning.rs: -------------------------------------------------------------------------------- 1 | use std::{hash::Hash, str::FromStr}; 2 | 3 | use proc_macro2::Span; 4 | use proc_macro2::TokenStream; 5 | use quote::{quote_spanned, ToTokens}; 6 | 7 | #[derive(Debug, Clone)] 8 | pub struct WithWarnings { 9 | pub data: T, 10 | pub warnings: Vec, 11 | } 12 | 13 | impl WithWarnings { 14 | pub fn new(data: T) -> Self { 15 | Self { 16 | data, 17 | warnings: vec![], 18 | } 19 | } 20 | 21 | pub fn new_with_warnings(data: T, warnings: Vec) -> Self { 22 | Self { data, warnings } 23 | } 24 | 25 | pub fn from_iter(data: impl IntoIterator>) -> WithWarnings> { 26 | let mut warnings = vec![]; 27 | let data = data 28 | .into_iter() 29 | .map(|WithWarnings { data, warnings: w }| { 30 | warnings.extend(w); 31 | data 32 | }) 33 | .collect::>(); 34 | WithWarnings { data, warnings } 35 | } 36 | } 37 | 38 | impl From> for WithWarnings> { 39 | fn from(with_warnings: WithWarnings) -> Self { 40 | WithWarnings { 41 | data: vec![with_warnings.data], 42 | warnings: with_warnings.warnings, 43 | } 44 | } 45 | } 46 | 47 | impl From for WithWarnings { 48 | fn from(data: T) -> Self { 49 | Self::new(data) 50 | } 51 | } 52 | 53 | #[derive(Debug, Clone)] 54 | pub enum Warning { 55 | Deprecated { 56 | ident: syn::Ident, 57 | note: String, 58 | span: Span, 59 | }, 60 | } 61 | 62 | impl Hash for Warning { 63 | fn hash(&self, state: &mut H) { 64 | match self { 65 | Self::Deprecated { ident, note, .. } => { 66 | ident.hash(state); 67 | note.hash(state); 68 | } 69 | } 70 | } 71 | } 72 | 73 | impl std::cmp::PartialEq for Warning { 74 | fn eq(&self, other: &Self) -> bool { 75 | match (self, other) { 76 | ( 77 | Self::Deprecated { 78 | ident: ident1, 79 | note: note1, 80 | .. 81 | }, 82 | Self::Deprecated { 83 | ident: ident2, 84 | note: note2, 85 | .. 86 | }, 87 | ) => ident1 == ident2 && note1 == note2, 88 | } 89 | } 90 | } 91 | 92 | impl std::cmp::Eq for Warning {} 93 | 94 | impl Warning { 95 | pub fn add_index(&self, index: usize) -> Self { 96 | match self { 97 | Self::Deprecated { ident, note, span } => Self::Deprecated { 98 | ident: syn::Ident::new(&format!("{}_{}", ident, index), ident.span()), 99 | note: note.clone(), 100 | span: *span, 101 | }, 102 | } 103 | } 104 | } 105 | 106 | impl ToTokens for Warning { 107 | fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { 108 | match self { 109 | Self::Deprecated { ident, note, span } => { 110 | let func_name = TokenStream::from_str(&format!( 111 | "__{}_warning", 112 | ident.to_string().to_lowercase() 113 | )) 114 | .unwrap(); 115 | 116 | quote_spanned!(*span => 117 | #[deprecated(note = #note)] 118 | #[allow(clippy::let_unit_value)] 119 | fn #func_name() { 120 | #[deprecated(note = #note)] 121 | #[allow(non_upper_case_globals)] 122 | const _deprecated: () = (); 123 | let _ = _deprecated; 124 | } 125 | ) 126 | .to_tokens(tokens) 127 | } 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /crates/serde_valid_literal/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "serde_valid_literal" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | description = "Literal Value type based JSON." 7 | repository.workspace = true 8 | license.workspace = true 9 | keywords.workspace = true 10 | categories = [] 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [dependencies] 15 | paste = { workspace = true } 16 | regex = { workspace = true } 17 | 18 | [features] 19 | default = [] 20 | i128 = [] 21 | -------------------------------------------------------------------------------- /crates/serde_valid_literal/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod literal; 2 | mod number; 3 | mod pattern; 4 | 5 | pub use literal::Literal; 6 | pub use number::Number; 7 | pub use pattern::Pattern; 8 | -------------------------------------------------------------------------------- /crates/serde_valid_literal/src/literal.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Clone, PartialEq, PartialOrd)] 2 | pub enum Literal { 3 | Bool(bool), 4 | Number(crate::Number), 5 | String(&'static str), 6 | Char(char), 7 | Null, 8 | } 9 | 10 | impl std::convert::From for Literal { 11 | fn from(item: bool) -> Self { 12 | Literal::Bool(item) 13 | } 14 | } 15 | 16 | impl std::convert::From for Literal 17 | where 18 | T: Into, 19 | { 20 | fn from(item: T) -> Self { 21 | Literal::Number(item.into()) 22 | } 23 | } 24 | 25 | impl std::convert::From<&'static str> for Literal { 26 | fn from(item: &'static str) -> Self { 27 | Literal::String(item) 28 | } 29 | } 30 | 31 | impl std::convert::From for Literal { 32 | fn from(item: char) -> Self { 33 | Literal::Char(item) 34 | } 35 | } 36 | 37 | impl std::convert::From> for Literal 38 | where 39 | Literal: From, 40 | { 41 | fn from(item: Option) -> Self { 42 | match item { 43 | Some(value) => std::convert::From::from(value), 44 | None => Literal::Null, 45 | } 46 | } 47 | } 48 | 49 | impl std::fmt::Display for Literal { 50 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 51 | match self { 52 | Literal::Bool(value) => write!(f, "{value}"), 53 | Literal::Number(value) => write!(f, "{value}"), 54 | Literal::String(value) => write!(f, "{value}"), 55 | Literal::Char(value) => write!(f, "{value}"), 56 | Literal::Null => write!(f, "null"), 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /crates/serde_valid_literal/src/number.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "i128")] 2 | use std::num::{NonZeroI128, NonZeroU128}; 3 | use std::num::{ 4 | NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, NonZeroIsize, NonZeroU16, NonZeroU32, 5 | NonZeroU64, NonZeroU8, NonZeroUsize, 6 | }; 7 | 8 | #[derive(Debug, Copy, Clone, PartialEq, PartialOrd)] 9 | pub enum Number { 10 | I8(i8), 11 | I16(i16), 12 | I32(i32), 13 | I64(i64), 14 | #[cfg(feature = "i128")] 15 | I128(i128), 16 | Isize(isize), 17 | U8(u8), 18 | U16(u16), 19 | U32(u32), 20 | U64(u64), 21 | #[cfg(feature = "i128")] 22 | U128(u128), 23 | Usize(usize), 24 | NonZeroI8(NonZeroI8), 25 | NonZeroI16(NonZeroI16), 26 | NonZeroI32(NonZeroI32), 27 | NonZeroI64(NonZeroI64), 28 | #[cfg(feature = "i128")] 29 | NonZeroI128(NonZeroI128), 30 | NonZeroIsize(NonZeroIsize), 31 | NonZeroU8(NonZeroU8), 32 | NonZeroU16(NonZeroU16), 33 | NonZeroU32(NonZeroU32), 34 | NonZeroU64(NonZeroU64), 35 | #[cfg(feature = "i128")] 36 | NonZeroU128(NonZeroU128), 37 | NonZeroUsize(NonZeroUsize), 38 | F32(f32), 39 | F64(f64), 40 | } 41 | 42 | impl std::fmt::Display for Number { 43 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 44 | match *self { 45 | Number::I8(num) => write!(f, "{:?}", num), 46 | Number::I16(num) => write!(f, "{:?}", num), 47 | Number::I32(num) => write!(f, "{:?}", num), 48 | Number::I64(num) => write!(f, "{:?}", num), 49 | #[cfg(feature = "i128")] 50 | Number::I128(num) => write!(f, "{:?}", num), 51 | Number::Isize(num) => write!(f, "{:?}", num), 52 | Number::U8(num) => write!(f, "{:?}", num), 53 | Number::U16(num) => write!(f, "{:?}", num), 54 | Number::U32(num) => write!(f, "{:?}", num), 55 | Number::U64(num) => write!(f, "{:?}", num), 56 | #[cfg(feature = "i128")] 57 | Number::U128(num) => write!(f, "{:?}", num), 58 | Number::Usize(num) => write!(f, "{:?}", num), 59 | Number::NonZeroI8(num) => write!(f, "{:?}", num), 60 | Number::NonZeroI16(num) => write!(f, "{:?}", num), 61 | Number::NonZeroI32(num) => write!(f, "{:?}", num), 62 | Number::NonZeroI64(num) => write!(f, "{:?}", num), 63 | #[cfg(feature = "i128")] 64 | Number::NonZeroI128(num) => write!(f, "{:?}", num), 65 | Number::NonZeroIsize(num) => write!(f, "{:?}", num), 66 | Number::NonZeroU8(num) => write!(f, "{:?}", num), 67 | Number::NonZeroU16(num) => write!(f, "{:?}", num), 68 | Number::NonZeroU32(num) => write!(f, "{:?}", num), 69 | Number::NonZeroU64(num) => write!(f, "{:?}", num), 70 | #[cfg(feature = "i128")] 71 | Number::NonZeroU128(num) => write!(f, "{:?}", num), 72 | Number::NonZeroUsize(num) => write!(f, "{:?}", num), 73 | Number::F32(num) => write!(f, "{:?}", num), 74 | Number::F64(num) => write!(f, "{:?}", num), 75 | } 76 | } 77 | } 78 | 79 | macro_rules! impl_from_trait { 80 | ($type:ty) => { 81 | paste::paste! { 82 | impl From<$type> for Number { 83 | fn from(item: $type) -> Self { 84 | Number::[<$type:camel>](item) 85 | } 86 | } 87 | 88 | impl From<&$type> for Number { 89 | fn from(item: &$type) -> Self { 90 | Number::[<$type:camel>](*item) 91 | } 92 | } 93 | } 94 | }; 95 | } 96 | 97 | impl_from_trait!(i8); 98 | impl_from_trait!(i16); 99 | impl_from_trait!(i32); 100 | impl_from_trait!(i64); 101 | #[cfg(feature = "i128")] 102 | impl_from_trait!(i128); 103 | impl_from_trait!(isize); 104 | impl_from_trait!(u8); 105 | impl_from_trait!(u16); 106 | impl_from_trait!(u32); 107 | impl_from_trait!(u64); 108 | #[cfg(feature = "i128")] 109 | impl_from_trait!(u128); 110 | impl_from_trait!(usize); 111 | impl_from_trait!(NonZeroI8); 112 | impl_from_trait!(NonZeroI16); 113 | impl_from_trait!(NonZeroI32); 114 | impl_from_trait!(NonZeroI64); 115 | #[cfg(feature = "i128")] 116 | impl_from_trait!(NonZeroI128); 117 | impl_from_trait!(NonZeroIsize); 118 | impl_from_trait!(NonZeroU8); 119 | impl_from_trait!(NonZeroU16); 120 | impl_from_trait!(NonZeroU32); 121 | impl_from_trait!(NonZeroU64); 122 | #[cfg(feature = "i128")] 123 | impl_from_trait!(NonZeroU128); 124 | impl_from_trait!(NonZeroUsize); 125 | impl_from_trait!(f32); 126 | impl_from_trait!(f64); 127 | -------------------------------------------------------------------------------- /crates/serde_valid_literal/src/pattern.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Clone, PartialEq, Eq, PartialOrd)] 2 | pub struct Pattern(String); 3 | 4 | macro_rules! impl_from_trait { 5 | ($type:ty) => { 6 | impl From<$type> for Pattern { 7 | fn from(item: $type) -> Self { 8 | Self(format!("{:?}", item)) 9 | } 10 | } 11 | }; 12 | } 13 | 14 | impl_from_trait!(regex::Regex); 15 | impl_from_trait!(®ex::Regex); 16 | impl_from_trait!(String); 17 | impl_from_trait!(&str); 18 | 19 | impl std::fmt::Display for Pattern { 20 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 21 | write!(f, "{:}", self.0) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | book 2 | -------------------------------------------------------------------------------- /docs/book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["yassun7010"] 3 | language = "en" 4 | multilingual = false 5 | src = "src" 6 | title = "serde_valid" 7 | 8 | [output.html] 9 | no-section-label = true 10 | -------------------------------------------------------------------------------- /docs/src/Attributes/array_max_items.md: -------------------------------------------------------------------------------- 1 | # Array: "max_items" validation 2 | 3 | The `#[validate(max_items = ???)]` attribute is used to determine the maximum number of items in an array. 4 | 5 | ```rust 6 | # extern crate serde_valid; 7 | use serde_valid::Validate; 8 | 9 | #[derive(Validate)] 10 | struct Data( 11 | #[validate(max_items = 2)] 12 | Vec, 13 | ); 14 | 15 | assert!(Data(vec![1, 2]).validate().is_ok()); 16 | assert!(Data(vec![1, 2, 3]).validate().is_err()); 17 | ``` 18 | -------------------------------------------------------------------------------- /docs/src/Attributes/array_min_items.md: -------------------------------------------------------------------------------- 1 | # Array: "min_items" validation 2 | 3 | The `#[validate(min_items = ???)]` attribute is used to determine the minimum number of items in an array. 4 | 5 | ```rust 6 | # extern crate serde_valid; 7 | use serde_valid::Validate; 8 | 9 | #[derive(Validate)] 10 | struct Data( 11 | #[validate(min_items = 3)] 12 | Vec, 13 | ); 14 | 15 | assert!(Data(vec![1, 2]).validate().is_err()); 16 | assert!(Data(vec![1, 2, 3]).validate().is_ok()); 17 | ``` 18 | -------------------------------------------------------------------------------- /docs/src/Attributes/custom_message.md: -------------------------------------------------------------------------------- 1 | # Custom Message 2 | 3 | For user custom message, Serde Valid provides `message_fn` or `message`. 4 | 5 | ```rust 6 | # extern crate serde_json; 7 | # extern crate serde_valid; 8 | use serde_json::json; 9 | use serde_valid::Validate; 10 | 11 | 12 | fn min_error_message(_params: &serde_valid::MinItemsError) -> String { 13 | "this is min custom message_fn.".to_string() 14 | } 15 | 16 | #[derive(Validate)] 17 | struct Data ( 18 | #[validate(min_items = 4, message_fn = min_error_message)] 19 | #[validate(max_items = 2, message = "this is max custom message.")] 20 | Vec, 21 | ); 22 | 23 | assert_eq!( 24 | Data(vec![1, 2, 3]).validate().unwrap_err().to_string(), 25 | json!({ 26 | "errors": [ 27 | "this is min custom message_fn.", 28 | "this is max custom message." 29 | ] 30 | }) 31 | .to_string() 32 | ); 33 | ``` 34 | 35 |
36 | Custom message is not supported in 37 | #[validate(custom(???))] validation. 38 | 39 | Custom validation allows you to create error messages without your own validation functions. 40 |
41 | -------------------------------------------------------------------------------- /docs/src/Attributes/custom_validation.md: -------------------------------------------------------------------------------- 1 | # Custom validation 2 | 3 | The `#[validate(custom(???))]` attribute allows you to define your own validation logic. 4 | 5 | ```rust 6 | # extern crate serde_valid; 7 | use serde_valid::Validate; 8 | 9 | fn user_validation(val: &i32) -> Result<(), serde_valid::validation::Error> { 10 | if *val == 1 { 11 | return Err( 12 | serde_valid::validation::Error::Custom("custom error".to_string()) 13 | ) 14 | } 15 | Ok(()) 16 | } 17 | 18 | #[derive(Validate)] 19 | struct Data ( 20 | #[validate(custom(user_validation))] 21 | i32, 22 | #[validate(custom(|v| user_validation(v)))] // you can also use closures 23 | i32, 24 | ); 25 | 26 | assert!(Data(0, 0).validate().is_ok()); 27 | assert!(Data(1, 1).validate().is_err()); 28 | ``` 29 | -------------------------------------------------------------------------------- /docs/src/Attributes/index.md: -------------------------------------------------------------------------------- 1 | # Attributes 2 | 3 | [Attributes](https://doc.rust-lang.org/book/attributes.html) are used to customize the Validate implementation 4 | produced by serde-valid derive. 5 | -------------------------------------------------------------------------------- /docs/src/Attributes/nested.md: -------------------------------------------------------------------------------- 1 | # Nested validation 2 | 3 | To validate nested structures, add the `#[validate]` attribute to the target field. 4 | 5 | ```rust 6 | # extern crate serde_valid; 7 | use serde_valid::Validate; 8 | 9 | #[derive(Validate)] 10 | struct ParentStruct { 11 | #[validate] // <--- Add #[validate] attribute to the nested type field! 12 | nested: ChildStruct, 13 | } 14 | 15 | #[derive(Validate)] 16 | struct ChildStruct { 17 | #[validate(maximum = 6)] 18 | val: i32, 19 | } 20 | 21 | assert!( 22 | ParentStruct { 23 | nested: ChildStruct{ 24 | val: 5 25 | } 26 | }.validate().is_ok() 27 | ); 28 | ``` 29 | -------------------------------------------------------------------------------- /docs/src/Attributes/numeric_exclusive_maximum.md: -------------------------------------------------------------------------------- 1 | # Numeric: "exclusive_maximum" validation 2 | 3 | The `#[validate(exclusive_maximum = ???)]` attribute is used to validate that a number is greater than a given value. 4 | 5 | ```rust 6 | # extern crate serde_valid; 7 | use serde_valid::Validate; 8 | 9 | #[derive(Validate)] 10 | struct Data ( 11 | #[validate(exclusive_maximum = 6)] 12 | i32 13 | ); 14 | 15 | assert!(Data(5).validate().is_ok()); 16 | assert!(Data(6).validate().is_err()); 17 | assert!(Data(7).validate().is_err()); 18 | ``` 19 | -------------------------------------------------------------------------------- /docs/src/Attributes/numeric_exclusive_minimum.md: -------------------------------------------------------------------------------- 1 | # Numeric: "exclusive_minimum" validation 2 | 3 | The `#[validate(exclusive_minimum = ???)]` attribute is used to validate that a number is greater than a given value. 4 | 5 | ```rust 6 | # extern crate serde_valid; 7 | use serde_valid::Validate; 8 | 9 | #[derive(Validate)] 10 | struct Data ( 11 | #[validate(exclusive_minimum = 2)] 12 | i32 13 | ); 14 | 15 | assert!(Data(1).validate().is_err()); 16 | assert!(Data(2).validate().is_err()); 17 | assert!(Data(3).validate().is_ok()); 18 | ``` 19 | -------------------------------------------------------------------------------- /docs/src/Attributes/numeric_maximum.md: -------------------------------------------------------------------------------- 1 | # Numeric: "maximum" validation 2 | 3 | The `#[validate(maximum = ???)]` attribute is used to ensure that a value is less than or equal to a given value. 4 | 5 | ```rust 6 | # extern crate serde_valid; 7 | use serde_valid::Validate; 8 | 9 | #[derive(Validate)] 10 | struct Data ( 11 | #[validate(maximum = 6)] 12 | i32 13 | ); 14 | 15 | assert!(Data(5).validate().is_ok()); 16 | assert!(Data(6).validate().is_ok()); 17 | assert!(Data(7).validate().is_err()); 18 | ``` 19 | -------------------------------------------------------------------------------- /docs/src/Attributes/numeric_minimum.md: -------------------------------------------------------------------------------- 1 | # Numeric: "minimum" validation 2 | 3 | The `#[validate(minimum = ???)]` attribute is used to validate that a field is greater than or equal to a given value. 4 | 5 | ```rust 6 | # extern crate serde_valid; 7 | use serde_valid::Validate; 8 | 9 | #[derive(Validate)] 10 | struct Data ( 11 | #[validate(minimum = 2)] 12 | i32, 13 | ); 14 | 15 | assert!(Data(1).validate().is_err()); 16 | assert!(Data(2).validate().is_ok()); 17 | ``` 18 | -------------------------------------------------------------------------------- /docs/src/Attributes/numeric_multiple_of.md: -------------------------------------------------------------------------------- 1 | # Numeric: "multiple_of" validation 2 | 3 | The `#[validate(multiple_of = ???)]` attribute is used to validate that a number is a multiple of given number. 4 | 5 | ```rust 6 | # extern crate serde_valid; 7 | use serde_valid::Validate; 8 | 9 | #[derive(Validate)] 10 | struct Data ( 11 | #[validate(multiple_of = 5)] 12 | i32, 13 | ); 14 | 15 | assert!(Data(15).validate().is_ok()); 16 | assert!(Data(14).validate().is_err()); 17 | ``` 18 | -------------------------------------------------------------------------------- /docs/src/Attributes/object_max_properties.md: -------------------------------------------------------------------------------- 1 | # Object: "max_properties" validation 2 | 3 | The `#[validate(max_properties = ???)]` attribute is used to determine the maximum number of properties allowed in a map. 4 | 5 | ```rust 6 | # extern crate serde_valid; 7 | use serde_valid::Validate; 8 | use std::collections::HashMap; 9 | 10 | #[derive(Validate)] 11 | struct Data( 12 | #[validate(max_properties = 2)] 13 | HashMap, 14 | ); 15 | 16 | let mut map = HashMap::new(); 17 | map.insert("key1".to_string(), "value1".to_string()); 18 | map.insert("key2".to_string(), "value2".to_string()); 19 | 20 | assert!(Data(map.clone()).validate().is_ok()); 21 | 22 | map.insert("key3".to_string(), "value3".to_string()); 23 | assert!(Data(map.clone()).validate().is_err()); 24 | ``` 25 | -------------------------------------------------------------------------------- /docs/src/Attributes/object_min_properties.md: -------------------------------------------------------------------------------- 1 | # Object: "min_properties" validation 2 | 3 | The `#[validate(min_properties = ???)]` attribute is used to determine the minimum number of properties allowed in a map. 4 | 5 | ```rust 6 | # extern crate serde_valid; 7 | use serde_valid::Validate; 8 | use std::collections::HashMap; 9 | 10 | #[derive(Validate)] 11 | struct Data( 12 | #[validate(min_properties = 3)] 13 | HashMap, 14 | ); 15 | 16 | let mut map = HashMap::new(); 17 | map.insert("key1".to_string(), "value1".to_string()); 18 | map.insert("key2".to_string(), "value2".to_string()); 19 | 20 | assert!(Data(map.clone()).validate().is_err()); 21 | 22 | map.insert("key3".to_string(), "value3".to_string()); 23 | 24 | assert!(Data(map.clone()).validate().is_ok()); 25 | ``` -------------------------------------------------------------------------------- /docs/src/Attributes/string_max_length.md: -------------------------------------------------------------------------------- 1 | # String: "max_length" validation 2 | 3 | The `#[validate(max_length = ???)]` attribute is used to validate that a `String` is no longer than a given length. 4 | 5 | ```rust 6 | # extern crate serde_valid; 7 | use serde_valid::Validate; 8 | 9 | #[derive(Validate)] 10 | struct Data ( 11 | #[validate(max_length = 4)] 12 | String, 13 | ); 14 | 15 | assert!(Data("test".to_owned()).validate().is_ok()); 16 | assert!(Data("test1".to_owned()).validate().is_err()); 17 | ``` 18 | -------------------------------------------------------------------------------- /docs/src/Attributes/string_min_length.md: -------------------------------------------------------------------------------- 1 | # String: "min_length" validation 2 | 3 | The `#[validate(min_length = ???)]` attribute is used to validate that a `String` is no longer than a given length. 4 | 5 | ```rust 6 | # extern crate serde_valid; 7 | use serde_valid::Validate; 8 | 9 | #[derive(Validate)] 10 | struct Data ( 11 | #[validate(min_length = 4)] 12 | String, 13 | ); 14 | 15 | assert!(Data("tes".to_owned()).validate().is_err()); 16 | assert!(Data("test".to_owned()).validate().is_ok()); 17 | ``` 18 | -------------------------------------------------------------------------------- /docs/src/Attributes/string_pattern.md: -------------------------------------------------------------------------------- 1 | # String: "pattern" validation 2 | 3 | The `#[validate(pattern = ???)]` attribute is used to validate a string against a regular expression. 4 | 5 | ```rust 6 | # extern crate regex; 7 | # extern crate serde_valid; 8 | use serde_valid::Validate; 9 | 10 | #[derive(Validate)] 11 | struct Data ( 12 | #[validate(pattern = r"^\d{4}-\d{2}-\d{2}$")] 13 | String, 14 | ); 15 | 16 | let s = Data("2020-09-10".to_owned()); 17 | assert!(s.validate().is_ok()); 18 | ``` 19 | -------------------------------------------------------------------------------- /docs/src/Features/fluent.md: -------------------------------------------------------------------------------- 1 | # fluent 2 | -------------------------------------------------------------------------------- /docs/src/Features/index.md: -------------------------------------------------------------------------------- 1 | # Features 2 | -------------------------------------------------------------------------------- /docs/src/Features/json.md: -------------------------------------------------------------------------------- 1 | # json 2 | 3 | -------------------------------------------------------------------------------- /docs/src/Features/toml.md: -------------------------------------------------------------------------------- 1 | # toml 2 | -------------------------------------------------------------------------------- /docs/src/Features/yaml.md: -------------------------------------------------------------------------------- 1 | # yaml 2 | -------------------------------------------------------------------------------- /docs/src/Overview.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | [![GitHub](/img/github.svg)](https://github.com/yassun7010/serde_valid) 3 | [![Documentation](/img/rustdoc.svg)](https://docs.rs/serde_valid) 4 | [![Latest Version](https://img.shields.io/crates/v/serde_valid.svg?style=social)](https://crates.io/crates/serde_valid) 5 | 6 | This is [JSON Schema](https://json-schema.org/) based validation tool using [serde](https://github.com/serde-rs/serde). 7 | 8 | ## Usage 9 | 10 | You derive `Validate` trait, and write validations. 11 | 12 | ```rust 13 | # extern crate serde_valid; 14 | use serde_valid::Validate; 15 | 16 | #[derive(Validate)] 17 | struct SampleStruct { 18 | #[validate(minimum = 0)] 19 | #[validate(maximum = 10)] 20 | val: i32, 21 | } 22 | 23 | #[derive(Validate)] 24 | enum SampleEnum { 25 | Named { 26 | #[validate] 27 | a: SampleStruct, 28 | }, 29 | } 30 | 31 | let s = SampleEnum::Named { 32 | a: SampleStruct { val: 5 }, 33 | }; 34 | 35 | assert!(s.validate().is_ok()); 36 | ``` 37 | -------------------------------------------------------------------------------- /docs/src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | - [Overview](./Overview.md) 4 | - [Attributes](./Attributes/index.md) 5 | - [String: "max_length"](./Attributes/string_max_length.md) 6 | - [String: "min_length"](./Attributes/string_min_length.md) 7 | - [String: "pattern"](./Attributes/string_pattern.md) 8 | - [Numeric: "maximum"](./Attributes/numeric_maximum.md) 9 | - [Numeric: "minimum"](./Attributes/numeric_minimum.md) 10 | - [Numeric: "exclusive_maximum"](./Attributes/numeric_exclusive_maximum.md) 11 | - [Numeric: "exclusive_minimum"](./Attributes/numeric_exclusive_minimum.md) 12 | - [Numeric: "multiple_of"](./Attributes/numeric_multiple_of.md) 13 | - [Object: "max_properties"](./Attributes/object_max_properties.md) 14 | - [Object: "min_properties"](./Attributes/object_min_properties.md) 15 | - [Array: "max_items"](./Attributes/array_max_items.md) 16 | - [Array: "min_items"](./Attributes/array_min_items.md) 17 | - [Nested validation](./Attributes/nested.md) 18 | - [Custom validation](./Attributes/custom_validation.md) 19 | - [Custom Message](./Attributes/custom_message.md) 20 | - [Trailt](./trait.md) 21 | - [Features](./Features/index.md) 22 | - [json](./Features/json.md) 23 | - [yaml](./Features/yaml.md) 24 | - [toml](./Features/toml.md) 25 | - [fluent](./Features/fluent.md) 26 | -------------------------------------------------------------------------------- /docs/src/img/github.svg: -------------------------------------------------------------------------------- 1 | GitHubGitHub 4 | -------------------------------------------------------------------------------- /docs/src/img/run.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ya7010/serde_valid/4a9fc3e0e461be5c402ee8e7f3fe8331a58f48c5/docs/src/img/run.png -------------------------------------------------------------------------------- /docs/src/img/runtab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ya7010/serde_valid/4a9fc3e0e461be5c402ee8e7f3fe8331a58f48c5/docs/src/img/runtab.png -------------------------------------------------------------------------------- /docs/src/img/rustdoc.svg: -------------------------------------------------------------------------------- 1 | API documentationAPI documentation 5 | -------------------------------------------------------------------------------- /docs/src/trait.md: -------------------------------------------------------------------------------- 1 | # Trailt 2 | 3 | `serde_valid` consists of traits for all validations, and you can define validations for your own types as well. 4 | 5 | See [this link](https://docs.rs/serde_valid/latest/serde_valid/#validations) for information on the trait needed to define each validation. 6 | 7 | ```rust 8 | # extern crate serde_valid; 9 | use serde_valid::Validate; 10 | 11 | struct MyType(String); 12 | 13 | impl serde_valid::ValidateMaxLength for MyType { 14 | fn validate_max_length(&self, max_length: usize) -> Result<(), serde_valid::MaxLengthError> { 15 | self.0.validate_max_length(max_length) 16 | } 17 | } 18 | 19 | #[derive(Validate)] 20 | struct Data ( 21 | #[validate(max_length = 5)] 22 | MyType, 23 | ); 24 | 25 | assert!(Data(MyType("😍👺🙋🏽👨‍🎤👨‍👩‍👧‍👦".to_string())).validate().is_ok()); 26 | ``` 27 | -------------------------------------------------------------------------------- /scripts/doctest.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | cd "$(dirname "$0")/.." 6 | 7 | cargo build 8 | 9 | cd docs 10 | 11 | mdbook test --library-path ../target/debug/deps 12 | -------------------------------------------------------------------------------- /scripts/generate_readme.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | cd "$(dirname "$0")/../crates/serde_valid" 6 | 7 | cargo readme --no-indent-headings --no-title --no-license >README.md 8 | -------------------------------------------------------------------------------- /scripts/publish.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | cd "$(dirname "$0")"/.. 6 | 7 | cargo xtask update-tags 8 | 9 | cd crates/serde_valid 10 | 11 | cd ../serde_valid_derive 12 | cargo publish 13 | 14 | cd ../serde_valid_literal 15 | cargo publish 16 | 17 | # wait tarball package publishment 18 | sleep 20 19 | 20 | cd ../serde_valid 21 | cargo publish 22 | -------------------------------------------------------------------------------- /scripts/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | cd "$(dirname "$0")/.." 6 | 7 | cargo test --all-features 8 | -------------------------------------------------------------------------------- /serde_valid.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "name": "root", 5 | "path": "." 6 | }, 7 | { 8 | "path": "docs" 9 | }, 10 | { 11 | "path": "xtask", 12 | } 13 | { 14 | "path": "crates/serde_valid_derive" 15 | }, 16 | { 17 | "path": "crates/serde_valid_literal" 18 | }, 19 | { 20 | "path": "crates/serde_valid" 21 | } 22 | ], 23 | "settings": { 24 | "rust-analyzer.cargo.features": "all", 25 | "rust-analyzer.checkOnSave": true, 26 | "rust-analyzer.check.command": "clippy" 27 | }, 28 | "extensions": { 29 | "recommendations": ["tamasfe.even-better-toml"] 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /xtask/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "xtask" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | repository.workspace = true 7 | license.workspace = true 8 | keywords.workspace = true 9 | 10 | [dependencies] 11 | clap = { version = "4.5.23", features = ["derive"] } 12 | toml_edit = "0.22.22" 13 | -------------------------------------------------------------------------------- /xtask/src/commands.rs: -------------------------------------------------------------------------------- 1 | pub mod update_tags; 2 | -------------------------------------------------------------------------------- /xtask/src/commands/update_tags.rs: -------------------------------------------------------------------------------- 1 | use toml_edit::DocumentMut; 2 | 3 | use crate::utils; 4 | 5 | #[derive(clap::Args, Debug)] 6 | pub struct Args {} 7 | 8 | pub fn run(_args: Args) { 9 | let project_root = utils::project_root(); 10 | 11 | let cargo_toml = std::fs::read_to_string(project_root.join("Cargo.toml")).unwrap(); 12 | let mut doc = cargo_toml.parse::().unwrap(); 13 | 14 | let version = doc["workspace"]["package"]["version"] 15 | .clone() 16 | .into_value() 17 | .unwrap(); 18 | doc["workspace"]["dependencies"]["serde_valid_derive"]["version"] = version.clone().into(); 19 | doc["workspace"]["dependencies"]["serde_valid_literal"]["version"] = version.into(); 20 | 21 | std::fs::write(project_root.join("Cargo.toml"), doc.to_string()).unwrap(); 22 | } 23 | -------------------------------------------------------------------------------- /xtask/src/main.rs: -------------------------------------------------------------------------------- 1 | mod commands; 2 | mod utils; 3 | 4 | use clap::Parser; 5 | 6 | #[derive(Debug, clap::Parser)] 7 | enum Args { 8 | UpdateTags(commands::update_tags::Args), 9 | } 10 | 11 | fn main() { 12 | let args = Args::parse(); 13 | match args { 14 | Args::UpdateTags(args) => { 15 | commands::update_tags::run(args); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /xtask/src/utils.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | /// Returns the path to the root directory of `tombi` project. 4 | pub fn project_root() -> PathBuf { 5 | let dir = std::env::var("CARGO_MANIFEST_DIR") 6 | .unwrap_or_else(|_| env!("CARGO_MANIFEST_DIR").to_owned()); 7 | PathBuf::from(dir).parent().unwrap().to_owned() 8 | } 9 | --------------------------------------------------------------------------------