Read #[validate(...)] attributes
This commit is contained in:
parent
dada8582ee
commit
6ab567f3a5
13 changed files with 532 additions and 33 deletions
|
@ -62,14 +62,18 @@ impl<T: JsonSchema> JsonSchema for Option<T> {
|
|||
parent: &mut SchemaObject,
|
||||
name: String,
|
||||
metadata: Option<Metadata>,
|
||||
_required: bool,
|
||||
required: Option<bool>,
|
||||
) {
|
||||
if required == Some(true) {
|
||||
T::add_schema_as_property(gen, parent, name, metadata, required)
|
||||
} else {
|
||||
let mut schema = gen.subschema_for::<Self>();
|
||||
schema = gen.apply_metadata(schema, metadata);
|
||||
|
||||
let object = parent.object();
|
||||
object.properties.insert(name, schema);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn add_null_type(instance_type: &mut SingleOrVec<InstanceType>) {
|
||||
|
|
|
@ -30,7 +30,7 @@ macro_rules! forward_impl {
|
|||
parent: &mut crate::schema::SchemaObject,
|
||||
name: String,
|
||||
metadata: Option<crate::schema::Metadata>,
|
||||
required: bool,
|
||||
required: Option<bool>,
|
||||
) {
|
||||
<$target>::add_schema_as_property(gen, parent, name, metadata, required)
|
||||
}
|
||||
|
|
|
@ -375,13 +375,13 @@ pub trait JsonSchema {
|
|||
parent: &mut SchemaObject,
|
||||
name: String,
|
||||
metadata: Option<schema::Metadata>,
|
||||
required: bool,
|
||||
required: Option<bool>,
|
||||
) {
|
||||
let mut schema = gen.subschema_for::<Self>();
|
||||
schema = gen.apply_metadata(schema, metadata);
|
||||
|
||||
let object = parent.object();
|
||||
if required {
|
||||
if required.unwrap_or(true) {
|
||||
object.required.insert(name.clone());
|
||||
}
|
||||
object.properties.insert(name, schema);
|
||||
|
|
|
@ -9,6 +9,7 @@ use crate::JsonSchema;
|
|||
use crate::{Map, Set};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use std::ops::Deref;
|
||||
|
||||
/// A JSON Schema.
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
|
@ -191,7 +192,13 @@ where
|
|||
macro_rules! get_or_insert_default_fn {
|
||||
($name:ident, $ret:ty) => {
|
||||
get_or_insert_default_fn!(
|
||||
concat!("Returns a mutable reference to this schema's [`", stringify!($ret), "`](#structfield.", stringify!($name), "), creating it if it was `None`."),
|
||||
concat!(
|
||||
"Returns a mutable reference to this schema's [`",
|
||||
stringify!($ret),
|
||||
"`](#structfield.",
|
||||
stringify!($name),
|
||||
"), creating it if it was `None`."
|
||||
),
|
||||
$name,
|
||||
$ret
|
||||
);
|
||||
|
@ -224,6 +231,13 @@ impl SchemaObject {
|
|||
self.reference.is_some()
|
||||
}
|
||||
|
||||
// TODO document
|
||||
pub fn has_type(&self, ty: InstanceType) -> bool {
|
||||
self.instance_type
|
||||
.as_ref()
|
||||
.map_or(true, |x| x.contains(&ty))
|
||||
}
|
||||
|
||||
get_or_insert_default_fn!(metadata, Metadata);
|
||||
get_or_insert_default_fn!(subschemas, SubschemaValidation);
|
||||
get_or_insert_default_fn!(number, NumberValidation);
|
||||
|
@ -506,3 +520,13 @@ impl<T> From<Vec<T>> for SingleOrVec<T> {
|
|||
SingleOrVec::Vec(vec)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: PartialEq> SingleOrVec<T> {
|
||||
// TODO document
|
||||
pub fn contains(&self, x: &T) -> bool {
|
||||
match self {
|
||||
SingleOrVec::Single(s) => s.deref() == x,
|
||||
SingleOrVec::Vec(v) => v.contains(x),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
81
schemars/tests/expected/validate.json
Normal file
81
schemars/tests/expected/validate.json
Normal file
|
@ -0,0 +1,81 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Struct",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"contains_str1",
|
||||
"contains_str2",
|
||||
"email_address",
|
||||
"homepage",
|
||||
"map_contains",
|
||||
"min_max",
|
||||
"non_empty_str",
|
||||
"pair",
|
||||
"regex_str1",
|
||||
"regex_str2",
|
||||
"required_option",
|
||||
"tel"
|
||||
],
|
||||
"properties": {
|
||||
"min_max": {
|
||||
"type": "number",
|
||||
"format": "float",
|
||||
"maximum": 100.0,
|
||||
"minimum": 0.01
|
||||
},
|
||||
"regex_str1": {
|
||||
"type": "string",
|
||||
"pattern": "^[Hh]ello\\b"
|
||||
},
|
||||
"regex_str2": {
|
||||
"type": "string",
|
||||
"pattern": "^[Hh]ello\\b"
|
||||
},
|
||||
"contains_str1": {
|
||||
"type": "string",
|
||||
"pattern": "substring\\.\\.\\."
|
||||
},
|
||||
"contains_str2": {
|
||||
"type": "string",
|
||||
"pattern": "substring\\.\\.\\."
|
||||
},
|
||||
"email_address": {
|
||||
"type": "string",
|
||||
"format": "email"
|
||||
},
|
||||
"tel": {
|
||||
"type": "string",
|
||||
"format": "phone"
|
||||
},
|
||||
"homepage": {
|
||||
"type": "string",
|
||||
"format": "uri"
|
||||
},
|
||||
"non_empty_str": {
|
||||
"type": "string",
|
||||
"maxLength": 100,
|
||||
"minLength": 1
|
||||
},
|
||||
"pair": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"maxItems": 2,
|
||||
"minItems": 2
|
||||
},
|
||||
"map_contains": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"map_key"
|
||||
],
|
||||
"additionalProperties": {
|
||||
"type": "null"
|
||||
}
|
||||
},
|
||||
"required_option": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
40
schemars/tests/validate.rs
Normal file
40
schemars/tests/validate.rs
Normal file
|
@ -0,0 +1,40 @@
|
|||
mod util;
|
||||
use schemars::JsonSchema;
|
||||
use std::collections::HashMap;
|
||||
use util::*;
|
||||
|
||||
// In real code, this would typically be a Regex, potentially created in a `lazy_static!`.
|
||||
static STARTS_WITH_HELLO: &'static str = r"^[Hh]ello\b";
|
||||
|
||||
#[derive(Debug, JsonSchema)]
|
||||
pub struct Struct {
|
||||
#[validate(range(min = 0.01, max = 100))]
|
||||
min_max: f32,
|
||||
#[validate(regex = "STARTS_WITH_HELLO")]
|
||||
regex_str1: String,
|
||||
#[validate(regex(path = "STARTS_WITH_HELLO", code = "foo"))]
|
||||
regex_str2: String,
|
||||
#[validate(contains = "substring...")]
|
||||
contains_str1: String,
|
||||
#[validate(contains(pattern = "substring...", message = "bar"))]
|
||||
contains_str2: String,
|
||||
#[validate(email)]
|
||||
email_address: String,
|
||||
#[validate(phone)]
|
||||
tel: String,
|
||||
#[validate(url)]
|
||||
homepage: String,
|
||||
#[validate(length(min = 1, max = 100))]
|
||||
non_empty_str: String,
|
||||
#[validate(length(equal = 2))]
|
||||
pair: Vec<i32>,
|
||||
#[validate(contains = "map_key")]
|
||||
map_contains: HashMap<String, ()>,
|
||||
#[validate(required)]
|
||||
required_option: Option<bool>,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate() -> TestResult {
|
||||
test_default_generated_schema::<Struct>("validate")
|
||||
}
|
|
@ -73,6 +73,7 @@ impl<'a> FromSerde for Field<'a> {
|
|||
ty: serde.ty,
|
||||
original: serde.original,
|
||||
attrs: Attrs::new(&serde.original.attrs, errors),
|
||||
validation_attrs: ValidationAttrs::new(&serde.original.attrs),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
mod from_serde;
|
||||
|
||||
use crate::attr::Attrs;
|
||||
use crate::attr::{Attrs, ValidationAttrs};
|
||||
use from_serde::FromSerde;
|
||||
use serde_derive_internals::ast as serde_ast;
|
||||
use serde_derive_internals::{Ctxt, Derive};
|
||||
|
@ -34,6 +34,7 @@ pub struct Field<'a> {
|
|||
pub ty: &'a syn::Type,
|
||||
pub original: &'a syn::Field,
|
||||
pub attrs: Attrs,
|
||||
pub validation_attrs: ValidationAttrs,
|
||||
}
|
||||
|
||||
impl<'a> Container<'a> {
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
mod doc;
|
||||
mod schemars_to_serde;
|
||||
mod validation;
|
||||
|
||||
pub use schemars_to_serde::process_serde_attrs;
|
||||
pub use validation::ValidationAttrs;
|
||||
|
||||
use proc_macro2::{Group, Span, TokenStream, TokenTree};
|
||||
use quote::ToTokens;
|
||||
|
|
308
schemars_derive/src/attr/validation.rs
Normal file
308
schemars_derive/src/attr/validation.rs
Normal file
|
@ -0,0 +1,308 @@
|
|||
use super::parse_lit_str;
|
||||
use proc_macro2::TokenStream;
|
||||
use syn::ExprLit;
|
||||
use syn::NestedMeta;
|
||||
use syn::{Expr, Lit, Meta, MetaNameValue, Path};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ValidationAttrs {
|
||||
pub length_min: Option<Expr>,
|
||||
pub length_max: Option<Expr>,
|
||||
pub length_equal: Option<Expr>,
|
||||
pub range_min: Option<Expr>,
|
||||
pub range_max: Option<Expr>,
|
||||
pub regex: Option<Path>,
|
||||
pub contains: Option<String>,
|
||||
pub required: bool,
|
||||
pub format: Option<&'static str>,
|
||||
}
|
||||
|
||||
impl ValidationAttrs {
|
||||
pub fn new(attrs: &[syn::Attribute]) -> Self {
|
||||
// TODO allow setting "validate" attributes through #[schemars(...)]
|
||||
ValidationAttrs::default().populate(attrs)
|
||||
}
|
||||
|
||||
fn populate(mut self, attrs: &[syn::Attribute]) -> Self {
|
||||
// TODO don't silently ignore unparseable attributes
|
||||
for meta_item in attrs
|
||||
.iter()
|
||||
.flat_map(|attr| get_meta_items(attr, "validate"))
|
||||
.flatten()
|
||||
{
|
||||
match &meta_item {
|
||||
NestedMeta::Meta(Meta::List(meta_list)) if meta_list.path.is_ident("length") => {
|
||||
for nested in meta_list.nested.iter() {
|
||||
match nested {
|
||||
NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("min") => {
|
||||
self.length_min = str_or_num_to_expr(&nv.lit);
|
||||
}
|
||||
NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("max") => {
|
||||
self.length_max = str_or_num_to_expr(&nv.lit);
|
||||
}
|
||||
NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("equal") => {
|
||||
self.length_equal = str_or_num_to_expr(&nv.lit);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NestedMeta::Meta(Meta::List(meta_list)) if meta_list.path.is_ident("range") => {
|
||||
for nested in meta_list.nested.iter() {
|
||||
match nested {
|
||||
NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("min") => {
|
||||
self.range_min = str_or_num_to_expr(&nv.lit);
|
||||
}
|
||||
NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("max") => {
|
||||
self.range_max = str_or_num_to_expr(&nv.lit);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NestedMeta::Meta(m)
|
||||
if m.path().is_ident("required") || m.path().is_ident("required_nested") =>
|
||||
{
|
||||
self.required = true;
|
||||
}
|
||||
|
||||
NestedMeta::Meta(m) if m.path().is_ident("email") => {
|
||||
self.format = Some("email");
|
||||
}
|
||||
|
||||
NestedMeta::Meta(m) if m.path().is_ident("url") => {
|
||||
self.format = Some("uri");
|
||||
}
|
||||
|
||||
NestedMeta::Meta(m) if m.path().is_ident("phone") => {
|
||||
self.format = Some("phone");
|
||||
}
|
||||
|
||||
NestedMeta::Meta(Meta::NameValue(MetaNameValue {
|
||||
path,
|
||||
lit: Lit::Str(regex),
|
||||
..
|
||||
})) if path.is_ident("regex") => self.regex = parse_lit_str(regex).ok(),
|
||||
|
||||
NestedMeta::Meta(Meta::List(meta_list)) if meta_list.path.is_ident("regex") => {
|
||||
self.regex = meta_list.nested.iter().find_map(|x| match x {
|
||||
NestedMeta::Meta(Meta::NameValue(MetaNameValue {
|
||||
path,
|
||||
lit: Lit::Str(regex),
|
||||
..
|
||||
})) if path.is_ident("path") => parse_lit_str(regex).ok(),
|
||||
_ => None,
|
||||
});
|
||||
}
|
||||
|
||||
NestedMeta::Meta(Meta::NameValue(MetaNameValue {
|
||||
path,
|
||||
lit: Lit::Str(contains),
|
||||
..
|
||||
})) if path.is_ident("contains") => self.contains = Some(contains.value()),
|
||||
|
||||
NestedMeta::Meta(Meta::List(meta_list)) if meta_list.path.is_ident("contains") => {
|
||||
self.contains = meta_list.nested.iter().find_map(|x| match x {
|
||||
NestedMeta::Meta(Meta::NameValue(MetaNameValue {
|
||||
path,
|
||||
lit: Lit::Str(contains),
|
||||
..
|
||||
})) if path.is_ident("pattern") => Some(contains.value()),
|
||||
_ => None,
|
||||
});
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn validation_statements(&self, field_name: &str) -> TokenStream {
|
||||
// Assume that the result will be interpolated in a context with the local variable
|
||||
// `schema_object` - the SchemaObject for the struct that contains this field.
|
||||
let mut statements = Vec::new();
|
||||
|
||||
if self.required {
|
||||
statements.push(quote! {
|
||||
schema_object.object().required.insert(#field_name.to_owned());
|
||||
});
|
||||
}
|
||||
|
||||
let mut array_validation = Vec::new();
|
||||
let mut number_validation = Vec::new();
|
||||
let mut object_validation = Vec::new();
|
||||
let mut string_validation = Vec::new();
|
||||
|
||||
if let Some(length_min) = self
|
||||
.length_min
|
||||
.as_ref()
|
||||
.or_else(|| self.length_equal.as_ref())
|
||||
{
|
||||
string_validation.push(quote! {
|
||||
validation.min_length = Some(#length_min as u32);
|
||||
});
|
||||
array_validation.push(quote! {
|
||||
validation.min_items = Some(#length_min as u32);
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(length_max) = self
|
||||
.length_max
|
||||
.as_ref()
|
||||
.or_else(|| self.length_equal.as_ref())
|
||||
{
|
||||
string_validation.push(quote! {
|
||||
validation.max_length = Some(#length_max as u32);
|
||||
});
|
||||
array_validation.push(quote! {
|
||||
validation.max_items = Some(#length_max as u32);
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(range_min) = &self.range_min {
|
||||
number_validation.push(quote! {
|
||||
validation.minimum = Some(#range_min as f64);
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(range_max) = &self.range_max {
|
||||
number_validation.push(quote! {
|
||||
validation.maximum = Some(#range_max as f64);
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(regex) = &self.regex {
|
||||
string_validation.push(quote! {
|
||||
validation.pattern = Some(#regex.to_string());
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(contains) = &self.contains {
|
||||
object_validation.push(quote! {
|
||||
validation.required.insert(#contains.to_string());
|
||||
});
|
||||
|
||||
if self.regex.is_none() {
|
||||
let pattern = crate::regex_syntax::escape(contains);
|
||||
string_validation.push(quote! {
|
||||
validation.pattern = Some(#pattern.to_string());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let format = self.format.as_ref().map(|f| {
|
||||
quote! {
|
||||
prop_schema_object.format = Some(#f.to_string());
|
||||
}
|
||||
});
|
||||
|
||||
let array_validation = wrap_array_validation(array_validation);
|
||||
let number_validation = wrap_number_validation(number_validation);
|
||||
let object_validation = wrap_object_validation(object_validation);
|
||||
let string_validation = wrap_string_validation(string_validation);
|
||||
|
||||
if array_validation.is_some()
|
||||
|| number_validation.is_some()
|
||||
|| object_validation.is_some()
|
||||
|| string_validation.is_some()
|
||||
|| format.is_some()
|
||||
{
|
||||
statements.push(quote! {
|
||||
if let Some(schemars::schema::Schema::Object(prop_schema_object)) = schema_object
|
||||
.object
|
||||
.as_mut()
|
||||
.and_then(|o| o.properties.get_mut(#field_name))
|
||||
{
|
||||
#array_validation
|
||||
#number_validation
|
||||
#object_validation
|
||||
#string_validation
|
||||
#format
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
statements.into_iter().collect()
|
||||
}
|
||||
}
|
||||
|
||||
fn wrap_array_validation(v: Vec<TokenStream>) -> Option<TokenStream> {
|
||||
if v.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(quote! {
|
||||
if prop_schema_object.has_type(schemars::schema::InstanceType::Array) {
|
||||
let validation = prop_schema_object.array();
|
||||
#(#v)*
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn wrap_number_validation(v: Vec<TokenStream>) -> Option<TokenStream> {
|
||||
if v.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(quote! {
|
||||
if prop_schema_object.has_type(schemars::schema::InstanceType::Integer)
|
||||
|| prop_schema_object.has_type(schemars::schema::InstanceType::Number) {
|
||||
let validation = prop_schema_object.number();
|
||||
#(#v)*
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn wrap_object_validation(v: Vec<TokenStream>) -> Option<TokenStream> {
|
||||
if v.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(quote! {
|
||||
if prop_schema_object.has_type(schemars::schema::InstanceType::Object) {
|
||||
let validation = prop_schema_object.object();
|
||||
#(#v)*
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn wrap_string_validation(v: Vec<TokenStream>) -> Option<TokenStream> {
|
||||
if v.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(quote! {
|
||||
if prop_schema_object.has_type(schemars::schema::InstanceType::String) {
|
||||
let validation = prop_schema_object.string();
|
||||
#(#v)*
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn get_meta_items(
|
||||
attr: &syn::Attribute,
|
||||
attr_type: &'static str,
|
||||
) -> Result<Vec<syn::NestedMeta>, ()> {
|
||||
if !attr.path.is_ident(attr_type) {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
match attr.parse_meta() {
|
||||
Ok(Meta::List(meta)) => Ok(meta.nested.into_iter().collect()),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn str_or_num_to_expr(lit: &Lit) -> Option<Expr> {
|
||||
match lit {
|
||||
Lit::Str(s) => parse_lit_str::<syn::ExprPath>(s).ok().map(Expr::Path),
|
||||
Lit::Int(_) | Lit::Float(_) => Some(Expr::Lit(ExprLit {
|
||||
attrs: Vec::new(),
|
||||
lit: lit.clone(),
|
||||
})),
|
||||
_ => None,
|
||||
}
|
||||
}
|
|
@ -9,12 +9,13 @@ extern crate proc_macro;
|
|||
mod ast;
|
||||
mod attr;
|
||||
mod metadata;
|
||||
mod regex_syntax;
|
||||
mod schema_exprs;
|
||||
|
||||
use ast::*;
|
||||
use proc_macro2::TokenStream;
|
||||
|
||||
#[proc_macro_derive(JsonSchema, attributes(schemars, serde))]
|
||||
#[proc_macro_derive(JsonSchema, attributes(schemars, serde, validate))]
|
||||
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)
|
||||
|
@ -72,7 +73,7 @@ fn derive_json_schema(
|
|||
parent: &mut schemars::schema::SchemaObject,
|
||||
name: String,
|
||||
metadata: Option<schemars::schema::Metadata>,
|
||||
required: bool,
|
||||
required: Option<bool>,
|
||||
) {
|
||||
<#ty as schemars::JsonSchema>::add_schema_as_property(gen, parent, name, metadata, required)
|
||||
}
|
||||
|
|
26
schemars_derive/src/regex_syntax.rs
Normal file
26
schemars_derive/src/regex_syntax.rs
Normal file
|
@ -0,0 +1,26 @@
|
|||
// Copied from regex_syntax crate to avoid pulling in the whole crate just for a utility function
|
||||
// https://github.com/rust-lang/regex/blob/ff283badce21dcebd581909d38b81f2c8c9bfb54/regex-syntax/src/lib.rs
|
||||
|
||||
pub fn escape(text: &str) -> String {
|
||||
let mut quoted = String::new();
|
||||
escape_into(text, &mut quoted);
|
||||
quoted
|
||||
}
|
||||
|
||||
fn escape_into(text: &str, buf: &mut String) {
|
||||
buf.reserve(text.len());
|
||||
for c in text.chars() {
|
||||
if is_meta_character(c) {
|
||||
buf.push('\\');
|
||||
}
|
||||
buf.push(c);
|
||||
}
|
||||
}
|
||||
|
||||
fn is_meta_character(c: char) -> bool {
|
||||
match c {
|
||||
'\\' | '.' | '+' | '*' | '?' | '(' | ')' | '|' | '[' | ']' | '{' | '}' | '^' | '$'
|
||||
| '#' | '&' | '-' | '~' => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
|
@ -390,13 +390,16 @@ fn expr_for_struct(
|
|||
|
||||
let mut type_defs = Vec::new();
|
||||
|
||||
let properties: Vec<_> = property_fields.into_iter().map(|field| {
|
||||
let properties: Vec<_> = property_fields
|
||||
.into_iter()
|
||||
.map(|field| {
|
||||
let name = field.name();
|
||||
let default = field_default_expr(field, set_container_default.is_some());
|
||||
|
||||
let required = match default {
|
||||
Some(_) => quote!(false),
|
||||
None => quote!(true),
|
||||
let required = match (&default, field.validation_attrs.required) {
|
||||
(Some(_), _) => quote!(Some(false)),
|
||||
(None, false) => quote!(None),
|
||||
(None, true) => quote!(Some(true)),
|
||||
};
|
||||
|
||||
let metadata = &SchemaMetadata {
|
||||
|
@ -411,11 +414,19 @@ fn expr_for_struct(
|
|||
type_defs.push(type_def);
|
||||
}
|
||||
|
||||
quote_spanned! {ty.span()=>
|
||||
<#ty as schemars::JsonSchema>::add_schema_as_property(gen, &mut schema_object, #name.to_owned(), #metadata, #required);
|
||||
}
|
||||
let validation = field.validation_attrs.validation_statements(&name);
|
||||
|
||||
}).collect();
|
||||
quote_spanned! {ty.span()=>
|
||||
<#ty as schemars::JsonSchema>::add_schema_as_property(
|
||||
gen,
|
||||
&mut schema_object,
|
||||
#name.to_owned(),
|
||||
#metadata,
|
||||
#required);
|
||||
#validation
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let flattens: Vec<_> = flattened_fields
|
||||
.into_iter()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue