Skip to content

Commit

Permalink
Disallow #[schemars(regex(path = ...))] and `#[validate(regex(patte…
Browse files Browse the repository at this point in the history
…rn = ...))]`

For compatibility with the validator crate, when the `pattern` form is given a string literal, that string's contents will be parsed as an expression. This is due to the behaviour of the darling crate (TedDriggs/darling#229). I'm concerned that this behaviour may change in a future version of darling/validator, so to minimise its exposure in schemars's public API, I'm restricting its usage now.
  • Loading branch information
GREsau committed Aug 27, 2024
1 parent 9942500 commit abe7e29
Show file tree
Hide file tree
Showing 9 changed files with 87 additions and 53 deletions.
6 changes: 3 additions & 3 deletions docs/_includes/attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,14 +211,14 @@ Validator docs: [range](https://github.com/Keats/validator#range)

<h3 id="regex">

`#[validate(regex(path = *static_regex)]` / `#[schemars(regex(path = *static_regex)]`<br />
`#[schemars(regex(pattern = r"^\d+$"))]`
`#[validate(regex(path = *static_regex)]`<br />
`#[schemars(regex(pattern = r"^\d+$"))]` / `#[schemars(regex(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.

Providing an inline regex pattern using `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.
`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.

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

Expand Down
5 changes: 0 additions & 5 deletions schemars/tests/expected/validate.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,6 @@
"type": "string",
"pattern": "^[Hh]ello\\b"
},
"regex_str3": {
"type": "string",
"pattern": "^\\d+$"
},
"contains_str1": {
"type": "string",
"pattern": "substring\\.\\.\\."
Expand Down Expand Up @@ -84,7 +80,6 @@
"min_max2",
"regex_str1",
"regex_str2",
"regex_str3",
"contains_str1",
"contains_str2",
"email_address",
Expand Down
5 changes: 0 additions & 5 deletions schemars/tests/expected/validate_schemars_attrs.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,6 @@
"pattern": "^[Hh]ello\\b"
},
"regex_str2": {
"type": "string",
"pattern": "^[Hh]ello\\b"
},
"regex_str3": {
"type": "string",
"pattern": "^\\d+$"
},
Expand Down Expand Up @@ -84,7 +80,6 @@
"min_max2",
"regex_str1",
"regex_str2",
"regex_str3",
"contains_str1",
"contains_str2",
"email_address",
Expand Down
2 changes: 2 additions & 0 deletions schemars/tests/ui/invalid_validation_attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub struct Struct3(
#[validate(
regex = "foo",
contains = "bar",
regex(pattern = "baz"),
regex(path = "baz"),
phone,
email,
Expand All @@ -27,6 +28,7 @@ pub struct Struct4(
regex = "foo",
contains = "bar",
regex(path = "baz"),
regex(pattern = "baz"),
phone,
email,
url
Expand Down
40 changes: 32 additions & 8 deletions schemars/tests/ui/invalid_validation_attrs.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -46,26 +46,50 @@ error: expected validate contains attribute item to be of the form `contains(...
15 | contains = "bar",
| ^^^^^^^^^^^^^^^^

error: `pattern` is not supported in `validate(regex(...))` attribute - use either `validate(regex(path = ...))` or `schemars(regex(pattern = ...))` instead
--> tests/ui/invalid_validation_attrs.rs:16:15
|
16 | regex(pattern = "baz"),
| ^^^^^^^^^^^^^^^

error: `validate(regex(...))` attribute requires `path = ...`
--> tests/ui/invalid_validation_attrs.rs:16:9
|
16 | regex(pattern = "baz"),
| ^^^^^^^^^^^^^^^^^^^^^^

error: expected schemars regex attribute item to be of the form `regex(...)`
--> tests/ui/invalid_validation_attrs.rs:27:9
--> tests/ui/invalid_validation_attrs.rs:28:9
|
27 | regex = "foo",
28 | regex = "foo",
| ^^^^^^^^^^^^^

error: expected schemars contains attribute item to be of the form `contains(...)`
--> tests/ui/invalid_validation_attrs.rs:28:9
--> tests/ui/invalid_validation_attrs.rs:29:9
|
28 | contains = "bar",
29 | contains = "bar",
| ^^^^^^^^^^^^^^^^

error: `path` is not supported in `schemars(regex(...))` attribute - use `schemars(regex(pattern = ...))` instead
--> tests/ui/invalid_validation_attrs.rs:30:15
|
30 | regex(path = "baz"),
| ^^^^^^^^^^^^

error: `schemars(regex(...))` attribute requires `pattern = ...`
--> tests/ui/invalid_validation_attrs.rs:30:9
|
30 | regex(path = "baz"),
| ^^^^^^^^^^^^^^^^^^^

error: schemars attribute cannot contain both `url` and `email`
--> tests/ui/invalid_validation_attrs.rs:32:9
--> tests/ui/invalid_validation_attrs.rs:34:9
|
32 | url
34 | url
| ^^^

error: unknown schemars attribute `phone`
--> tests/ui/invalid_validation_attrs.rs:30:9
--> tests/ui/invalid_validation_attrs.rs:32:9
|
30 | phone,
32 | phone,
| ^^^^^
8 changes: 2 additions & 6 deletions schemars/tests/validate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ pub struct Struct {
regex_str1: String,
#[validate(regex(path = "STARTS_WITH_HELLO", code = "foo"))]
regex_str2: String,
#[validate(regex(pattern = r"^\d+$"))]
regex_str3: String,
#[validate(contains(pattern = "substring..."))]
contains_str1: String,
#[validate(contains(pattern = "substring...", message = "bar"))]
Expand Down Expand Up @@ -65,12 +63,10 @@ pub struct Struct2 {
#[schemars(range(min = "MIN", max = "MAX"))]
min_max2: f32,
#[validate(regex(path = overridden))]
#[schemars(regex(path = &*STARTS_WITH_HELLO))]
#[schemars(regex(pattern = &*STARTS_WITH_HELLO))]
regex_str1: String,
#[schemars(regex(path = "STARTS_WITH_HELLO"))]
regex_str2: String,
#[schemars(regex(pattern = r"^\d+$"))]
regex_str3: String,
regex_str2: String,
#[validate(contains(pattern = "overridden"))]
#[schemars(contains(pattern = "substring..."))]
contains_str1: String,
Expand Down
2 changes: 1 addition & 1 deletion schemars/tests/validate_inner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ pub struct Struct<'a> {
array_str_length: [&'a str; 2],
#[schemars(inner(contains(pattern = "substring...")))]
slice_str_contains: &'a [&'a str],
#[schemars(inner(regex(path = "STARTS_WITH_HELLO")))]
#[schemars(inner(regex(pattern = STARTS_WITH_HELLO)))]
vec_str_regex: Vec<String>,
#[schemars(inner(length(min = 1, max = 100)))]
vec_str_length: Vec<&'a str>,
Expand Down
56 changes: 38 additions & 18 deletions schemars_derive/src/attr/parse_meta.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,37 +127,57 @@ pub fn parse_length_or_range(outer_meta: Meta, cx: &AttrCtxt) -> Result<LengthOr
Ok(result)
}

pub fn parse_regex(outer_meta: Meta, cx: &AttrCtxt) -> Result<Expr, ()> {
let mut path = None;
pub fn parse_schemars_regex(outer_meta: Meta, cx: &AttrCtxt) -> Result<Expr, ()> {
let mut pattern = None;

for nested_meta in parse_nested_meta(outer_meta.clone(), cx)? {
match path_str(nested_meta.path()).as_str() {
"path" => match (&path, &pattern) {
(Some(_), _) => cx.duplicate_error(&nested_meta),
(_, Some(_)) => cx.mutual_exclusive_error(&nested_meta, "pattern"),
_ => path = parse_name_value_expr_handle_lit_str(nested_meta, cx).ok(),
"pattern" => match &pattern {
Some(_) => cx.duplicate_error(&nested_meta),
None => pattern = parse_name_value_expr(nested_meta, cx).ok(),
},
"pattern" => match (&path, &pattern) {
(Some(_), _) => cx.mutual_exclusive_error(&nested_meta, "path"),
(_, Some(_)) => cx.duplicate_error(&nested_meta),
_ => pattern = parse_name_value_expr(nested_meta, cx).ok(),
"path" => {
cx.error_spanned_by(nested_meta, "`path` is not supported in `schemars(regex(...))` attribute - use `schemars(regex(pattern = ...))` instead")
},
unknown => {
if cx.attr_type == "schemars" {
cx.error_spanned_by(
nested_meta,
format_args!("unknown item in schemars `regex` attribute: `{unknown}`"),
);
}
cx.error_spanned_by(
nested_meta,
format_args!("unknown item in schemars `regex` attribute: `{unknown}`"),
);
}
}
}

pattern.ok_or_else(|| {
cx.error_spanned_by(
outer_meta,
"`schemars(regex(...))` attribute requires `pattern = ...`",
)
})
}

pub fn parse_validate_regex(outer_meta: Meta, cx: &AttrCtxt) -> Result<Expr, ()> {
let mut path = None;

for nested_meta in parse_nested_meta(outer_meta.clone(), cx)? {
match path_str(nested_meta.path()).as_str() {
"path" => match &path{
Some(_) => cx.duplicate_error(&nested_meta),
None => path = parse_name_value_expr_handle_lit_str(nested_meta, cx).ok(),
},
"pattern" => {
cx.error_spanned_by(nested_meta, "`pattern` is not supported in `validate(regex(...))` attribute - use either `validate(regex(path = ...))` or `schemars(regex(pattern = ...))` instead")
},
_ => {
// ignore unknown properties in `validate` attribute
}
}
}

path.or(pattern).ok_or_else(|| {
path.ok_or_else(|| {
cx.error_spanned_by(
outer_meta,
"`regex` attribute item requires `pattern = ...` or `path = ...`",
"`validate(regex(...))` attribute requires `path = ...`",
)
})
}
Expand Down
16 changes: 9 additions & 7 deletions schemars_derive/src/attr/validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ use crate::idents::SCHEMA;

use super::{
parse_meta::{
parse_contains, parse_length_or_range, parse_nested_meta, parse_regex, require_path_only,
LengthOrRange,
parse_contains, parse_length_or_range, parse_nested_meta, parse_schemars_regex,
parse_validate_regex, require_path_only, LengthOrRange,
},
AttrCtxt,
};
Expand Down Expand Up @@ -36,7 +36,7 @@ impl Format {
match s {
"email" => Format::Email,
"url" => Format::Uri,
_ => panic!("Invalid format attr string `{}`. This is a bug in schemars, please raise an issue!", s),
_ => panic!("Invalid format attr string `{s}`. This is a bug in schemars, please raise an issue!"),
}
}
}
Expand Down Expand Up @@ -151,10 +151,12 @@ impl ValidationAttrs {
}
}

"regex" => match (&self.regex, &self.contains) {
(Some(_), _) => cx.duplicate_error(&meta),
(_, Some(_)) => cx.mutual_exclusive_error(&meta, "contains"),
(None, None) => self.regex = parse_regex(meta, cx).ok(),
"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) => panic!("Unexpected attr type `{wat}` for regex item. This is a bug in schemars, please raise an issue!"),
},
"contains" => match (&self.regex, &self.contains) {
(Some(_), _) => cx.mutual_exclusive_error(&meta, "regex"),
Expand Down

0 comments on commit abe7e29

Please sign in to comment.