Skip to content

Commit

Permalink
Add #[schemars(inner(...)] attribute to specify schema for array items (
Browse files Browse the repository at this point in the history
  • Loading branch information
jirutka authored Sep 9, 2023
1 parent 30e513a commit a5e51b2
Show file tree
Hide file tree
Showing 11 changed files with 231 additions and 60 deletions.
14 changes: 14 additions & 0 deletions docs/1.1-attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,20 @@ Set the Rust built-in [`deprecated`](https://doc.rust-lang.org/edition-guide/rus

Set the path to the schemars crate instance the generated code should depend on. This is mostly useful for other crates that depend on schemars in their macros.

<h3 id="inner">

`#[schemars(inner(...))]`
</h3>

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

```rs
struct Struct {
#[schemars(inner(url, regex(pattern = "^https://")))]
urls: Vec<String>,
}
```

<h3 id="doc">

Doc Comments (`#[doc = "..."]`)
Expand Down
2 changes: 2 additions & 0 deletions docs/_includes/examples/schemars_attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ pub struct MyStruct {
pub my_bool: bool,
#[schemars(default)]
pub my_nullable_enum: Option<MyEnum>,
#[schemars(inner(regex(pattern = "^x$")))]
pub my_vec_str: Vec<String>,
}

#[derive(Deserialize, Serialize, JsonSchema)]
Expand Down
10 changes: 9 additions & 1 deletion docs/_includes/examples/schemars_attrs.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
"type": "object",
"required": [
"myBool",
"myNumber"
"myNumber",
"myVecStr"
],
"properties": {
"myBool": {
Expand All @@ -26,6 +27,13 @@
"format": "int32",
"maximum": 10.0,
"minimum": 1.0
},
"myVecStr": {
"type": "array",
"items": {
"type": "string",
"pattern": "^x$"
}
}
},
"additionalProperties": false,
Expand Down
2 changes: 2 additions & 0 deletions schemars/examples/schemars_attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ pub struct MyStruct {
pub my_bool: bool,
#[schemars(default)]
pub my_nullable_enum: Option<MyEnum>,
#[schemars(inner(regex(pattern = "^x$")))]
pub my_vec_str: Vec<String>,
}

#[derive(Deserialize, Serialize, JsonSchema)]
Expand Down
10 changes: 9 additions & 1 deletion schemars/examples/schemars_attrs.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
"type": "object",
"required": [
"myBool",
"myNumber"
"myNumber",
"myVecStr"
],
"properties": {
"myBool": {
Expand All @@ -26,6 +27,13 @@
"format": "int32",
"maximum": 10.0,
"minimum": 1.0
},
"myVecStr": {
"type": "array",
"items": {
"type": "string",
"pattern": "^x$"
}
}
},
"additionalProperties": false,
Expand Down
74 changes: 74 additions & 0 deletions schemars/tests/expected/validate_inner.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Struct",
"type": "object",
"required": [
"array_str_length",
"slice_str_contains",
"vec_i32_range",
"vec_str_length",
"vec_str_length2",
"vec_str_regex",
"vec_str_url"
],
"properties": {
"array_str_length": {
"type": "array",
"items": {
"type": "string",
"maxLength": 100,
"minLength": 5
},
"maxItems": 2,
"minItems": 2
},
"slice_str_contains": {
"type": "array",
"items": {
"type": "string",
"pattern": "substring\\.\\.\\."
}
},
"vec_i32_range": {
"type": "array",
"items": {
"type": "integer",
"format": "int32",
"maximum": 10.0,
"minimum": -10.0
}
},
"vec_str_length": {
"type": "array",
"items": {
"type": "string",
"maxLength": 100,
"minLength": 1
}
},
"vec_str_length2": {
"type": "array",
"items": {
"type": "string",
"maxLength": 100,
"minLength": 1
},
"maxItems": 3,
"minItems": 1
},
"vec_str_regex": {
"type": "array",
"items": {
"type": "string",
"pattern": "^[Hh]ello\\b"
}
},
"vec_str_url": {
"type": "array",
"items": {
"type": "string",
"format": "uri"
}
}
}
}
2 changes: 1 addition & 1 deletion schemars/tests/expected/validate_schemars_attrs.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Struct",
"title": "Struct2",
"type": "object",
"required": [
"contains_str1",
Expand Down
2 changes: 1 addition & 1 deletion schemars/tests/validate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ pub struct Struct2 {

#[test]
fn validate_schemars_attrs() -> TestResult {
test_default_generated_schema::<Struct>("validate_schemars_attrs")
test_default_generated_schema::<Struct2>("validate_schemars_attrs")
}

#[derive(JsonSchema)]
Expand Down
31 changes: 31 additions & 0 deletions schemars/tests/validate_inner.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
mod util;

use schemars::JsonSchema;
use util::*;

// In real code, this would typically be a Regex, potentially created in a `lazy_static!`.
static STARTS_WITH_HELLO: &str = r"^[Hh]ello\b";

#[allow(dead_code)]
#[derive(JsonSchema)]
pub struct Struct<'a> {
#[schemars(inner(length(min = 5, max = 100)))]
array_str_length: [&'a str; 2],
#[schemars(inner(contains(pattern = "substring...")))]
slice_str_contains: &'a[&'a str],
#[schemars(inner(regex = "STARTS_WITH_HELLO"))]
vec_str_regex: Vec<String>,
#[schemars(inner(length(min = 1, max = 100)))]
vec_str_length: Vec<&'a str>,
#[schemars(length(min = 1, max = 3), inner(length(min = 1, max = 100)))]
vec_str_length2: Vec<String>,
#[schemars(inner(url))]
vec_str_url: Vec<String>,
#[schemars(inner(range(min = -10, max = 10)))]
vec_i32_range: Vec<i32>,
}

#[test]
fn validate_inner() -> TestResult {
test_default_generated_schema::<Struct>("validate_inner")
}
43 changes: 20 additions & 23 deletions schemars_derive/src/attr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,11 +103,7 @@ impl Attrs {
}
};

