@@ -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)
@@ -184,25 +184,28 @@ Serde docs: [container](https://serde.rs/container-attrs.html#bound)
-## Supported Validator Attributes
+## Supported Validator/Garde Attributes
-
+
-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)
-`#[validate(length(min = 1, max = 10))]` / `#[schemars(length(min = 1, max = 10))]`
-`#[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))]`
+`#[validate(length(equal = 10))]` / `#[garde(length(equal = 10))]` / `#[schemars(length(equal = 10))]`
@@ -212,7 +215,7 @@ Validator docs: [length](https://github.com/Keats/validator#length)
-`#[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))]`
@@ -223,29 +226,31 @@ Validator docs: [range](https://github.com/Keats/validator#range)
`#[validate(regex(path = *static_regex)]`
-`#[schemars(regex(pattern = r"^\d+$"))]` / `#[schemars(regex(pattern = *static_regex))]`
+`#[schemars(regex(pattern = r"^\d+$"))]` / `#[schemars(regex(pattern = *static_regex))]`
+`#[garde(pattern(r"^\d+$")]` / `#[schemars(pattern(r"^\d+$")]`/ `#[schemars(pattern(*static_regex)]`
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)
-`#[validate(contains(pattern = "string"))]` / `#[schemars(contains(pattern = "string"))]`
+`#[validate(contains(pattern = "string"))]` / `#[schemars(contains(pattern = "string"))]`
+`#[garde(contains("string"))]` / `#[schemars(contains("string"))]`
-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)
-`#[validate(required)]` / `#[schemars(required)]`
+`#[validate(required)]` / `#[garde(required)]` / `#[schemars(required)]`
@@ -305,7 +310,7 @@ Set the path to the schemars crate instance the generated code should depend on.
-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 {
diff --git a/schemars/src/_private/mod.rs b/schemars/src/_private/mod.rs
index 75966b8..404d015 100644
--- a/schemars/src/_private/mod.rs
+++ b/schemars/src/_private/mod.rs
@@ -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) -> ()) {
diff --git a/schemars/tests/expected/garde.json b/schemars/tests/expected/garde.json
new file mode 100644
index 0000000..1f5d8a6
--- /dev/null
+++ b/schemars/tests/expected/garde.json
@@ -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"
+ ]
+}
\ No newline at end of file
diff --git a/schemars/tests/expected/garde_newtype.json b/schemars/tests/expected/garde_newtype.json
new file mode 100644
index 0000000..cd835f5
--- /dev/null
+++ b/schemars/tests/expected/garde_newtype.json
@@ -0,0 +1,8 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "title": "NewType",
+ "type": "integer",
+ "format": "uint8",
+ "minimum": 0,
+ "maximum": 10
+}
\ No newline at end of file
diff --git a/schemars/tests/expected/garde_schemars_attrs.json b/schemars/tests/expected/garde_schemars_attrs.json
new file mode 100644
index 0000000..f548706
--- /dev/null
+++ b/schemars/tests/expected/garde_schemars_attrs.json
@@ -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"
+ ]
+}
\ No newline at end of file
diff --git a/schemars/tests/expected/garde_tuple.json b/schemars/tests/expected/garde_tuple.json
new file mode 100644
index 0000000..fa81224
--- /dev/null
+++ b/schemars/tests/expected/garde_tuple.json
@@ -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
+}
\ No newline at end of file
diff --git a/schemars/tests/expected/validate.json b/schemars/tests/expected/validate.json
index b2c893a..64b571f 100644
--- a/schemars/tests/expected/validate.json
+++ b/schemars/tests/expected/validate.json
@@ -58,15 +58,6 @@
"minItems": 2,
"maxItems": 2
},
- "map_contains": {
- "type": "object",
- "additionalProperties": {
- "type": "null"
- },
- "required": [
- "map_key"
- ]
- },
"required_option": {
"type": "boolean"
},
@@ -87,7 +78,6 @@
"non_empty_str",
"non_empty_str2",
"pair",
- "map_contains",
"required_option",
"x"
]
diff --git a/schemars/tests/expected/validate_schemars_attrs.json b/schemars/tests/expected/validate_schemars_attrs.json
index e258f18..1f2e3eb 100644
--- a/schemars/tests/expected/validate_schemars_attrs.json
+++ b/schemars/tests/expected/validate_schemars_attrs.json
@@ -58,15 +58,6 @@
"minItems": 2,
"maxItems": 2
},
- "map_contains": {
- "type": "object",
- "additionalProperties": {
- "type": "null"
- },
- "required": [
- "map_key"
- ]
- },
"required_option": {
"type": "boolean"
},
@@ -87,7 +78,6 @@
"non_empty_str",
"non_empty_str2",
"pair",
- "map_contains",
"required_option",
"x"
]
diff --git a/schemars/tests/garde.rs b/schemars/tests/garde.rs
new file mode 100644
index 0000000..8b7ce85
--- /dev/null
+++ b/schemars/tests/garde.rs
@@ -0,0 +1,99 @@
+mod util;
+use schemars::JsonSchema;
+use util::*;
+
+const MIN: u32 = 1;
+const MAX: u32 = 1000;
+
+#[allow(dead_code)]
+#[derive(JsonSchema)]
+pub struct Struct {
+ #[garde(range(min = 0.01, max = 100))]
+ min_max: f32,
+ #[garde(range(min = MIN, max = MAX))]
+ min_max2: f32,
+ #[garde(pattern(r"^[Hh]ello\b"))]
+ regex_str1: String,
+ #[garde(contains(concat!("sub","string...")))]
+ contains_str1: String,
+ #[garde(email)]
+ email_address: String,
+ #[garde(url)]
+ homepage: String,
+ #[garde(length(min = 1, max = 100))]
+ non_empty_str: String,
+ #[garde(length(min = MIN, max = MAX))]
+ non_empty_str2: String,
+ #[garde(length(equal = 2))]
+ pair: Vec,
+ #[garde(required)]
+ required_option: Option,
+ #[garde(required)]
+ #[serde(flatten)]
+ required_flattened: Option,
+}
+
+#[allow(dead_code)]
+#[derive(JsonSchema)]
+pub struct Inner {
+ x: i32,
+}
+
+#[test]
+fn garde() -> TestResult {
+ test_default_generated_schema::("garde")
+}
+
+#[allow(dead_code)]
+#[derive(JsonSchema)]
+pub struct Struct2 {
+ #[schemars(range(min = 0.01, max = 100))]
+ min_max: f32,
+ #[schemars(range(min = MIN, max = MAX))]
+ min_max2: f32,
+ #[schemars(pattern(r"^[Hh]ello\b"))]
+ regex_str1: String,
+ #[schemars(contains(concat!("sub","string...")))]
+ contains_str1: String,
+ #[schemars(email)]
+ email_address: String,
+ #[schemars(url)]
+ homepage: String,
+ #[schemars(length(min = 1, max = 100))]
+ non_empty_str: String,
+ #[schemars(length(min = MIN, max = MAX))]
+ non_empty_str2: String,
+ #[schemars(length(equal = 2))]
+ pair: Vec,
+ #[schemars(required)]
+ required_option: Option,
+ #[schemars(required)]
+ #[serde(flatten)]
+ required_flattened: Option,
+}
+
+#[test]
+fn garde_schemars_attrs() -> TestResult {
+ test_default_generated_schema::("garde_schemars_attrs")
+}
+
+#[allow(dead_code)]
+#[derive(JsonSchema)]
+pub struct Tuple(
+ #[garde(range(max = 10))] u8,
+ #[garde(required)] Option,
+);
+
+#[test]
+fn garde_tuple() -> TestResult {
+ test_default_generated_schema::("garde_tuple")
+}
+
+#[allow(dead_code)]
+#[derive(JsonSchema)]
+pub struct NewType(#[garde(range(max = 10))] u8);
+
+#[test]
+fn garde_newtype() -> TestResult {
+ test_default_generated_schema::("garde_newtype")
+}
diff --git a/schemars/tests/validate.rs b/schemars/tests/validate.rs
index bd4f3a1..410593d 100644
--- a/schemars/tests/validate.rs
+++ b/schemars/tests/validate.rs
@@ -1,6 +1,5 @@
mod util;
use schemars::JsonSchema;
-use std::collections::BTreeMap;
use util::*;
struct FakeRegex(&'static str);
@@ -42,8 +41,6 @@ pub struct Struct {
non_empty_str2: String,
#[validate(length(equal = 2))]
pair: Vec,
- #[validate(contains(pattern = "map_key"))]
- map_contains: BTreeMap,
#[validate(required)]
required_option: Option,
#[validate(required)]
@@ -90,8 +87,6 @@ pub struct Struct2 {
non_empty_str2: String,
#[schemars(length(equal = 2))]
pair: Vec,
- #[schemars(contains(pattern = "map_key"))]
- map_contains: BTreeMap,
#[schemars(required)]
required_option: Option,
#[schemars(required)]
diff --git a/schemars_derive/src/attr/mod.rs b/schemars_derive/src/attr/mod.rs
index 9698731..28bc192 100644
--- a/schemars_derive/src/attr/mod.rs
+++ b/schemars_derive/src/attr/mod.rs
@@ -210,9 +210,10 @@ impl FieldAttrs {
let schemars_cx = &mut AttrCtxt::new(cx, attrs, "schemars");
let serde_cx = &mut AttrCtxt::new(cx, attrs, "serde");
let validate_cx = &mut AttrCtxt::new(cx, attrs, "validate");
+ let garde_cx = &mut AttrCtxt::new(cx, attrs, "garde");
self.common.populate(attrs, schemars_cx, serde_cx);
- self.validation.populate(schemars_cx, validate_cx);
+ self.validation.populate(schemars_cx, validate_cx, garde_cx);
self.process_attr(schemars_cx);
self.process_attr(serde_cx);
}
@@ -277,6 +278,7 @@ impl ContainerAttrs {
None => self.crate_name = parse_name_value_lit_str(meta, cx).ok(),
},
+ // The actual parsing of `rename` is done by serde
"rename" => self.is_renamed = true,
_ => return Some(meta),
diff --git a/schemars_derive/src/attr/parse_meta.rs b/schemars_derive/src/attr/parse_meta.rs
index 9259641..94c89ee 100644
--- a/schemars_derive/src/attr/parse_meta.rs
+++ b/schemars_derive/src/attr/parse_meta.rs
@@ -103,7 +103,7 @@ pub fn parse_extensions(
cx: &AttrCtxt,
) -> Result, ()> {
let parser = Punctuated::::parse_terminated;
- parse_meta_list(meta, cx, parser)
+ parse_meta_list_with(&meta, cx, parser)
}
pub fn parse_length_or_range(outer_meta: Meta, cx: &AttrCtxt) -> Result {
@@ -144,6 +144,10 @@ pub fn parse_length_or_range(outer_meta: Meta, cx: &AttrCtxt) -> Result Result {
+ parse_meta_list_with(&meta, cx, Expr::parse)
+}
+
pub fn parse_schemars_regex(outer_meta: Meta, cx: &AttrCtxt) -> Result {
let mut pattern = None;
@@ -200,9 +204,47 @@ pub fn parse_validate_regex(outer_meta: Meta, cx: &AttrCtxt) -> Result
}
pub fn parse_contains(outer_meta: Meta, cx: &AttrCtxt) -> Result {
+ #[derive(Debug)]
+ enum ContainsFormat {
+ Metas(Punctuated),
+ Expr(Expr),
+ }
+
+ impl Parse for ContainsFormat {
+ fn parse(input: ParseStream) -> syn::Result {
+ // An imperfect but good-enough heuristic for determining whether it looks more like a
+ // comma-separated meta list (validator-style), or a single expression (garde-style).
+ // This heuristic may not generalise well-enough for attributes other than `contains`!
+ // `foo = bar` => Metas (not Expr::Assign)
+ // `foo, bar` => Metas
+ // `foo` => Expr (not Meta::Path)
+ // `foo(bar)` => Expr (not Meta::List)
+ if input.peek2(Token![,]) || input.peek2(Token![=]) {
+ Punctuated::parse_terminated(input).map(Self::Metas)
+ } else {
+ input.parse().map(Self::Expr)
+ }
+ }
+ }
+
+ let nested_meta_or_expr = match cx.attr_type {
+ "validate" => parse_meta_list_with(&outer_meta, cx, Punctuated::parse_terminated)
+ .map(ContainsFormat::Metas),
+ "garde" => parse_meta_list_with(&outer_meta, cx, Expr::parse).map(ContainsFormat::Expr),
+ "schemars" => parse_meta_list_with(&outer_meta, cx, ContainsFormat::parse),
+ wat => {
+ unreachable!("Unexpected attr type `{wat}` for `contains` item. This is a bug in schemars, please raise an issue!")
+ }
+ }?;
+
+ let nested_metas = match nested_meta_or_expr {
+ ContainsFormat::Expr(expr) => return Ok(expr),
+ ContainsFormat::Metas(m) => m,
+ };
+
let mut pattern = None;
- for nested_meta in parse_nested_meta(outer_meta.clone(), cx)? {
+ for nested_meta in nested_metas {
match path_str(nested_meta.path()).as_str() {
"pattern" => match &pattern {
Some(_) => cx.duplicate_error(&nested_meta),
@@ -229,10 +271,10 @@ pub fn parse_contains(outer_meta: Meta, cx: &AttrCtxt) -> Result {
pub fn parse_nested_meta(meta: Meta, cx: &AttrCtxt) -> Result, ()> {
let parser = Punctuated::::parse_terminated;
- parse_meta_list(meta, cx, parser)
+ parse_meta_list_with(&meta, cx, parser)
}
-fn parse_meta_list(meta: Meta, cx: &AttrCtxt, parser: F) -> Result {
+fn parse_meta_list_with(meta: &Meta, cx: &AttrCtxt, parser: F) -> Result {
let Meta::List(meta_list) = meta else {
let name = path_str(meta.path());
cx.error_spanned_by(
diff --git a/schemars_derive/src/attr/validation.rs b/schemars_derive/src/attr/validation.rs
index 41031db..c8bf880 100644
--- a/schemars_derive/src/attr/validation.rs
+++ b/schemars_derive/src/attr/validation.rs
@@ -5,8 +5,8 @@ use crate::idents::SCHEMA;
use super::{
parse_meta::{
- parse_contains, parse_length_or_range, parse_nested_meta, parse_schemars_regex,
- parse_validate_regex, require_path_only, LengthOrRange,
+ parse_contains, parse_length_or_range, parse_nested_meta, parse_pattern,
+ parse_schemars_regex, parse_validate_regex, require_path_only, LengthOrRange,
},
AttrCtxt,
};
@@ -15,6 +15,9 @@ use super::{
pub enum Format {
Email,
Uri,
+ Ip,
+ Ipv4,
+ Ipv6,
}
impl Format {
@@ -22,6 +25,9 @@ impl Format {
match self {
Format::Email => "email",
Format::Uri => "url",
+ Format::Ip => "ip",
+ Format::Ipv4 => "ipv4",
+ Format::Ipv6 => "ipv6",
}
}
@@ -29,6 +35,9 @@ impl Format {
match self {
Format::Email => "email",
Format::Uri => "uri",
+ Format::Ip => "ip",
+ Format::Ipv4 => "ipv4",
+ Format::Ipv6 => "ipv6",
}
}
@@ -36,6 +45,9 @@ impl Format {
Some(match s {
"email" => Format::Email,
"url" => Format::Uri,
+ "ip" => Format::Ip,
+ "ipv4" => Format::Ipv4,
+ "ipv6" => Format::Ipv6,
_ => return None,
})
}
@@ -45,6 +57,7 @@ impl Format {
pub struct ValidationAttrs {
pub length: Option,
pub range: Option,
+ pub pattern: Option,
pub regex: Option,
pub contains: Option,
pub required: bool,
@@ -67,7 +80,7 @@ impl ValidationAttrs {
Self::add_length_or_range(range, mutators, "number", "imum", mut_ref_schema);
}
- if let Some(regex) = &self.regex {
+ if let Some(regex) = self.regex.as_ref().or(self.pattern.as_ref()) {
mutators.push(quote! {
schemars::_private::insert_validation_property(#mut_ref_schema, "string", "pattern", (#regex).to_string());
});
@@ -75,7 +88,7 @@ impl ValidationAttrs {
if let Some(contains) = &self.contains {
mutators.push(quote! {
- schemars::_private::must_contain(#mut_ref_schema, #contains.to_string());
+ schemars::_private::must_contain(#mut_ref_schema, contains.to_string());
});
}
@@ -120,9 +133,15 @@ impl ValidationAttrs {
}
}
- pub(super) fn populate(&mut self, schemars_cx: &mut AttrCtxt, validate_cx: &mut AttrCtxt) {
+ pub(super) fn populate(
+ &mut self,
+ schemars_cx: &mut AttrCtxt,
+ validate_cx: &mut AttrCtxt,
+ garde_cx: &mut AttrCtxt,
+ ) {
self.process_attr(schemars_cx);
self.process_attr(validate_cx);
+ self.process_attr(garde_cx);
}
fn process_attr(&mut self, cx: &mut AttrCtxt) {
@@ -153,22 +172,36 @@ impl ValidationAttrs {
}
}
- "regex" => match (&self.regex, &self.contains, cx.attr_type) {
- (Some(_), _, _) => cx.duplicate_error(&meta),
- (_, Some(_), _) => cx.mutual_exclusive_error(&meta, "contains"),
- (None, None, "schemars") => self.regex = parse_schemars_regex(meta, cx).ok(),
- (None, None, "validate") => self.regex = parse_validate_regex(meta, cx).ok(),
- (None, None, wat) => {
- unreachable!("Unexpected attr type `{wat}` for regex item. This is a bug in schemars, please raise an issue!")
+ "pattern" if cx.attr_type != "validate" => {
+ match (&self.pattern, &self.regex, &self.contains) {
+ (Some(_p), _, _) => cx.duplicate_error(&meta),
+ (_, Some(_r), _) => cx.mutual_exclusive_error(&meta, "regex"),
+ (_, _, Some(_c)) => cx.mutual_exclusive_error(&meta, "contains"),
+ (None, None, None) => self.pattern = parse_pattern(meta, cx).ok(),
}
- },
- "contains" => match (&self.regex, &self.contains) {
- (Some(_), _) => cx.mutual_exclusive_error(&meta, "regex"),
- (_, Some(_)) => cx.duplicate_error(&meta),
- (None, None) => self.contains = parse_contains(meta, cx).ok(),
+ }
+ "regex" if cx.attr_type != "garde" => {
+ match (&self.pattern, &self.regex, &self.contains) {
+ (Some(_p), _, _) => cx.mutual_exclusive_error(&meta, "pattern"),
+ (_, Some(_r), _) => cx.duplicate_error(&meta),
+ (_, _, Some(_c)) => cx.mutual_exclusive_error(&meta, "contains"),
+ (None, None, None) => {
+ if cx.attr_type == "validate" {
+ self.regex = parse_validate_regex(meta, cx).ok()
+ } else {
+ self.regex = parse_schemars_regex(meta, cx).ok()
+ }
+ }
+ }
+ }
+ "contains" => match (&self.pattern, &self.regex, &self.contains) {
+ (Some(_p), _, _) => cx.mutual_exclusive_error(&meta, "pattern"),
+ (_, Some(_r), _) => cx.mutual_exclusive_error(&meta, "regex"),
+ (_, _, Some(_c)) => cx.duplicate_error(&meta),
+ (None, None, None) => self.contains = parse_contains(meta, cx).ok(),
},
- "inner" => {
+ "inner" if cx.attr_type != "validate" => {
if let Ok(nested_meta) = parse_nested_meta(meta, cx) {
let inner = self
.inner
diff --git a/schemars_derive/src/lib.rs b/schemars_derive/src/lib.rs
index 48afe80..578f45c 100644
--- a/schemars_derive/src/lib.rs
+++ b/schemars_derive/src/lib.rs
@@ -18,7 +18,7 @@ use syn::spanned::Spanned;
#[doc = "Derive macro for `JsonSchema` trait."]
#[cfg_attr(not(doctest), doc = include_str!("../deriving.md"), doc = include_str!("../attributes.md"))]
-#[proc_macro_derive(JsonSchema, attributes(schemars, serde, validate))]
+#[proc_macro_derive(JsonSchema, attributes(schemars, serde, validate, garde))]
pub fn derive_json_schema_wrapper(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = parse_macro_input!(input as syn::DeriveInput);
derive_json_schema(input, false)