Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/yassun7010/serde_valid

JSON Schema based validation tool using serde.
https://github.com/yassun7010/serde_valid

json-schema rust serde validation

Last synced: 4 days ago
JSON representation

JSON Schema based validation tool using serde.

Awesome Lists containing this project

README

        

# Serde Valid

[![Latest Version](https://img.shields.io/crates/v/serde_valid.svg?color=green&style=flat-square)](https://crates.io/crates/serde_valid)
[![crate docs](https://docs.rs/serde_valid/badge.svg)](https://docs.rs/serde_valid/latest/serde_valid/)
[![GitHub license](https://badgen.net/github/license/Naereen/Strapdown.js?style=flat-square)](https://github.com/Naereen/StrapDown.js/blob/master/LICENSE)

This is [JSON Schema](https://json-schema.org/) based validation tool using [serde](https://github.com/serde-rs/serde).

## Usage

You derive `Validate` trait, and write validations.

```rust
use serde_valid::Validate;

#[derive(Validate)]
struct Data {
#[validate(minimum = 0)]
#[validate(maximum = 10)]
val: i32,
}

#[derive(Validate)]
enum DataEnum {
Named {
#[validate]
a: Data,
},
}

let s = DataEnum::Named {
a: Data { val: 5 },
};

assert!(s.validate().is_ok());
```

## Feature Flags

- `toml` - provide serialization/deserialization in `toml` format.
- `yaml` - provide serialization/deserialization in `yaml` format.
- `i128` - support `i128`/`u128` type (default).
- `fluent` - provide localization using [fluent](https://projectfluent.org/).

## Validations

Serde Valid support standard validation based JSON Schema.

| Type | Serde Valid (validate derive) | Serde Valid (validate trait) | JSON Schema |
| :-----: | :------------------------------------- | :--------------------------- | :-------------------------------------------------------------------------------------------- |
| String | `#[validate(max_length = 5)]` | [`ValidateMaxLength`] | [maxLength](https://json-schema.org/understanding-json-schema/reference/string#length) |
| String | `#[validate(min_length = 5)]` | [`ValidateMinLength`] | [minLength](https://json-schema.org/understanding-json-schema/reference/string#length) |
| String | `#[validate(pattern = r"^\d{5}$")]` | [`ValidatePattern`] | [pattern](https://json-schema.org/understanding-json-schema/reference/string#regexp) |
| Numeric | `#[validate(maximum = 5)]` | [`ValidateMaximum`] | [maximum](https://json-schema.org/understanding-json-schema/reference/numeric#range) |
| Numeric | `#[validate(minimum = 5)]` | [`ValidateMinimum`] | [minimum](https://json-schema.org/understanding-json-schema/reference/numeric#range) |
| Numeric | `#[validate(exclusive_maximum = 5)]` | [`ValidateExclusiveMaximum`] | [exclusiveMaximum](https://json-schema.org/understanding-json-schema/reference/numeric#range) |
| Numeric | `#[validate(exclusive_minimum = 5)]` | [`ValidateExclusiveMinimum`] | [exclusiveMinimum](https://json-schema.org/understanding-json-schema/reference/numeric#range) |
| Numeric | `#[validate(multiple_of = 5)]` | [`ValidateMultipleOf`] | [multipleOf](https://json-schema.org/understanding-json-schema/reference/numeric#multiples) |
| Object | `#[validate(max_properties = 5)]` | [`ValidateMaxProperties`] | [maxProperties](https://json-schema.org/understanding-json-schema/reference/object#size) |
| Object | `#[validate(min_properties = 5)]` | [`ValidateMinProperties`] | [minProperties](https://json-schema.org/understanding-json-schema/reference/object#size) |
| Array | `#[validate(max_items = 5)]` | [`ValidateMaxItems`] | [maxItems](https://json-schema.org/understanding-json-schema/reference/array#length) |
| Array | `#[validate(min_items = 5)]` | [`ValidateMinItems`] | [minItems](https://json-schema.org/understanding-json-schema/reference/array#length) |
| Array | `#[validate(unique_items)]` | [`ValidateUniqueItems`] | [uniqueItems](https://json-schema.org/understanding-json-schema/reference/array#uniqueItems) |
| Generic | `#[validate(enumerate = [5, 10, 15])]` | [`ValidateEnumerate`] | [enum](https://json-schema.org/understanding-json-schema/reference/enum) |

In addition, [serde_valid::utils][module@crate::utils] provides a type of validation not described in the JSON schema specification.

| Type | Serde Valid (validate derive) | Serde Valid (validation function) |
| :------------------------------------------------------------------: | :--------------------------------------------------------- | :----------------------------------------------------------------------- |
| [Duration](https://doc.rust-lang.org/core/time/struct.Duration.html) | `#[validate(custom = duration_maximum(SECOND))]` | [duration_maximum][`crate::utils::duration_maximum`] |
| [Duration](https://doc.rust-lang.org/core/time/struct.Duration.html) | `#[validate(custom = duration_minimum(ZERO))]` | [duration_minimum][`crate::utils::duration_minimum`] |
| [Duration](https://doc.rust-lang.org/core/time/struct.Duration.html) | `#[validate(custom = duration_exclusive_maximum(SECOND))]` | [duration_exclusive_maximum][`crate::utils::duration_exclusive_maximum`] |
| [Duration](https://doc.rust-lang.org/core/time/struct.Duration.html) | `#[validate(custom = duration_exclusive_minimum(ZERO))]` | [duration_exclusive_minimum][`crate::utils::duration_exclusive_minimum`] |

## Complete Constructor (Deserialization)

Serde Valid support complete constructor method using by
[`serde_valid::json::FromJsonValue`](json::FromJsonValue) trait.

```rust
use serde::Deserialize;
use serde_valid::Validate;
use serde_valid::json::{json, FromJsonValue};

#[derive(Debug, Deserialize, Validate)]
struct Data {
#[validate(maximum = 100)]
val: i32,
}

// Deserialization and Validation!! ๐Ÿš€
let err = Data::from_json_value(json!({ "val": 123 })).unwrap_err();

assert_eq!(
err.to_string(),
json!({
"errors": [],
"properties": {
"val": {
"errors": ["The number must be `<= 100`."]
}
}
})
.to_string()
);
```

You can force validation by only deserialization through `serde_valid`, and removing
`serde_json` from `Cargo.toml` of your project.

## Serialization

For serialization, provides [`serde_valid::json::ToJsonString`](json::ToJsonString) trait.

```rust
use serde::Serialize;
use serde_valid::Validate;
use serde_valid::json::{json, ToJsonString};

#[derive(Debug, Serialize, Validate)]
struct Data {
#[validate(maximum = 100)]
val: i32,
}

assert_eq!(
Data{ val: 12i32 }.to_json_string().unwrap(),
json!({ "val": 12i32 }).to_json_string().unwrap()
);
```

## Custom Message

For user custom message, Serde Valid provides `message_fn` or `message`.

```rust
use serde_json::json;
use serde_valid::Validate;

#[inline]
fn min_error_message(_params: &serde_valid::MinItemsError) -> String {
"this is custom message_fn.".to_string()
}

#[derive(Validate)]
struct Data {
#[validate(min_items = 4, message_fn = min_error_message)]
#[validate(max_items = 2, message = "this is custom message.")]
val: Vec,
}

let s = Data { val: vec![1, 2, 3] };

assert_eq!(
s.validate().unwrap_err().to_string(),
json!({
"errors": [],
"properties": {
"val": {
"errors": [
"this is custom message_fn.",
"this is custom message."
]
}
}
})
.to_string()
);
```

### Fluent localization

fluent feature is required.

You can also use [fluent](https://projectfluent.org/) localization by using `fluent` feature.

Allow the following attributes:
- `#[validate(..., fluent("message-id", key1 = value1, ...))]`
- `#[validate(..., message_l10n = fluent("message-id", key1 = value1, ...))]`

```rust
use unic_langid::LanguageIdentifier;
use serde_json::json;
use serde_valid::{fluent::Localize, Validate};

#[derive(Validate)]
struct Data (
#[validate(min_length = 3, fluent("name-min-length", min_length = 3))]
String,
);

assert_eq!(
Data("็”ฐไธญ".to_string()).validate()
.unwrap_err()
.localize(&get_bundle("name-min-length = ๅๅ‰ใฎ้•ทใ•ใฏ { $min_length } ๆ–‡ๅญ—ไปฅไธŠใงใชใ„ใจใ„ใ‘ใพใ›ใ‚“ใ€‚"))
.to_string(),
json!({
"errors": ["ๅๅ‰ใฎ้•ทใ•ใฏ \u{2068}3\u{2069} ๆ–‡ๅญ—ไปฅไธŠใงใชใ„ใจใ„ใ‘ใพใ›ใ‚“ใ€‚"]
})
.to_string()
);
```

## Custom Validation
### Single Error Validation
You can use your custom validation using by `#[validate(custom = ...)]`.

```rust
use serde_valid::Validate;

fn user_validation(_val: &i32) -> Result<(), serde_valid::validation::Error> {
Ok(())
}

#[derive(Validate)]
struct Data {
#[validate(custom = user_validation)]
val: i32,
}

let s = Data { val: 1 };

assert!(s.validate().is_ok());
```

And you can also use closure.

```rust
use serde_valid::Validate;

fn user_validation(_val: &i32, param1: bool) -> Result<(), serde_valid::validation::Error> {
Ok(())
}

#[derive(Validate)]
struct Data {
#[validate(custom = |v| user_validation(v, true))]
val: i32,
}

let s = Data { val: 1 };

assert!(s.validate().is_ok());
```

Custom validation is suitable for handling convenience validations not defined in JSON Schema.
`serde_valid::utils::*` provides convenience functions for specific types.

```rust
use serde_json::json;
use serde_valid::Validate;
use serde_valid::utils::{duration_maximum, duration_minimum};

#[derive(Validate)]
struct Data {
#[validate(custom = duration_maximum(std::time::Duration::from_micros(5)))]
#[validate(custom = duration_minimum(std::time::Duration::from_micros(0)))]
val1: std::time::Duration,
}

let s = Data {
val1: std::time::Duration::from_micros(1),
};

assert!(s.validate().is_ok());
```

### Multi Errors Validation
If you want to return multiple errors in the use custom validation method, you can use `#[validate(custom = ...)]` same as single error.

```rust
use serde_valid::Validate;

// ๐Ÿš€ Just change the return type from `Result<(), Error>` to `Result<(), Vec>` !!
fn user_validation(_val: &i32) -> Result<(), Vec> {
Ok(())
}

#[derive(Validate)]
struct Data {
#[validate(custom = user_validation)]
val: i32,
}

let s = Data { val: 1 };

assert!(s.validate().is_ok());
```

### Multi Fields Validation
Now, you can use `#[validate(custom = ...)]` for multi fields validation.

```rust
use serde_json::json;
use serde_valid::Validate;

fn sample_validation(val1: i32, val2: &str) -> Result<(), serde_valid::validation::Error> {
Ok(())
}

#[derive(Validate)]
#[validate(custom = |s| sample_validation(s.val1, &s.val2))]
struct Data {
val1: i32,
val2: String,
}

let s = Data {
val1: 1,
val2: "val2".to_owned(),
};

assert!(s.validate().is_ok());
```

## Validate Traits

By implementing the validation trait, Your original type can uses Serde Valid validations.

```rust
use serde_valid::Validate;

struct MyType(String);

impl serde_valid::ValidateMaxLength for MyType {
fn validate_max_length(&self, max_length: usize) -> Result<(), serde_valid::MaxLengthError> {
self.0.validate_max_length(max_length)
}
}

#[derive(Validate)]
struct Data {
#[validate(max_length = 5)]
val: MyType,
}

let s = Data {
val: MyType(String::from("๐Ÿ˜๐Ÿ‘บ๐Ÿ™‹๐Ÿฝ๐Ÿ‘จโ€๐ŸŽค๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ")),
};

assert!(s.validate().is_ok());
```

## Validation Errors Format
### Named Struct
Field errors are output to `properties`.

```rust
use serde_json::json;
use serde_valid::Validate;

#[derive(Validate)]
struct Data {
#[validate(maximum = 4)]
val: u32,
}

let s = Data { val: 5 };

assert_eq!(
s.validate().unwrap_err().to_string(),
json!({
"errors": [],
"properties": {
"val": {
"errors": ["The number must be `<= 4`."]
}
}
})
.to_string()
);
```

### Unnamed Struct
Field errors are output to `items`. The key for `items` is guaranteed to be a string of positive
numbers.

```rust
use serde_json::json;
use serde_valid::Validate;

#[derive(Validate)]
struct Data (
#[validate(maximum = 4)] u32,
#[validate(maximum = 3)] u32,
);

let s = Data ( 5, 4 );

assert_eq!(
s.validate().unwrap_err().to_string(),
json!({
"errors": [],
"items": {
"0": {
"errors": ["The number must be `<= 4`."]
},
"1": {
"errors": ["The number must be `<= 3`."]
}
}
})
.to_string()
);
```

### New Type
Field errors are output to `errors`.

```rust
use serde_json::json;
use serde_valid::Validate;

#[derive(Validate)]
struct Data (
#[validate(maximum = 4)] u32
);

let s = Data (5);

assert_eq!(
s.validate().unwrap_err().to_string(),
json!({
"errors": ["The number must be `<= 4`."]
})
.to_string()
);
```

### Named Enum
Variant errors are output to `properties`.

```rust
use serde_json::json;
use serde_valid::Validate;

#[derive(Validate)]
enum Data {
Named {
#[validate(maximum = 5)]
a: i32,
#[validate(maximum = 5)]
b: i32,
},
}

let s = Data::Named { a: 6, b: 6 };

assert_eq!(
s.validate().unwrap_err().to_string(),
json!({
"errors": [],
"properties": {
"a": {
"errors": ["The number must be `<= 5`."]
},
"b": {
"errors": ["The number must be `<= 5`."]
}
}
})
.to_string()
);
```

### Unnamed Enum
Variant errors are output to `items`. The key for `items` is guaranteed to be a string of
positive numbers.

```rust
use serde_json::json;
use serde_valid::Validate;

#[derive(Validate)]
enum Data {
Unnamed (
#[validate(maximum = 5)] i32,
#[validate(maximum = 5)] i32,
),
}

let s = Data::Unnamed ( 6, 6 );

assert_eq!(
s.validate().unwrap_err().to_string(),
json!({
"errors": [],
"items": {
"0": {
"errors": ["The number must be `<= 5`."]
},
"1": {
"errors": ["The number must be `<= 5`."]
}
}
})
.to_string()
);
```

### New Type Enum
Variant errors are output to `errors`.

```rust
use serde_json::json;
use serde_valid::Validate;

#[derive(Validate)]
enum Data {
NewType (
#[validate(maximum = 5)] i32,
),
}

let s = Data::NewType ( 6 );

assert_eq!(
s.validate().unwrap_err().to_string(),
json!({
"errors": ["The number must be `<= 5`."]
})
.to_string()
);
```