Skip to content

Commit

Permalink
Read #[garde(...)] attributes in addition to #[validate(...)] (#331)
Browse files Browse the repository at this point in the history
  • Loading branch information
GREsau authored Aug 29, 2024
1 parent 56cdd45 commit 9770301
Show file tree
Hide file tree
Showing 16 changed files with 421 additions and 87 deletions.
20 changes: 20 additions & 0 deletions docs/0-migrating.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,3 +173,23 @@ fn my_transform2(schema: &mut Schema) {
let mut schema = schemars::schema_for!(str);
RecursiveTransform(my_transform2).transform(&mut schema);
```

## Changes to `#[validate(...)]` attributes

Since [adding support for `#[validate(...)]` attributes](https://graham.cool/schemars/v0/deriving/attributes/#supported-validator-attributes), the [Validator](https://github.com/Keats/validator) crate has made several changes to its supported attributes. Accordingly, Schemars 1.0 has updated its handling of `#[validate(...)]` attributes to match the latest version (currently 0.18.1) of the Validator crate - this removes some attributes, and changes the syntax of others:

- The `#[validate(phone)]`/`#[schemars(phone)]` attribute is removed. If you want the old behaviour of setting the "format" property on the generated schema, you can use `#[schemars(extend("format = "phone"))]` instead.
- The `#[validate(required_nested)]`/`#[schemars(required_nested)]` attribute is removed. If you want the old behaviour, you can use `#[schemars(required)]` instead.
- The `#[validate(regex = "...")]`/`#[schemars(regex = "...")]` attribute can no longer use `name = "value"` syntax. Instead, you can use:

- `#[validate(regex(path = ...)]`
- `#[schemars(regex(pattern = ...)]`
- `#[schemars(pattern(...)]` (Garde-style)

- Similarly, the `#[validate(contains = "...")]`/`#[schemars(contains = "...")]` attribute can no longer use `name = "value"` syntax. Instead, you can use:

- `#[validate(contains(pattern = ...))]`
- `#[schemars(contains(pattern = ...))]`
- `#[schemars(contains(...))]` (Garde-style)

As an alternative option, Schemars 1.0 also adds support for `#[garde(...)]` attributes used with the [Garde](https://github.com/jprochazk/garde) crate, along with equivalent `#[schemars(...)]` attributes. See [the documentation](https://graham.cool/schemars/deriving/attributes/#supported-validatorgarde-attributes) for a list of all supported attributes.
1 change: 1 addition & 0 deletions docs/1.1-attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ permalink: /deriving/attributes/
<style>
h3 code {
font-weight: bold;
text-wrap: nowrap;
}
</style>

Expand Down
41 changes: 23 additions & 18 deletions docs/_includes/attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ You can add attributes to your types to customize Schemars's derived `JsonSchema

[Serde](https://serde.rs/) allows setting `#[serde(...)]` attributes which change how types are serialized, and Schemars will generally respect these attributes to ensure that generated schemas will match how the type is serialized by serde_json. `#[serde(...)]` attributes can be overriden using `#[schemars(...)]` attributes, which behave identically (e.g. `#[schemars(rename_all = "camelCase")]`). You may find this useful if you want to change the generated schema without affecting Serde's behaviour, or if you're just not using Serde.

[Validator](https://github.com/Keats/validator) allows setting `#[validate(...)]` attributes to restrict valid values of particular fields, many of which will be used by Schemars to generate more accurate schemas. These can also be overridden by `#[schemars(...)]` attributes.
[Validator](https://github.com/Keats/validator) and [Garde](https://github.com/jprochazk/garde) allow setting `#[validate(...)]`/`#[garde(...)]` attributes to restrict valid values of particular fields, many of which will be used by Schemars to generate more accurate schemas. These can also be overridden by `#[schemars(...)]` attributes.

<details open>
<summary style="font-weight: bold">
Expand All @@ -23,11 +23,11 @@ TABLE OF CONTENTS
- [`flatten`](#flatten)
- [`with`](#with)
- [`bound`](#bound)
1. [Supported Validator Attributes](#supported-validator-attributes)
- [`email` / `url`](#email-url)
1. [Supported Validator/Garde Attributes](#supported-validatorgarde-attributes)
- [`email` / `url` / `ip` / `ipv4` / `ipv6`](#formats)
- [`length`](#length)
- [`range`](#range)
- [`regex`](#regex)
- [`regex` / `pattern`](#regex)
- [`contains`](#contains)
- [`required`](#required)
1. [Other Attributes](#other-attributes)
Expand Down Expand Up @@ -184,25 +184,28 @@ Serde docs: [container](https://serde.rs/container-attrs.html#bound)

</div>

## Supported Validator Attributes
## Supported Validator/Garde Attributes

<div class="indented">

<h3 id="email-url">
<h3 id="formats">

`#[validate(email)]` / `#[schemars(email)]`<br />
`#[validate(url)]` / `#[schemars(url)]`
`#[validate(email)]` / `#[garde(email)]` / `#[schemars(email)]`<br />
`#[validate(url)]` / `#[garde(url)]`/ `#[schemars(url)]`<br />
`#[garde(ip)]`/ `#[schemars(ip)]`<br />
`#[garde(ipv4)]`/ `#[schemars(ipv4)]`<br />
`#[garde(ipv6)]`/ `#[schemars(ip)v6]`<br />

</h3>

Sets the schema's `format` to `email`/`uri`, as appropriate. Only one of these attributes may be present on a single field.
Sets the schema's `format` to `email`/`uri`/`ip`/`ipv4`/`ipv6`, as appropriate. Only one of these attributes may be present on a single field.

Validator docs: [email](https://github.com/Keats/validator#email) / [url](https://github.com/Keats/validator#url)

<h3 id="length">

`#[validate(length(min = 1, max = 10))]` / `#[schemars(length(min = 1, max = 10))]`<br />
`#[validate(length(equal = 10))]` / `#[schemars(length(equal = 10))]`
`#[validate(length(min = 1, max = 10))]` / `#[garde(length(min = 1, max = 10))]` / `#[schemars(length(min = 1, max = 10))]`<br />
`#[validate(length(equal = 10))]` / `#[garde(length(equal = 10))]` / `#[schemars(length(equal = 10))]`

</h3>

Expand All @@ -212,7 +215,7 @@ Validator docs: [length](https://github.com/Keats/validator#length)

<h3 id="range">

`#[validate(range(min = 1, max = 10))]` / `#[schemars(range(min = 1, max = 10))]`
`#[validate(range(min = 1, max = 10))]` / `#[garde(range(min = 1, max = 10))]` / `#[schemars(range(min = 1, max = 10))]`

</h3>

Expand All @@ -223,29 +226,31 @@ Validator docs: [range](https://github.com/Keats/validator#range)
<h3 id="regex">

`#[validate(regex(path = *static_regex)]`<br />
`#[schemars(regex(pattern = r"^\d+$"))]` / `#[schemars(regex(pattern = *static_regex))]`
`#[schemars(regex(pattern = r"^\d+$"))]` / `#[schemars(regex(pattern = *static_regex))]`<br />
`#[garde(pattern(r"^\d+$")]` / `#[schemars(pattern(r"^\d+$")]`/ `#[schemars(pattern(*static_regex)]`

</h3>

Sets the `pattern` property for string schemas. The `static_regex` will typically refer to a [`Regex`](https://docs.rs/regex/*/regex/struct.Regex.html) instance, but Schemars allows it to be any value with a `to_string()` method.

`regex(pattern = ...)` is a Schemars extension, and not currently supported by the Validator crate. When using this form, you may want to use a `r"raw string literal"` so that `\\` characters in the regex pattern are not interpreted as escape sequences in the string. Using the `path` form is not allowed in a `#[schemars(...)]` attribute.
`regex(pattern = ...)` is a Schemars extension, and not currently supported by the Validator crate. When using this form (or the Garde-style `pattern` attribute), you may want to use a `r"raw string literal"` so that `\\` characters in the regex pattern are not interpreted as escape sequences in the string. Using the `path` form is not allowed in a `#[schemars(...)]` attribute.

Validator docs: [regex](https://github.com/Keats/validator#regex)

<h3 id="contains">

`#[validate(contains(pattern = "string"))]` / `#[schemars(contains(pattern = "string"))]`
`#[validate(contains(pattern = "string"))]` / `#[schemars(contains(pattern = "string"))]`<br />
`#[garde(contains("string"))]` / `#[schemars(contains("string"))]`

</h3>

For string schemas, sets the `pattern` property to the given value, with any regex special characters escaped. For object schemas (e.g. when the attribute is set on a HashMap field), includes the value in the `required` property, indicating that the map must contain it as a key.
For string schemas, sets the `pattern` property to the given value, with any regex special characters escaped.

Validator docs: [contains](https://github.com/Keats/validator#contains)

<h3 id="required">

`#[validate(required)]` / `#[schemars(required)]`<br />
`#[validate(required)]` / `#[garde(required)]` / `#[schemars(required)]`<br />

</h3>

Expand Down Expand Up @@ -305,7 +310,7 @@ Set the path to the schemars crate instance the generated code should depend on.

</h3>

Sets properties specified by [validator attributes](#supported-validator-attributes) on items of an array schema. For example:
Sets properties specified by [validator attributes](#supported-validatorgarde-attributes) on items of an array schema. For example:

```rust
struct Struct {
Expand Down
23 changes: 3 additions & 20 deletions schemars/src/_private/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,26 +199,9 @@ pub fn insert_validation_property(
}
}

pub fn must_contain(schema: &mut Schema, contain: String) {
if schema.has_type("string") {
let pattern = regex_syntax::escape(&contain);
schema
.ensure_object()
.insert("pattern".to_owned(), pattern.into());
}

if schema.has_type("object") {
if let Value::Array(array) = schema
.ensure_object()
.entry("required")
.or_insert(Value::Array(Vec::new()))
{
let value = Value::from(contain);
if !array.contains(&value) {
array.push(value);
}
}
}
pub fn must_contain(schema: &mut Schema, substring: &str) {
let escaped = regex_syntax::escape(substring);
insert_validation_property(schema, "string", "pattern", escaped);
}

pub fn apply_inner_validation(schema: &mut Schema, f: fn(&mut Schema) -> ()) {
Expand Down
74 changes: 74 additions & 0 deletions schemars/tests/expected/garde.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Struct",
"type": "object",
"properties": {
"min_max": {
"type": "number",
"format": "float",
"minimum": 0.01,
"maximum": 100
},
"min_max2": {
"type": "number",
"format": "float",
"minimum": 1,
"maximum": 1000
},
"regex_str1": {
"type": "string",
"pattern": "^[Hh]ello\\b"
},
"contains_str1": {
"type": "string",
"pattern": "substring\\.\\.\\."
},
"email_address": {
"type": "string",
"format": "email"
},
"homepage": {
"type": "string",
"format": "uri"
},
"non_empty_str": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"non_empty_str2": {
"type": "string",
"minLength": 1,
"maxLength": 1000
},
"pair": {
"type": "array",
"items": {
"type": "integer",
"format": "int32"
},
"minItems": 2,
"maxItems": 2
},
"required_option": {
"type": "boolean"
},
"x": {
"type": "integer",
"format": "int32"
}
},
"required": [
"min_max",
"min_max2",
"regex_str1",
"contains_str1",
"email_address",
"homepage",
"non_empty_str",
"non_empty_str2",
"pair",
"required_option",
"x"
]
}
8 changes: 8 additions & 0 deletions schemars/tests/expected/garde_newtype.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "NewType",
"type": "integer",
"format": "uint8",
"minimum": 0,
"maximum": 10
}
74 changes: 74 additions & 0 deletions schemars/tests/expected/garde_schemars_attrs.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Struct2",
"type": "object",
"properties": {
"min_max": {
"type": "number",
"format": "float",
"minimum": 0.01,
"maximum": 100
},
"min_max2": {
"type": "number",
"format": "float",
"minimum": 1,
"maximum": 1000
},
"regex_str1": {
"type": "string",
"pattern": "^[Hh]ello\\b"
},
"contains_str1": {
"type": "string",
"pattern": "substring\\.\\.\\."
},
"email_address": {
"type": "string",
"format": "email"
},
"homepage": {
"type": "string",
"format": "uri"
},
"non_empty_str": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"non_empty_str2": {
"type": "string",
"minLength": 1,
"maxLength": 1000
},
"pair": {
"type": "array",
"items": {
"type": "integer",
"format": "int32"
},
"minItems": 2,
"maxItems": 2
},
"required_option": {
"type": "boolean"
},
"x": {
"type": "integer",
"format": "int32"
}
},
"required": [
"min_max",
"min_max2",
"regex_str1",
"contains_str1",
"email_address",
"homepage",
"non_empty_str",
"non_empty_str2",
"pair",
"required_option",
"x"
]
}
18 changes: 18 additions & 0 deletions schemars/tests/expected/garde_tuple.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Tuple",
"type": "array",
"prefixItems": [
{
"type": "integer",
"format": "uint8",
"minimum": 0,
"maximum": 10
},
{
"type": "boolean"
}
],
"minItems": 2,
"maxItems": 2
}
10 changes: 0 additions & 10 deletions schemars/tests/expected/validate.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,6 @@
"minItems": 2,
"maxItems": 2
},
"map_contains": {
"type": "object",
"additionalProperties": {
"type": "null"
},
"required": [
"map_key"
]
},
"required_option": {
"type": "boolean"
},
Expand All @@ -87,7 +78,6 @@
"non_empty_str",
"non_empty_str2",
"pair",
"map_contains",
"required_option",
"x"
]
Expand Down
Loading

0 comments on commit 9770301

Please sign in to comment.