From 6ab567f3a5c0f7e79106cae90a3006758446437f Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Mon, 29 Mar 2021 16:38:55 +0100 Subject: [PATCH 01/44] Read #[validate(...)] attributes --- schemars/src/json_schema_impls/core.rs | 16 +- schemars/src/json_schema_impls/mod.rs | 2 +- schemars/src/lib.rs | 4 +- schemars/src/schema.rs | 26 ++- schemars/tests/expected/validate.json | 81 +++++++ schemars/tests/validate.rs | 40 ++++ schemars_derive/src/ast/from_serde.rs | 1 + schemars_derive/src/ast/mod.rs | 3 +- schemars_derive/src/attr/mod.rs | 2 + schemars_derive/src/attr/validation.rs | 308 +++++++++++++++++++++++++ schemars_derive/src/lib.rs | 5 +- schemars_derive/src/regex_syntax.rs | 26 +++ schemars_derive/src/schema_exprs.rs | 53 +++-- 13 files changed, 533 insertions(+), 34 deletions(-) create mode 100644 schemars/tests/expected/validate.json create mode 100644 schemars/tests/validate.rs create mode 100644 schemars_derive/src/attr/validation.rs create mode 100644 schemars_derive/src/regex_syntax.rs diff --git a/schemars/src/json_schema_impls/core.rs b/schemars/src/json_schema_impls/core.rs index 5cefbddd..647030fd 100644 --- a/schemars/src/json_schema_impls/core.rs +++ b/schemars/src/json_schema_impls/core.rs @@ -62,13 +62,17 @@ impl JsonSchema for Option { parent: &mut SchemaObject, name: String, metadata: Option, - _required: bool, + required: Option, ) { - let mut schema = gen.subschema_for::(); - schema = gen.apply_metadata(schema, metadata); - - let object = parent.object(); - object.properties.insert(name, schema); + if required == Some(true) { + T::add_schema_as_property(gen, parent, name, metadata, required) + } else { + let mut schema = gen.subschema_for::(); + schema = gen.apply_metadata(schema, metadata); + + let object = parent.object(); + object.properties.insert(name, schema); + } } } diff --git a/schemars/src/json_schema_impls/mod.rs b/schemars/src/json_schema_impls/mod.rs index d493ea00..4d96ee2a 100644 --- a/schemars/src/json_schema_impls/mod.rs +++ b/schemars/src/json_schema_impls/mod.rs @@ -30,7 +30,7 @@ macro_rules! forward_impl { parent: &mut crate::schema::SchemaObject, name: String, metadata: Option, - required: bool, + required: Option, ) { <$target>::add_schema_as_property(gen, parent, name, metadata, required) } diff --git a/schemars/src/lib.rs b/schemars/src/lib.rs index eb6d5acb..9a9e65c3 100644 --- a/schemars/src/lib.rs +++ b/schemars/src/lib.rs @@ -375,13 +375,13 @@ pub trait JsonSchema { parent: &mut SchemaObject, name: String, metadata: Option, - required: bool, + required: Option, ) { let mut schema = gen.subschema_for::(); 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); diff --git a/schemars/src/schema.rs b/schemars/src/schema.rs index a4c6e32b..e9a01242 100644 --- a/schemars/src/schema.rs +++ b/schemars/src/schema.rs @@ -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 From> for SingleOrVec { SingleOrVec::Vec(vec) } } + +impl SingleOrVec { + // TODO document + pub fn contains(&self, x: &T) -> bool { + match self { + SingleOrVec::Single(s) => s.deref() == x, + SingleOrVec::Vec(v) => v.contains(x), + } + } +} diff --git a/schemars/tests/expected/validate.json b/schemars/tests/expected/validate.json new file mode 100644 index 00000000..228f249b --- /dev/null +++ b/schemars/tests/expected/validate.json @@ -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" + } + } +} \ No newline at end of file diff --git a/schemars/tests/validate.rs b/schemars/tests/validate.rs new file mode 100644 index 00000000..6d67531f --- /dev/null +++ b/schemars/tests/validate.rs @@ -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, + #[validate(contains = "map_key")] + map_contains: HashMap, + #[validate(required)] + required_option: Option, +} + +#[test] +fn validate() -> TestResult { + test_default_generated_schema::("validate") +} diff --git a/schemars_derive/src/ast/from_serde.rs b/schemars_derive/src/ast/from_serde.rs index 0d9add36..db2e092e 100644 --- a/schemars_derive/src/ast/from_serde.rs +++ b/schemars_derive/src/ast/from_serde.rs @@ -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), }) } } diff --git a/schemars_derive/src/ast/mod.rs b/schemars_derive/src/ast/mod.rs index a394acda..99fe1882 100644 --- a/schemars_derive/src/ast/mod.rs +++ b/schemars_derive/src/ast/mod.rs @@ -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> { diff --git a/schemars_derive/src/attr/mod.rs b/schemars_derive/src/attr/mod.rs index 65667d9c..d7a66287 100644 --- a/schemars_derive/src/attr/mod.rs +++ b/schemars_derive/src/attr/mod.rs @@ -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; diff --git a/schemars_derive/src/attr/validation.rs b/schemars_derive/src/attr/validation.rs new file mode 100644 index 00000000..8f13168d --- /dev/null +++ b/schemars_derive/src/attr/validation.rs @@ -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, + pub length_max: Option, + pub length_equal: Option, + pub range_min: Option, + pub range_max: Option, + pub regex: Option, + pub contains: Option, + 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) -> Option { + 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) -> Option { + 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) -> Option { + 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) -> Option { + 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, ()> { + 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 { + match lit { + Lit::Str(s) => parse_lit_str::(s).ok().map(Expr::Path), + Lit::Int(_) | Lit::Float(_) => Some(Expr::Lit(ExprLit { + attrs: Vec::new(), + lit: lit.clone(), + })), + _ => None, + } +} diff --git a/schemars_derive/src/lib.rs b/schemars_derive/src/lib.rs index c81eb406..c737380c 100644 --- a/schemars_derive/src/lib.rs +++ b/schemars_derive/src/lib.rs @@ -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, - required: bool, + required: Option, ) { <#ty as schemars::JsonSchema>::add_schema_as_property(gen, parent, name, metadata, required) } diff --git a/schemars_derive/src/regex_syntax.rs b/schemars_derive/src/regex_syntax.rs new file mode 100644 index 00000000..353bf8d3 --- /dev/null +++ b/schemars_derive/src/regex_syntax.rs @@ -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, + } +} diff --git a/schemars_derive/src/schema_exprs.rs b/schemars_derive/src/schema_exprs.rs index a5cf2cac..4965fd53 100644 --- a/schemars_derive/src/schema_exprs.rs +++ b/schemars_derive/src/schema_exprs.rs @@ -390,32 +390,43 @@ fn expr_for_struct( let mut type_defs = Vec::new(); - 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 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 { - read_only: field.serde_attrs.skip_deserializing(), - write_only: field.serde_attrs.skip_serializing(), - default, - ..SchemaMetadata::from_attrs(&field.attrs) - }; + let metadata = &SchemaMetadata { + read_only: field.serde_attrs.skip_deserializing(), + write_only: field.serde_attrs.skip_serializing(), + default, + ..SchemaMetadata::from_attrs(&field.attrs) + }; - let (ty, type_def) = type_for_schema(field, type_defs.len()); - if let Some(type_def) = type_def { - type_defs.push(type_def); - } + let (ty, type_def) = type_for_schema(field, type_defs.len()); + if let Some(type_def) = type_def { + 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() From 1a2dafc1a5cecea8022d0d0eddb1775f9c182b27 Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Thu, 15 Apr 2021 18:11:28 +0100 Subject: [PATCH 02/44] Handle required flattened Option fields --- schemars/src/_private.rs | 12 ++++++++++-- schemars/tests/expected/validate.json | 7 ++++++- schemars/tests/validate.rs | 8 ++++++++ schemars_derive/src/attr/validation.rs | 18 +++++------------- schemars_derive/src/schema_exprs.rs | 10 ++++++++-- 5 files changed, 37 insertions(+), 18 deletions(-) diff --git a/schemars/src/_private.rs b/schemars/src/_private.rs index 8b8e2d1a..84283705 100644 --- a/schemars/src/_private.rs +++ b/schemars/src/_private.rs @@ -4,9 +4,14 @@ use crate::schema::{Metadata, Schema, SchemaObject}; use crate::JsonSchema; // Helper for generating schemas for flattened `Option` fields. -pub fn json_schema_for_flatten(gen: &mut SchemaGenerator) -> Schema { +pub fn json_schema_for_flatten( + gen: &mut SchemaGenerator, + required: Option, +) -> Schema { let mut schema = T::_schemars_private_non_optional_json_schema(gen); - if T::_schemars_private_is_option() { + + let required = required.unwrap_or_else(|| !T::_schemars_private_is_option()); + if !required { if let Schema::Object(SchemaObject { object: Some(ref mut object_validation), .. @@ -15,6 +20,7 @@ pub fn json_schema_for_flatten(gen: &mut SchemaGenerator object_validation.required.clear(); } } + schema } @@ -28,11 +34,13 @@ pub fn add_schema_as_property( ) { let is_type_option = T::_schemars_private_is_option(); let required = required.unwrap_or(!is_type_option); + let mut schema = if required && is_type_option { T::_schemars_private_non_optional_json_schema(gen) } else { gen.subschema_for::() }; + schema = apply_metadata(schema, metadata); let object = parent.object(); diff --git a/schemars/tests/expected/validate.json b/schemars/tests/expected/validate.json index 228f249b..f5a6fc24 100644 --- a/schemars/tests/expected/validate.json +++ b/schemars/tests/expected/validate.json @@ -14,7 +14,8 @@ "regex_str1", "regex_str2", "required_option", - "tel" + "tel", + "x" ], "properties": { "min_max": { @@ -76,6 +77,10 @@ }, "required_option": { "type": "boolean" + }, + "x": { + "type": "integer", + "format": "int32" } } } \ No newline at end of file diff --git a/schemars/tests/validate.rs b/schemars/tests/validate.rs index 6d67531f..9992029b 100644 --- a/schemars/tests/validate.rs +++ b/schemars/tests/validate.rs @@ -32,6 +32,14 @@ pub struct Struct { map_contains: HashMap, #[validate(required)] required_option: Option, + #[validate(required)] + #[serde(flatten)] + required_flattened: Option, +} + +#[derive(Debug, JsonSchema)] +pub struct Inner { + x: i32, } #[test] diff --git a/schemars_derive/src/attr/validation.rs b/schemars_derive/src/attr/validation.rs index a721ec5a..043cccb4 100644 --- a/schemars_derive/src/attr/validation.rs +++ b/schemars_derive/src/attr/validation.rs @@ -120,17 +120,9 @@ impl ValidationAttrs { self } - pub fn validation_statements(&self, field_name: &str) -> TokenStream { + pub fn validation_statements(&self, field_name: &str) -> Option { // 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(); @@ -210,7 +202,7 @@ impl ValidationAttrs { || string_validation.is_some() || format.is_some() { - statements.push(quote! { + Some(quote! { if let Some(schemars::schema::Schema::Object(prop_schema_object)) = schema_object .object .as_mut() @@ -222,10 +214,10 @@ impl ValidationAttrs { #string_validation #format } - }); + }) + } else { + None } - - statements.into_iter().collect() } } diff --git a/schemars_derive/src/schema_exprs.rs b/schemars_derive/src/schema_exprs.rs index ed698739..5c68c52f 100644 --- a/schemars_derive/src/schema_exprs.rs +++ b/schemars_derive/src/schema_exprs.rs @@ -449,9 +449,15 @@ fn expr_for_struct( type_defs.push(type_def); } - let gen = quote!(gen); + let required = if field.validation_attrs.required { + quote!(Some(true)) + } else { + quote!(None) + }; + + let args = quote!(gen, #required); quote_spanned! {ty.span()=> - .flatten(schemars::_private::json_schema_for_flatten::<#ty>(#gen)) + .flatten(schemars::_private::json_schema_for_flatten::<#ty>(#args)) } }) .collect(); From 60a98694482d1a11afc629aa6c7a93f193e7177e Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Fri, 16 Apr 2021 10:42:03 +0100 Subject: [PATCH 03/44] Refactor out `add_schema_as_property` --- schemars/src/_private.rs | 40 +++----------- schemars/tests/expected/macro_built_enum.json | 11 +++- schemars/tests/macro.rs | 4 +- schemars_derive/src/attr/validation.rs | 49 +++++++++-------- schemars_derive/src/metadata.rs | 34 ++++-------- schemars_derive/src/schema_exprs.rs | 52 +++++++++++++------ 6 files changed, 89 insertions(+), 101 deletions(-) diff --git a/schemars/src/_private.rs b/schemars/src/_private.rs index 84283705..1799f415 100644 --- a/schemars/src/_private.rs +++ b/schemars/src/_private.rs @@ -24,40 +24,12 @@ pub fn json_schema_for_flatten( schema } -// Helper for generating schemas for `Option` fields. -pub fn add_schema_as_property( - gen: &mut SchemaGenerator, - parent: &mut SchemaObject, - name: String, - metadata: Option, - required: Option, -) { - let is_type_option = T::_schemars_private_is_option(); - let required = required.unwrap_or(!is_type_option); - - let mut schema = if required && is_type_option { - T::_schemars_private_non_optional_json_schema(gen) +pub fn apply_metadata(schema: Schema, metadata: Metadata) -> Schema { + if metadata == Metadata::default() { + schema } else { - gen.subschema_for::() - }; - - schema = apply_metadata(schema, metadata); - - let object = parent.object(); - if required { - object.required.insert(name.clone()); - } - object.properties.insert(name, schema); -} - -pub fn apply_metadata(schema: Schema, metadata: Option) -> Schema { - match metadata { - None => schema, - Some(ref metadata) if *metadata == Metadata::default() => schema, - Some(metadata) => { - let mut schema_obj = schema.into_object(); - schema_obj.metadata = Some(Box::new(metadata)).merge(schema_obj.metadata); - Schema::Object(schema_obj) - } + let mut schema_obj = schema.into_object(); + schema_obj.metadata = Some(Box::new(metadata)).merge(schema_obj.metadata); + Schema::Object(schema_obj) } } diff --git a/schemars/tests/expected/macro_built_enum.json b/schemars/tests/expected/macro_built_enum.json index 8564ef70..8a14a4b3 100644 --- a/schemars/tests/expected/macro_built_enum.json +++ b/schemars/tests/expected/macro_built_enum.json @@ -17,7 +17,16 @@ ], "definitions": { "InnerStruct": { - "type": "object" + "type": "object", + "required": [ + "x" + ], + "properties": { + "x": { + "type": "integer", + "format": "int32" + } + } } } } \ No newline at end of file diff --git a/schemars/tests/macro.rs b/schemars/tests/macro.rs index 9991494c..ca7dee8f 100644 --- a/schemars/tests/macro.rs +++ b/schemars/tests/macro.rs @@ -56,7 +56,9 @@ build_enum!( #[derive(Debug, JsonSchema)] OuterEnum { #[derive(Debug, JsonSchema)] - InnerStruct {} + InnerStruct { + x: i32 + } } ); diff --git a/schemars_derive/src/attr/validation.rs b/schemars_derive/src/attr/validation.rs index 043cccb4..c1f0ca7c 100644 --- a/schemars_derive/src/attr/validation.rs +++ b/schemars_derive/src/attr/validation.rs @@ -120,9 +120,7 @@ impl ValidationAttrs { self } - pub fn validation_statements(&self, field_name: &str) -> Option { - // 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. + pub fn apply_to_schema(&self, schema_expr: TokenStream) -> TokenStream { let mut array_validation = Vec::new(); let mut number_validation = Vec::new(); let mut object_validation = Vec::new(); @@ -187,7 +185,7 @@ impl ValidationAttrs { let format = self.format.as_ref().map(|f| { quote! { - prop_schema_object.format = Some(#f.to_string()); + schema_object.format = Some(#f.to_string()); } }); @@ -202,21 +200,22 @@ impl ValidationAttrs { || string_validation.is_some() || format.is_some() { - Some(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)) + quote! { { - #array_validation - #number_validation - #object_validation - #string_validation - #format + let mut schema = #schema_expr; + if let schemars::schema::Schema::Object(schema_object) = &mut schema + { + #array_validation + #number_validation + #object_validation + #string_validation + #format + } + schema } - }) + } } else { - None + schema_expr } } } @@ -226,8 +225,8 @@ fn wrap_array_validation(v: Vec) -> Option { None } else { Some(quote! { - if prop_schema_object.has_type(schemars::schema::InstanceType::Array) { - let validation = prop_schema_object.array(); + if schema_object.has_type(schemars::schema::InstanceType::Array) { + let validation = schema_object.array(); #(#v)* } }) @@ -239,9 +238,9 @@ fn wrap_number_validation(v: Vec) -> Option { 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(); + if schema_object.has_type(schemars::schema::InstanceType::Integer) + || schema_object.has_type(schemars::schema::InstanceType::Number) { + let validation = schema_object.number(); #(#v)* } }) @@ -253,8 +252,8 @@ fn wrap_object_validation(v: Vec) -> Option { None } else { Some(quote! { - if prop_schema_object.has_type(schemars::schema::InstanceType::Object) { - let validation = prop_schema_object.object(); + if schema_object.has_type(schemars::schema::InstanceType::Object) { + let validation = schema_object.object(); #(#v)* } }) @@ -266,8 +265,8 @@ fn wrap_string_validation(v: Vec) -> Option { None } else { Some(quote! { - if prop_schema_object.has_type(schemars::schema::InstanceType::String) { - let validation = prop_schema_object.string(); + if schema_object.has_type(schemars::schema::InstanceType::String) { + let validation = schema_object.string(); #(#v)* } }) diff --git a/schemars_derive/src/metadata.rs b/schemars_derive/src/metadata.rs index a84decad..05e5faff 100644 --- a/schemars_derive/src/metadata.rs +++ b/schemars_derive/src/metadata.rs @@ -1,7 +1,6 @@ use crate::attr; use attr::Attrs; -use proc_macro2::{Ident, Span, TokenStream}; -use quote::{ToTokens, TokenStreamExt}; +use proc_macro2::TokenStream; #[derive(Debug, Clone)] pub struct SchemaMetadata<'a> { @@ -14,24 +13,6 @@ pub struct SchemaMetadata<'a> { pub default: Option, } -impl ToTokens for SchemaMetadata<'_> { - fn to_tokens(&self, tokens: &mut TokenStream) { - let setters = self.make_setters(); - if setters.is_empty() { - tokens.append(Ident::new("None", Span::call_site())) - } else { - tokens.extend(quote! { - Some({ - schemars::schema::Metadata { - #(#setters)* - ..Default::default() - } - }) - }) - } - } -} - impl<'a> SchemaMetadata<'a> { pub fn from_attrs(attrs: &'a Attrs) -> Self { SchemaMetadata { @@ -46,10 +27,15 @@ impl<'a> SchemaMetadata<'a> { } pub fn apply_to_schema(&self, schema_expr: TokenStream) -> TokenStream { - quote! { - { - let schema = #schema_expr; - schemars::_private::apply_metadata(schema, #self) + let setters = self.make_setters(); + if setters.is_empty() { + schema_expr + } else { + quote! { + schemars::_private::apply_metadata(#schema_expr, schemars::schema::Metadata { + #(#setters)* + ..Default::default() + }) } } } diff --git a/schemars_derive/src/schema_exprs.rs b/schemars_derive/src/schema_exprs.rs index 5c68c52f..d8740e4a 100644 --- a/schemars_derive/src/schema_exprs.rs +++ b/schemars_derive/src/schema_exprs.rs @@ -413,30 +413,49 @@ fn expr_for_struct( let name = field.name(); let default = field_default_expr(field, set_container_default.is_some()); - let required = match (&default, field.validation_attrs.required) { - (Some(_), _) => quote!(Some(false)), - (None, false) => quote!(None), - (None, true) => quote!(Some(true)), + let (ty, type_def) = type_for_field_schema(field, type_defs.len()); + if let Some(type_def) = type_def { + type_defs.push(type_def); + } + + let gen = quote!(gen); + let schema_expr = if field.validation_attrs.required { + quote_spanned! {ty.span()=> + <#ty as schemars::JsonSchema>::_schemars_private_non_optional_json_schema(#gen) + } + } else { + quote_spanned! {ty.span()=> + #gen.subschema_for::<#ty>() + } + }; + + let maybe_insert_required = match (&default, field.validation_attrs.required) { + (Some(_), _) => TokenStream::new(), + (None, false) => { + quote! { + if !<#ty as schemars::JsonSchema>::_schemars_private_is_option() { + object_validation.required.insert(#name.to_owned()); + } + } + } + (None, true) => quote! { + object_validation.required.insert(#name.to_owned()); + }, }; - let metadata = &SchemaMetadata { + let metadata = SchemaMetadata { read_only: field.serde_attrs.skip_deserializing(), write_only: field.serde_attrs.skip_serializing(), default, ..SchemaMetadata::from_attrs(&field.attrs) }; - let (ty, type_def) = type_for_field_schema(field, type_defs.len()); - if let Some(type_def) = type_def { - type_defs.push(type_def); - } - - let args = quote!(gen, &mut schema_object, #name.to_owned(), #metadata, #required); - let validation = field.validation_attrs.validation_statements(&name); + let schema_expr = metadata.apply_to_schema(schema_expr); + let schema_expr = field.validation_attrs.apply_to_schema(schema_expr); - quote_spanned! {ty.span()=> - schemars::_private::add_schema_as_property::<#ty>(#args); - #validation + quote! { + object_validation.properties.insert(#name.to_owned(), #schema_expr); + #maybe_insert_required } }) .collect(); @@ -464,7 +483,7 @@ fn expr_for_struct( let set_additional_properties = if deny_unknown_fields { quote! { - schema_object.object().additional_properties = Some(Box::new(false.into())); + object_validation.additional_properties = Some(Box::new(false.into())); } } else { TokenStream::new() @@ -477,6 +496,7 @@ fn expr_for_struct( instance_type: Some(schemars::schema::InstanceType::Object.into()), ..Default::default() }; + let object_validation = schema_object.object(); #set_additional_properties #(#properties)* schemars::schema::Schema::Object(schema_object) From 31a5893d1004349887e129a8c3daba2929a047ee Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Fri, 16 Apr 2021 12:30:52 +0100 Subject: [PATCH 04/44] Process validation attributes in newtype structs --- schemars/tests/expected/validate_newtype.json | 8 ++++++++ schemars/tests/validate.rs | 8 ++++++++ schemars_derive/src/schema_exprs.rs | 4 ++-- 3 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 schemars/tests/expected/validate_newtype.json diff --git a/schemars/tests/expected/validate_newtype.json b/schemars/tests/expected/validate_newtype.json new file mode 100644 index 00000000..796aecde --- /dev/null +++ b/schemars/tests/expected/validate_newtype.json @@ -0,0 +1,8 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "NewType", + "type": "integer", + "format": "uint8", + "maximum": 10.0, + "minimum": 0.0 +} \ No newline at end of file diff --git a/schemars/tests/validate.rs b/schemars/tests/validate.rs index 9992029b..8992df24 100644 --- a/schemars/tests/validate.rs +++ b/schemars/tests/validate.rs @@ -46,3 +46,11 @@ pub struct Inner { fn validate() -> TestResult { test_default_generated_schema::("validate") } + +#[derive(Debug, JsonSchema)] +pub struct NewType(#[validate(range(max = 10))] u8); + +#[test] +fn validate_newtype() -> TestResult { + test_default_generated_schema::("validate_newtype") +} diff --git a/schemars_derive/src/schema_exprs.rs b/schemars_derive/src/schema_exprs.rs index d8740e4a..261d6bce 100644 --- a/schemars_derive/src/schema_exprs.rs +++ b/schemars_derive/src/schema_exprs.rs @@ -61,7 +61,7 @@ fn expr_for_field(field: &Field, allow_ref: bool) -> TokenStream { let span = field.original.span(); let gen = quote!(gen); - if allow_ref { + field.validation_attrs.apply_to_schema(if allow_ref { quote_spanned! {span=> { #type_def @@ -75,7 +75,7 @@ fn expr_for_field(field: &Field, allow_ref: bool) -> TokenStream { <#ty as schemars::JsonSchema>::json_schema(#gen) } } - } + }) } pub fn type_for_field_schema(field: &Field, local_id: usize) -> (syn::Type, Option) { From 9e507272dab27a65717e7a6f70ceb5e0ff9f2480 Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Fri, 16 Apr 2021 13:56:26 +0100 Subject: [PATCH 05/44] Process validation attributes in tuple structs --- schemars/tests/expected/validate_tuple.json | 18 +++++++++++ schemars/tests/validate.rs | 11 +++++++ schemars_derive/src/schema_exprs.rs | 36 +++++++++++++++------ 3 files changed, 55 insertions(+), 10 deletions(-) create mode 100644 schemars/tests/expected/validate_tuple.json diff --git a/schemars/tests/expected/validate_tuple.json b/schemars/tests/expected/validate_tuple.json new file mode 100644 index 00000000..8ab6eaa6 --- /dev/null +++ b/schemars/tests/expected/validate_tuple.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Tuple", + "type": "array", + "items": [ + { + "type": "integer", + "format": "uint8", + "maximum": 10.0, + "minimum": 0.0 + }, + { + "type": "boolean" + } + ], + "maxItems": 2, + "minItems": 2 +} \ No newline at end of file diff --git a/schemars/tests/validate.rs b/schemars/tests/validate.rs index 8992df24..00d1b575 100644 --- a/schemars/tests/validate.rs +++ b/schemars/tests/validate.rs @@ -47,6 +47,17 @@ fn validate() -> TestResult { test_default_generated_schema::("validate") } +#[derive(Debug, JsonSchema)] +pub struct Tuple( + #[validate(range(max = 10))] u8, + #[validate(required)] Option, +); + +#[test] +fn validate_tuple() -> TestResult { + test_default_generated_schema::("validate_tuple") +} + #[derive(Debug, JsonSchema)] pub struct NewType(#[validate(range(max = 10))] u8); diff --git a/schemars_derive/src/schema_exprs.rs b/schemars_derive/src/schema_exprs.rs index 261d6bce..739641c7 100644 --- a/schemars_derive/src/schema_exprs.rs +++ b/schemars_derive/src/schema_exprs.rs @@ -61,7 +61,14 @@ fn expr_for_field(field: &Field, allow_ref: bool) -> TokenStream { let span = field.original.span(); let gen = quote!(gen); - field.validation_attrs.apply_to_schema(if allow_ref { + let schema_expr = if field.validation_attrs.required { + quote_spanned! {span=> + { + #type_def + <#ty as schemars::JsonSchema>::_schemars_private_non_optional_json_schema(#gen) + } + } + } else if allow_ref { quote_spanned! {span=> { #type_def @@ -75,7 +82,8 @@ fn expr_for_field(field: &Field, allow_ref: bool) -> TokenStream { <#ty as schemars::JsonSchema>::json_schema(#gen) } } - }) + }; + field.validation_attrs.apply_to_schema(schema_expr) } pub fn type_for_field_schema(field: &Field, local_id: usize) -> (syn::Type, Option) { @@ -375,17 +383,25 @@ fn expr_for_newtype_struct(field: &Field) -> TokenStream { } fn expr_for_tuple_struct(fields: &[Field]) -> TokenStream { - let (types, type_defs): (Vec<_>, Vec<_>) = fields + let fields: Vec<_> = fields .iter() .filter(|f| !f.serde_attrs.skip_deserializing()) - .enumerate() - .map(|(i, f)| type_for_field_schema(f, i)) - .unzip(); + .map(|f| expr_for_field(f, true)) + .collect(); + let len = fields.len() as u32; + quote! { - { - #(#type_defs)* - gen.subschema_for::<(#(#types),*)>() - } + schemars::schema::Schema::Object( + schemars::schema::SchemaObject { + instance_type: Some(schemars::schema::InstanceType::Array.into()), + array: Some(Box::new(schemars::schema::ArrayValidation { + items: Some(vec![#(#fields),*].into()), + max_items: Some(#len), + min_items: Some(#len), + ..Default::default() + })), + ..Default::default() + }) } } From 4be21bd8119a0a229328c2ba44a5954344ec7781 Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Fri, 16 Apr 2021 14:23:10 +0100 Subject: [PATCH 06/44] Refactor out "local_id" for type definitions --- schemars_derive/src/lib.rs | 2 +- schemars_derive/src/schema_exprs.rs | 39 +++++++++++++---------------- 2 files changed, 19 insertions(+), 22 deletions(-) diff --git a/schemars_derive/src/lib.rs b/schemars_derive/src/lib.rs index 5325f927..5fa4ac2f 100644 --- a/schemars_derive/src/lib.rs +++ b/schemars_derive/src/lib.rs @@ -51,7 +51,7 @@ fn derive_json_schema( let (impl_generics, ty_generics, where_clause) = cont.generics.split_for_impl(); if let Some(transparent_field) = cont.transparent_field() { - let (ty, type_def) = schema_exprs::type_for_field_schema(transparent_field, 0); + let (ty, type_def) = schema_exprs::type_for_field_schema(transparent_field); return Ok(quote! { const _: () = { #crate_alias diff --git a/schemars_derive/src/schema_exprs.rs b/schemars_derive/src/schema_exprs.rs index 739641c7..68dcec6e 100644 --- a/schemars_derive/src/schema_exprs.rs +++ b/schemars_derive/src/schema_exprs.rs @@ -57,7 +57,7 @@ pub fn expr_for_repr(cont: &Container) -> Result { } fn expr_for_field(field: &Field, allow_ref: bool) -> TokenStream { - let (ty, type_def) = type_for_field_schema(field, 0); + let (ty, type_def) = type_for_field_schema(field); let span = field.original.span(); let gen = quote!(gen); @@ -86,18 +86,18 @@ fn expr_for_field(field: &Field, allow_ref: bool) -> TokenStream { field.validation_attrs.apply_to_schema(schema_expr) } -pub fn type_for_field_schema(field: &Field, local_id: usize) -> (syn::Type, Option) { +pub fn type_for_field_schema(field: &Field) -> (syn::Type, Option) { match &field.attrs.with { None => (field.ty.to_owned(), None), - Some(with_attr) => type_for_schema(with_attr, local_id), + Some(with_attr) => type_for_schema(with_attr), } } -fn type_for_schema(with_attr: &WithAttr, local_id: usize) -> (syn::Type, Option) { +fn type_for_schema(with_attr: &WithAttr) -> (syn::Type, Option) { match with_attr { WithAttr::Type(ty) => (ty.to_owned(), None), WithAttr::Function(fun) => { - let ty_name = format_ident!("_SchemarsSchemaWithFunction{}", local_id); + let ty_name = syn::Ident::new("_SchemarsSchemaWithFunction", Span::call_site()); let fn_name = fun.segments.last().unwrap().ident.to_string(); let type_def = quote_spanned! {fun.span()=> @@ -331,7 +331,7 @@ fn expr_for_adjacent_tagged_enum<'a>( fn expr_for_untagged_enum_variant(variant: &Variant, deny_unknown_fields: bool) -> TokenStream { if let Some(with_attr) = &variant.attrs.with { - let (ty, type_def) = type_for_schema(with_attr, 0); + let (ty, type_def) = type_for_schema(with_attr); let gen = quote!(gen); return quote_spanned! {variant.original.span()=> { @@ -354,7 +354,7 @@ fn expr_for_untagged_enum_variant_for_flatten( deny_unknown_fields: bool, ) -> Option { if let Some(with_attr) = &variant.attrs.with { - let (ty, type_def) = type_for_schema(with_attr, 0); + let (ty, type_def) = type_for_schema(with_attr); let gen = quote!(gen); return Some(quote_spanned! {variant.original.span()=> { @@ -421,18 +421,13 @@ fn expr_for_struct( SerdeDefault::Path(path) => Some(quote!(let container_default = #path();)), }; - let mut type_defs = Vec::new(); - 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 (ty, type_def) = type_for_field_schema(field, type_defs.len()); - if let Some(type_def) = type_def { - type_defs.push(type_def); - } + let (ty, type_def) = type_for_field_schema(field); let gen = quote!(gen); let schema_expr = if field.validation_attrs.required { @@ -470,8 +465,11 @@ fn expr_for_struct( let schema_expr = field.validation_attrs.apply_to_schema(schema_expr); quote! { - object_validation.properties.insert(#name.to_owned(), #schema_expr); - #maybe_insert_required + { + #type_def + object_validation.properties.insert(#name.to_owned(), #schema_expr); + #maybe_insert_required + } } }) .collect(); @@ -479,10 +477,7 @@ fn expr_for_struct( let flattens: Vec<_> = flattened_fields .into_iter() .map(|field| { - let (ty, type_def) = type_for_field_schema(field, type_defs.len()); - if let Some(type_def) = type_def { - type_defs.push(type_def); - } + let (ty, type_def) = type_for_field_schema(field); let required = if field.validation_attrs.required { quote!(Some(true)) @@ -492,7 +487,10 @@ fn expr_for_struct( let args = quote!(gen, #required); quote_spanned! {ty.span()=> - .flatten(schemars::_private::json_schema_for_flatten::<#ty>(#args)) + .flatten({ + #type_def + schemars::_private::json_schema_for_flatten::<#ty>(#args) + }) } }) .collect(); @@ -506,7 +504,6 @@ fn expr_for_struct( }; quote! { { - #(#type_defs)* #set_container_default let mut schema_object = schemars::schema::SchemaObject { instance_type: Some(schemars::schema::InstanceType::Object.into()), From 5f841f2e5ce9e8179dca58b2852ef11626c8af4c Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Fri, 16 Apr 2021 16:53:15 +0100 Subject: [PATCH 07/44] Refactoring --- schemars_derive/src/attr/mod.rs | 22 ++++ schemars_derive/src/attr/validation.rs | 6 +- schemars_derive/src/metadata.rs | 31 +---- schemars_derive/src/schema_exprs.rs | 161 ++++++++++++++----------- 4 files changed, 118 insertions(+), 102 deletions(-) diff --git a/schemars_derive/src/attr/mod.rs b/schemars_derive/src/attr/mod.rs index 10ed7729..93f76c59 100644 --- a/schemars_derive/src/attr/mod.rs +++ b/schemars_derive/src/attr/mod.rs @@ -5,6 +5,7 @@ mod validation; pub use schemars_to_serde::process_serde_attrs; pub use validation::ValidationAttrs; +use crate::metadata::SchemaMetadata; use proc_macro2::{Group, Span, TokenStream, TokenTree}; use quote::ToTokens; use serde_derive_internals::Ctxt; @@ -53,6 +54,27 @@ impl Attrs { result } + pub fn as_metadata(&self) -> SchemaMetadata<'_> { + #[allow(clippy::ptr_arg)] + fn none_if_empty(s: &String) -> Option<&str> { + if s.is_empty() { + None + } else { + Some(s) + } + } + + SchemaMetadata { + title: self.title.as_ref().and_then(none_if_empty), + description: self.description.as_ref().and_then(none_if_empty), + deprecated: self.deprecated, + examples: &self.examples, + read_only: false, + write_only: false, + default: None, + } + } + fn populate( mut self, attrs: &[syn::Attribute], diff --git a/schemars_derive/src/attr/validation.rs b/schemars_derive/src/attr/validation.rs index c1f0ca7c..db72f31f 100644 --- a/schemars_derive/src/attr/validation.rs +++ b/schemars_derive/src/attr/validation.rs @@ -120,7 +120,7 @@ impl ValidationAttrs { self } - pub fn apply_to_schema(&self, schema_expr: TokenStream) -> TokenStream { + pub fn apply_to_schema(&self, schema_expr: &mut TokenStream) { let mut array_validation = Vec::new(); let mut number_validation = Vec::new(); let mut object_validation = Vec::new(); @@ -200,7 +200,7 @@ impl ValidationAttrs { || string_validation.is_some() || format.is_some() { - quote! { + *schema_expr = quote! { { let mut schema = #schema_expr; if let schemars::schema::Schema::Object(schema_object) = &mut schema @@ -214,8 +214,6 @@ impl ValidationAttrs { schema } } - } else { - schema_expr } } } diff --git a/schemars_derive/src/metadata.rs b/schemars_derive/src/metadata.rs index 05e5faff..aefe243e 100644 --- a/schemars_derive/src/metadata.rs +++ b/schemars_derive/src/metadata.rs @@ -1,5 +1,3 @@ -use crate::attr; -use attr::Attrs; use proc_macro2::TokenStream; #[derive(Debug, Clone)] @@ -14,24 +12,10 @@ pub struct SchemaMetadata<'a> { } impl<'a> SchemaMetadata<'a> { - pub fn from_attrs(attrs: &'a Attrs) -> Self { - SchemaMetadata { - title: attrs.title.as_ref().and_then(none_if_empty), - description: attrs.description.as_ref().and_then(none_if_empty), - deprecated: attrs.deprecated, - examples: &attrs.examples, - read_only: false, - write_only: false, - default: None, - } - } - - pub fn apply_to_schema(&self, schema_expr: TokenStream) -> TokenStream { + pub fn apply_to_schema(&self, schema_expr: &mut TokenStream) { let setters = self.make_setters(); - if setters.is_empty() { - schema_expr - } else { - quote! { + if !setters.is_empty() { + *schema_expr = quote! { schemars::_private::apply_metadata(#schema_expr, schemars::schema::Metadata { #(#setters)* ..Default::default() @@ -91,12 +75,3 @@ impl<'a> SchemaMetadata<'a> { setters } } - -#[allow(clippy::ptr_arg)] -fn none_if_empty(s: &String) -> Option<&str> { - if s.is_empty() { - None - } else { - Some(s) - } -} diff --git a/schemars_derive/src/schema_exprs.rs b/schemars_derive/src/schema_exprs.rs index 68dcec6e..b85e64d4 100644 --- a/schemars_derive/src/schema_exprs.rs +++ b/schemars_derive/src/schema_exprs.rs @@ -5,7 +5,7 @@ use serde_derive_internals::attr::{self as serde_attr, Default as SerdeDefault, use syn::spanned::Spanned; pub fn expr_for_container(cont: &Container) -> TokenStream { - let schema_expr = match &cont.data { + let mut schema_expr = match &cont.data { Data::Struct(Style::Unit, _) => expr_for_unit_struct(), Data::Struct(Style::Newtype, fields) => expr_for_newtype_struct(&fields[0]), Data::Struct(Style::Tuple, fields) => expr_for_tuple_struct(fields), @@ -17,8 +17,8 @@ pub fn expr_for_container(cont: &Container) -> TokenStream { Data::Enum(variants) => expr_for_enum(variants, &cont.serde_attrs), }; - let doc_metadata = SchemaMetadata::from_attrs(&cont.attrs); - doc_metadata.apply_to_schema(schema_expr) + cont.attrs.as_metadata().apply_to_schema(&mut schema_expr); + schema_expr } pub fn expr_for_repr(cont: &Container) -> Result { @@ -47,13 +47,13 @@ pub fn expr_for_repr(cont: &Container) -> Result { let enum_ident = &cont.ident; let variant_idents = variants.iter().map(|v| &v.ident); - let schema_expr = schema_object(quote! { + let mut schema_expr = schema_object(quote! { instance_type: Some(schemars::schema::InstanceType::Integer.into()), enum_values: Some(vec![#((#enum_ident::#variant_idents as #repr_type).into()),*]), }); - let doc_metadata = SchemaMetadata::from_attrs(&cont.attrs); - Ok(doc_metadata.apply_to_schema(schema_expr)) + cont.attrs.as_metadata().apply_to_schema(&mut schema_expr); + Ok(schema_expr) } fn expr_for_field(field: &Field, allow_ref: bool) -> TokenStream { @@ -61,29 +61,24 @@ fn expr_for_field(field: &Field, allow_ref: bool) -> TokenStream { let span = field.original.span(); let gen = quote!(gen); - let schema_expr = if field.validation_attrs.required { + let mut schema_expr = if field.validation_attrs.required { quote_spanned! {span=> - { - #type_def - <#ty as schemars::JsonSchema>::_schemars_private_non_optional_json_schema(#gen) - } + <#ty as schemars::JsonSchema>::_schemars_private_non_optional_json_schema(#gen) } } else if allow_ref { quote_spanned! {span=> - { - #type_def - #gen.subschema_for::<#ty>() - } + #gen.subschema_for::<#ty>() } } else { quote_spanned! {span=> - { - #type_def - <#ty as schemars::JsonSchema>::json_schema(#gen) - } + <#ty as schemars::JsonSchema>::json_schema(#gen) } }; - field.validation_attrs.apply_to_schema(schema_expr) + + prepend_type_def(type_def, &mut schema_expr); + field.validation_attrs.apply_to_schema(&mut schema_expr); + + schema_expr } pub fn type_for_field_schema(field: &Field) -> (syn::Type, Option) { @@ -167,7 +162,7 @@ fn expr_for_external_tagged_enum<'a>( let name = variant.name(); let sub_schema = expr_for_untagged_enum_variant(variant, deny_unknown_fields); - let schema_expr = schema_object(quote! { + let mut schema_expr = schema_object(quote! { instance_type: Some(schemars::schema::InstanceType::Object.into()), object: Some(Box::new(schemars::schema::ObjectValidation { properties: { @@ -184,8 +179,13 @@ fn expr_for_external_tagged_enum<'a>( ..Default::default() })), }); - let doc_metadata = SchemaMetadata::from_attrs(&variant.attrs); - doc_metadata.apply_to_schema(schema_expr) + + variant + .attrs + .as_metadata() + .apply_to_schema(&mut schema_expr); + + schema_expr })); schema_object(quote! { @@ -208,7 +208,7 @@ fn expr_for_internal_tagged_enum<'a>( enum_values: Some(vec![#name.into()]), }); - let tag_schema = schema_object(quote! { + let mut tag_schema = schema_object(quote! { instance_type: Some(schemars::schema::InstanceType::Object.into()), object: Some(Box::new(schemars::schema::ObjectValidation { properties: { @@ -224,15 +224,16 @@ fn expr_for_internal_tagged_enum<'a>( ..Default::default() })), }); - let doc_metadata = SchemaMetadata::from_attrs(&variant.attrs); - let tag_schema = doc_metadata.apply_to_schema(tag_schema); - - match expr_for_untagged_enum_variant_for_flatten(&variant, deny_unknown_fields) { - Some(variant_schema) => quote! { - #tag_schema.flatten(#variant_schema) - }, - None => tag_schema, + + variant.attrs.as_metadata().apply_to_schema(&mut tag_schema); + + if let Some(variant_schema) = + expr_for_untagged_enum_variant_for_flatten(&variant, deny_unknown_fields) + { + tag_schema.extend(quote!(.flatten(#variant_schema))) } + + tag_schema }); schema_object(quote! { @@ -248,9 +249,14 @@ fn expr_for_untagged_enum<'a>( deny_unknown_fields: bool, ) -> TokenStream { let schemas = variants.map(|variant| { - let schema_expr = expr_for_untagged_enum_variant(variant, deny_unknown_fields); - let doc_metadata = SchemaMetadata::from_attrs(&variant.attrs); - doc_metadata.apply_to_schema(schema_expr) + let mut schema_expr = expr_for_untagged_enum_variant(variant, deny_unknown_fields); + + variant + .attrs + .as_metadata() + .apply_to_schema(&mut schema_expr); + + schema_expr }); schema_object(quote! { @@ -297,7 +303,7 @@ fn expr_for_adjacent_tagged_enum<'a>( TokenStream::new() }; - let outer_schema = schema_object(quote! { + let mut outer_schema = schema_object(quote! { instance_type: Some(schemars::schema::InstanceType::Object.into()), object: Some(Box::new(schemars::schema::ObjectValidation { properties: { @@ -317,8 +323,12 @@ fn expr_for_adjacent_tagged_enum<'a>( })), }); - let doc_metadata = SchemaMetadata::from_attrs(&variant.attrs); - doc_metadata.apply_to_schema(outer_schema) + variant + .attrs + .as_metadata() + .apply_to_schema(&mut outer_schema); + + outer_schema }); schema_object(quote! { @@ -333,12 +343,12 @@ fn expr_for_untagged_enum_variant(variant: &Variant, deny_unknown_fields: bool) if let Some(with_attr) = &variant.attrs.with { let (ty, type_def) = type_for_schema(with_attr); let gen = quote!(gen); - return quote_spanned! {variant.original.span()=> - { - #type_def - #gen.subschema_for::<#ty>() - } + let mut schema_expr = quote_spanned! {variant.original.span()=> + #gen.subschema_for::<#ty>() }; + + prepend_type_def(type_def, &mut schema_expr); + return schema_expr; } match variant.style { @@ -356,12 +366,12 @@ fn expr_for_untagged_enum_variant_for_flatten( if let Some(with_attr) = &variant.attrs.with { let (ty, type_def) = type_for_schema(with_attr); let gen = quote!(gen); - return Some(quote_spanned! {variant.original.span()=> - { - #type_def - <#ty as schemars::JsonSchema>::json_schema(#gen) - } - }); + let mut schema_expr = quote_spanned! {variant.original.span()=> + <#ty as schemars::JsonSchema>::json_schema(#gen) + }; + + prepend_type_def(type_def, &mut schema_expr); + return Some(schema_expr); } Some(match variant.style { @@ -429,17 +439,6 @@ fn expr_for_struct( let (ty, type_def) = type_for_field_schema(field); - let gen = quote!(gen); - let schema_expr = if field.validation_attrs.required { - quote_spanned! {ty.span()=> - <#ty as schemars::JsonSchema>::_schemars_private_non_optional_json_schema(#gen) - } - } else { - quote_spanned! {ty.span()=> - #gen.subschema_for::<#ty>() - } - }; - let maybe_insert_required = match (&default, field.validation_attrs.required) { (Some(_), _) => TokenStream::new(), (None, false) => { @@ -458,11 +457,22 @@ fn expr_for_struct( read_only: field.serde_attrs.skip_deserializing(), write_only: field.serde_attrs.skip_serializing(), default, - ..SchemaMetadata::from_attrs(&field.attrs) + ..field.attrs.as_metadata() }; - let schema_expr = metadata.apply_to_schema(schema_expr); - let schema_expr = field.validation_attrs.apply_to_schema(schema_expr); + let gen = quote!(gen); + let mut schema_expr = if field.validation_attrs.required { + quote_spanned! {ty.span()=> + <#ty as schemars::JsonSchema>::_schemars_private_non_optional_json_schema(#gen) + } + } else { + quote_spanned! {ty.span()=> + #gen.subschema_for::<#ty>() + } + }; + + metadata.apply_to_schema(&mut schema_expr); + field.validation_attrs.apply_to_schema(&mut schema_expr); quote! { { @@ -486,12 +496,12 @@ fn expr_for_struct( }; let args = quote!(gen, #required); - quote_spanned! {ty.span()=> - .flatten({ - #type_def - schemars::_private::json_schema_for_flatten::<#ty>(#args) - }) - } + let mut schema_expr = quote_spanned! {ty.span()=> + schemars::_private::json_schema_for_flatten::<#ty>(#args) + }; + + prepend_type_def(type_def, &mut schema_expr); + schema_expr }) .collect(); @@ -513,7 +523,7 @@ fn expr_for_struct( #set_additional_properties #(#properties)* schemars::schema::Schema::Object(schema_object) - #(#flattens)* + #(.flatten(#flattens))* } } } @@ -582,3 +592,14 @@ fn schema_object(properties: TokenStream) -> TokenStream { }) } } + +fn prepend_type_def(type_def: Option, schema_expr: &mut TokenStream) { + if let Some(type_def) = type_def { + *schema_expr = quote! { + { + #type_def + #schema_expr + } + } + } +} From c013052f599cb6442df3497cefc89644a631471e Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Fri, 16 Apr 2021 22:31:03 +0100 Subject: [PATCH 08/44] Support inline regex --- schemars/tests/expected/validate.json | 5 +++++ schemars/tests/validate.rs | 2 ++ schemars_derive/src/attr/validation.rs | 20 ++++++++++++++++---- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/schemars/tests/expected/validate.json b/schemars/tests/expected/validate.json index f5a6fc24..0878e84a 100644 --- a/schemars/tests/expected/validate.json +++ b/schemars/tests/expected/validate.json @@ -13,6 +13,7 @@ "pair", "regex_str1", "regex_str2", + "regex_str3", "required_option", "tel", "x" @@ -32,6 +33,10 @@ "type": "string", "pattern": "^[Hh]ello\\b" }, + "regex_str3": { + "type": "string", + "pattern": "^\\d+$" + }, "contains_str1": { "type": "string", "pattern": "substring\\.\\.\\." diff --git a/schemars/tests/validate.rs b/schemars/tests/validate.rs index 00d1b575..ca704a71 100644 --- a/schemars/tests/validate.rs +++ b/schemars/tests/validate.rs @@ -14,6 +14,8 @@ 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 = "substring...")] contains_str1: String, #[validate(contains(pattern = "substring...", message = "bar"))] diff --git a/schemars_derive/src/attr/validation.rs b/schemars_derive/src/attr/validation.rs index db72f31f..8594db12 100644 --- a/schemars_derive/src/attr/validation.rs +++ b/schemars_derive/src/attr/validation.rs @@ -2,7 +2,7 @@ use super::parse_lit_str; use proc_macro2::TokenStream; use syn::ExprLit; use syn::NestedMeta; -use syn::{Expr, Lit, Meta, MetaNameValue, Path}; +use syn::{Expr, Lit, Meta, MetaNameValue}; #[derive(Debug, Default)] pub struct ValidationAttrs { @@ -11,7 +11,7 @@ pub struct ValidationAttrs { pub length_equal: Option, pub range_min: Option, pub range_max: Option, - pub regex: Option, + pub regex: Option, pub contains: Option, pub required: bool, pub format: Option<&'static str>, @@ -84,7 +84,9 @@ impl ValidationAttrs { path, lit: Lit::Str(regex), .. - })) if path.is_ident("regex") => self.regex = parse_lit_str(regex).ok(), + })) if path.is_ident("regex") => { + self.regex = parse_lit_str::(regex).ok().map(Expr::Path) + } NestedMeta::Meta(Meta::List(meta_list)) if meta_list.path.is_ident("regex") => { self.regex = meta_list.nested.iter().find_map(|x| match x { @@ -92,7 +94,17 @@ impl ValidationAttrs { path, lit: Lit::Str(regex), .. - })) if path.is_ident("path") => parse_lit_str(regex).ok(), + })) if path.is_ident("path") => { + parse_lit_str::(regex).ok().map(Expr::Path) + } + NestedMeta::Meta(Meta::NameValue(MetaNameValue { + path, + lit: Lit::Str(regex), + .. + })) if path.is_ident("pattern") => Some(Expr::Lit(syn::ExprLit { + attrs: Vec::new(), + lit: Lit::Str(regex.clone()), + })), _ => None, }); } From 7914593d89fe703458e64dd12c5475796b883232 Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Sun, 18 Apr 2021 22:17:53 +0100 Subject: [PATCH 09/44] Allow setting validation attributes via #[schemars(...)] --- docs/_includes/examples/schemars_attrs.rs | 9 +- .../examples/schemars_attrs.schema.json | 11 +- docs/_includes/examples/validate.rs | 24 ++ docs/_includes/examples/validate.schema.json | 64 +++++ schemars/examples/schemars_attrs.rs | 9 +- schemars/examples/schemars_attrs.schema.json | 11 +- schemars/examples/validate.rs | 24 ++ schemars/examples/validate.schema.json | 64 +++++ schemars/tests/expected/validate.json | 13 + .../expected/validate_schemars_attrs.json | 104 +++++++ schemars/tests/ui/invalid_attrs.stderr | 2 +- schemars/tests/ui/invalid_validation_attrs.rs | 9 + .../tests/ui/invalid_validation_attrs.stderr | 29 ++ schemars/tests/validate.rs | 49 ++++ schemars_derive/src/ast/from_serde.rs | 2 +- schemars_derive/src/attr/mod.rs | 22 +- schemars_derive/src/attr/validation.rs | 260 +++++++++++++----- 17 files changed, 607 insertions(+), 99 deletions(-) create mode 100644 docs/_includes/examples/validate.rs create mode 100644 docs/_includes/examples/validate.schema.json create mode 100644 schemars/examples/validate.rs create mode 100644 schemars/examples/validate.schema.json create mode 100644 schemars/tests/expected/validate_schemars_attrs.json create mode 100644 schemars/tests/ui/invalid_validation_attrs.rs create mode 100644 schemars/tests/ui/invalid_validation_attrs.stderr diff --git a/docs/_includes/examples/schemars_attrs.rs b/docs/_includes/examples/schemars_attrs.rs index f830e9f0..cd69b527 100644 --- a/docs/_includes/examples/schemars_attrs.rs +++ b/docs/_includes/examples/schemars_attrs.rs @@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize}; #[schemars(rename_all = "camelCase", deny_unknown_fields)] pub struct MyStruct { #[serde(rename = "thisIsOverridden")] - #[schemars(rename = "myNumber")] + #[schemars(rename = "myNumber", range(min = 1, max = 10))] pub my_int: i32, pub my_bool: bool, #[schemars(default)] @@ -15,8 +15,11 @@ pub struct MyStruct { #[derive(Deserialize, Serialize, JsonSchema)] #[schemars(untagged)] pub enum MyEnum { - StringNewType(String), - StructVariant { floats: Vec }, + StringNewType(#[schemars(phone)] String), + StructVariant { + #[schemars(length(min = 1, max = 100))] + floats: Vec, + }, } fn main() { diff --git a/docs/_includes/examples/schemars_attrs.schema.json b/docs/_includes/examples/schemars_attrs.schema.json index d0441932..958cb6bb 100644 --- a/docs/_includes/examples/schemars_attrs.schema.json +++ b/docs/_includes/examples/schemars_attrs.schema.json @@ -23,7 +23,9 @@ }, "myNumber": { "type": "integer", - "format": "int32" + "format": "int32", + "maximum": 10.0, + "minimum": 1.0 } }, "additionalProperties": false, @@ -31,7 +33,8 @@ "MyEnum": { "anyOf": [ { - "type": "string" + "type": "string", + "format": "phone" }, { "type": "object", @@ -44,7 +47,9 @@ "items": { "type": "number", "format": "float" - } + }, + "maxItems": 100, + "minItems": 1 } } } diff --git a/docs/_includes/examples/validate.rs b/docs/_includes/examples/validate.rs new file mode 100644 index 00000000..41169765 --- /dev/null +++ b/docs/_includes/examples/validate.rs @@ -0,0 +1,24 @@ +use schemars::{schema_for, JsonSchema}; + +#[derive(JsonSchema)] +pub struct MyStruct { + #[validate(range(min = 1, max = 10))] + pub my_int: i32, + pub my_bool: bool, + #[validate(required)] + pub my_nullable_enum: Option, +} + +#[derive(JsonSchema)] +pub enum MyEnum { + StringNewType(#[validate(phone)] String), + StructVariant { + #[validate(length(min = 1, max = 100))] + floats: Vec, + }, +} + +fn main() { + let schema = schema_for!(MyStruct); + println!("{}", serde_json::to_string_pretty(&schema).unwrap()); +} diff --git a/docs/_includes/examples/validate.schema.json b/docs/_includes/examples/validate.schema.json new file mode 100644 index 00000000..e8ed35e6 --- /dev/null +++ b/docs/_includes/examples/validate.schema.json @@ -0,0 +1,64 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MyStruct", + "type": "object", + "required": [ + "my_bool", + "my_int", + "my_nullable_enum" + ], + "properties": { + "my_bool": { + "type": "boolean" + }, + "my_int": { + "type": "integer", + "format": "int32", + "maximum": 10.0, + "minimum": 1.0 + }, + "my_nullable_enum": { + "anyOf": [ + { + "type": "object", + "required": [ + "StringNewType" + ], + "properties": { + "StringNewType": { + "type": "string", + "format": "phone" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "StructVariant" + ], + "properties": { + "StructVariant": { + "type": "object", + "required": [ + "floats" + ], + "properties": { + "floats": { + "type": "array", + "items": { + "type": "number", + "format": "float" + }, + "maxItems": 100, + "minItems": 1 + } + } + } + }, + "additionalProperties": false + } + ] + } + } +} diff --git a/schemars/examples/schemars_attrs.rs b/schemars/examples/schemars_attrs.rs index f830e9f0..cd69b527 100644 --- a/schemars/examples/schemars_attrs.rs +++ b/schemars/examples/schemars_attrs.rs @@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize}; #[schemars(rename_all = "camelCase", deny_unknown_fields)] pub struct MyStruct { #[serde(rename = "thisIsOverridden")] - #[schemars(rename = "myNumber")] + #[schemars(rename = "myNumber", range(min = 1, max = 10))] pub my_int: i32, pub my_bool: bool, #[schemars(default)] @@ -15,8 +15,11 @@ pub struct MyStruct { #[derive(Deserialize, Serialize, JsonSchema)] #[schemars(untagged)] pub enum MyEnum { - StringNewType(String), - StructVariant { floats: Vec }, + StringNewType(#[schemars(phone)] String), + StructVariant { + #[schemars(length(min = 1, max = 100))] + floats: Vec, + }, } fn main() { diff --git a/schemars/examples/schemars_attrs.schema.json b/schemars/examples/schemars_attrs.schema.json index d0441932..958cb6bb 100644 --- a/schemars/examples/schemars_attrs.schema.json +++ b/schemars/examples/schemars_attrs.schema.json @@ -23,7 +23,9 @@ }, "myNumber": { "type": "integer", - "format": "int32" + "format": "int32", + "maximum": 10.0, + "minimum": 1.0 } }, "additionalProperties": false, @@ -31,7 +33,8 @@ "MyEnum": { "anyOf": [ { - "type": "string" + "type": "string", + "format": "phone" }, { "type": "object", @@ -44,7 +47,9 @@ "items": { "type": "number", "format": "float" - } + }, + "maxItems": 100, + "minItems": 1 } } } diff --git a/schemars/examples/validate.rs b/schemars/examples/validate.rs new file mode 100644 index 00000000..41169765 --- /dev/null +++ b/schemars/examples/validate.rs @@ -0,0 +1,24 @@ +use schemars::{schema_for, JsonSchema}; + +#[derive(JsonSchema)] +pub struct MyStruct { + #[validate(range(min = 1, max = 10))] + pub my_int: i32, + pub my_bool: bool, + #[validate(required)] + pub my_nullable_enum: Option, +} + +#[derive(JsonSchema)] +pub enum MyEnum { + StringNewType(#[validate(phone)] String), + StructVariant { + #[validate(length(min = 1, max = 100))] + floats: Vec, + }, +} + +fn main() { + let schema = schema_for!(MyStruct); + println!("{}", serde_json::to_string_pretty(&schema).unwrap()); +} diff --git a/schemars/examples/validate.schema.json b/schemars/examples/validate.schema.json new file mode 100644 index 00000000..e8ed35e6 --- /dev/null +++ b/schemars/examples/validate.schema.json @@ -0,0 +1,64 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MyStruct", + "type": "object", + "required": [ + "my_bool", + "my_int", + "my_nullable_enum" + ], + "properties": { + "my_bool": { + "type": "boolean" + }, + "my_int": { + "type": "integer", + "format": "int32", + "maximum": 10.0, + "minimum": 1.0 + }, + "my_nullable_enum": { + "anyOf": [ + { + "type": "object", + "required": [ + "StringNewType" + ], + "properties": { + "StringNewType": { + "type": "string", + "format": "phone" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "StructVariant" + ], + "properties": { + "StructVariant": { + "type": "object", + "required": [ + "floats" + ], + "properties": { + "floats": { + "type": "array", + "items": { + "type": "number", + "format": "float" + }, + "maxItems": 100, + "minItems": 1 + } + } + } + }, + "additionalProperties": false + } + ] + } + } +} diff --git a/schemars/tests/expected/validate.json b/schemars/tests/expected/validate.json index 0878e84a..d4a14e3f 100644 --- a/schemars/tests/expected/validate.json +++ b/schemars/tests/expected/validate.json @@ -9,7 +9,9 @@ "homepage", "map_contains", "min_max", + "min_max2", "non_empty_str", + "non_empty_str2", "pair", "regex_str1", "regex_str2", @@ -25,6 +27,12 @@ "maximum": 100.0, "minimum": 0.01 }, + "min_max2": { + "type": "number", + "format": "float", + "maximum": 1000.0, + "minimum": 1.0 + }, "regex_str1": { "type": "string", "pattern": "^[Hh]ello\\b" @@ -62,6 +70,11 @@ "maxLength": 100, "minLength": 1 }, + "non_empty_str2": { + "type": "string", + "maxLength": 1000, + "minLength": 1 + }, "pair": { "type": "array", "items": { diff --git a/schemars/tests/expected/validate_schemars_attrs.json b/schemars/tests/expected/validate_schemars_attrs.json new file mode 100644 index 00000000..d4a14e3f --- /dev/null +++ b/schemars/tests/expected/validate_schemars_attrs.json @@ -0,0 +1,104 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Struct", + "type": "object", + "required": [ + "contains_str1", + "contains_str2", + "email_address", + "homepage", + "map_contains", + "min_max", + "min_max2", + "non_empty_str", + "non_empty_str2", + "pair", + "regex_str1", + "regex_str2", + "regex_str3", + "required_option", + "tel", + "x" + ], + "properties": { + "min_max": { + "type": "number", + "format": "float", + "maximum": 100.0, + "minimum": 0.01 + }, + "min_max2": { + "type": "number", + "format": "float", + "maximum": 1000.0, + "minimum": 1.0 + }, + "regex_str1": { + "type": "string", + "pattern": "^[Hh]ello\\b" + }, + "regex_str2": { + "type": "string", + "pattern": "^[Hh]ello\\b" + }, + "regex_str3": { + "type": "string", + "pattern": "^\\d+$" + }, + "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 + }, + "non_empty_str2": { + "type": "string", + "maxLength": 1000, + "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" + }, + "x": { + "type": "integer", + "format": "int32" + } + } +} \ No newline at end of file diff --git a/schemars/tests/ui/invalid_attrs.stderr b/schemars/tests/ui/invalid_attrs.stderr index fa3a4f46..48238591 100644 --- a/schemars/tests/ui/invalid_attrs.stderr +++ b/schemars/tests/ui/invalid_attrs.stderr @@ -22,7 +22,7 @@ error: duplicate serde attribute `deny_unknown_fields` 8 | #[schemars(default = 0, foo, deny_unknown_fields, deny_unknown_fields)] | ^^^^^^^^^^^^^^^^^^^ -error: unknown schemars container attribute `foo` +error: unknown schemars attribute `foo` --> $DIR/invalid_attrs.rs:8:25 | 8 | #[schemars(default = 0, foo, deny_unknown_fields, deny_unknown_fields)] diff --git a/schemars/tests/ui/invalid_validation_attrs.rs b/schemars/tests/ui/invalid_validation_attrs.rs new file mode 100644 index 00000000..144ddaa0 --- /dev/null +++ b/schemars/tests/ui/invalid_validation_attrs.rs @@ -0,0 +1,9 @@ +use schemars::JsonSchema; + +#[derive(JsonSchema)] +pub struct Struct1(#[validate(regex = 0, foo, length(min = 1, equal = 2, bar))] String); + +#[derive(JsonSchema)] +pub struct Struct2(#[schemars(regex = 0, foo, length(min = 1, equal = 2, bar))] String); + +fn main() {} diff --git a/schemars/tests/ui/invalid_validation_attrs.stderr b/schemars/tests/ui/invalid_validation_attrs.stderr new file mode 100644 index 00000000..3d680824 --- /dev/null +++ b/schemars/tests/ui/invalid_validation_attrs.stderr @@ -0,0 +1,29 @@ +error: expected validate regex attribute to be a string: `regex = "..."` + --> $DIR/invalid_validation_attrs.rs:4:39 + | +4 | pub struct Struct1(#[validate(regex = 0, foo, length(min = 1, equal = 2, bar))] String); + | ^ + +error: unknown schemars attribute `foo` + --> $DIR/invalid_validation_attrs.rs:7:42 + | +7 | pub struct Struct2(#[schemars(regex = 0, foo, length(min = 1, equal = 2, bar))] String); + | ^^^ + +error: expected schemars regex attribute to be a string: `regex = "..."` + --> $DIR/invalid_validation_attrs.rs:7:39 + | +7 | pub struct Struct2(#[schemars(regex = 0, foo, length(min = 1, equal = 2, bar))] String); + | ^ + +error: schemars attribute cannot contain both `equal` and `min` + --> $DIR/invalid_validation_attrs.rs:7:63 + | +7 | pub struct Struct2(#[schemars(regex = 0, foo, length(min = 1, equal = 2, bar))] String); + | ^^^^^^^^^ + +error: unknown item in schemars length attribute + --> $DIR/invalid_validation_attrs.rs:7:74 + | +7 | pub struct Struct2(#[schemars(regex = 0, foo, length(min = 1, equal = 2, bar))] String); + | ^^^ diff --git a/schemars/tests/validate.rs b/schemars/tests/validate.rs index ca704a71..825b558e 100644 --- a/schemars/tests/validate.rs +++ b/schemars/tests/validate.rs @@ -6,10 +6,15 @@ 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"; +const MIN: u32 = 1; +const MAX: u32 = 1000; + #[derive(Debug, JsonSchema)] pub struct Struct { #[validate(range(min = 0.01, max = 100))] min_max: f32, + #[validate(range(min = "MIN", max = "MAX"))] + min_max2: f32, #[validate(regex = "STARTS_WITH_HELLO")] regex_str1: String, #[validate(regex(path = "STARTS_WITH_HELLO", code = "foo"))] @@ -28,6 +33,8 @@ pub struct Struct { homepage: String, #[validate(length(min = 1, max = 100))] non_empty_str: String, + #[validate(length(min = "MIN", max = "MAX"))] + non_empty_str2: String, #[validate(length(equal = 2))] pair: Vec, #[validate(contains = "map_key")] @@ -49,6 +56,48 @@ fn validate() -> TestResult { test_default_generated_schema::("validate") } +#[derive(Debug, JsonSchema)] +pub struct Struct2 { + #[schemars(range(min = 0.01, max = 100))] + min_max: f32, + #[schemars(range(min = "MIN", max = "MAX"))] + min_max2: f32, + #[schemars(regex = "STARTS_WITH_HELLO")] + regex_str1: String, + #[schemars(regex(path = "STARTS_WITH_HELLO"))] + regex_str2: String, + #[schemars(regex(pattern = r"^\d+$"))] + regex_str3: String, + #[schemars(contains = "substring...")] + contains_str1: String, + #[schemars(contains(pattern = "substring..."))] + contains_str2: String, + #[schemars(email)] + email_address: String, + #[schemars(phone)] + tel: 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(contains = "map_key")] + map_contains: HashMap, + #[schemars(required)] + required_option: Option, + #[schemars(required)] + #[serde(flatten)] + required_flattened: Option, +} + +#[test] +fn validate_schemars_attrs() -> TestResult { + test_default_generated_schema::("validate_schemars_attrs") +} + #[derive(Debug, JsonSchema)] pub struct Tuple( #[validate(range(max = 10))] u8, diff --git a/schemars_derive/src/ast/from_serde.rs b/schemars_derive/src/ast/from_serde.rs index db2e092e..83bfcf30 100644 --- a/schemars_derive/src/ast/from_serde.rs +++ b/schemars_derive/src/ast/from_serde.rs @@ -73,7 +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), + validation_attrs: ValidationAttrs::new(&serde.original.attrs, errors), }) } } diff --git a/schemars_derive/src/attr/mod.rs b/schemars_derive/src/attr/mod.rs index 93f76c59..cc81a920 100644 --- a/schemars_derive/src/attr/mod.rs +++ b/schemars_derive/src/attr/mod.rs @@ -165,10 +165,7 @@ impl Attrs { _ if ignore_errors => {} Meta(meta_item) => { - let is_known_serde_keyword = schemars_to_serde::SERDE_KEYWORDS - .iter() - .any(|k| meta_item.path().is_ident(k)); - if !is_known_serde_keyword { + if !is_known_serde_or_validation_keyword(meta_item) { let path = meta_item .path() .into_token_stream() @@ -176,16 +173,13 @@ impl Attrs { .replace(' ', ""); errors.error_spanned_by( meta_item.path(), - format!("unknown schemars container attribute `{}`", path), + format!("unknown schemars attribute `{}`", path), ); } } Lit(lit) => { - errors.error_spanned_by( - lit, - "unexpected literal in schemars container attribute", - ); + errors.error_spanned_by(lit, "unexpected literal in schemars attribute"); } } } @@ -193,6 +187,16 @@ impl Attrs { } } +fn is_known_serde_or_validation_keyword(meta: &syn::Meta) -> bool { + let mut known_keywords = schemars_to_serde::SERDE_KEYWORDS + .iter() + .chain(validation::VALIDATION_KEYWORDS); + meta.path() + .get_ident() + .map(|i| known_keywords.any(|k| i == k)) + .unwrap_or(false) +} + fn get_meta_items( attr: &syn::Attribute, attr_type: &'static str, diff --git a/schemars_derive/src/attr/validation.rs b/schemars_derive/src/attr/validation.rs index 8594db12..9b807b38 100644 --- a/schemars_derive/src/attr/validation.rs +++ b/schemars_derive/src/attr/validation.rs @@ -1,8 +1,11 @@ -use super::parse_lit_str; +use super::{get_lit_str, get_meta_items, parse_lit_into_path, parse_lit_str}; use proc_macro2::TokenStream; -use syn::ExprLit; -use syn::NestedMeta; -use syn::{Expr, Lit, Meta, MetaNameValue}; +use serde_derive_internals::Ctxt; +use syn::{Expr, ExprLit, ExprPath, Lit, Meta, MetaNameValue, NestedMeta}; + +pub(crate) static VALIDATION_KEYWORDS: &[&str] = &[ + "range", "regex", "contains", "email", "phone", "url", "length", "required", +]; #[derive(Debug, Default)] pub struct ValidationAttrs { @@ -18,16 +21,43 @@ pub struct ValidationAttrs { } impl ValidationAttrs { - pub fn new(attrs: &[syn::Attribute]) -> Self { + pub fn new(attrs: &[syn::Attribute], errors: &Ctxt) -> Self { // TODO allow setting "validate" attributes through #[schemars(...)] - ValidationAttrs::default().populate(attrs) + ValidationAttrs::default() + .populate(attrs, "schemars", false, errors) + .populate(attrs, "validate", true, errors) } - fn populate(mut self, attrs: &[syn::Attribute]) -> Self { - // TODO don't silently ignore unparseable attributes + fn populate( + mut self, + attrs: &[syn::Attribute], + attr_type: &'static str, + ignore_errors: bool, + errors: &Ctxt, + ) -> Self { + let duplicate_error = |meta: &MetaNameValue| { + if !ignore_errors { + let msg = format!( + "duplicate schemars attribute `{}`", + meta.path.get_ident().unwrap() + ); + errors.error_spanned_by(meta, msg) + } + }; + let mutual_exclusive_error = |meta: &MetaNameValue, other: &str| { + if !ignore_errors { + let msg = format!( + "schemars attribute cannot contain both `{}` and `{}`", + meta.path.get_ident().unwrap(), + other, + ); + errors.error_spanned_by(meta, msg) + } + }; + for meta_item in attrs .iter() - .flat_map(|attr| get_meta_items(attr, "validate")) + .flat_map(|attr| get_meta_items(attr, attr_type, errors)) .flatten() { match &meta_item { @@ -35,15 +65,43 @@ impl ValidationAttrs { 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); + if self.length_min.is_some() { + duplicate_error(nv) + } else if self.length_equal.is_some() { + mutual_exclusive_error(nv, "equal") + } else { + self.length_min = str_or_num_to_expr(&errors, "min", &nv.lit); + } } NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("max") => { - self.length_max = str_or_num_to_expr(&nv.lit); + if self.length_max.is_some() { + duplicate_error(nv) + } else if self.length_equal.is_some() { + mutual_exclusive_error(nv, "equal") + } else { + self.length_max = str_or_num_to_expr(&errors, "max", &nv.lit); + } } NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("equal") => { - self.length_equal = str_or_num_to_expr(&nv.lit); + if self.length_equal.is_some() { + duplicate_error(nv) + } else if self.length_min.is_some() { + mutual_exclusive_error(nv, "min") + } else if self.length_max.is_some() { + mutual_exclusive_error(nv, "max") + } else { + self.length_equal = + str_or_num_to_expr(&errors, "equal", &nv.lit); + } + } + meta => { + if !ignore_errors { + errors.error_spanned_by( + meta, + format!("unknown item in schemars length attribute"), + ); + } } - _ => {} } } } @@ -52,78 +110,118 @@ impl ValidationAttrs { 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); + if self.range_min.is_some() { + duplicate_error(nv) + } else { + self.range_min = str_or_num_to_expr(&errors, "min", &nv.lit); + } } NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("max") => { - self.range_max = str_or_num_to_expr(&nv.lit); + if self.range_max.is_some() { + duplicate_error(nv) + } else { + self.range_max = str_or_num_to_expr(&errors, "max", &nv.lit); + } + } + meta => { + if !ignore_errors { + errors.error_spanned_by( + meta, + format!("unknown item in schemars range attribute"), + ); + } } - _ => {} } } } - NestedMeta::Meta(m) - if m.path().is_ident("required") || m.path().is_ident("required_nested") => + NestedMeta::Meta(Meta::Path(m)) + if m.is_ident("required") || m.is_ident("required_nested") => { self.required = true; } - NestedMeta::Meta(m) if m.path().is_ident("email") => { + // TODO cause compile error if format is already Some + // FIXME #[validate(...)] overrides #[schemars(...)] - should be other way around! + NestedMeta::Meta(Meta::Path(m)) if m.is_ident("email") => { self.format = Some("email"); } - - NestedMeta::Meta(m) if m.path().is_ident("url") => { + NestedMeta::Meta(Meta::Path(m)) if m.is_ident("url") => { self.format = Some("uri"); } - - NestedMeta::Meta(m) if m.path().is_ident("phone") => { + NestedMeta::Meta(Meta::Path(m)) if m.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().map(Expr::Path) + // TODO cause compile error if regex/contains are specified more than once + // FIXME #[validate(...)] overrides #[schemars(...)] - should be other way around! + NestedMeta::Meta(Meta::NameValue(MetaNameValue { path, lit, .. })) + if path.is_ident("regex") => + { + self.regex = parse_lit_into_expr_path(errors, attr_type, "regex", lit).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().map(Expr::Path) + for x in meta_list.nested.iter() { + match x { + NestedMeta::Meta(Meta::NameValue(MetaNameValue { + path, lit, .. + })) if path.is_ident("path") => { + self.regex = + parse_lit_into_expr_path(errors, attr_type, "path", lit).ok() + } + NestedMeta::Meta(Meta::NameValue(MetaNameValue { + path, lit, .. + })) if path.is_ident("pattern") => { + self.regex = get_lit_str(errors, attr_type, "pattern", lit) + .ok() + .map(|litstr| { + Expr::Lit(syn::ExprLit { + attrs: Vec::new(), + lit: Lit::Str(litstr.clone()), + }) + }) + } + meta => { + if !ignore_errors { + errors.error_spanned_by( + meta, + format!("unknown item in schemars regex attribute"), + ); + } + } } - NestedMeta::Meta(Meta::NameValue(MetaNameValue { - path, - lit: Lit::Str(regex), - .. - })) if path.is_ident("pattern") => Some(Expr::Lit(syn::ExprLit { - attrs: Vec::new(), - lit: Lit::Str(regex.clone()), - })), - _ => None, - }); + } } - NestedMeta::Meta(Meta::NameValue(MetaNameValue { - path, - lit: Lit::Str(contains), - .. - })) if path.is_ident("contains") => self.contains = Some(contains.value()), + NestedMeta::Meta(Meta::NameValue(MetaNameValue { path, lit, .. })) + if path.is_ident("contains") => + { + self.contains = get_lit_str(errors, attr_type, "contains", lit) + .ok() + .map(|litstr| litstr.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, - }); + for x in meta_list.nested.iter() { + match x { + NestedMeta::Meta(Meta::NameValue(MetaNameValue { + path, lit, .. + })) if path.is_ident("pattern") => { + self.contains = get_lit_str(errors, attr_type, "contains", lit) + .ok() + .map(|litstr| litstr.value()) + } + meta => { + if !ignore_errors { + errors.error_spanned_by( + meta, + format!("unknown item in schemars contains attribute"), + ); + } + } + } + } } _ => {} @@ -230,6 +328,21 @@ impl ValidationAttrs { } } +fn parse_lit_into_expr_path( + cx: &Ctxt, + attr_type: &'static str, + meta_item_name: &'static str, + lit: &syn::Lit, +) -> Result { + parse_lit_into_path(cx, attr_type, meta_item_name, lit).map(|path| { + Expr::Path(ExprPath { + attrs: Vec::new(), + qself: None, + path, + }) + }) +} + fn wrap_array_validation(v: Vec) -> Option { if v.is_empty() { None @@ -283,27 +396,22 @@ fn wrap_string_validation(v: Vec) -> Option { } } -fn get_meta_items( - attr: &syn::Attribute, - attr_type: &'static str, -) -> Result, ()> { - 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 { +fn str_or_num_to_expr(cx: &Ctxt, meta_item_name: &str, lit: &Lit) -> Option { match lit { - Lit::Str(s) => parse_lit_str::(s).ok().map(Expr::Path), + Lit::Str(s) => parse_lit_str::(s).ok().map(Expr::Path), Lit::Int(_) | Lit::Float(_) => Some(Expr::Lit(ExprLit { attrs: Vec::new(), lit: lit.clone(), })), - _ => None, + _ => { + cx.error_spanned_by( + lit, + format!( + "expected `{}` to be a string or number literal", + meta_item_name + ), + ); + None + } } } From d99a96fc8aa7e8cd546ed0cd8b86e4b6f1d9857f Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Sat, 24 Apr 2021 11:46:07 +0100 Subject: [PATCH 10/44] Add some doc comments --- schemars/src/_private.rs | 5 ++--- schemars/src/schema.rs | 21 +++++++++++++++++++-- schemars_derive/src/attr/validation.rs | 1 - schemars_derive/src/schema_exprs.rs | 6 +----- 4 files changed, 22 insertions(+), 11 deletions(-) diff --git a/schemars/src/_private.rs b/schemars/src/_private.rs index 1799f415..b914699f 100644 --- a/schemars/src/_private.rs +++ b/schemars/src/_private.rs @@ -6,12 +6,11 @@ use crate::JsonSchema; // Helper for generating schemas for flattened `Option` fields. pub fn json_schema_for_flatten( gen: &mut SchemaGenerator, - required: Option, + required: bool, ) -> Schema { let mut schema = T::_schemars_private_non_optional_json_schema(gen); - let required = required.unwrap_or_else(|| !T::_schemars_private_is_option()); - if !required { + if T::_schemars_private_is_option() && !required { if let Schema::Object(SchemaObject { object: Some(ref mut object_validation), .. diff --git a/schemars/src/schema.rs b/schemars/src/schema.rs index e9a01242..d358a444 100644 --- a/schemars/src/schema.rs +++ b/schemars/src/schema.rs @@ -231,7 +231,11 @@ impl SchemaObject { self.reference.is_some() } - // TODO document + /// Returns `true` if `self` accepts values of the given type, according to the [`instance_type`] field. + /// + /// This is a basic check that always returns `true` if no `instance_type` is specified on the schema, + /// and does not check any subschemas. Because of this, both `{}` and `{"not": {}}` accept any type according + /// to this method. pub fn has_type(&self, ty: InstanceType) -> bool { self.instance_type .as_ref() @@ -522,7 +526,20 @@ impl From> for SingleOrVec { } impl SingleOrVec { - // TODO document + /// Returns `true` if `self` is either a `Single` equal to `x`, or a `Vec` containing `x`. + /// + /// # Examples + /// + /// ``` + /// let s = SingleOrVec::Single(10); + /// assert!(s.contains(&10)); + /// assert!(!s.contains(&20)); + /// + /// let v = SingleOrVec::Vec(vec![10, 20]); + /// assert!(s.contains(&10)); + /// assert!(s.contains(&20)); + /// assert!(!s.contains(&30)); + /// ``` pub fn contains(&self, x: &T) -> bool { match self { SingleOrVec::Single(s) => s.deref() == x, diff --git a/schemars_derive/src/attr/validation.rs b/schemars_derive/src/attr/validation.rs index 9b807b38..1dccc6d9 100644 --- a/schemars_derive/src/attr/validation.rs +++ b/schemars_derive/src/attr/validation.rs @@ -22,7 +22,6 @@ pub struct ValidationAttrs { impl ValidationAttrs { pub fn new(attrs: &[syn::Attribute], errors: &Ctxt) -> Self { - // TODO allow setting "validate" attributes through #[schemars(...)] ValidationAttrs::default() .populate(attrs, "schemars", false, errors) .populate(attrs, "validate", true, errors) diff --git a/schemars_derive/src/schema_exprs.rs b/schemars_derive/src/schema_exprs.rs index b85e64d4..c50f3324 100644 --- a/schemars_derive/src/schema_exprs.rs +++ b/schemars_derive/src/schema_exprs.rs @@ -489,11 +489,7 @@ fn expr_for_struct( .map(|field| { let (ty, type_def) = type_for_field_schema(field); - let required = if field.validation_attrs.required { - quote!(Some(true)) - } else { - quote!(None) - }; + let required = field.validation_attrs.required; let args = quote!(gen, #required); let mut schema_expr = quote_spanned! {ty.span()=> From af69a8ea11905ff8496f67d3052074cc44d962ad Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Sat, 24 Apr 2021 13:43:45 +0100 Subject: [PATCH 11/44] Fix doc test --- schemars/src/schema.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/schemars/src/schema.rs b/schemars/src/schema.rs index d358a444..01fce865 100644 --- a/schemars/src/schema.rs +++ b/schemars/src/schema.rs @@ -531,14 +531,16 @@ impl SingleOrVec { /// # Examples /// /// ``` - /// let s = SingleOrVec::Single(10); + /// use schemars::schema::SingleOrVec; + /// + /// let s = SingleOrVec::from(10); /// assert!(s.contains(&10)); /// assert!(!s.contains(&20)); /// - /// let v = SingleOrVec::Vec(vec![10, 20]); - /// assert!(s.contains(&10)); - /// assert!(s.contains(&20)); - /// assert!(!s.contains(&30)); + /// let v = SingleOrVec::from(vec![10, 20]); + /// assert!(v.contains(&10)); + /// assert!(v.contains(&20)); + /// assert!(!v.contains(&30)); /// ``` pub fn contains(&self, x: &T) -> bool { match self { From 605db3bba8a6b91632478a8626d08dac1bb8df7d Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Fri, 17 Sep 2021 22:57:51 +0100 Subject: [PATCH 12/44] Emit compilation errors for duplicate validation attributes --- schemars/tests/ui/invalid_validation_attrs.rs | 26 ++ .../tests/ui/invalid_validation_attrs.stderr | 32 ++- schemars/tests/ui/repr_missing.stderr | 2 +- schemars/tests/ui/schema_for_arg_value.stderr | 2 +- schemars/tests/validate.rs | 2 + schemars_derive/src/attr/validation.rs | 248 ++++++++++++------ schemars_derive/src/schema_exprs.rs | 8 +- 7 files changed, 230 insertions(+), 90 deletions(-) diff --git a/schemars/tests/ui/invalid_validation_attrs.rs b/schemars/tests/ui/invalid_validation_attrs.rs index 144ddaa0..be843625 100644 --- a/schemars/tests/ui/invalid_validation_attrs.rs +++ b/schemars/tests/ui/invalid_validation_attrs.rs @@ -6,4 +6,30 @@ pub struct Struct1(#[validate(regex = 0, foo, length(min = 1, equal = 2, bar))] #[derive(JsonSchema)] pub struct Struct2(#[schemars(regex = 0, foo, length(min = 1, equal = 2, bar))] String); +#[derive(JsonSchema)] +pub struct Struct3( + #[validate( + regex = "foo", + contains = "bar", + regex(path = "baz"), + phone, + email, + url + )] + String, +); + +#[derive(JsonSchema)] +pub struct Struct4( + #[schemars( + regex = "foo", + contains = "bar", + regex(path = "baz"), + phone, + email, + url + )] + String, +); + fn main() {} diff --git a/schemars/tests/ui/invalid_validation_attrs.stderr b/schemars/tests/ui/invalid_validation_attrs.stderr index 3d680824..933fd66f 100644 --- a/schemars/tests/ui/invalid_validation_attrs.stderr +++ b/schemars/tests/ui/invalid_validation_attrs.stderr @@ -20,10 +20,40 @@ error: schemars attribute cannot contain both `equal` and `min` --> $DIR/invalid_validation_attrs.rs:7:63 | 7 | pub struct Struct2(#[schemars(regex = 0, foo, length(min = 1, equal = 2, bar))] String); - | ^^^^^^^^^ + | ^^^^^ error: unknown item in schemars length attribute --> $DIR/invalid_validation_attrs.rs:7:74 | 7 | pub struct Struct2(#[schemars(regex = 0, foo, length(min = 1, equal = 2, bar))] String); | ^^^ + +error: schemars attribute cannot contain both `contains` and `regex` + --> $DIR/invalid_validation_attrs.rs:26:9 + | +26 | contains = "bar", + | ^^^^^^^^ + +error: duplicate schemars attribute `regex` + --> $DIR/invalid_validation_attrs.rs:27:9 + | +27 | regex(path = "baz"), + | ^^^^^ + +error: schemars attribute cannot contain both `phone` and `email` + --> $DIR/invalid_validation_attrs.rs:29:9 + | +29 | email, + | ^^^^^ + +error: schemars attribute cannot contain both `phone` and `url` + --> $DIR/invalid_validation_attrs.rs:30:9 + | +30 | url + | ^^^ + +error[E0425]: cannot find value `foo` in this scope + --> $DIR/invalid_validation_attrs.rs:12:17 + | +12 | regex = "foo", + | ^^^^^ not found in this scope diff --git a/schemars/tests/ui/repr_missing.stderr b/schemars/tests/ui/repr_missing.stderr index 495c1778..a7016b2d 100644 --- a/schemars/tests/ui/repr_missing.stderr +++ b/schemars/tests/ui/repr_missing.stderr @@ -4,4 +4,4 @@ error: JsonSchema_repr: missing #[repr(...)] attribute 3 | #[derive(JsonSchema_repr)] | ^^^^^^^^^^^^^^^ | - = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the derive macro `JsonSchema_repr` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/schemars/tests/ui/schema_for_arg_value.stderr b/schemars/tests/ui/schema_for_arg_value.stderr index c7879859..a3163065 100644 --- a/schemars/tests/ui/schema_for_arg_value.stderr +++ b/schemars/tests/ui/schema_for_arg_value.stderr @@ -4,4 +4,4 @@ error: This argument to `schema_for!` is not a type - did you mean to use `schem 4 | let _schema = schema_for!(123); | ^^^^^^^^^^^^^^^^ | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `schema_for` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/schemars/tests/validate.rs b/schemars/tests/validate.rs index 825b558e..c2060e0a 100644 --- a/schemars/tests/validate.rs +++ b/schemars/tests/validate.rs @@ -62,12 +62,14 @@ pub struct Struct2 { min_max: f32, #[schemars(range(min = "MIN", max = "MAX"))] min_max2: f32, + #[validate(regex = "overridden")] #[schemars(regex = "STARTS_WITH_HELLO")] regex_str1: String, #[schemars(regex(path = "STARTS_WITH_HELLO"))] regex_str2: String, #[schemars(regex(pattern = r"^\d+$"))] regex_str3: String, + #[validate(regex = "overridden")] #[schemars(contains = "substring...")] contains_str1: String, #[schemars(contains(pattern = "substring..."))] diff --git a/schemars_derive/src/attr/validation.rs b/schemars_derive/src/attr/validation.rs index 1dccc6d9..398fa591 100644 --- a/schemars_derive/src/attr/validation.rs +++ b/schemars_derive/src/attr/validation.rs @@ -1,23 +1,48 @@ use super::{get_lit_str, get_meta_items, parse_lit_into_path, parse_lit_str}; use proc_macro2::TokenStream; use serde_derive_internals::Ctxt; -use syn::{Expr, ExprLit, ExprPath, Lit, Meta, MetaNameValue, NestedMeta}; +use syn::{Expr, ExprLit, ExprPath, Lit, Meta, MetaNameValue, NestedMeta, Path}; pub(crate) static VALIDATION_KEYWORDS: &[&str] = &[ "range", "regex", "contains", "email", "phone", "url", "length", "required", ]; +#[derive(Debug, Clone, Copy, PartialEq)] +enum Format { + Email, + Uri, + Phone, +} + +impl Format { + fn attr_str(self) -> &'static str { + match self { + Format::Email => "email", + Format::Uri => "url", + Format::Phone => "phone", + } + } + + fn schema_str(self) -> &'static str { + match self { + Format::Email => "email", + Format::Uri => "uri", + Format::Phone => "phone", + } + } +} + #[derive(Debug, Default)] pub struct ValidationAttrs { - pub length_min: Option, - pub length_max: Option, - pub length_equal: Option, - pub range_min: Option, - pub range_max: Option, - pub regex: Option, - pub contains: Option, - pub required: bool, - pub format: Option<&'static str>, + length_min: Option, + length_max: Option, + length_equal: Option, + range_min: Option, + range_max: Option, + regex: Option, + contains: Option, + required: bool, + format: Option, } impl ValidationAttrs { @@ -27,6 +52,10 @@ impl ValidationAttrs { .populate(attrs, "validate", true, errors) } + pub fn required(&self) -> bool { + self.required + } + fn populate( mut self, attrs: &[syn::Attribute], @@ -34,23 +63,37 @@ impl ValidationAttrs { ignore_errors: bool, errors: &Ctxt, ) -> Self { - let duplicate_error = |meta: &MetaNameValue| { + let duplicate_error = |path: &Path| { if !ignore_errors { let msg = format!( "duplicate schemars attribute `{}`", - meta.path.get_ident().unwrap() + path.get_ident().unwrap() ); - errors.error_spanned_by(meta, msg) + errors.error_spanned_by(path, msg) } }; - let mutual_exclusive_error = |meta: &MetaNameValue, other: &str| { + let mutual_exclusive_error = |path: &Path, other: &str| { if !ignore_errors { let msg = format!( "schemars attribute cannot contain both `{}` and `{}`", - meta.path.get_ident().unwrap(), + path.get_ident().unwrap(), other, ); - errors.error_spanned_by(meta, msg) + errors.error_spanned_by(path, msg) + } + }; + let duplicate_format_error = |existing: Format, new: Format, path: &syn::Path| { + if !ignore_errors { + let msg = if existing == new { + format!("duplicate schemars attribute `{}`", existing.attr_str()) + } else { + format!( + "schemars attribute cannot contain both `{}` and `{}`", + existing.attr_str(), + new.attr_str(), + ) + }; + errors.error_spanned_by(path, msg) } }; @@ -65,29 +108,29 @@ impl ValidationAttrs { match nested { NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("min") => { if self.length_min.is_some() { - duplicate_error(nv) + duplicate_error(&nv.path) } else if self.length_equal.is_some() { - mutual_exclusive_error(nv, "equal") + mutual_exclusive_error(&nv.path, "equal") } else { self.length_min = str_or_num_to_expr(&errors, "min", &nv.lit); } } NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("max") => { if self.length_max.is_some() { - duplicate_error(nv) + duplicate_error(&nv.path) } else if self.length_equal.is_some() { - mutual_exclusive_error(nv, "equal") + mutual_exclusive_error(&nv.path, "equal") } else { self.length_max = str_or_num_to_expr(&errors, "max", &nv.lit); } } NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("equal") => { if self.length_equal.is_some() { - duplicate_error(nv) + duplicate_error(&nv.path) } else if self.length_min.is_some() { - mutual_exclusive_error(nv, "min") + mutual_exclusive_error(&nv.path, "min") } else if self.length_max.is_some() { - mutual_exclusive_error(nv, "max") + mutual_exclusive_error(&nv.path, "max") } else { self.length_equal = str_or_num_to_expr(&errors, "equal", &nv.lit); @@ -110,14 +153,14 @@ impl ValidationAttrs { match nested { NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("min") => { if self.range_min.is_some() { - duplicate_error(nv) + duplicate_error(&nv.path) } else { self.range_min = str_or_num_to_expr(&errors, "min", &nv.lit); } } NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("max") => { if self.range_max.is_some() { - duplicate_error(nv) + duplicate_error(&nv.path) } else { self.range_max = str_or_num_to_expr(&errors, "max", &nv.lit); } @@ -140,53 +183,74 @@ impl ValidationAttrs { self.required = true; } - // TODO cause compile error if format is already Some - // FIXME #[validate(...)] overrides #[schemars(...)] - should be other way around! - NestedMeta::Meta(Meta::Path(m)) if m.is_ident("email") => { - self.format = Some("email"); + NestedMeta::Meta(Meta::Path(p)) if p.is_ident(Format::Email.attr_str()) => { + match self.format { + Some(f) => duplicate_format_error(f, Format::Email, p), + None => self.format = Some(Format::Email), + } } - NestedMeta::Meta(Meta::Path(m)) if m.is_ident("url") => { - self.format = Some("uri"); + NestedMeta::Meta(Meta::Path(p)) if p.is_ident(Format::Uri.attr_str()) => { + match self.format { + Some(f) => duplicate_format_error(f, Format::Uri, p), + None => self.format = Some(Format::Uri), + } } - NestedMeta::Meta(Meta::Path(m)) if m.is_ident("phone") => { - self.format = Some("phone"); + NestedMeta::Meta(Meta::Path(p)) if p.is_ident(Format::Phone.attr_str()) => { + match self.format { + Some(f) => duplicate_format_error(f, Format::Phone, p), + None => self.format = Some(Format::Phone), + } } - // TODO cause compile error if regex/contains are specified more than once - // FIXME #[validate(...)] overrides #[schemars(...)] - should be other way around! - NestedMeta::Meta(Meta::NameValue(MetaNameValue { path, lit, .. })) - if path.is_ident("regex") => - { - self.regex = parse_lit_into_expr_path(errors, attr_type, "regex", lit).ok() + NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("regex") => { + match (&self.regex, &self.contains) { + (Some(_), _) => duplicate_error(&nv.path), + (None, Some(_)) => mutual_exclusive_error(&nv.path, "contains"), + (None, None) => { + self.regex = + parse_lit_into_expr_path(errors, attr_type, "regex", &nv.lit).ok() + } + } } NestedMeta::Meta(Meta::List(meta_list)) if meta_list.path.is_ident("regex") => { - for x in meta_list.nested.iter() { - match x { - NestedMeta::Meta(Meta::NameValue(MetaNameValue { - path, lit, .. - })) if path.is_ident("path") => { - self.regex = - parse_lit_into_expr_path(errors, attr_type, "path", lit).ok() - } - NestedMeta::Meta(Meta::NameValue(MetaNameValue { - path, lit, .. - })) if path.is_ident("pattern") => { - self.regex = get_lit_str(errors, attr_type, "pattern", lit) - .ok() - .map(|litstr| { - Expr::Lit(syn::ExprLit { - attrs: Vec::new(), - lit: Lit::Str(litstr.clone()), - }) - }) - } - meta => { - if !ignore_errors { - errors.error_spanned_by( - meta, - format!("unknown item in schemars regex attribute"), - ); + match (&self.regex, &self.contains) { + (Some(_), _) => duplicate_error(&meta_list.path), + (None, Some(_)) => mutual_exclusive_error(&meta_list.path, "contains"), + (None, None) => { + for x in meta_list.nested.iter() { + match x { + NestedMeta::Meta(Meta::NameValue(MetaNameValue { + path, + lit, + .. + })) if path.is_ident("path") => { + self.regex = + parse_lit_into_expr_path(errors, attr_type, "path", lit) + .ok() + } + NestedMeta::Meta(Meta::NameValue(MetaNameValue { + path, + lit, + .. + })) if path.is_ident("pattern") => { + self.regex = get_lit_str(errors, attr_type, "pattern", lit) + .ok() + .map(|litstr| { + Expr::Lit(syn::ExprLit { + attrs: Vec::new(), + lit: Lit::Str(litstr.clone()), + }) + }) + } + meta => { + if !ignore_errors { + errors.error_spanned_by( + meta, + format!("unknown item in schemars regex attribute"), + ); + } + } } } } @@ -196,27 +260,44 @@ impl ValidationAttrs { NestedMeta::Meta(Meta::NameValue(MetaNameValue { path, lit, .. })) if path.is_ident("contains") => { - self.contains = get_lit_str(errors, attr_type, "contains", lit) - .ok() - .map(|litstr| litstr.value()) + match (&self.contains, &self.regex) { + (Some(_), _) => duplicate_error(&path), + (None, Some(_)) => mutual_exclusive_error(&path, "regex"), + (None, None) => { + self.contains = get_lit_str(errors, attr_type, "contains", lit) + .map(|litstr| litstr.value()) + .ok() + } + } } NestedMeta::Meta(Meta::List(meta_list)) if meta_list.path.is_ident("contains") => { - for x in meta_list.nested.iter() { - match x { - NestedMeta::Meta(Meta::NameValue(MetaNameValue { - path, lit, .. - })) if path.is_ident("pattern") => { - self.contains = get_lit_str(errors, attr_type, "contains", lit) - .ok() - .map(|litstr| litstr.value()) - } - meta => { - if !ignore_errors { - errors.error_spanned_by( - meta, - format!("unknown item in schemars contains attribute"), - ); + match (&self.contains, &self.regex) { + (Some(_), _) => duplicate_error(&meta_list.path), + (None, Some(_)) => mutual_exclusive_error(&meta_list.path, "regex"), + (None, None) => { + for x in meta_list.nested.iter() { + match x { + NestedMeta::Meta(Meta::NameValue(MetaNameValue { + path, + lit, + .. + })) if path.is_ident("pattern") => { + self.contains = + get_lit_str(errors, attr_type, "contains", lit) + .ok() + .map(|litstr| litstr.value()) + } + meta => { + if !ignore_errors { + errors.error_spanned_by( + meta, + format!( + "unknown item in schemars contains attribute" + ), + ); + } + } } } } @@ -293,6 +374,7 @@ impl ValidationAttrs { } let format = self.format.as_ref().map(|f| { + let f = f.schema_str(); quote! { schema_object.format = Some(#f.to_string()); } diff --git a/schemars_derive/src/schema_exprs.rs b/schemars_derive/src/schema_exprs.rs index c50f3324..f2c76e90 100644 --- a/schemars_derive/src/schema_exprs.rs +++ b/schemars_derive/src/schema_exprs.rs @@ -61,7 +61,7 @@ fn expr_for_field(field: &Field, allow_ref: bool) -> TokenStream { let span = field.original.span(); let gen = quote!(gen); - let mut schema_expr = if field.validation_attrs.required { + let mut schema_expr = if field.validation_attrs.required() { quote_spanned! {span=> <#ty as schemars::JsonSchema>::_schemars_private_non_optional_json_schema(#gen) } @@ -439,7 +439,7 @@ fn expr_for_struct( let (ty, type_def) = type_for_field_schema(field); - let maybe_insert_required = match (&default, field.validation_attrs.required) { + let maybe_insert_required = match (&default, field.validation_attrs.required()) { (Some(_), _) => TokenStream::new(), (None, false) => { quote! { @@ -461,7 +461,7 @@ fn expr_for_struct( }; let gen = quote!(gen); - let mut schema_expr = if field.validation_attrs.required { + let mut schema_expr = if field.validation_attrs.required() { quote_spanned! {ty.span()=> <#ty as schemars::JsonSchema>::_schemars_private_non_optional_json_schema(#gen) } @@ -489,7 +489,7 @@ fn expr_for_struct( .map(|field| { let (ty, type_def) = type_for_field_schema(field); - let required = field.validation_attrs.required; + let required = field.validation_attrs.required(); let args = quote!(gen, #required); let mut schema_expr = quote_spanned! {ty.span()=> From 63b3055e7b451ec2432778745f0356297da0bf23 Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Fri, 17 Sep 2021 23:53:46 +0100 Subject: [PATCH 13/44] Fix indexmap tests for rust 1.37 --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index be571f77..90f4f720 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,6 +38,8 @@ jobs: run: cargo check --verbose --no-default-features continue-on-error: ${{ matrix.allow_failure }} working-directory: ./schemars + - if: matrix.rust == '1.37.0' + run: cargo update -p indexmap --precise 1.6.2 - name: Run tests run: cargo test --verbose ${{ matrix.test_features }} --no-fail-fast continue-on-error: ${{ matrix.allow_failure }} From df0a14869dbb509c5d770a0dc305daae8a46bbd7 Mon Sep 17 00:00:00 2001 From: lerencao Date: Sat, 18 Sep 2021 14:57:16 +0800 Subject: [PATCH 14/44] upgrade diem dep (#1) --- schemars/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/schemars/Cargo.toml b/schemars/Cargo.toml index 9b79bd34..04ad2e5a 100644 --- a/schemars/Cargo.toml +++ b/schemars/Cargo.toml @@ -26,9 +26,9 @@ smallvec = { version = "1.0", optional = true } arrayvec = { version = "0.5", default-features = false, optional = true } url = { version = "2.0", default-features = false, optional = true } bytes = { version = "1.0", optional = true } -move-core-types = { git = "https://github.com/starcoinorg/diem", rev = "69ab01213a2e4128a1a8c8216bbf666c9ef90abd" } -diem-crypto = { package="diem-crypto", git = "https://github.com/starcoinorg/diem", rev="69ab01213a2e4128a1a8c8216bbf666c9ef90abd", features = ["fuzzing"] } multiaddr = { version = "0.13.0" } +move-core-types = { git = "https://github.com/starcoinorg/diem", rev = "347ebb76c60f360084d8b8043ca0e53d93015bc1" } +diem-crypto = { package="diem-crypto", git = "https://github.com/starcoinorg/diem", rev="347ebb76c60f360084d8b8043ca0e53d93015bc1", features = ["fuzzing"] } [dev-dependencies] pretty_assertions = "0.6.1" trybuild = "1.0" From 1d3541b4b146e8efcf8576a7554859185d8bb33e Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Sat, 18 Sep 2021 23:00:14 +0100 Subject: [PATCH 15/44] Update changelog and docs --- CHANGELOG.md | 1 + docs/1.1-attributes.md | 80 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 12c8f800..ab27de29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## [0.8.4] - **In-dev** ### Added: - `#[schemars(schema_with = "...")]` attribute can now be set on enum variants. +- Deriving JsonSchema will now take into account `#[validate(...)]` attributes, compatible with the [validator](https://github.com/Keats/validator) crate (https://github.com/GREsau/schemars/pull/78) ## [0.8.3] - 2021-04-05 ### Added: diff --git a/docs/1.1-attributes.md b/docs/1.1-attributes.md index 64d4d148..bbdd4c8e 100644 --- a/docs/1.1-attributes.md +++ b/docs/1.1-attributes.md @@ -16,7 +16,9 @@ h3 code { You can add attributes to your types to customize Schemars's derived `JsonSchema` implementation. -Serde also 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. +[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.
@@ -33,6 +35,13 @@ TABLE OF CONTENTS - [`skip_deserializing`](#skip_deserializing) - [`flatten`](#flatten) - [`with`](#with) +1. [Supported Validator Attributes](#supported-validator-attributes) + - [`email` / `phone` / `url`](#email-phone-url) + - [`length`](#length) + - [`range`](#range) + - [`regex`](#regex) + - [`contains`](#contains) + - [`required` / `required_nested`](#required) 1. [Other Attributes](#other-attributes) - [`schema_with`](#schema_with) - [`title` / `description`](#title-description) @@ -153,6 +162,75 @@ Serde docs: [container](https://serde.rs/container-attrs.html#transparent) +## Supported Validator Attributes + +*These attributes will be processed in Schemars v0.8.4* + +
+ +

+ +`#[validate(email)]` / `#[schemars(email)]`
+`#[validate(phone)]` / `#[schemars(phone)]`
+`#[validate(url)]` / `#[schemars(url)]` +

+ +Sets the schema's `format` to `email`/`phone`/`uri`, as appropriate. Only one of these attributes may be present on a single field. + +Validator docs: [email](https://github.com/Keats/validator#email) / [phone](https://github.com/Keats/validator#phone) / [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))]` +

+ +Sets the `minLength`/`maxLength` properties for string schemas, or the `minItems`/`maxItems` properties for array schemas. + +Validator docs: [length](https://github.com/Keats/validator#length) + +

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

+ +Sets the `minimum`/`maximum` properties for number schemas. + +Validator docs: [range](https://github.com/Keats/validator#range) + +

+ +`#[validate(regex = "path::to::regex")]` / `#[schemars(regex = "path::to::regex")]` +`#[schemars(regex(pattern = r"^\d+$"))]` +

+ +Sets the `pattern` property for string schemas. The `path::to::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. + +Validator docs: [regex](https://github.com/Keats/validator#regex) + +

+ +`#[validate(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. + +Validator docs: [contains](https://github.com/Keats/validator#contains) + +

+ +`#[validate(required)]` / `#[schemars(required)]` +`#[validate(required_nested)]` +

+ +When set on an `Option` field, this will create a schemas as though the field were a `T`. + +Validator docs: [required](https://github.com/Keats/validator#required) / [required_nested](https://github.com/Keats/validator#required_nested) + +
+ ## Other Attributes

From 7a8eeafde2a2106b6f59ef748735092715a8d11a Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Sat, 18 Sep 2021 23:03:28 +0100 Subject: [PATCH 16/44] Add newline to attributes docs --- docs/1.1-attributes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/1.1-attributes.md b/docs/1.1-attributes.md index bbdd4c8e..2793c26c 100644 --- a/docs/1.1-attributes.md +++ b/docs/1.1-attributes.md @@ -221,7 +221,7 @@ Validator docs: [contains](https://github.com/Keats/validator#contains)

-`#[validate(required)]` / `#[schemars(required)]` +`#[validate(required)]` / `#[schemars(required)]`
`#[validate(required_nested)]`

From 00e482c3a19e7b3e15f8a0de99c7d717bf96595f Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Sun, 19 Sep 2021 10:43:29 +0100 Subject: [PATCH 17/44] v0.8.4 --- CHANGELOG.md | 2 +- docs/1.1-attributes.md | 2 -- schemars/Cargo.toml | 4 ++-- schemars_derive/Cargo.toml | 2 +- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab27de29..5792dd37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## [0.8.4] - **In-dev** +## [0.8.4] - 2021-09-19 ### Added: - `#[schemars(schema_with = "...")]` attribute can now be set on enum variants. - Deriving JsonSchema will now take into account `#[validate(...)]` attributes, compatible with the [validator](https://github.com/Keats/validator) crate (https://github.com/GREsau/schemars/pull/78) diff --git a/docs/1.1-attributes.md b/docs/1.1-attributes.md index 2793c26c..32d6bd7c 100644 --- a/docs/1.1-attributes.md +++ b/docs/1.1-attributes.md @@ -164,8 +164,6 @@ Serde docs: [container](https://serde.rs/container-attrs.html#transparent) ## Supported Validator Attributes -*These attributes will be processed in Schemars v0.8.4* -

diff --git a/schemars/Cargo.toml b/schemars/Cargo.toml index 6fe31219..af7059ed 100644 --- a/schemars/Cargo.toml +++ b/schemars/Cargo.toml @@ -3,7 +3,7 @@ name = "schemars" description = "Generate JSON Schemas from Rust code" homepage = "https://graham.cool/schemars/" repository = "https://github.com/GREsau/schemars" -version = "0.8.3" +version = "0.8.4" authors = ["Graham Esau "] edition = "2018" license = "MIT" @@ -13,7 +13,7 @@ categories = ["encoding"] build = "build.rs" [dependencies] -schemars_derive = { version = "=0.8.3", optional = true, path = "../schemars_derive" } +schemars_derive = { version = "=0.8.4", optional = true, path = "../schemars_derive" } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" dyn-clone = "1.0" diff --git a/schemars_derive/Cargo.toml b/schemars_derive/Cargo.toml index 0535f1dc..d92827eb 100644 --- a/schemars_derive/Cargo.toml +++ b/schemars_derive/Cargo.toml @@ -3,7 +3,7 @@ name = "schemars_derive" description = "Macros for #[derive(JsonSchema)], for use with schemars" homepage = "https://graham.cool/schemars/" repository = "https://github.com/GREsau/schemars" -version = "0.8.3" +version = "0.8.4" authors = ["Graham Esau "] edition = "2018" license = "MIT" From de7314f30560c274c19694b6e0403bc2bc8703d8 Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Mon, 20 Sep 2021 16:48:16 +0100 Subject: [PATCH 18/44] Allow empty #[validate] attributes. Fixes #109 --- schemars/tests/validate.rs | 1 + schemars_derive/src/attr/mod.rs | 11 ++++++++--- schemars_derive/src/attr/validation.rs | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/schemars/tests/validate.rs b/schemars/tests/validate.rs index c2060e0a..c8b68091 100644 --- a/schemars/tests/validate.rs +++ b/schemars/tests/validate.rs @@ -42,6 +42,7 @@ pub struct Struct { #[validate(required)] required_option: Option, #[validate(required)] + #[validate] #[serde(flatten)] required_flattened: Option, } diff --git a/schemars_derive/src/attr/mod.rs b/schemars_derive/src/attr/mod.rs index cc81a920..b1b9f04f 100644 --- a/schemars_derive/src/attr/mod.rs +++ b/schemars_derive/src/attr/mod.rs @@ -104,7 +104,7 @@ impl Attrs { for meta_item in attrs .iter() - .flat_map(|attr| get_meta_items(attr, attr_type, errors)) + .flat_map(|attr| get_meta_items(attr, attr_type, errors, ignore_errors)) .flatten() { match &meta_item { @@ -201,6 +201,7 @@ fn get_meta_items( attr: &syn::Attribute, attr_type: &'static str, errors: &Ctxt, + ignore_errors: bool, ) -> Result, ()> { if !attr.path.is_ident(attr_type) { return Ok(Vec::new()); @@ -209,11 +210,15 @@ fn get_meta_items( match attr.parse_meta() { Ok(List(meta)) => Ok(meta.nested.into_iter().collect()), Ok(other) => { - errors.error_spanned_by(other, format!("expected #[{}(...)]", attr_type)); + if !ignore_errors { + errors.error_spanned_by(other, format!("expected #[{}(...)]", attr_type)) + } Err(()) } Err(err) => { - errors.error_spanned_by(attr, err); + if !ignore_errors { + errors.error_spanned_by(attr, err) + } Err(()) } } diff --git a/schemars_derive/src/attr/validation.rs b/schemars_derive/src/attr/validation.rs index 398fa591..1a93a2a4 100644 --- a/schemars_derive/src/attr/validation.rs +++ b/schemars_derive/src/attr/validation.rs @@ -99,7 +99,7 @@ impl ValidationAttrs { for meta_item in attrs .iter() - .flat_map(|attr| get_meta_items(attr, attr_type, errors)) + .flat_map(|attr| get_meta_items(attr, attr_type, errors, ignore_errors)) .flatten() { match &meta_item { From dec8bcc3b7adfdffe8d242a1f1d88076e6b20491 Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Mon, 20 Sep 2021 16:49:50 +0100 Subject: [PATCH 19/44] v0.8.5 --- CHANGELOG.md | 4 ++++ schemars/Cargo.toml | 4 ++-- schemars_derive/Cargo.toml | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5792dd37..7198d6ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## [0.8.5] - 2021-09-20 +### Fixed: +- Allow fields with plain `#[validate]` attributes (https://github.com/GREsau/schemars/issues/109) + ## [0.8.4] - 2021-09-19 ### Added: - `#[schemars(schema_with = "...")]` attribute can now be set on enum variants. diff --git a/schemars/Cargo.toml b/schemars/Cargo.toml index af7059ed..3a384e64 100644 --- a/schemars/Cargo.toml +++ b/schemars/Cargo.toml @@ -3,7 +3,7 @@ name = "schemars" description = "Generate JSON Schemas from Rust code" homepage = "https://graham.cool/schemars/" repository = "https://github.com/GREsau/schemars" -version = "0.8.4" +version = "0.8.5" authors = ["Graham Esau "] edition = "2018" license = "MIT" @@ -13,7 +13,7 @@ categories = ["encoding"] build = "build.rs" [dependencies] -schemars_derive = { version = "=0.8.4", optional = true, path = "../schemars_derive" } +schemars_derive = { version = "=0.8.5", optional = true, path = "../schemars_derive" } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" dyn-clone = "1.0" diff --git a/schemars_derive/Cargo.toml b/schemars_derive/Cargo.toml index d92827eb..7216a306 100644 --- a/schemars_derive/Cargo.toml +++ b/schemars_derive/Cargo.toml @@ -3,7 +3,7 @@ name = "schemars_derive" description = "Macros for #[derive(JsonSchema)], for use with schemars" homepage = "https://graham.cool/schemars/" repository = "https://github.com/GREsau/schemars" -version = "0.8.4" +version = "0.8.5" authors = ["Graham Esau "] edition = "2018" license = "MIT" From 0a1200baac8e8136c2f9db4f1d85c06f9f7086b5 Mon Sep 17 00:00:00 2001 From: Adam Leventhal Date: Sun, 26 Sep 2021 10:02:44 -0700 Subject: [PATCH 20/44] Use oneOf for enums when possible (#108) --- schemars/tests/expected/deprecated-enum.json | 10 +- .../tests/expected/doc_comments_enum.json | 2 +- .../expected/enum-adjacent-tagged-duf.json | 76 ++--- .../tests/expected/enum-adjacent-tagged.json | 76 ++--- .../tests/expected/enum-external-duf.json | 20 +- schemars/tests/expected/enum-external.json | 20 +- .../tests/expected/enum-internal-duf.json | 30 +- schemars/tests/expected/enum-internal.json | 30 +- schemars/tests/expected/macro_built_enum.json | 2 +- .../schema_with-enum-adjacent-tagged.json | 38 +-- .../expected/schema_with-enum-external.json | 2 +- .../expected/schema_with-enum-internal.json | 8 +- .../tests/expected/skip_enum_variants.json | 2 +- schemars/tests/util/mod.rs | 4 +- schemars_derive/src/schema_exprs.rs | 264 ++++++++++-------- 15 files changed, 305 insertions(+), 279 deletions(-) diff --git a/schemars/tests/expected/deprecated-enum.json b/schemars/tests/expected/deprecated-enum.json index f869be21..074310f3 100644 --- a/schemars/tests/expected/deprecated-enum.json +++ b/schemars/tests/expected/deprecated-enum.json @@ -2,7 +2,7 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "DeprecatedEnum", "deprecated": true, - "anyOf": [ + "oneOf": [ { "type": "string", "enum": [ @@ -24,13 +24,13 @@ "foo" ], "properties": { - "foo": { - "type": "integer", - "format": "int32" - }, "deprecated_field": { "deprecated": true, "type": "boolean" + }, + "foo": { + "type": "integer", + "format": "int32" } } } diff --git a/schemars/tests/expected/doc_comments_enum.json b/schemars/tests/expected/doc_comments_enum.json index edcb3391..0adc3801 100644 --- a/schemars/tests/expected/doc_comments_enum.json +++ b/schemars/tests/expected/doc_comments_enum.json @@ -2,7 +2,7 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "This is the enum's title", "description": "This is the enum's description.", - "anyOf": [ + "oneOf": [ { "type": "string", "enum": [ diff --git a/schemars/tests/expected/enum-adjacent-tagged-duf.json b/schemars/tests/expected/enum-adjacent-tagged-duf.json index 011b3782..c5b54c81 100644 --- a/schemars/tests/expected/enum-adjacent-tagged-duf.json +++ b/schemars/tests/expected/enum-adjacent-tagged-duf.json @@ -1,7 +1,7 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Adjacent", - "anyOf": [ + "oneOf": [ { "type": "object", "required": [ @@ -24,17 +24,17 @@ "t" ], "properties": { - "t": { - "type": "string", - "enum": [ - "StringMap" - ] - }, "c": { "type": "object", "additionalProperties": { "type": "string" } + }, + "t": { + "type": "string", + "enum": [ + "StringMap" + ] } }, "additionalProperties": false @@ -46,14 +46,14 @@ "t" ], "properties": { + "c": { + "$ref": "#/definitions/UnitStruct" + }, "t": { "type": "string", "enum": [ "UnitStructNewType" ] - }, - "c": { - "$ref": "#/definitions/UnitStruct" } }, "additionalProperties": false @@ -65,14 +65,14 @@ "t" ], "properties": { + "c": { + "$ref": "#/definitions/Struct" + }, "t": { "type": "string", "enum": [ "StructNewType" ] - }, - "c": { - "$ref": "#/definitions/Struct" } }, "additionalProperties": false @@ -84,12 +84,6 @@ "t" ], "properties": { - "t": { - "type": "string", - "enum": [ - "Struct" - ] - }, "c": { "type": "object", "required": [ @@ -97,15 +91,21 @@ "foo" ], "properties": { + "bar": { + "type": "boolean" + }, "foo": { "type": "integer", "format": "int32" - }, - "bar": { - "type": "boolean" } }, "additionalProperties": false + }, + "t": { + "type": "string", + "enum": [ + "Struct" + ] } }, "additionalProperties": false @@ -117,12 +117,6 @@ "t" ], "properties": { - "t": { - "type": "string", - "enum": [ - "Tuple" - ] - }, "c": { "type": "array", "items": [ @@ -136,6 +130,12 @@ ], "maxItems": 2, "minItems": 2 + }, + "t": { + "type": "string", + "enum": [ + "Tuple" + ] } }, "additionalProperties": false @@ -162,24 +162,21 @@ "t" ], "properties": { + "c": { + "type": "integer", + "format": "int32" + }, "t": { "type": "string", "enum": [ "WithInt" ] - }, - "c": { - "type": "integer", - "format": "int32" } }, "additionalProperties": false } ], "definitions": { - "UnitStruct": { - "type": "null" - }, "Struct": { "type": "object", "required": [ @@ -187,14 +184,17 @@ "foo" ], "properties": { + "bar": { + "type": "boolean" + }, "foo": { "type": "integer", "format": "int32" - }, - "bar": { - "type": "boolean" } } + }, + "UnitStruct": { + "type": "null" } } } \ No newline at end of file diff --git a/schemars/tests/expected/enum-adjacent-tagged.json b/schemars/tests/expected/enum-adjacent-tagged.json index 4e0ca123..efe19dc8 100644 --- a/schemars/tests/expected/enum-adjacent-tagged.json +++ b/schemars/tests/expected/enum-adjacent-tagged.json @@ -1,7 +1,7 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Adjacent", - "anyOf": [ + "oneOf": [ { "type": "object", "required": [ @@ -23,17 +23,17 @@ "t" ], "properties": { - "t": { - "type": "string", - "enum": [ - "StringMap" - ] - }, "c": { "type": "object", "additionalProperties": { "type": "string" } + }, + "t": { + "type": "string", + "enum": [ + "StringMap" + ] } } }, @@ -44,14 +44,14 @@ "t" ], "properties": { + "c": { + "$ref": "#/definitions/UnitStruct" + }, "t": { "type": "string", "enum": [ "UnitStructNewType" ] - }, - "c": { - "$ref": "#/definitions/UnitStruct" } } }, @@ -62,14 +62,14 @@ "t" ], "properties": { + "c": { + "$ref": "#/definitions/Struct" + }, "t": { "type": "string", "enum": [ "StructNewType" ] - }, - "c": { - "$ref": "#/definitions/Struct" } } }, @@ -80,12 +80,6 @@ "t" ], "properties": { - "t": { - "type": "string", - "enum": [ - "Struct" - ] - }, "c": { "type": "object", "required": [ @@ -93,14 +87,20 @@ "foo" ], "properties": { + "bar": { + "type": "boolean" + }, "foo": { "type": "integer", "format": "int32" - }, - "bar": { - "type": "boolean" } } + }, + "t": { + "type": "string", + "enum": [ + "Struct" + ] } } }, @@ -111,12 +111,6 @@ "t" ], "properties": { - "t": { - "type": "string", - "enum": [ - "Tuple" - ] - }, "c": { "type": "array", "items": [ @@ -130,6 +124,12 @@ ], "maxItems": 2, "minItems": 2 + }, + "t": { + "type": "string", + "enum": [ + "Tuple" + ] } } }, @@ -154,23 +154,20 @@ "t" ], "properties": { + "c": { + "type": "integer", + "format": "int32" + }, "t": { "type": "string", "enum": [ "WithInt" ] - }, - "c": { - "type": "integer", - "format": "int32" } } } ], "definitions": { - "UnitStruct": { - "type": "null" - }, "Struct": { "type": "object", "required": [ @@ -178,14 +175,17 @@ "foo" ], "properties": { + "bar": { + "type": "boolean" + }, "foo": { "type": "integer", "format": "int32" - }, - "bar": { - "type": "boolean" } } + }, + "UnitStruct": { + "type": "null" } } } \ No newline at end of file diff --git a/schemars/tests/expected/enum-external-duf.json b/schemars/tests/expected/enum-external-duf.json index 4bbe7e42..b6b7b99a 100644 --- a/schemars/tests/expected/enum-external-duf.json +++ b/schemars/tests/expected/enum-external-duf.json @@ -1,7 +1,7 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "title": "External", - "anyOf": [ + "oneOf": [ { "type": "string", "enum": [ @@ -61,12 +61,12 @@ "foo" ], "properties": { + "bar": { + "type": "boolean" + }, "foo": { "type": "integer", "format": "int32" - }, - "bar": { - "type": "boolean" } }, "additionalProperties": false @@ -112,9 +112,6 @@ } ], "definitions": { - "UnitStruct": { - "type": "null" - }, "Struct": { "type": "object", "required": [ @@ -122,14 +119,17 @@ "foo" ], "properties": { + "bar": { + "type": "boolean" + }, "foo": { "type": "integer", "format": "int32" - }, - "bar": { - "type": "boolean" } } + }, + "UnitStruct": { + "type": "null" } } } \ No newline at end of file diff --git a/schemars/tests/expected/enum-external.json b/schemars/tests/expected/enum-external.json index cdc2fe45..cc721dfd 100644 --- a/schemars/tests/expected/enum-external.json +++ b/schemars/tests/expected/enum-external.json @@ -1,7 +1,7 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "title": "External", - "anyOf": [ + "oneOf": [ { "type": "string", "enum": [ @@ -61,12 +61,12 @@ "foo" ], "properties": { + "bar": { + "type": "boolean" + }, "foo": { "type": "integer", "format": "int32" - }, - "bar": { - "type": "boolean" } } } @@ -111,9 +111,6 @@ } ], "definitions": { - "UnitStruct": { - "type": "null" - }, "Struct": { "type": "object", "required": [ @@ -121,14 +118,17 @@ "foo" ], "properties": { + "bar": { + "type": "boolean" + }, "foo": { "type": "integer", "format": "int32" - }, - "bar": { - "type": "boolean" } } + }, + "UnitStruct": { + "type": "null" } } } \ No newline at end of file diff --git a/schemars/tests/expected/enum-internal-duf.json b/schemars/tests/expected/enum-internal-duf.json index 8b6fef85..deb39664 100644 --- a/schemars/tests/expected/enum-internal-duf.json +++ b/schemars/tests/expected/enum-internal-duf.json @@ -1,7 +1,7 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Internal", - "anyOf": [ + "oneOf": [ { "type": "object", "required": [ @@ -55,18 +55,18 @@ "typeProperty" ], "properties": { - "typeProperty": { - "type": "string", - "enum": [ - "StructNewType" - ] + "bar": { + "type": "boolean" }, "foo": { "type": "integer", "format": "int32" }, - "bar": { - "type": "boolean" + "typeProperty": { + "type": "string", + "enum": [ + "StructNewType" + ] } } }, @@ -78,18 +78,18 @@ "typeProperty" ], "properties": { - "typeProperty": { - "type": "string", - "enum": [ - "Struct" - ] + "bar": { + "type": "boolean" }, "foo": { "type": "integer", "format": "int32" }, - "bar": { - "type": "boolean" + "typeProperty": { + "type": "string", + "enum": [ + "Struct" + ] } }, "additionalProperties": false diff --git a/schemars/tests/expected/enum-internal.json b/schemars/tests/expected/enum-internal.json index f3c15331..b43b779a 100644 --- a/schemars/tests/expected/enum-internal.json +++ b/schemars/tests/expected/enum-internal.json @@ -1,7 +1,7 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Internal", - "anyOf": [ + "oneOf": [ { "type": "object", "required": [ @@ -55,18 +55,18 @@ "typeProperty" ], "properties": { - "typeProperty": { - "type": "string", - "enum": [ - "StructNewType" - ] + "bar": { + "type": "boolean" }, "foo": { "type": "integer", "format": "int32" }, - "bar": { - "type": "boolean" + "typeProperty": { + "type": "string", + "enum": [ + "StructNewType" + ] } } }, @@ -78,18 +78,18 @@ "typeProperty" ], "properties": { - "typeProperty": { - "type": "string", - "enum": [ - "Struct" - ] + "bar": { + "type": "boolean" }, "foo": { "type": "integer", "format": "int32" }, - "bar": { - "type": "boolean" + "typeProperty": { + "type": "string", + "enum": [ + "Struct" + ] } } }, diff --git a/schemars/tests/expected/macro_built_enum.json b/schemars/tests/expected/macro_built_enum.json index 8a14a4b3..d030787d 100644 --- a/schemars/tests/expected/macro_built_enum.json +++ b/schemars/tests/expected/macro_built_enum.json @@ -1,7 +1,7 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "title": "OuterEnum", - "anyOf": [ + "oneOf": [ { "type": "object", "required": [ diff --git a/schemars/tests/expected/schema_with-enum-adjacent-tagged.json b/schemars/tests/expected/schema_with-enum-adjacent-tagged.json index f464511b..3ecba4c8 100644 --- a/schemars/tests/expected/schema_with-enum-adjacent-tagged.json +++ b/schemars/tests/expected/schema_with-enum-adjacent-tagged.json @@ -1,7 +1,7 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Adjacent", - "anyOf": [ + "oneOf": [ { "type": "object", "required": [ @@ -9,12 +9,6 @@ "t" ], "properties": { - "t": { - "type": "string", - "enum": [ - "Struct" - ] - }, "c": { "type": "object", "required": [ @@ -25,6 +19,12 @@ "type": "boolean" } } + }, + "t": { + "type": "string", + "enum": [ + "Struct" + ] } } }, @@ -35,14 +35,14 @@ "t" ], "properties": { + "c": { + "type": "boolean" + }, "t": { "type": "string", "enum": [ "NewType" ] - }, - "c": { - "type": "boolean" } } }, @@ -53,12 +53,6 @@ "t" ], "properties": { - "t": { - "type": "string", - "enum": [ - "Tuple" - ] - }, "c": { "type": "array", "items": [ @@ -72,6 +66,12 @@ ], "maxItems": 2, "minItems": 2 + }, + "t": { + "type": "string", + "enum": [ + "Tuple" + ] } } }, @@ -82,14 +82,14 @@ "t" ], "properties": { + "c": { + "type": "boolean" + }, "t": { "type": "string", "enum": [ "Unit" ] - }, - "c": { - "type": "boolean" } } } diff --git a/schemars/tests/expected/schema_with-enum-external.json b/schemars/tests/expected/schema_with-enum-external.json index 92fb5a63..dea02199 100644 --- a/schemars/tests/expected/schema_with-enum-external.json +++ b/schemars/tests/expected/schema_with-enum-external.json @@ -1,7 +1,7 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "title": "External", - "anyOf": [ + "oneOf": [ { "type": "object", "required": [ diff --git a/schemars/tests/expected/schema_with-enum-internal.json b/schemars/tests/expected/schema_with-enum-internal.json index ea39afe7..7ede7e6e 100644 --- a/schemars/tests/expected/schema_with-enum-internal.json +++ b/schemars/tests/expected/schema_with-enum-internal.json @@ -1,7 +1,7 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Internal", - "anyOf": [ + "oneOf": [ { "type": "object", "required": [ @@ -9,14 +9,14 @@ "typeProperty" ], "properties": { + "foo": { + "type": "boolean" + }, "typeProperty": { "type": "string", "enum": [ "Struct" ] - }, - "foo": { - "type": "boolean" } } }, diff --git a/schemars/tests/expected/skip_enum_variants.json b/schemars/tests/expected/skip_enum_variants.json index 4acb2eb4..ba1bf23c 100644 --- a/schemars/tests/expected/skip_enum_variants.json +++ b/schemars/tests/expected/skip_enum_variants.json @@ -1,7 +1,7 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "title": "MyEnum", - "anyOf": [ + "oneOf": [ { "type": "string", "enum": [ diff --git a/schemars/tests/util/mod.rs b/schemars/tests/util/mod.rs index 9f365d69..e83671f2 100644 --- a/schemars/tests/util/mod.rs +++ b/schemars/tests/util/mod.rs @@ -22,7 +22,7 @@ pub fn test_schema(actual: &RootSchema, file: &str) -> TestResult { let expected_json = match fs::read_to_string(format!("tests/expected/{}.json", file)) { Ok(j) => j, Err(e) => { - write_actual_to_file(&actual, file)?; + write_actual_to_file(actual, file)?; return Err(Box::from(e)); } }; @@ -32,7 +32,7 @@ pub fn test_schema(actual: &RootSchema, file: &str) -> TestResult { write_actual_to_file(actual, file)?; } - assert_eq!(actual, expected); + assert_eq!(expected, actual); Ok(()) } diff --git a/schemars_derive/src/schema_exprs.rs b/schemars_derive/src/schema_exprs.rs index f2c76e90..4c463dc8 100644 --- a/schemars_derive/src/schema_exprs.rs +++ b/schemars_derive/src/schema_exprs.rs @@ -1,3 +1,5 @@ +use std::collections::HashSet; + use crate::{ast::*, attr::WithAttr, metadata::SchemaMetadata}; use proc_macro2::{Span, TokenStream}; use serde_derive_internals::ast::Style; @@ -140,8 +142,14 @@ fn expr_for_external_tagged_enum<'a>( variants: impl Iterator>, deny_unknown_fields: bool, ) -> TokenStream { - let (unit_variants, complex_variants): (Vec<_>, Vec<_>) = - variants.partition(|v| v.is_unit() && v.attrs.with.is_none()); + let mut unique_names = HashSet::::new(); + let mut count = 0; + let (unit_variants, complex_variants): (Vec<_>, Vec<_>) = variants + .inspect(|v| { + unique_names.insert(v.name()); + count += 1; + }) + .partition(|v| v.is_unit() && v.attrs.with.is_none()); let unit_names = unit_variants.iter().map(|v| v.name()); let unit_schema = schema_object(quote! { @@ -188,12 +196,7 @@ fn expr_for_external_tagged_enum<'a>( schema_expr })); - schema_object(quote! { - subschemas: Some(Box::new(schemars::schema::SubschemaValidation { - any_of: Some(vec![#(#schemas),*]), - ..Default::default() - })), - }) + variant_subschemas(unique_names.len() == count, schemas) } fn expr_for_internal_tagged_enum<'a>( @@ -201,70 +204,71 @@ fn expr_for_internal_tagged_enum<'a>( tag_name: &str, deny_unknown_fields: bool, ) -> TokenStream { - let variant_schemas = variants.map(|variant| { - let name = variant.name(); - let type_schema = schema_object(quote! { - instance_type: Some(schemars::schema::InstanceType::String.into()), - enum_values: Some(vec![#name.into()]), - }); - - let mut tag_schema = schema_object(quote! { - instance_type: Some(schemars::schema::InstanceType::Object.into()), - object: Some(Box::new(schemars::schema::ObjectValidation { - properties: { - let mut props = schemars::Map::new(); - props.insert(#tag_name.to_owned(), #type_schema); - props - }, - required: { - let mut required = schemars::Set::new(); - required.insert(#tag_name.to_owned()); - required - }, - ..Default::default() - })), - }); - - variant.attrs.as_metadata().apply_to_schema(&mut tag_schema); - - if let Some(variant_schema) = - expr_for_untagged_enum_variant_for_flatten(&variant, deny_unknown_fields) - { - tag_schema.extend(quote!(.flatten(#variant_schema))) - } + let mut unique_names = HashSet::new(); + let mut count = 0; + let variant_schemas = variants + .map(|variant| { + unique_names.insert(variant.name()); + count += 1; + + let name = variant.name(); + let type_schema = schema_object(quote! { + instance_type: Some(schemars::schema::InstanceType::String.into()), + enum_values: Some(vec![#name.into()]), + }); + + let mut tag_schema = schema_object(quote! { + instance_type: Some(schemars::schema::InstanceType::Object.into()), + object: Some(Box::new(schemars::schema::ObjectValidation { + properties: { + let mut props = schemars::Map::new(); + props.insert(#tag_name.to_owned(), #type_schema); + props + }, + required: { + let mut required = schemars::Set::new(); + required.insert(#tag_name.to_owned()); + required + }, + ..Default::default() + })), + }); + + variant.attrs.as_metadata().apply_to_schema(&mut tag_schema); + + if let Some(variant_schema) = + expr_for_untagged_enum_variant_for_flatten(variant, deny_unknown_fields) + { + tag_schema.extend(quote!(.flatten(#variant_schema))) + } - tag_schema - }); + tag_schema + }) + .collect(); - schema_object(quote! { - subschemas: Some(Box::new(schemars::schema::SubschemaValidation { - any_of: Some(vec![#(#variant_schemas),*]), - ..Default::default() - })), - }) + variant_subschemas(unique_names.len() == count, variant_schemas) } fn expr_for_untagged_enum<'a>( variants: impl Iterator>, deny_unknown_fields: bool, ) -> TokenStream { - let schemas = variants.map(|variant| { - let mut schema_expr = expr_for_untagged_enum_variant(variant, deny_unknown_fields); + let schemas = variants + .map(|variant| { + let mut schema_expr = expr_for_untagged_enum_variant(variant, deny_unknown_fields); - variant - .attrs - .as_metadata() - .apply_to_schema(&mut schema_expr); + variant + .attrs + .as_metadata() + .apply_to_schema(&mut schema_expr); - schema_expr - }); + schema_expr + }) + .collect(); - schema_object(quote! { - subschemas: Some(Box::new(schemars::schema::SubschemaValidation { - any_of: Some(vec![#(#schemas),*]), - ..Default::default() - })), - }) + // Untagged enums can easily have variants whose schemas overlap; rather + // that checking the exclusivity of each subschema we simply us `any_of`. + variant_subschemas(false, schemas) } fn expr_for_adjacent_tagged_enum<'a>( @@ -273,70 +277,92 @@ fn expr_for_adjacent_tagged_enum<'a>( content_name: &str, deny_unknown_fields: bool, ) -> TokenStream { - let schemas = variants.map(|variant| { - let content_schema = if variant.is_unit() && variant.attrs.with.is_none() { - None - } else { - Some(expr_for_untagged_enum_variant(variant, deny_unknown_fields)) - }; + let mut unique_names = HashSet::new(); + let mut count = 0; + let schemas = variants + .map(|variant| { + unique_names.insert(variant.name()); + count += 1; + + let content_schema = if variant.is_unit() && variant.attrs.with.is_none() { + None + } else { + Some(expr_for_untagged_enum_variant(variant, deny_unknown_fields)) + }; - let (add_content_to_props, add_content_to_required) = content_schema - .map(|content_schema| { - ( - quote!(props.insert(#content_name.to_owned(), #content_schema);), - quote!(required.insert(#content_name.to_owned());), - ) - }) - .unwrap_or_default(); + let (add_content_to_props, add_content_to_required) = content_schema + .map(|content_schema| { + ( + quote!(props.insert(#content_name.to_owned(), #content_schema);), + quote!(required.insert(#content_name.to_owned());), + ) + }) + .unwrap_or_default(); + + let name = variant.name(); + let tag_schema = schema_object(quote! { + instance_type: Some(schemars::schema::InstanceType::String.into()), + enum_values: Some(vec![#name.into()]), + }); + + let set_additional_properties = if deny_unknown_fields { + quote! { + additional_properties: Some(Box::new(false.into())), + } + } else { + TokenStream::new() + }; - let name = variant.name(); - let tag_schema = schema_object(quote! { - instance_type: Some(schemars::schema::InstanceType::String.into()), - enum_values: Some(vec![#name.into()]), - }); + let mut outer_schema = schema_object(quote! { + instance_type: Some(schemars::schema::InstanceType::Object.into()), + object: Some(Box::new(schemars::schema::ObjectValidation { + properties: { + let mut props = schemars::Map::new(); + props.insert(#tag_name.to_owned(), #tag_schema); + #add_content_to_props + props + }, + required: { + let mut required = schemars::Set::new(); + required.insert(#tag_name.to_owned()); + #add_content_to_required + required + }, + #set_additional_properties + ..Default::default() + })), + }); + + variant + .attrs + .as_metadata() + .apply_to_schema(&mut outer_schema); + + outer_schema + }) + .collect(); - let set_additional_properties = if deny_unknown_fields { - quote! { - additional_properties: Some(Box::new(false.into())), - } - } else { - TokenStream::new() - }; + variant_subschemas(unique_names.len() == count, schemas) +} - let mut outer_schema = schema_object(quote! { - instance_type: Some(schemars::schema::InstanceType::Object.into()), - object: Some(Box::new(schemars::schema::ObjectValidation { - properties: { - let mut props = schemars::Map::new(); - props.insert(#tag_name.to_owned(), #tag_schema); - #add_content_to_props - props - }, - required: { - let mut required = schemars::Set::new(); - required.insert(#tag_name.to_owned()); - #add_content_to_required - required - }, - #set_additional_properties +/// Callers must determine if all subschemas are mutually exclusive. This can +/// be done for most tagging regimes by checking that all tag names are unique. +fn variant_subschemas(unique: bool, schemas: Vec) -> TokenStream { + if unique { + schema_object(quote! { + subschemas: Some(Box::new(schemars::schema::SubschemaValidation { + one_of: Some(vec![#(#schemas),*]), ..Default::default() })), - }); - - variant - .attrs - .as_metadata() - .apply_to_schema(&mut outer_schema); - - outer_schema - }); - - schema_object(quote! { - subschemas: Some(Box::new(schemars::schema::SubschemaValidation { - any_of: Some(vec![#(#schemas),*]), - ..Default::default() - })), - }) + }) + } else { + schema_object(quote! { + subschemas: Some(Box::new(schemars::schema::SubschemaValidation { + any_of: Some(vec![#(#schemas),*]), + ..Default::default() + })), + }) + } } fn expr_for_untagged_enum_variant(variant: &Variant, deny_unknown_fields: bool) -> TokenStream { From 33e54d3c424078e94cd0c4ac33023d1b8a7df275 Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Sun, 26 Sep 2021 18:39:11 +0100 Subject: [PATCH 21/44] v0.8.6 --- CHANGELOG.md | 4 ++++ schemars/Cargo.toml | 4 ++-- schemars_derive/Cargo.toml | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7198d6ba..bc8f5048 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## [0.8.6] - 2021-09-26 +### Fixed: +- Use `oneOf` instead of `anyOf` for enums when possible (https://github.com/GREsau/schemars/issues/108) + ## [0.8.5] - 2021-09-20 ### Fixed: - Allow fields with plain `#[validate]` attributes (https://github.com/GREsau/schemars/issues/109) diff --git a/schemars/Cargo.toml b/schemars/Cargo.toml index 3a384e64..53a6584a 100644 --- a/schemars/Cargo.toml +++ b/schemars/Cargo.toml @@ -3,7 +3,7 @@ name = "schemars" description = "Generate JSON Schemas from Rust code" homepage = "https://graham.cool/schemars/" repository = "https://github.com/GREsau/schemars" -version = "0.8.5" +version = "0.8.6" authors = ["Graham Esau "] edition = "2018" license = "MIT" @@ -13,7 +13,7 @@ categories = ["encoding"] build = "build.rs" [dependencies] -schemars_derive = { version = "=0.8.5", optional = true, path = "../schemars_derive" } +schemars_derive = { version = "=0.8.6", optional = true, path = "../schemars_derive" } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" dyn-clone = "1.0" diff --git a/schemars_derive/Cargo.toml b/schemars_derive/Cargo.toml index 7216a306..d4be61ac 100644 --- a/schemars_derive/Cargo.toml +++ b/schemars_derive/Cargo.toml @@ -3,7 +3,7 @@ name = "schemars_derive" description = "Macros for #[derive(JsonSchema)], for use with schemars" homepage = "https://graham.cool/schemars/" repository = "https://github.com/GREsau/schemars" -version = "0.8.5" +version = "0.8.6" authors = ["Graham Esau "] edition = "2018" license = "MIT" From 515a87a5649ce1ea30fe80874baf581247aff315 Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Sun, 26 Sep 2021 18:43:53 +0100 Subject: [PATCH 22/44] Correct latest changelog entry --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc8f5048..ce09bc1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # Changelog ## [0.8.6] - 2021-09-26 -### Fixed: +### Changed: - Use `oneOf` instead of `anyOf` for enums when possible (https://github.com/GREsau/schemars/issues/108) ## [0.8.5] - 2021-09-20 From 44133de95bd7fe9dbd9ea16a8c012cbfcdae0445 Mon Sep 17 00:00:00 2001 From: caojiafeng Date: Thu, 30 Sep 2021 09:06:14 +0800 Subject: [PATCH 23/44] update diem dep --- schemars/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/schemars/Cargo.toml b/schemars/Cargo.toml index 04ad2e5a..69830e68 100644 --- a/schemars/Cargo.toml +++ b/schemars/Cargo.toml @@ -27,8 +27,8 @@ arrayvec = { version = "0.5", default-features = false, optional = true } url = { version = "2.0", default-features = false, optional = true } bytes = { version = "1.0", optional = true } multiaddr = { version = "0.13.0" } -move-core-types = { git = "https://github.com/starcoinorg/diem", rev = "347ebb76c60f360084d8b8043ca0e53d93015bc1" } -diem-crypto = { package="diem-crypto", git = "https://github.com/starcoinorg/diem", rev="347ebb76c60f360084d8b8043ca0e53d93015bc1", features = ["fuzzing"] } +move-core-types = { git = "https://github.com/starcoinorg/diem", rev = "6c2c02bb99728136dd144175b647ba3b1695d224" } +diem-crypto = { package="diem-crypto", git = "https://github.com/starcoinorg/diem", rev="6c2c02bb99728136dd144175b647ba3b1695d224", features = ["fuzzing"] } [dev-dependencies] pretty_assertions = "0.6.1" trybuild = "1.0" From d059686da8ebe963988ee4e4ff5b08da0e5950f4 Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Sun, 18 Jul 2021 18:45:25 -0500 Subject: [PATCH 24/44] Implement JsonSchema on EnumSet type --- .github/workflows/ci.yml | 2 +- README.md | 1 + docs/4-features.md | 1 + schemars/Cargo.toml | 5 +++++ schemars/src/json_schema_impls/enumset.rs | 6 ++++++ schemars/src/json_schema_impls/mod.rs | 2 ++ schemars/src/lib.rs | 1 + schemars/tests/enumset.rs | 15 +++++++++++++++ schemars/tests/expected/enumset.json | 18 ++++++++++++++++++ 9 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 schemars/src/json_schema_impls/enumset.rs create mode 100644 schemars/tests/enumset.rs create mode 100644 schemars/tests/expected/enumset.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 90f4f720..b27b9832 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ jobs: include: - rust: 1.37.0 # exclude ui_test as the output is slightly different in rustc 1.37 - test_features: "--features impl_json_schema,chrono,indexmap,either,uuid,smallvec,arrayvec" + test_features: "--features impl_json_schema,chrono,indexmap,either,uuid,smallvec,arrayvec,enumset" allow_failure: false - rust: stable test_features: "--all-features" diff --git a/README.md b/README.md index 571045dc..f5cafb9a 100644 --- a/README.md +++ b/README.md @@ -274,3 +274,4 @@ Schemars can implement `JsonSchema` on types from several popular crates, enable - [`arrayvec`](https://crates.io/crates/arrayvec) (^0.5) - [`url`](https://crates.io/crates/url) (^2.0) - [`bytes`](https://crates.io/crates/bytes) (^1.0) +- [`enumset`](https://crates.io/crates/enumset) (^1.0) diff --git a/docs/4-features.md b/docs/4-features.md index d0809084..613d3529 100644 --- a/docs/4-features.md +++ b/docs/4-features.md @@ -28,3 +28,4 @@ Schemars can implement `JsonSchema` on types from several popular crates, enable - [`arrayvec`](https://crates.io/crates/arrayvec) (^0.5) - [`url`](https://crates.io/crates/url) (^2.0) - [`bytes`](https://crates.io/crates/bytes) (^1.0) +- [`enumset`](https://crates.io/crates/enumset) (^1.0) diff --git a/schemars/Cargo.toml b/schemars/Cargo.toml index 53a6584a..c7b8bffe 100644 --- a/schemars/Cargo.toml +++ b/schemars/Cargo.toml @@ -26,6 +26,7 @@ smallvec = { version = "1.0", optional = true } arrayvec = { version = "0.5", default-features = false, optional = true } url = { version = "2.0", default-features = false, optional = true } bytes = { version = "1.0", optional = true } +enumset = { version = "1.0", optional = true } [dev-dependencies] pretty_assertions = "0.6.1" @@ -87,5 +88,9 @@ required-features = ["ui_test"] name = "url" required-features = ["url"] +[[test]] +name = "enumset" +required-features = ["enumset"] + [package.metadata.docs.rs] all-features = true diff --git a/schemars/src/json_schema_impls/enumset.rs b/schemars/src/json_schema_impls/enumset.rs new file mode 100644 index 00000000..22a3ebcf --- /dev/null +++ b/schemars/src/json_schema_impls/enumset.rs @@ -0,0 +1,6 @@ +use crate::gen::SchemaGenerator; +use crate::schema::*; +use crate::JsonSchema; +use enumset::{EnumSet, EnumSetType}; + +forward_impl!(( JsonSchema for EnumSet where T: EnumSetType + JsonSchema) => std::collections::BTreeSet); diff --git a/schemars/src/json_schema_impls/mod.rs b/schemars/src/json_schema_impls/mod.rs index ff2e1e71..d9c81c85 100644 --- a/schemars/src/json_schema_impls/mod.rs +++ b/schemars/src/json_schema_impls/mod.rs @@ -47,6 +47,8 @@ mod chrono; mod core; #[cfg(feature = "either")] mod either; +#[cfg(feature = "enumset")] +mod enumset; mod ffi; #[cfg(feature = "indexmap")] mod indexmap; diff --git a/schemars/src/lib.rs b/schemars/src/lib.rs index e400ccf4..d62f1d35 100644 --- a/schemars/src/lib.rs +++ b/schemars/src/lib.rs @@ -269,6 +269,7 @@ Schemars can implement `JsonSchema` on types from several popular crates, enable - [`arrayvec`](https://crates.io/crates/arrayvec) (^0.5) - [`url`](https://crates.io/crates/url) (^2.0) - [`bytes`](https://crates.io/crates/bytes) (^1.0) +- [`enumset`](https://crates.io/crates/enumset) (^1.0) */ /// The map type used by schemars types. diff --git a/schemars/tests/enumset.rs b/schemars/tests/enumset.rs new file mode 100644 index 00000000..27f20307 --- /dev/null +++ b/schemars/tests/enumset.rs @@ -0,0 +1,15 @@ +mod util; +use enumset::{EnumSet, EnumSetType}; +use schemars::JsonSchema; +use util::*; + +#[derive(EnumSetType, JsonSchema)] +enum Foo { + Bar, + Baz, +} + +#[test] +fn enumset() -> TestResult { + test_default_generated_schema::>("enumset") +} diff --git a/schemars/tests/expected/enumset.json b/schemars/tests/expected/enumset.json new file mode 100644 index 00000000..4950c939 --- /dev/null +++ b/schemars/tests/expected/enumset.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Set_of_Foo", + "type": "array", + "items": { + "$ref": "#/definitions/Foo" + }, + "uniqueItems": true, + "definitions": { + "Foo": { + "type": "string", + "enum": [ + "Bar", + "Baz" + ] + } + } +} From 67e185dd17fa24ad349bf2ad0828068cdae77505 Mon Sep 17 00:00:00 2001 From: caojiafeng Date: Wed, 13 Oct 2021 22:24:16 +0800 Subject: [PATCH 25/44] update diem dep --- schemars/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/schemars/Cargo.toml b/schemars/Cargo.toml index 69830e68..25cb6c4f 100644 --- a/schemars/Cargo.toml +++ b/schemars/Cargo.toml @@ -27,8 +27,8 @@ arrayvec = { version = "0.5", default-features = false, optional = true } url = { version = "2.0", default-features = false, optional = true } bytes = { version = "1.0", optional = true } multiaddr = { version = "0.13.0" } -move-core-types = { git = "https://github.com/starcoinorg/diem", rev = "6c2c02bb99728136dd144175b647ba3b1695d224" } -diem-crypto = { package="diem-crypto", git = "https://github.com/starcoinorg/diem", rev="6c2c02bb99728136dd144175b647ba3b1695d224", features = ["fuzzing"] } +move-core-types = { git = "https://github.com/starcoinorg/diem", rev = "6ff224f42a408f584acbcd37eebac14de619b16f" } +diem-crypto = { package="diem-crypto", git = "https://github.com/starcoinorg/diem", rev="6ff224f42a408f584acbcd37eebac14de619b16f", features = ["fuzzing"] } [dev-dependencies] pretty_assertions = "0.6.1" trybuild = "1.0" From 0dabf7726f1d856ea18df685c10e51d6fae53e8c Mon Sep 17 00:00:00 2001 From: jolestar Date: Fri, 12 Nov 2021 19:34:35 +0800 Subject: [PATCH 26/44] Upgrade move deps (#3) * [deps] Upgrade move types dep. --- schemars/Cargo.toml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/schemars/Cargo.toml b/schemars/Cargo.toml index 25cb6c4f..0c3e0375 100644 --- a/schemars/Cargo.toml +++ b/schemars/Cargo.toml @@ -27,8 +27,9 @@ arrayvec = { version = "0.5", default-features = false, optional = true } url = { version = "2.0", default-features = false, optional = true } bytes = { version = "1.0", optional = true } multiaddr = { version = "0.13.0" } -move-core-types = { git = "https://github.com/starcoinorg/diem", rev = "6ff224f42a408f584acbcd37eebac14de619b16f" } -diem-crypto = { package="diem-crypto", git = "https://github.com/starcoinorg/diem", rev="6ff224f42a408f584acbcd37eebac14de619b16f", features = ["fuzzing"] } +move-core-types = { git = "https://github.com/starcoinorg/diem", rev = "5a8ea734641cce096914fe10a2fbd8aea2e65fd1" } +diem-crypto = { package="diem-crypto", git = "https://github.com/starcoinorg/diem", rev="5a8ea734641cce096914fe10a2fbd8aea2e65fd1", features = ["fuzzing"] } + [dev-dependencies] pretty_assertions = "0.6.1" trybuild = "1.0" From 6f39a1372460f32dde73f088a6d9cd3562d5a70d Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Sun, 14 Nov 2021 19:05:09 +0000 Subject: [PATCH 27/44] Update examples after 0a1200b --- docs/_includes/examples/custom_settings.schema.json | 2 +- docs/_includes/examples/doc_comments.schema.json | 2 +- docs/_includes/examples/main.schema.json | 2 +- docs/_includes/examples/validate.schema.json | 2 +- schemars/examples/custom_settings.schema.json | 2 +- schemars/examples/doc_comments.schema.json | 2 +- schemars/examples/main.schema.json | 2 +- schemars/examples/validate.schema.json | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/_includes/examples/custom_settings.schema.json b/docs/_includes/examples/custom_settings.schema.json index 59939be9..12ac7d59 100644 --- a/docs/_includes/examples/custom_settings.schema.json +++ b/docs/_includes/examples/custom_settings.schema.json @@ -25,7 +25,7 @@ }, "definitions": { "MyEnum": { - "anyOf": [ + "oneOf": [ { "type": "object", "required": [ diff --git a/docs/_includes/examples/doc_comments.schema.json b/docs/_includes/examples/doc_comments.schema.json index 0f3405c7..121cdb42 100644 --- a/docs/_includes/examples/doc_comments.schema.json +++ b/docs/_includes/examples/doc_comments.schema.json @@ -33,7 +33,7 @@ "definitions": { "MyEnum": { "title": "My Amazing Enum", - "anyOf": [ + "oneOf": [ { "description": "A wrapper around a `String`", "type": "object", diff --git a/docs/_includes/examples/main.schema.json b/docs/_includes/examples/main.schema.json index 737bcbc2..ddbd9d33 100644 --- a/docs/_includes/examples/main.schema.json +++ b/docs/_includes/examples/main.schema.json @@ -27,7 +27,7 @@ }, "definitions": { "MyEnum": { - "anyOf": [ + "oneOf": [ { "type": "object", "required": [ diff --git a/docs/_includes/examples/validate.schema.json b/docs/_includes/examples/validate.schema.json index e8ed35e6..1e45a969 100644 --- a/docs/_includes/examples/validate.schema.json +++ b/docs/_includes/examples/validate.schema.json @@ -18,7 +18,7 @@ "minimum": 1.0 }, "my_nullable_enum": { - "anyOf": [ + "oneOf": [ { "type": "object", "required": [ diff --git a/schemars/examples/custom_settings.schema.json b/schemars/examples/custom_settings.schema.json index 59939be9..12ac7d59 100644 --- a/schemars/examples/custom_settings.schema.json +++ b/schemars/examples/custom_settings.schema.json @@ -25,7 +25,7 @@ }, "definitions": { "MyEnum": { - "anyOf": [ + "oneOf": [ { "type": "object", "required": [ diff --git a/schemars/examples/doc_comments.schema.json b/schemars/examples/doc_comments.schema.json index 0f3405c7..121cdb42 100644 --- a/schemars/examples/doc_comments.schema.json +++ b/schemars/examples/doc_comments.schema.json @@ -33,7 +33,7 @@ "definitions": { "MyEnum": { "title": "My Amazing Enum", - "anyOf": [ + "oneOf": [ { "description": "A wrapper around a `String`", "type": "object", diff --git a/schemars/examples/main.schema.json b/schemars/examples/main.schema.json index 737bcbc2..ddbd9d33 100644 --- a/schemars/examples/main.schema.json +++ b/schemars/examples/main.schema.json @@ -27,7 +27,7 @@ }, "definitions": { "MyEnum": { - "anyOf": [ + "oneOf": [ { "type": "object", "required": [ diff --git a/schemars/examples/validate.schema.json b/schemars/examples/validate.schema.json index e8ed35e6..1e45a969 100644 --- a/schemars/examples/validate.schema.json +++ b/schemars/examples/validate.schema.json @@ -18,7 +18,7 @@ "minimum": 1.0 }, "my_nullable_enum": { - "anyOf": [ + "oneOf": [ { "type": "object", "required": [ From 690fe4434335c84806a3bc00383b6aa440dc464d Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Sun, 14 Nov 2021 19:16:46 +0000 Subject: [PATCH 28/44] Allow non-Serialize default values. Default values that don't implement Serialize are now ignored, rather than causing a compile error. This is done by simulating specialization using a technique copied from Rocket: https://github.com/SergioBenitez/Rocket/blob/5ebefa97c992c37bdc476299304a339d429a43fc/core/lib/src/sentinel.rs#L391-L445 Fixes #115 --- schemars/src/_private.rs | 33 ++++++++++++++++++++++++++++ schemars/tests/default.rs | 9 +++++--- schemars/tests/expected/default.json | 6 +++++ schemars_derive/src/metadata.rs | 2 +- 4 files changed, 46 insertions(+), 4 deletions(-) diff --git a/schemars/src/_private.rs b/schemars/src/_private.rs index b914699f..aa6b570a 100644 --- a/schemars/src/_private.rs +++ b/schemars/src/_private.rs @@ -2,6 +2,8 @@ use crate::flatten::Merge; use crate::gen::SchemaGenerator; use crate::schema::{Metadata, Schema, SchemaObject}; use crate::JsonSchema; +use serde::Serialize; +use serde_json::Value; // Helper for generating schemas for flattened `Option` fields. pub fn json_schema_for_flatten( @@ -32,3 +34,34 @@ pub fn apply_metadata(schema: Schema, metadata: Metadata) -> Schema { Schema::Object(schema_obj) } } + +/// Hack to simulate specialization: +/// `MaybeSerializeWrapper(x).maybe_to_value()` will resolve to either +/// - The inherent method `MaybeSerializeWrapper::maybe_to_value(...)` if x is `Serialize` +/// - The trait method `NoSerialize::maybe_to_value(...)` from the blanket impl otherwise +#[doc(hidden)] +#[macro_export] +macro_rules! _schemars_maybe_to_value { + ($expression:expr) => {{ + #[allow(unused_imports)] + use $crate::_private::{MaybeSerializeWrapper, NoSerialize as _}; + + MaybeSerializeWrapper($expression).maybe_to_value() + }}; +} + +pub struct MaybeSerializeWrapper(pub T); + +pub trait NoSerialize: Sized { + fn maybe_to_value(self) -> Option { + None + } +} + +impl NoSerialize for T {} + +impl MaybeSerializeWrapper { + pub fn maybe_to_value(self) -> Option { + serde_json::value::to_value(self.0).ok() + } +} diff --git a/schemars/tests/default.rs b/schemars/tests/default.rs index d91585a4..0d68d0e2 100644 --- a/schemars/tests/default.rs +++ b/schemars/tests/default.rs @@ -1,6 +1,5 @@ mod util; use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; use util::*; fn is_default(value: &T) -> bool { @@ -25,7 +24,7 @@ where ser.collect_str(&format_args!("i:{} b:{}", value.my_int, value.my_bool)) } -#[derive(Default, Deserialize, Serialize, JsonSchema, Debug)] +#[derive(Default, JsonSchema, Debug)] #[serde(default)] pub struct MyStruct { pub my_int: i32, @@ -37,9 +36,10 @@ pub struct MyStruct { skip_serializing_if = "is_default" )] pub my_struct2_default_skipped: MyStruct2, + pub not_serialize: NotSerialize, } -#[derive(Default, Deserialize, Serialize, JsonSchema, Debug, PartialEq)] +#[derive(Default, JsonSchema, Debug, PartialEq)] #[serde(default = "ten_and_true")] pub struct MyStruct2 { #[serde(default = "six")] @@ -47,6 +47,9 @@ pub struct MyStruct2 { pub my_bool: bool, } +#[derive(Default, JsonSchema, Debug)] +pub struct NotSerialize; + #[test] fn schema_default_values() -> TestResult { test_default_generated_schema::("default") diff --git a/schemars/tests/expected/default.json b/schemars/tests/expected/default.json index 6210986d..aefef83d 100644 --- a/schemars/tests/expected/default.json +++ b/schemars/tests/expected/default.json @@ -22,6 +22,9 @@ }, "my_struct2_default_skipped": { "$ref": "#/definitions/MyStruct2" + }, + "not_serialize": { + "$ref": "#/definitions/NotSerialize" } }, "definitions": { @@ -38,6 +41,9 @@ "type": "boolean" } } + }, + "NotSerialize": { + "type": "null" } } } \ No newline at end of file diff --git a/schemars_derive/src/metadata.rs b/schemars_derive/src/metadata.rs index aefe243e..32dbf671 100644 --- a/schemars_derive/src/metadata.rs +++ b/schemars_derive/src/metadata.rs @@ -68,7 +68,7 @@ impl<'a> SchemaMetadata<'a> { if let Some(default) = &self.default { setters.push(quote! { - default: #default.and_then(|d| schemars::_serde_json::value::to_value(d).ok()), + default: #default.and_then(|d| schemars::_schemars_maybe_to_value!(d)), }); } From 1a13ba9f9b374e45f9a8eacc5e18229976e07d1f Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Sun, 14 Nov 2021 19:23:15 +0000 Subject: [PATCH 29/44] v0.8.7 --- CHANGELOG.md | 7 +++++++ schemars/Cargo.toml | 4 ++-- schemars_derive/Cargo.toml | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce09bc1b..5b9f4518 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [0.8.7] - 2021-11-14 +### Added: +- Implement `JsonSchema` for `EnumSet` (https://github.com/GREsau/schemars/pull/92) + +### Fixed: +- Do not cause compile error when using a default value that doesn't implement `Serialize` (https://github.com/GREsau/schemars/issues/115) + ## [0.8.6] - 2021-09-26 ### Changed: - Use `oneOf` instead of `anyOf` for enums when possible (https://github.com/GREsau/schemars/issues/108) diff --git a/schemars/Cargo.toml b/schemars/Cargo.toml index c7b8bffe..993f1263 100644 --- a/schemars/Cargo.toml +++ b/schemars/Cargo.toml @@ -3,7 +3,7 @@ name = "schemars" description = "Generate JSON Schemas from Rust code" homepage = "https://graham.cool/schemars/" repository = "https://github.com/GREsau/schemars" -version = "0.8.6" +version = "0.8.7" authors = ["Graham Esau "] edition = "2018" license = "MIT" @@ -13,7 +13,7 @@ categories = ["encoding"] build = "build.rs" [dependencies] -schemars_derive = { version = "=0.8.6", optional = true, path = "../schemars_derive" } +schemars_derive = { version = "=0.8.7", optional = true, path = "../schemars_derive" } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" dyn-clone = "1.0" diff --git a/schemars_derive/Cargo.toml b/schemars_derive/Cargo.toml index d4be61ac..4e08ee0b 100644 --- a/schemars_derive/Cargo.toml +++ b/schemars_derive/Cargo.toml @@ -3,7 +3,7 @@ name = "schemars_derive" description = "Macros for #[derive(JsonSchema)], for use with schemars" homepage = "https://graham.cool/schemars/" repository = "https://github.com/GREsau/schemars" -version = "0.8.6" +version = "0.8.7" authors = ["Graham Esau "] edition = "2018" license = "MIT" From c1d2fa85bb89b9a7cd6943933b7c828ceb44dd89 Mon Sep 17 00:00:00 2001 From: caojiafeng Date: Tue, 23 Nov 2021 11:10:45 +0800 Subject: [PATCH 30/44] update diem deps --- schemars/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/schemars/Cargo.toml b/schemars/Cargo.toml index 0c3e0375..f35536b1 100644 --- a/schemars/Cargo.toml +++ b/schemars/Cargo.toml @@ -27,8 +27,8 @@ arrayvec = { version = "0.5", default-features = false, optional = true } url = { version = "2.0", default-features = false, optional = true } bytes = { version = "1.0", optional = true } multiaddr = { version = "0.13.0" } -move-core-types = { git = "https://github.com/starcoinorg/diem", rev = "5a8ea734641cce096914fe10a2fbd8aea2e65fd1" } -diem-crypto = { package="diem-crypto", git = "https://github.com/starcoinorg/diem", rev="5a8ea734641cce096914fe10a2fbd8aea2e65fd1", features = ["fuzzing"] } +move-core-types = { git = "https://github.com/starcoinorg/diem", rev = "533a5432a7b29199f784956c9335bef625e6f8ac" } +diem-crypto = { package="diem-crypto", git = "https://github.com/starcoinorg/diem", rev="533a5432a7b29199f784956c9335bef625e6f8ac", features = ["fuzzing"] } [dev-dependencies] pretty_assertions = "0.6.1" From 3cac0e5048e0afb37297b00ccc7223cd6c648983 Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Thu, 25 Nov 2021 21:12:30 +0000 Subject: [PATCH 31/44] Add example for optional dependency in readme Based on https://github.com/GREsau/schemars/pull/118/files --- README.md | 7 +++++++ schemars/src/lib.rs | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/README.md b/README.md index f5cafb9a..c9e55f25 100644 --- a/README.md +++ b/README.md @@ -275,3 +275,10 @@ Schemars can implement `JsonSchema` on types from several popular crates, enable - [`url`](https://crates.io/crates/url) (^2.0) - [`bytes`](https://crates.io/crates/bytes) (^1.0) - [`enumset`](https://crates.io/crates/enumset) (^1.0) + +For example, to implement `JsonSchema` on types from `chrono`, enable it as a feature in the `schemars` dependency in your `Cargo.toml` like so: + +```toml +[dependencies] +schemars = { version = "0.8", features = ["chrono"] } +``` diff --git a/schemars/src/lib.rs b/schemars/src/lib.rs index d62f1d35..c0f6e1fa 100644 --- a/schemars/src/lib.rs +++ b/schemars/src/lib.rs @@ -270,6 +270,13 @@ Schemars can implement `JsonSchema` on types from several popular crates, enable - [`url`](https://crates.io/crates/url) (^2.0) - [`bytes`](https://crates.io/crates/bytes) (^1.0) - [`enumset`](https://crates.io/crates/enumset) (^1.0) + +For example, to implement `JsonSchema` on types from `chrono`, enable it as a feature in the `schemars` dependency in your `Cargo.toml` like so: + +```toml +[dependencies] +schemars = { version = "0.8", features = ["chrono"] } +``` */ /// The map type used by schemars types. From f0d2b1c50c292859b47653eb12229e4f29010cef Mon Sep 17 00:00:00 2001 From: timando Date: Fri, 26 Nov 2021 07:42:25 +1000 Subject: [PATCH 32/44] Add support for rust_decimal and bigdecimal (#101) --- schemars/Cargo.toml | 2 ++ schemars/src/json_schema_impls/decimal.rs | 31 +++++++++++++++++++++++ schemars/src/json_schema_impls/mod.rs | 2 ++ 3 files changed, 35 insertions(+) create mode 100644 schemars/src/json_schema_impls/decimal.rs diff --git a/schemars/Cargo.toml b/schemars/Cargo.toml index 993f1263..013c0d64 100644 --- a/schemars/Cargo.toml +++ b/schemars/Cargo.toml @@ -26,6 +26,8 @@ smallvec = { version = "1.0", optional = true } arrayvec = { version = "0.5", default-features = false, optional = true } url = { version = "2.0", default-features = false, optional = true } bytes = { version = "1.0", optional = true } +rust_decimal = { version = "1", default-features = false, optional = true } +bigdecimal = { version = "0.3", default-features = false, optional = true } enumset = { version = "1.0", optional = true } [dev-dependencies] diff --git a/schemars/src/json_schema_impls/decimal.rs b/schemars/src/json_schema_impls/decimal.rs new file mode 100644 index 00000000..6058a7fd --- /dev/null +++ b/schemars/src/json_schema_impls/decimal.rs @@ -0,0 +1,31 @@ +use crate::gen::SchemaGenerator; +use crate::schema::*; +use crate::JsonSchema; + +macro_rules! decimal_impl { + ($type:ty) => { + decimal_impl!($type => Number, "Number"); + }; + ($type:ty => $instance_type:ident, $name:expr) => { + impl JsonSchema for $type { + no_ref_schema!(); + + fn schema_name() -> String { + $name.to_owned() + } + + fn json_schema(_: &mut SchemaGenerator) -> Schema { + SchemaObject { + instance_type: Some(InstanceType::$instance_type.into()), + ..Default::default() + } + .into() + } + } + }; +} + +#[cfg(feature="rust_decimal")] +decimal_impl!(rust_decimal::Decimal); +#[cfg(feature="bigdecimal")] +decimal_impl!(bigdecimal::BigDecimal); diff --git a/schemars/src/json_schema_impls/mod.rs b/schemars/src/json_schema_impls/mod.rs index d9c81c85..950f0f7c 100644 --- a/schemars/src/json_schema_impls/mod.rs +++ b/schemars/src/json_schema_impls/mod.rs @@ -45,6 +45,8 @@ mod bytes; #[cfg(feature = "chrono")] mod chrono; mod core; +#[cfg(any(feature = "rust_decimal", feature="bigdecimal"))] +mod decimal; #[cfg(feature = "either")] mod either; #[cfg(feature = "enumset")] From d5499571832c1f3877ebcb1d4ca01ffce58fcf7d Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Thu, 25 Nov 2021 21:36:00 +0000 Subject: [PATCH 33/44] Document new optional dependencies --- README.md | 2 ++ docs/4-features.md | 2 ++ schemars/src/lib.rs | 2 ++ 3 files changed, 6 insertions(+) diff --git a/README.md b/README.md index c9e55f25..dbdcf8bb 100644 --- a/README.md +++ b/README.md @@ -275,6 +275,8 @@ Schemars can implement `JsonSchema` on types from several popular crates, enable - [`url`](https://crates.io/crates/url) (^2.0) - [`bytes`](https://crates.io/crates/bytes) (^1.0) - [`enumset`](https://crates.io/crates/enumset) (^1.0) +- [`rust_decimal`](https://crates.io/crates/rust_decimal) (^1.0) +- [`bigdecimal`](https://crates.io/crates/bigdecimal) (^0.3) For example, to implement `JsonSchema` on types from `chrono`, enable it as a feature in the `schemars` dependency in your `Cargo.toml` like so: diff --git a/docs/4-features.md b/docs/4-features.md index 613d3529..a6aee04a 100644 --- a/docs/4-features.md +++ b/docs/4-features.md @@ -29,3 +29,5 @@ Schemars can implement `JsonSchema` on types from several popular crates, enable - [`url`](https://crates.io/crates/url) (^2.0) - [`bytes`](https://crates.io/crates/bytes) (^1.0) - [`enumset`](https://crates.io/crates/enumset) (^1.0) +- [`rust_decimal`](https://crates.io/crates/rust_decimal) (^1.0) +- [`bigdecimal`](https://crates.io/crates/bigdecimal) (^0.3) diff --git a/schemars/src/lib.rs b/schemars/src/lib.rs index c0f6e1fa..5ef0bdfa 100644 --- a/schemars/src/lib.rs +++ b/schemars/src/lib.rs @@ -270,6 +270,8 @@ Schemars can implement `JsonSchema` on types from several popular crates, enable - [`url`](https://crates.io/crates/url) (^2.0) - [`bytes`](https://crates.io/crates/bytes) (^1.0) - [`enumset`](https://crates.io/crates/enumset) (^1.0) +- [`rust_decimal`](https://crates.io/crates/rust_decimal) (^1.0) +- [`bigdecimal`](https://crates.io/crates/bigdecimal) (^0.3) For example, to implement `JsonSchema` on types from `chrono`, enable it as a feature in the `schemars` dependency in your `Cargo.toml` like so: From 98ad16288b848e6abb0be5d30a22a663957c36f7 Mon Sep 17 00:00:00 2001 From: "Adam H. Leventhal" Date: Thu, 7 Oct 2021 23:21:05 -0700 Subject: [PATCH 34/44] Internally tagged enums don't honor deny_unknown_fields as precisely as they might. flatten doesn't act quite as intended with regard to additional_properties --- schemars/src/flatten.rs | 29 ++++++++++- schemars/tests/enum.rs | 13 +++++ schemars/tests/enum_deny_unknown_fields.rs | 13 +++++ .../tests/expected/enum-internal-duf.json | 12 +++-- schemars/tests/expected/enum-internal.json | 3 -- .../expected/enum-simple-internal-duf.json | 51 +++++++++++++++++++ .../tests/expected/enum-simple-internal.json | 48 +++++++++++++++++ schemars_derive/src/schema_exprs.rs | 18 +++++++ 8 files changed, 178 insertions(+), 9 deletions(-) create mode 100644 schemars/tests/expected/enum-simple-internal-duf.json create mode 100644 schemars/tests/expected/enum-simple-internal.json diff --git a/schemars/src/flatten.rs b/schemars/src/flatten.rs index 646f614f..35a734fa 100644 --- a/schemars/src/flatten.rs +++ b/schemars/src/flatten.rs @@ -38,6 +38,31 @@ macro_rules! impl_merge { }; } +// For ObjectValidation::additional_properties. +impl Merge for Option> { + fn merge(self, other: Self) -> Self { + match (self.map(|x| *x), other.map(|x| *x)) { + // Perfer permissive schemas. + (Some(Schema::Bool(true)), _) => Some(Box::new(true.into())), + (_, Some(Schema::Bool(true))) => Some(Box::new(true.into())), + (None, _) => None, + (_, None) => None, + + // Merge if we have two non-trivial schemas. + (Some(Schema::Object(s1)), Some(Schema::Object(s2))) => { + Some(Box::new(Schema::Object(s1.merge(s2)))) + } + + // Perfer the more permissive schema. + (Some(s1 @ Schema::Object(_)), Some(Schema::Bool(false))) => Some(Box::new(s1)), + (Some(Schema::Bool(false)), Some(s2 @ Schema::Object(_))) => Some(Box::new(s2)), + + // Default to the null schema. + (Some(Schema::Bool(false)), Some(Schema::Bool(false))) => Some(Box::new(false.into())), + } + } +} + impl_merge!(SchemaObject { merge: extensions instance_type enum_values metadata subschemas number string array object, @@ -76,8 +101,8 @@ impl_merge!(ArrayValidation { }); impl_merge!(ObjectValidation { - merge: required properties pattern_properties, - or: max_properties min_properties additional_properties property_names, + merge: required properties pattern_properties additional_properties, + or: max_properties min_properties property_names, }); impl Merge for Option { diff --git a/schemars/tests/enum.rs b/schemars/tests/enum.rs index ea34d06e..48e0d34a 100644 --- a/schemars/tests/enum.rs +++ b/schemars/tests/enum.rs @@ -99,3 +99,16 @@ pub enum Adjacent { fn enum_adjacent_tagged() -> TestResult { test_default_generated_schema::("enum-adjacent-tagged") } + +#[derive(Debug, JsonSchema)] +#[schemars(tag = "typeProperty")] +pub enum SimpleInternal { + A, + B, + C, +} + +#[test] +fn enum_simple_internal_tag() -> TestResult { + test_default_generated_schema::("enum-simple-internal") +} diff --git a/schemars/tests/enum_deny_unknown_fields.rs b/schemars/tests/enum_deny_unknown_fields.rs index bf0f4003..8720b729 100644 --- a/schemars/tests/enum_deny_unknown_fields.rs +++ b/schemars/tests/enum_deny_unknown_fields.rs @@ -104,3 +104,16 @@ pub enum Adjacent { fn enum_adjacent_tagged() -> TestResult { test_default_generated_schema::("enum-adjacent-tagged-duf") } + +#[derive(Debug, JsonSchema)] +#[schemars(tag = "typeProperty", deny_unknown_fields)] +pub enum SimpleInternal { + A, + B, + C, +} + +#[test] +fn enum_simple_internal_tag() -> TestResult { + test_default_generated_schema::("enum-simple-internal-duf") +} diff --git a/schemars/tests/expected/enum-internal-duf.json b/schemars/tests/expected/enum-internal-duf.json index deb39664..fc36644f 100644 --- a/schemars/tests/expected/enum-internal-duf.json +++ b/schemars/tests/expected/enum-internal-duf.json @@ -14,7 +14,8 @@ "UnitOne" ] } - } + }, + "additionalProperties": false }, { "type": "object", @@ -45,7 +46,8 @@ "UnitStructNewType" ] } - } + }, + "additionalProperties": false }, { "type": "object", @@ -106,7 +108,8 @@ "UnitTwo" ] } - } + }, + "additionalProperties": false }, { "type": [ @@ -124,7 +127,8 @@ "WithInt" ] } - } + }, + "additionalProperties": false } ] } \ No newline at end of file diff --git a/schemars/tests/expected/enum-internal.json b/schemars/tests/expected/enum-internal.json index b43b779a..37739b09 100644 --- a/schemars/tests/expected/enum-internal.json +++ b/schemars/tests/expected/enum-internal.json @@ -28,9 +28,6 @@ "StringMap" ] } - }, - "additionalProperties": { - "type": "string" } }, { diff --git a/schemars/tests/expected/enum-simple-internal-duf.json b/schemars/tests/expected/enum-simple-internal-duf.json new file mode 100644 index 00000000..833f7b73 --- /dev/null +++ b/schemars/tests/expected/enum-simple-internal-duf.json @@ -0,0 +1,51 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "SimpleInternal", + "oneOf": [ + { + "type": "object", + "required": [ + "typeProperty" + ], + "properties": { + "typeProperty": { + "type": "string", + "enum": [ + "A" + ] + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "typeProperty" + ], + "properties": { + "typeProperty": { + "type": "string", + "enum": [ + "B" + ] + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "typeProperty" + ], + "properties": { + "typeProperty": { + "type": "string", + "enum": [ + "C" + ] + } + }, + "additionalProperties": false + } + ] +} \ No newline at end of file diff --git a/schemars/tests/expected/enum-simple-internal.json b/schemars/tests/expected/enum-simple-internal.json new file mode 100644 index 00000000..50cd62c1 --- /dev/null +++ b/schemars/tests/expected/enum-simple-internal.json @@ -0,0 +1,48 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "SimpleInternal", + "oneOf": [ + { + "type": "object", + "required": [ + "typeProperty" + ], + "properties": { + "typeProperty": { + "type": "string", + "enum": [ + "A" + ] + } + } + }, + { + "type": "object", + "required": [ + "typeProperty" + ], + "properties": { + "typeProperty": { + "type": "string", + "enum": [ + "B" + ] + } + } + }, + { + "type": "object", + "required": [ + "typeProperty" + ], + "properties": { + "typeProperty": { + "type": "string", + "enum": [ + "C" + ] + } + } + } + ] +} \ No newline at end of file diff --git a/schemars_derive/src/schema_exprs.rs b/schemars_derive/src/schema_exprs.rs index 4c463dc8..c5b1672d 100644 --- a/schemars_derive/src/schema_exprs.rs +++ b/schemars_derive/src/schema_exprs.rs @@ -183,6 +183,12 @@ fn expr_for_external_tagged_enum<'a>( required.insert(#name.to_owned()); required }, + // Externally tagged variants must prohibit additional + // properties irrespective of the disposition of + // `deny_unknown_fields`. If additional properties were allowed + // one could easily construct an object that validated against + // multiple variants since here it's the properties rather than + // the values of a property that distingish between variants. additional_properties: Some(Box::new(false.into())), ..Default::default() })), @@ -206,6 +212,13 @@ fn expr_for_internal_tagged_enum<'a>( ) -> TokenStream { let mut unique_names = HashSet::new(); let mut count = 0; + let set_additional_properties = if deny_unknown_fields { + quote! { + additional_properties: Some(Box::new(false.into())), + } + } else { + TokenStream::new() + }; let variant_schemas = variants .map(|variant| { unique_names.insert(variant.name()); @@ -230,6 +243,9 @@ fn expr_for_internal_tagged_enum<'a>( required.insert(#tag_name.to_owned()); required }, + // As we're creating a "wrapper" object, we can honor the + // disposition of deny_unknown_fields. + #set_additional_properties ..Default::default() })), }); @@ -328,6 +344,8 @@ fn expr_for_adjacent_tagged_enum<'a>( #add_content_to_required required }, + // As we're creating a "wrapper" object, we can honor the + // disposition of deny_unknown_fields. #set_additional_properties ..Default::default() })), From e9d5f4057e84f37a4519a5413ddded82cb13ea71 Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Thu, 25 Nov 2021 22:27:16 +0000 Subject: [PATCH 35/44] v0.8.8 --- CHANGELOG.md | 7 +++++++ schemars/Cargo.toml | 4 ++-- schemars_derive/Cargo.toml | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b9f4518..3e78341c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [0.8.8] - 2021-11-25 +### Added: +- Implement `JsonSchema` for types from `rust_decimal` and `bigdecimal` crates (https://github.com/GREsau/schemars/pull/101) + +### Fixed: +- Fixes for internally tagged enums and flattening additional_properties (https://github.com/GREsau/schemars/pull/113) + ## [0.8.7] - 2021-11-14 ### Added: - Implement `JsonSchema` for `EnumSet` (https://github.com/GREsau/schemars/pull/92) diff --git a/schemars/Cargo.toml b/schemars/Cargo.toml index 013c0d64..bc1097fb 100644 --- a/schemars/Cargo.toml +++ b/schemars/Cargo.toml @@ -3,7 +3,7 @@ name = "schemars" description = "Generate JSON Schemas from Rust code" homepage = "https://graham.cool/schemars/" repository = "https://github.com/GREsau/schemars" -version = "0.8.7" +version = "0.8.8" authors = ["Graham Esau "] edition = "2018" license = "MIT" @@ -13,7 +13,7 @@ categories = ["encoding"] build = "build.rs" [dependencies] -schemars_derive = { version = "=0.8.7", optional = true, path = "../schemars_derive" } +schemars_derive = { version = "=0.8.8", optional = true, path = "../schemars_derive" } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" dyn-clone = "1.0" diff --git a/schemars_derive/Cargo.toml b/schemars_derive/Cargo.toml index 4e08ee0b..53a64088 100644 --- a/schemars_derive/Cargo.toml +++ b/schemars_derive/Cargo.toml @@ -3,7 +3,7 @@ name = "schemars_derive" description = "Macros for #[derive(JsonSchema)], for use with schemars" homepage = "https://graham.cool/schemars/" repository = "https://github.com/GREsau/schemars" -version = "0.8.7" +version = "0.8.8" authors = ["Graham Esau "] edition = "2018" license = "MIT" From 764532fd87674a97a39397618319b57000a81f40 Mon Sep 17 00:00:00 2001 From: caojiafeng Date: Tue, 30 Nov 2021 11:00:36 +0800 Subject: [PATCH 36/44] update diem deps to latest --- schemars/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/schemars/Cargo.toml b/schemars/Cargo.toml index f35536b1..d64f0d99 100644 --- a/schemars/Cargo.toml +++ b/schemars/Cargo.toml @@ -27,8 +27,8 @@ arrayvec = { version = "0.5", default-features = false, optional = true } url = { version = "2.0", default-features = false, optional = true } bytes = { version = "1.0", optional = true } multiaddr = { version = "0.13.0" } -move-core-types = { git = "https://github.com/starcoinorg/diem", rev = "533a5432a7b29199f784956c9335bef625e6f8ac" } -diem-crypto = { package="diem-crypto", git = "https://github.com/starcoinorg/diem", rev="533a5432a7b29199f784956c9335bef625e6f8ac", features = ["fuzzing"] } +move-core-types = { git = "https://github.com/starcoinorg/diem", rev = "5afe3cb94e2d01ee2eb444729fc31d627b8e698c" } +diem-crypto = { package="diem-crypto", git = "https://github.com/starcoinorg/diem", rev="5afe3cb94e2d01ee2eb444729fc31d627b8e698c", features = ["fuzzing"] } [dev-dependencies] pretty_assertions = "0.6.1" From 3f8ecc27f02b27d56c716de58d7880ae7816390c Mon Sep 17 00:00:00 2001 From: caojiafeng Date: Thu, 2 Dec 2021 10:01:15 +0800 Subject: [PATCH 37/44] update diem deps --- schemars/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/schemars/Cargo.toml b/schemars/Cargo.toml index d64f0d99..4acc75d4 100644 --- a/schemars/Cargo.toml +++ b/schemars/Cargo.toml @@ -27,8 +27,8 @@ arrayvec = { version = "0.5", default-features = false, optional = true } url = { version = "2.0", default-features = false, optional = true } bytes = { version = "1.0", optional = true } multiaddr = { version = "0.13.0" } -move-core-types = { git = "https://github.com/starcoinorg/diem", rev = "5afe3cb94e2d01ee2eb444729fc31d627b8e698c" } -diem-crypto = { package="diem-crypto", git = "https://github.com/starcoinorg/diem", rev="5afe3cb94e2d01ee2eb444729fc31d627b8e698c", features = ["fuzzing"] } +move-core-types = { git = "https://github.com/starcoinorg/diem", rev = "6b1533f22b3075cc15ff68db21b5b84e6baa2955" } +diem-crypto = { package="diem-crypto", git = "https://github.com/starcoinorg/diem", rev="6b1533f22b3075cc15ff68db21b5b84e6baa2955", features = ["fuzzing"] } [dev-dependencies] pretty_assertions = "0.6.1" From 8ada318f4283a4dbb5adeea8a31a8db6a05e0f4d Mon Sep 17 00:00:00 2001 From: fikgol Date: Mon, 10 Jan 2022 14:21:53 +0800 Subject: [PATCH 38/44] Update dep --- schemars/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/schemars/Cargo.toml b/schemars/Cargo.toml index 4acc75d4..8ab0a9d6 100644 --- a/schemars/Cargo.toml +++ b/schemars/Cargo.toml @@ -27,8 +27,8 @@ arrayvec = { version = "0.5", default-features = false, optional = true } url = { version = "2.0", default-features = false, optional = true } bytes = { version = "1.0", optional = true } multiaddr = { version = "0.13.0" } -move-core-types = { git = "https://github.com/starcoinorg/diem", rev = "6b1533f22b3075cc15ff68db21b5b84e6baa2955" } -diem-crypto = { package="diem-crypto", git = "https://github.com/starcoinorg/diem", rev="6b1533f22b3075cc15ff68db21b5b84e6baa2955", features = ["fuzzing"] } +move-core-types = { git = "https://github.com/starcoinorg/diem", rev = "cb1ebfd821f4750cd525efbd6bdc8e4a71a4bf3b" } +diem-crypto = { package="diem-crypto", git = "https://github.com/starcoinorg/diem", rev="cb1ebfd821f4750cd525efbd6bdc8e4a71a4bf3b", features = ["fuzzing"] } [dev-dependencies] pretty_assertions = "0.6.1" From fa9abf1ea670677f0d628112c83d08937c743bad Mon Sep 17 00:00:00 2001 From: caojiafeng Date: Thu, 13 Jan 2022 09:19:14 +0800 Subject: [PATCH 39/44] update diem deps --- rust-toolchain | 2 +- schemars/Cargo.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/rust-toolchain b/rust-toolchain index d2d62553..f27a1d54 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -1.51.0 \ No newline at end of file +1.57.0 \ No newline at end of file diff --git a/schemars/Cargo.toml b/schemars/Cargo.toml index 8ab0a9d6..216a09d8 100644 --- a/schemars/Cargo.toml +++ b/schemars/Cargo.toml @@ -27,8 +27,8 @@ arrayvec = { version = "0.5", default-features = false, optional = true } url = { version = "2.0", default-features = false, optional = true } bytes = { version = "1.0", optional = true } multiaddr = { version = "0.13.0" } -move-core-types = { git = "https://github.com/starcoinorg/diem", rev = "cb1ebfd821f4750cd525efbd6bdc8e4a71a4bf3b" } -diem-crypto = { package="diem-crypto", git = "https://github.com/starcoinorg/diem", rev="cb1ebfd821f4750cd525efbd6bdc8e4a71a4bf3b", features = ["fuzzing"] } +move-core-types = { git = "https://github.com/starcoinorg/diem", rev = "bdb6f73f3a196f9f80ea8ae09a859c6a3579d702" } +diem-crypto = { package="diem-crypto", git = "https://github.com/starcoinorg/diem", rev="bdb6f73f3a196f9f80ea8ae09a859c6a3579d702", features = ["fuzzing"] } [dev-dependencies] pretty_assertions = "0.6.1" From 463aa92b0bdb20304906759f43fd5803f0ab2ff6 Mon Sep 17 00:00:00 2001 From: fikgol Date: Mon, 17 Jan 2022 10:28:52 +0800 Subject: [PATCH 40/44] Update rust toolchain and dep --- schemars/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/schemars/Cargo.toml b/schemars/Cargo.toml index 216a09d8..89a5f8e8 100644 --- a/schemars/Cargo.toml +++ b/schemars/Cargo.toml @@ -27,8 +27,8 @@ arrayvec = { version = "0.5", default-features = false, optional = true } url = { version = "2.0", default-features = false, optional = true } bytes = { version = "1.0", optional = true } multiaddr = { version = "0.13.0" } -move-core-types = { git = "https://github.com/starcoinorg/diem", rev = "bdb6f73f3a196f9f80ea8ae09a859c6a3579d702" } -diem-crypto = { package="diem-crypto", git = "https://github.com/starcoinorg/diem", rev="bdb6f73f3a196f9f80ea8ae09a859c6a3579d702", features = ["fuzzing"] } +move-core-types = { git = "https://github.com/starcoinorg/diem", rev = "eda7f1d40ff76b003f0635658ad11eb974a760ee" } +diem-crypto = { package="diem-crypto", git = "https://github.com/starcoinorg/diem", rev="eda7f1d40ff76b003f0635658ad11eb974a760ee", features = ["fuzzing"] } [dev-dependencies] pretty_assertions = "0.6.1" From fd4e39f69bf331464a7520676bd67d202a14a6db Mon Sep 17 00:00:00 2001 From: caojiafeng Date: Thu, 20 Jan 2022 11:55:53 +0800 Subject: [PATCH 41/44] update diem dep --- schemars/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/schemars/Cargo.toml b/schemars/Cargo.toml index 89a5f8e8..82857a8a 100644 --- a/schemars/Cargo.toml +++ b/schemars/Cargo.toml @@ -27,8 +27,8 @@ arrayvec = { version = "0.5", default-features = false, optional = true } url = { version = "2.0", default-features = false, optional = true } bytes = { version = "1.0", optional = true } multiaddr = { version = "0.13.0" } -move-core-types = { git = "https://github.com/starcoinorg/diem", rev = "eda7f1d40ff76b003f0635658ad11eb974a760ee" } -diem-crypto = { package="diem-crypto", git = "https://github.com/starcoinorg/diem", rev="eda7f1d40ff76b003f0635658ad11eb974a760ee", features = ["fuzzing"] } +move-core-types = { git = "https://github.com/starcoinorg/diem", rev = "94cad072661257a7d55713d6a6df81638a9580ae" } +diem-crypto = { package="diem-crypto", git = "https://github.com/starcoinorg/diem", rev="94cad072661257a7d55713d6a6df81638a9580ae", features = ["fuzzing"] } [dev-dependencies] pretty_assertions = "0.6.1" From 08416753ea9fdb9ba27d255b37e036425b7c55a3 Mon Sep 17 00:00:00 2001 From: fikgol Date: Thu, 17 Feb 2022 16:39:15 +0800 Subject: [PATCH 42/44] Update diem dep --- schemars/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/schemars/Cargo.toml b/schemars/Cargo.toml index 82857a8a..605672bf 100644 --- a/schemars/Cargo.toml +++ b/schemars/Cargo.toml @@ -27,8 +27,8 @@ arrayvec = { version = "0.5", default-features = false, optional = true } url = { version = "2.0", default-features = false, optional = true } bytes = { version = "1.0", optional = true } multiaddr = { version = "0.13.0" } -move-core-types = { git = "https://github.com/starcoinorg/diem", rev = "94cad072661257a7d55713d6a6df81638a9580ae" } -diem-crypto = { package="diem-crypto", git = "https://github.com/starcoinorg/diem", rev="94cad072661257a7d55713d6a6df81638a9580ae", features = ["fuzzing"] } +move-core-types = { git = "https://github.com/starcoinorg/diem", rev = "59f3187dabfcd4fdf5f1a3d0248ca67a27aba498" } +diem-crypto = { package="diem-crypto", git = "https://github.com/starcoinorg/diem", rev="59f3187dabfcd4fdf5f1a3d0248ca67a27aba498", features = ["fuzzing"] } [dev-dependencies] pretty_assertions = "0.6.1" From d303c93f1cfc9c0993e8c412c1732b93799c0faf Mon Sep 17 00:00:00 2001 From: jolestar Date: Thu, 24 Feb 2022 10:00:16 +0800 Subject: [PATCH 43/44] [crypto] Update dependency crypto to use starcoinorg/starcoin-crypto repo (#5) * [crypto] Update dependency crypto to use starcoinorg/starcoin-crypto repo. * [deps] Update move-core-types to starcoinorg/move --- .gitignore | 1 + schemars/Cargo.toml | 4 ++-- schemars/src/json_schema_impls/diem_types.rs | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 088ba6ba..cc96bb34 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ Cargo.lock # These are backup files generated by rustfmt **/*.rs.bk +.idea diff --git a/schemars/Cargo.toml b/schemars/Cargo.toml index 605672bf..eef48a4f 100644 --- a/schemars/Cargo.toml +++ b/schemars/Cargo.toml @@ -27,8 +27,8 @@ arrayvec = { version = "0.5", default-features = false, optional = true } url = { version = "2.0", default-features = false, optional = true } bytes = { version = "1.0", optional = true } multiaddr = { version = "0.13.0" } -move-core-types = { git = "https://github.com/starcoinorg/diem", rev = "59f3187dabfcd4fdf5f1a3d0248ca67a27aba498" } -diem-crypto = { package="diem-crypto", git = "https://github.com/starcoinorg/diem", rev="59f3187dabfcd4fdf5f1a3d0248ca67a27aba498", features = ["fuzzing"] } +move-core-types = { git = "https://github.com/starcoinorg/move", rev="1d252312cdce83c10c93be349c21390a2b9a465a" } +starcoin-crypto = { git = "https://github.com/starcoinorg/starcoin-crypto", rev="78aaf55a8b85a5eff208037e0cc798cf985e1a4e", features = ["fuzzing"] } [dev-dependencies] pretty_assertions = "0.6.1" diff --git a/schemars/src/json_schema_impls/diem_types.rs b/schemars/src/json_schema_impls/diem_types.rs index 551ad475..1a3d7077 100644 --- a/schemars/src/json_schema_impls/diem_types.rs +++ b/schemars/src/json_schema_impls/diem_types.rs @@ -1,7 +1,7 @@ use crate::gen::SchemaGenerator; use crate::schema::*; use crate::JsonSchema; -use diem_crypto::HashValue; +use starcoin_crypto::HashValue; use move_core_types::account_address::AccountAddress; use multiaddr::Multiaddr; impl JsonSchema for AccountAddress { From eb0d997d189b6355e1f980bcba490c7bf5081caf Mon Sep 17 00:00:00 2001 From: fikgol Date: Mon, 7 Mar 2022 11:58:31 +0800 Subject: [PATCH 44/44] Remove diem types --- schemars/src/json_schema_impls/diem_types.rs | 54 -------------------- schemars/src/json_schema_impls/mod.rs | 3 +- schemars/src/json_schema_impls/multi_addr.rs | 20 ++++++++ 3 files changed, 21 insertions(+), 56 deletions(-) delete mode 100644 schemars/src/json_schema_impls/diem_types.rs create mode 100644 schemars/src/json_schema_impls/multi_addr.rs diff --git a/schemars/src/json_schema_impls/diem_types.rs b/schemars/src/json_schema_impls/diem_types.rs deleted file mode 100644 index 1a3d7077..00000000 --- a/schemars/src/json_schema_impls/diem_types.rs +++ /dev/null @@ -1,54 +0,0 @@ -use crate::gen::SchemaGenerator; -use crate::schema::*; -use crate::JsonSchema; -use starcoin_crypto::HashValue; -use move_core_types::account_address::AccountAddress; -use multiaddr::Multiaddr; -impl JsonSchema for AccountAddress { - no_ref_schema!(); - - fn schema_name() -> String { - "AccountAddress".to_owned() - } - - fn json_schema(_: &mut SchemaGenerator) -> Schema { - SchemaObject { - instance_type: Some(InstanceType::String.into()), - format: Some("AccountAddress".to_owned()), - ..Default::default() - } - .into() - } -} -impl JsonSchema for HashValue { - no_ref_schema!(); - - fn schema_name() -> String { - "HashValue".to_owned() - } - - fn json_schema(_: &mut SchemaGenerator) -> Schema { - SchemaObject { - instance_type: Some(InstanceType::String.into()), - format: Some("HashValue".to_owned()), - ..Default::default() - } - .into() - } -} -impl JsonSchema for Multiaddr { - no_ref_schema!(); - - fn schema_name() -> String { - "Multiaddr".to_owned() - } - - fn json_schema(_: &mut SchemaGenerator) -> Schema { - SchemaObject { - instance_type: Some(InstanceType::String.into()), - format: Some("Multiaddr".to_owned()), - ..Default::default() - } - .into() - } -} diff --git a/schemars/src/json_schema_impls/mod.rs b/schemars/src/json_schema_impls/mod.rs index 4e661156..12f20f4a 100644 --- a/schemars/src/json_schema_impls/mod.rs +++ b/schemars/src/json_schema_impls/mod.rs @@ -65,5 +65,4 @@ mod url; #[cfg(feature = "uuid")] mod uuid; mod wrapper; - -mod diem_types; +mod multi_addr; diff --git a/schemars/src/json_schema_impls/multi_addr.rs b/schemars/src/json_schema_impls/multi_addr.rs new file mode 100644 index 00000000..f0a0f9f9 --- /dev/null +++ b/schemars/src/json_schema_impls/multi_addr.rs @@ -0,0 +1,20 @@ +use crate::gen::SchemaGenerator; +use crate::schema::*; +use crate::JsonSchema; +use multiaddr::Multiaddr; +impl JsonSchema for Multiaddr { + no_ref_schema!(); + + fn schema_name() -> String { + "Multiaddr".to_owned() + } + + fn json_schema(_: &mut SchemaGenerator) -> Schema { + SchemaObject { + instance_type: Some(InstanceType::String.into()), + format: Some("Multiaddr".to_owned()), + ..Default::default() + } + .into() + } +} \ No newline at end of file