for meta_item in attrs
.iter()
.flat_map(|attr| get_meta_items(attr, attr_type, errors, ignore_errors))
.flatten()
{
for meta_item in get_meta_items(attrs, attr_type, errors, ignore_errors) {
match &meta_item {
Meta(NameValue(m)) if m.path.is_ident("with") => {
if let Ok(ty) = parse_lit_into_ty(errors, attr_type, "with", &m.lit) {
Expand Down Expand Up @@ -167,6 +163,12 @@ impl Attrs {

_ if ignore_errors => {}

Meta(List(m)) if m.path.is_ident("inner") && attr_type == "schemars" => {
// This will be processed with the validation attributes.
// It's allowed only for the schemars attribute because the
// validator crate doesn't support it yet.
}

Meta(meta_item) => {
if !is_known_serde_or_validation_keyword(meta_item) {
let path = meta_item
Expand Down Expand Up @@ -214,30 +216,25 @@ fn is_known_serde_or_validation_keyword(meta: &syn::Meta) -> bool {
}

fn get_meta_items(
attr: &syn::Attribute,
attrs: &[syn::Attribute],
attr_type: &'static str,
errors: &Ctxt,
ignore_errors: bool,
) -> Result<Vec<syn::NestedMeta>, ()> {
if !attr.path.is_ident(attr_type) {
return Ok(Vec::new());
}

match attr.parse_meta() {
Ok(List(meta)) => Ok(meta.nested.into_iter().collect()),
Ok(other) => {
if !ignore_errors {
errors.error_spanned_by(other, format!("expected #[{}(...)]", attr_type))
}
Err(())
) -> Vec<syn::NestedMeta> {
attrs.iter().fold(vec![], |mut acc, attr| {
if !attr.path.is_ident(attr_type) {
return acc;
}
Err(err) => {
if !ignore_errors {
errors.error_spanned_by(attr, err)
match attr.parse_meta() {
Ok(List(meta)) => acc.extend(meta.nested),
Ok(other) if !ignore_errors => {
errors.error_spanned_by(other, format!("expected #[{}(...)]", attr_type))
}
Err(())
Err(err) if !ignore_errors => errors.error_spanned_by(attr, err),
_ => (),
}
}
acc
})
}

fn get_lit_str<'a>(
Expand Down
Loading

0 comments on commit a5e51b2

Please sign in to comment.