Make Option<T> fields optional in generated schemas (#16)
This commit is contained in:
parent
60284fdf93
commit
4ad5000232
14 changed files with 269 additions and 148 deletions
|
@ -1,5 +1,9 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## Current changes (version TBC)
|
||||||
|
### Fixed:
|
||||||
|
- When deriving `JsonSchema` on structs, `Option<T>` struct fields are no longer included in the list of required properties in the schema (https://github.com/GREsau/schemars/issues/11)
|
||||||
|
|
||||||
## [0.7.0-alpha-1] - 2019-12-29
|
## [0.7.0-alpha-1] - 2019-12-29
|
||||||
### Changed:
|
### Changed:
|
||||||
- **BREAKING CHANGE** - `SchemaSettings` can no longer be created using struct initialization syntax. Instead, if you need to use custom schema settings, you can use a constructor function and either:
|
- **BREAKING CHANGE** - `SchemaSettings` can no longer be created using struct initialization syntax. Instead, if you need to use custom schema settings, you can use a constructor function and either:
|
||||||
|
|
|
@ -15,7 +15,7 @@ impl Schema {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
trait Merge: Sized {
|
pub(crate) trait Merge: Sized {
|
||||||
fn merge(self, other: Self) -> Self;
|
fn merge(self, other: Self) -> Self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use crate::flatten::Merge;
|
||||||
use crate::schema::*;
|
use crate::schema::*;
|
||||||
use crate::{JsonSchema, Map};
|
use crate::{JsonSchema, Map};
|
||||||
|
|
||||||
|
@ -169,9 +170,9 @@ impl SchemaGenerator {
|
||||||
&self.settings
|
&self.settings
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a `SchemaObject` equivalent to the given `schema` which may have validation, metadata or other properties set on it.
|
/// Modifies the given `SchemaObject` so that it may have validation, metadata or other properties set on it.
|
||||||
///
|
///
|
||||||
/// If `schema` is not a `$ref` schema, then this returns `schema` unmodified. Otherwise, depending on this generator's settings,
|
/// If `schema` is not a `$ref` schema, then this does not modify `schema`. Otherwise, depending on this generator's settings,
|
||||||
/// this may wrap the `$ref` in another schema. This is required because in many JSON Schema implementations, a schema with `$ref`
|
/// this may wrap the `$ref` in another schema. This is required because in many JSON Schema implementations, a schema with `$ref`
|
||||||
/// set may not include other properties.
|
/// set may not include other properties.
|
||||||
///
|
///
|
||||||
|
@ -184,24 +185,19 @@ impl SchemaGenerator {
|
||||||
/// let ref_schema = SchemaObject::new_ref("foo".to_owned());
|
/// let ref_schema = SchemaObject::new_ref("foo".to_owned());
|
||||||
/// assert!(ref_schema.is_ref());
|
/// assert!(ref_schema.is_ref());
|
||||||
///
|
///
|
||||||
/// let extensible_schema = gen.make_extensible(ref_schema.clone());
|
/// let mut extensible_schema = ref_schema.clone();
|
||||||
|
/// gen.make_extensible(&mut extensible_schema);
|
||||||
/// assert_ne!(ref_schema, extensible_schema);
|
/// assert_ne!(ref_schema, extensible_schema);
|
||||||
/// assert!(!extensible_schema.is_ref());
|
/// assert!(!extensible_schema.is_ref());
|
||||||
///
|
///
|
||||||
/// let extensible_schema2 = gen.make_extensible(extensible_schema.clone());
|
/// let mut extensible_schema2 = extensible_schema.clone();
|
||||||
|
/// gen.make_extensible(&mut extensible_schema);
|
||||||
/// assert_eq!(extensible_schema, extensible_schema2);
|
/// assert_eq!(extensible_schema, extensible_schema2);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn make_extensible(&self, schema: SchemaObject) -> SchemaObject {
|
pub fn make_extensible(&self, schema: &mut SchemaObject) {
|
||||||
if schema.is_ref() && !self.settings().allow_ref_siblings {
|
if schema.is_ref() && !self.settings().allow_ref_siblings {
|
||||||
SchemaObject {
|
let original = std::mem::replace(schema, SchemaObject::default());
|
||||||
subschemas: Some(Box::new(SubschemaValidation {
|
schema.subschemas().all_of = Some(vec![original.into()]);
|
||||||
all_of: Some(vec![schema.into()]),
|
|
||||||
..Default::default()
|
|
||||||
})),
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
schema
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -279,8 +275,8 @@ impl SchemaGenerator {
|
||||||
/// add them to the `SchemaGenerator`'s schema definitions and include them in the returned `SchemaObject`'s
|
/// add them to the `SchemaGenerator`'s schema definitions and include them in the returned `SchemaObject`'s
|
||||||
/// [`definitions`](../schema/struct.Metadata.html#structfield.definitions)
|
/// [`definitions`](../schema/struct.Metadata.html#structfield.definitions)
|
||||||
pub fn root_schema_for<T: ?Sized + JsonSchema>(&mut self) -> RootSchema {
|
pub fn root_schema_for<T: ?Sized + JsonSchema>(&mut self) -> RootSchema {
|
||||||
let schema = T::json_schema(self);
|
let mut schema = T::json_schema(self).into();
|
||||||
let mut schema: SchemaObject = self.make_extensible(schema.into());
|
self.make_extensible(&mut schema);
|
||||||
schema.metadata().title.get_or_insert_with(T::schema_name);
|
schema.metadata().title.get_or_insert_with(T::schema_name);
|
||||||
RootSchema {
|
RootSchema {
|
||||||
meta_schema: self.settings.meta_schema.clone(),
|
meta_schema: self.settings.meta_schema.clone(),
|
||||||
|
@ -294,8 +290,8 @@ impl SchemaGenerator {
|
||||||
/// If `T`'s schema depends on any [referenceable](JsonSchema::is_referenceable) schemas, then this method will
|
/// If `T`'s schema depends on any [referenceable](JsonSchema::is_referenceable) schemas, then this method will
|
||||||
/// include them in the returned `SchemaObject`'s [`definitions`](../schema/struct.Metadata.html#structfield.definitions)
|
/// include them in the returned `SchemaObject`'s [`definitions`](../schema/struct.Metadata.html#structfield.definitions)
|
||||||
pub fn into_root_schema_for<T: ?Sized + JsonSchema>(mut self) -> RootSchema {
|
pub fn into_root_schema_for<T: ?Sized + JsonSchema>(mut self) -> RootSchema {
|
||||||
let schema = T::json_schema(&mut self);
|
let mut schema = T::json_schema(&mut self).into();
|
||||||
let mut schema: SchemaObject = self.make_extensible(schema.into());
|
self.make_extensible(&mut schema);
|
||||||
schema.metadata().title.get_or_insert_with(T::schema_name);
|
schema.metadata().title.get_or_insert_with(T::schema_name);
|
||||||
RootSchema {
|
RootSchema {
|
||||||
meta_schema: self.settings.meta_schema,
|
meta_schema: self.settings.meta_schema,
|
||||||
|
@ -346,4 +342,11 @@ impl SchemaGenerator {
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO should this take a Schema instead of SchemaObject?
|
||||||
|
pub(crate) fn apply_metadata(&self, schema: &mut SchemaObject, metadata: Metadata) {
|
||||||
|
self.make_extensible(schema);
|
||||||
|
// TODO get rid of the clone
|
||||||
|
schema.metadata = Some(Box::new(metadata)).merge(schema.metadata.clone());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,8 @@ impl<T: JsonSchema> JsonSchema for Option<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if gen.settings().option_nullable {
|
if gen.settings().option_nullable {
|
||||||
let mut schema_obj = gen.make_extensible(schema.into());
|
let mut schema_obj = schema.into();
|
||||||
|
gen.make_extensible(&mut schema_obj);
|
||||||
schema_obj
|
schema_obj
|
||||||
.extensions
|
.extensions
|
||||||
.insert("nullable".to_owned(), json!(true));
|
.insert("nullable".to_owned(), json!(true));
|
||||||
|
@ -44,8 +45,8 @@ impl<T: JsonSchema> JsonSchema for Option<T> {
|
||||||
schema
|
schema
|
||||||
}
|
}
|
||||||
|
|
||||||
fn json_schema_optional(gen: &mut SchemaGenerator) -> Schema {
|
fn json_schema_for_flatten(gen: &mut SchemaGenerator) -> Schema {
|
||||||
let mut schema = T::json_schema_optional(gen);
|
let mut schema = T::json_schema_for_flatten(gen);
|
||||||
if let Schema::Object(SchemaObject {
|
if let Schema::Object(SchemaObject {
|
||||||
object: Some(ref mut object_validation),
|
object: Some(ref mut object_validation),
|
||||||
..
|
..
|
||||||
|
@ -55,6 +56,25 @@ impl<T: JsonSchema> JsonSchema for Option<T> {
|
||||||
}
|
}
|
||||||
schema
|
schema
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn add_schema_as_property(
|
||||||
|
gen: &mut SchemaGenerator,
|
||||||
|
parent: &mut SchemaObject,
|
||||||
|
name: String,
|
||||||
|
metadata: Option<Metadata>,
|
||||||
|
_required: bool,
|
||||||
|
) {
|
||||||
|
let mut schema = gen.subschema_for::<Self>();
|
||||||
|
|
||||||
|
if let Some(metadata) = metadata {
|
||||||
|
let mut schema_obj = schema.into();
|
||||||
|
gen.apply_metadata(&mut schema_obj, metadata);
|
||||||
|
schema = Schema::Object(schema_obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
let object = parent.object();
|
||||||
|
object.properties.insert(name, schema);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_null_type(instance_type: &mut SingleOrVec<InstanceType>) {
|
fn add_null_type(instance_type: &mut SingleOrVec<InstanceType>) {
|
||||||
|
|
|
@ -21,8 +21,18 @@ macro_rules! forward_impl {
|
||||||
<$target>::json_schema(gen)
|
<$target>::json_schema(gen)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn json_schema_optional(gen: &mut SchemaGenerator) -> Schema {
|
fn json_schema_for_flatten(gen: &mut SchemaGenerator) -> Schema {
|
||||||
<$target>::json_schema_optional(gen)
|
<$target>::json_schema_for_flatten(gen)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_schema_as_property(
|
||||||
|
gen: &mut SchemaGenerator,
|
||||||
|
parent: &mut crate::schema::SchemaObject,
|
||||||
|
name: String,
|
||||||
|
metadata: Option<crate::schema::Metadata>,
|
||||||
|
required: bool,
|
||||||
|
) {
|
||||||
|
<$target>::add_schema_as_property(gen, parent, name, metadata, required)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -236,7 +236,7 @@ pub use schemars_derive::*;
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub use serde_json as _serde_json;
|
pub use serde_json as _serde_json;
|
||||||
|
|
||||||
use schema::Schema;
|
use schema::{Schema, SchemaObject};
|
||||||
|
|
||||||
/// A type which can be described as a JSON Schema document.
|
/// A type which can be described as a JSON Schema document.
|
||||||
///
|
///
|
||||||
|
@ -281,11 +281,39 @@ pub trait JsonSchema {
|
||||||
|
|
||||||
/// Helper for generating schemas for flattened `Option` fields.
|
/// Helper for generating schemas for flattened `Option` fields.
|
||||||
///
|
///
|
||||||
/// This should not need to be called or implemented by code outside of `schemars`.
|
/// This should not need to be called or implemented by code outside of `schemars`,
|
||||||
|
/// and should not be considered part of the public API.
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
fn json_schema_optional(gen: &mut gen::SchemaGenerator) -> Schema {
|
fn json_schema_for_flatten(gen: &mut gen::SchemaGenerator) -> Schema {
|
||||||
Self::json_schema(gen)
|
Self::json_schema(gen)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helper for generating schemas for `Option` fields.
|
||||||
|
///
|
||||||
|
/// This should not need to be called or implemented by code outside of `schemars`,
|
||||||
|
/// and should not be considered part of the public API.
|
||||||
|
#[doc(hidden)]
|
||||||
|
fn add_schema_as_property(
|
||||||
|
gen: &mut gen::SchemaGenerator,
|
||||||
|
parent: &mut SchemaObject,
|
||||||
|
name: String,
|
||||||
|
metadata: Option<schema::Metadata>,
|
||||||
|
required: bool,
|
||||||
|
) {
|
||||||
|
let mut schema = gen.subschema_for::<Self>();
|
||||||
|
|
||||||
|
if let Some(metadata) = metadata {
|
||||||
|
let mut schema_obj = schema.into();
|
||||||
|
gen.apply_metadata(&mut schema_obj, metadata);
|
||||||
|
schema = Schema::Object(schema_obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
let object = parent.object();
|
||||||
|
if required {
|
||||||
|
object.required.insert(name.clone());
|
||||||
|
}
|
||||||
|
object.properties.insert(name, schema);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -19,9 +19,6 @@
|
||||||
"properties": {
|
"properties": {
|
||||||
"Complex": {
|
"Complex": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
|
||||||
"my_nullable_string"
|
|
||||||
],
|
|
||||||
"properties": {
|
"properties": {
|
||||||
"my_nullable_string": {
|
"my_nullable_string": {
|
||||||
"title": "A nullable string",
|
"title": "A nullable string",
|
||||||
|
|
34
schemars/tests/expected/flatten.json
Normal file
34
schemars/tests/expected/flatten.json
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"title": "Flat",
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"b",
|
||||||
|
"f",
|
||||||
|
"s",
|
||||||
|
"v"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"b": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"f": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "float"
|
||||||
|
},
|
||||||
|
"os": {
|
||||||
|
"default": "",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"s": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"v": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,6 +10,12 @@
|
||||||
"bar": {
|
"bar": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"baz": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
"foo": {
|
"foo": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"format": "int32"
|
"format": "int32"
|
||||||
|
|
|
@ -9,8 +9,14 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"maxItems": 2,
|
"maxItems": 3,
|
||||||
"minItems": 2
|
"minItems": 3
|
||||||
}
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
mod util;
|
mod util;
|
||||||
use pretty_assertions::assert_eq;
|
use schemars::JsonSchema;
|
||||||
use schemars::{schema_for, JsonSchema};
|
use util::*;
|
||||||
|
|
||||||
#[derive(Debug, JsonSchema)]
|
#[derive(Debug, JsonSchema)]
|
||||||
struct Flat {
|
struct Flat {
|
||||||
|
@ -43,8 +43,11 @@ struct Deep4 {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn flatten_schema() {
|
fn test_flat_schema() -> TestResult {
|
||||||
let flat = schema_for!(Flat);
|
test_default_generated_schema::<Flat>("flatten")
|
||||||
let deep = schema_for!(Deep1);
|
}
|
||||||
assert_eq!(flat, deep);
|
|
||||||
|
#[test]
|
||||||
|
fn test_flattened_schema() -> TestResult {
|
||||||
|
test_default_generated_schema::<Deep1>("flatten")
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ use util::*;
|
||||||
pub struct Struct {
|
pub struct Struct {
|
||||||
foo: i32,
|
foo: i32,
|
||||||
bar: bool,
|
bar: bool,
|
||||||
|
baz: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -14,7 +15,7 @@ fn struct_normal() -> TestResult {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, JsonSchema)]
|
#[derive(Debug, JsonSchema)]
|
||||||
pub struct Tuple(i32, bool);
|
pub struct Tuple(i32, bool, Option<String>);
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn struct_tuple() -> TestResult {
|
fn struct_tuple() -> TestResult {
|
||||||
|
|
|
@ -38,7 +38,8 @@ pub fn derive_json_schema(input: proc_macro::TokenStream) -> proc_macro::TokenSt
|
||||||
Data::Struct(Style::Struct, ref fields) => schema_for_struct(fields, Some(&cont.attrs)),
|
Data::Struct(Style::Struct, ref fields) => schema_for_struct(fields, Some(&cont.attrs)),
|
||||||
Data::Enum(ref variants) => schema_for_enum(variants, &cont.attrs),
|
Data::Enum(ref variants) => schema_for_enum(variants, &cont.attrs),
|
||||||
};
|
};
|
||||||
let schema_expr = set_metadata_on_schema_from_docs(schema_expr, &cont.original.attrs);
|
let doc_metadata = SchemaMetadata::from_doc_attrs(&cont.original.attrs);
|
||||||
|
let schema_expr = doc_metadata.apply_to_schema(schema_expr);
|
||||||
|
|
||||||
let type_name = cont.ident;
|
let type_name = cont.ident;
|
||||||
let type_params: Vec<_> = cont.generics.type_params().map(|ty| &ty.ident).collect();
|
let type_params: Vec<_> = cont.generics.type_params().map(|ty| &ty.ident).collect();
|
||||||
|
@ -176,7 +177,8 @@ fn schema_for_external_tagged_enum<'a>(
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})),
|
})),
|
||||||
});
|
});
|
||||||
set_metadata_on_schema_from_docs(schema_expr, &variant.original.attrs)
|
let doc_metadata = SchemaMetadata::from_doc_attrs(&variant.original.attrs);
|
||||||
|
doc_metadata.apply_to_schema(schema_expr)
|
||||||
}));
|
}));
|
||||||
|
|
||||||
wrap_schema_fields(quote! {
|
wrap_schema_fields(quote! {
|
||||||
|
@ -214,7 +216,8 @@ fn schema_for_internal_tagged_enum<'a>(
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})),
|
})),
|
||||||
});
|
});
|
||||||
let tag_schema = set_metadata_on_schema_from_docs(tag_schema, &variant.original.attrs);
|
let doc_metadata = SchemaMetadata::from_doc_attrs(&variant.original.attrs);
|
||||||
|
let tag_schema = doc_metadata.apply_to_schema(tag_schema);
|
||||||
|
|
||||||
let variant_schema = match variant.style {
|
let variant_schema = match variant.style {
|
||||||
Style::Unit => return tag_schema,
|
Style::Unit => return tag_schema,
|
||||||
|
@ -244,7 +247,8 @@ fn schema_for_internal_tagged_enum<'a>(
|
||||||
fn schema_for_untagged_enum<'a>(variants: impl Iterator<Item = &'a Variant<'a>>) -> TokenStream {
|
fn schema_for_untagged_enum<'a>(variants: impl Iterator<Item = &'a Variant<'a>>) -> TokenStream {
|
||||||
let schemas = variants.map(|variant| {
|
let schemas = variants.map(|variant| {
|
||||||
let schema_expr = schema_for_untagged_enum_variant(variant);
|
let schema_expr = schema_for_untagged_enum_variant(variant);
|
||||||
set_metadata_on_schema_from_docs(schema_expr, &variant.original.attrs)
|
let doc_metadata = SchemaMetadata::from_doc_attrs(&variant.original.attrs);
|
||||||
|
doc_metadata.apply_to_schema(schema_expr)
|
||||||
});
|
});
|
||||||
|
|
||||||
wrap_schema_fields(quote! {
|
wrap_schema_fields(quote! {
|
||||||
|
@ -288,7 +292,7 @@ fn schema_for_tuple_struct(fields: &[Field]) -> TokenStream {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn schema_for_struct(fields: &[Field], cattrs: Option<&serde_attr::Container>) -> TokenStream {
|
fn schema_for_struct(fields: &[Field], cattrs: Option<&serde_attr::Container>) -> TokenStream {
|
||||||
let (flat, nested): (Vec<_>, Vec<_>) = fields
|
let (flattened_fields, property_fields): (Vec<_>, Vec<_>) = fields
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|f| !f.attrs.skip_deserializing() || !f.attrs.skip_serializing())
|
.filter(|f| !f.attrs.skip_deserializing() || !f.attrs.skip_serializing())
|
||||||
.partition(|f| f.attrs.flatten());
|
.partition(|f| f.attrs.flatten());
|
||||||
|
@ -299,63 +303,50 @@ fn schema_for_struct(fields: &[Field], cattrs: Option<&serde_attr::Container>) -
|
||||||
SerdeDefault::Path(path) => Some(quote!(let container_default = #path();)),
|
SerdeDefault::Path(path) => Some(quote!(let container_default = #path();)),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut required = Vec::new();
|
let properties = property_fields.iter().map(|field| {
|
||||||
let recurse = nested.iter().map(|field| {
|
|
||||||
let name = field.attrs.name().deserialize_name();
|
let name = field.attrs.name().deserialize_name();
|
||||||
let default = field_default_expr(field, set_container_default.is_some());
|
let default = field_default_expr(field, set_container_default.is_some());
|
||||||
|
|
||||||
if default.is_none() {
|
let required = match default {
|
||||||
required.push(name.clone());
|
Some(_) => quote!(false),
|
||||||
}
|
None => quote!(true),
|
||||||
|
|
||||||
let ty = get_json_schema_type(field);
|
|
||||||
let span = field.original.span();
|
|
||||||
let schema_expr = quote_spanned! {span=>
|
|
||||||
gen.subschema_for::<#ty>()
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let metadata = SchemaMetadata {
|
let metadata = &SchemaMetadata {
|
||||||
read_only: field.attrs.skip_deserializing(),
|
read_only: field.attrs.skip_deserializing(),
|
||||||
write_only: field.attrs.skip_serializing(),
|
write_only: field.attrs.skip_serializing(),
|
||||||
default,
|
default,
|
||||||
skip_default_if: field.attrs.skip_serializing_if().cloned(),
|
skip_default_if: field.attrs.skip_serializing_if().cloned(),
|
||||||
..get_metadata_from_docs(&field.original.attrs)
|
..SchemaMetadata::from_doc_attrs(&field.original.attrs)
|
||||||
};
|
};
|
||||||
let schema_expr = set_metadata_on_schema(schema_expr, &metadata);
|
|
||||||
|
let ty = get_json_schema_type(field);
|
||||||
|
let span = field.original.span();
|
||||||
|
|
||||||
quote_spanned! {span=>
|
quote_spanned! {span=>
|
||||||
props.insert(#name.to_owned(), #schema_expr);
|
<#ty>::add_schema_as_property(gen, &mut schema_object, #name.to_owned(), #metadata, #required);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let schema = wrap_schema_fields(quote! {
|
let flattens = flattened_fields.iter().map(|field| {
|
||||||
instance_type: Some(schemars::schema::InstanceType::Object.into()),
|
|
||||||
object: Some(Box::new(schemars::schema::ObjectValidation {
|
|
||||||
properties: {
|
|
||||||
let mut props = schemars::Map::new();
|
|
||||||
#(#recurse)*
|
|
||||||
props
|
|
||||||
},
|
|
||||||
required: {
|
|
||||||
let mut required = schemars::Set::new();
|
|
||||||
#(required.insert(#required.to_owned());)*
|
|
||||||
required
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
})),
|
|
||||||
});
|
|
||||||
|
|
||||||
let flattens = flat.iter().map(|field| {
|
|
||||||
let ty = get_json_schema_type(field);
|
let ty = get_json_schema_type(field);
|
||||||
quote_spanned! {field.original.span()=>
|
let span = field.original.span();
|
||||||
.flatten(<#ty>::json_schema_optional(gen))
|
|
||||||
|
quote_spanned! {span=>
|
||||||
|
.flatten(<#ty>::json_schema_for_flatten(gen))
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
{
|
{
|
||||||
#set_container_default
|
#set_container_default
|
||||||
#schema #(#flattens)*
|
let mut schema_object = schemars::schema::SchemaObject {
|
||||||
|
instance_type: Some(schemars::schema::InstanceType::Object.into()),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
#(#properties)*
|
||||||
|
schemars::schema::Schema::Object(schema_object)
|
||||||
|
#(#flattens)*
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::attr;
|
use crate::attr;
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::{Ident, Span, TokenStream};
|
||||||
|
use quote::{ToTokens, TokenStreamExt};
|
||||||
use syn::{Attribute, ExprPath};
|
use syn::{Attribute, ExprPath};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
|
@ -12,74 +13,91 @@ pub struct SchemaMetadata {
|
||||||
pub skip_default_if: Option<ExprPath>,
|
pub skip_default_if: Option<ExprPath>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_metadata_on_schema_from_docs(
|
impl ToTokens for SchemaMetadata {
|
||||||
schema_expr: TokenStream,
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
attrs: &[Attribute],
|
let setters = self.make_setters();
|
||||||
) -> TokenStream {
|
if setters.is_empty() {
|
||||||
let metadata = get_metadata_from_docs(attrs);
|
tokens.append(Ident::new("None", Span::call_site()))
|
||||||
set_metadata_on_schema(schema_expr, &metadata)
|
} else {
|
||||||
}
|
tokens.extend(quote! {
|
||||||
|
Some({
|
||||||
pub fn get_metadata_from_docs(attrs: &[Attribute]) -> SchemaMetadata {
|
let mut metadata = schemars::schema::Metadata::default();
|
||||||
let (title, description) = attr::get_title_and_desc_from_doc(attrs);
|
#(#setters)*
|
||||||
SchemaMetadata {
|
metadata
|
||||||
title,
|
})
|
||||||
description,
|
})
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_metadata_on_schema(schema_expr: TokenStream, metadata: &SchemaMetadata) -> TokenStream {
|
|
||||||
let mut setters = Vec::<TokenStream>::new();
|
|
||||||
|
|
||||||
if let Some(title) = &metadata.title {
|
|
||||||
setters.push(quote! {
|
|
||||||
metadata.title = Some(#title.to_owned());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if let Some(description) = &metadata.description {
|
|
||||||
setters.push(quote! {
|
|
||||||
metadata.description = Some(#description.to_owned());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if metadata.read_only {
|
|
||||||
setters.push(quote! {
|
|
||||||
metadata.read_only = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if metadata.write_only {
|
|
||||||
setters.push(quote! {
|
|
||||||
metadata.write_only = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
match (&metadata.default, &metadata.skip_default_if) {
|
|
||||||
(Some(default), Some(skip_if)) => setters.push(quote! {
|
|
||||||
{
|
|
||||||
let default = #default;
|
|
||||||
if !#skip_if(&default) {
|
|
||||||
metadata.default = schemars::_serde_json::value::to_value(default).ok();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
(Some(default), None) => setters.push(quote! {
|
|
||||||
metadata.default = schemars::_serde_json::value::to_value(#default).ok();
|
|
||||||
}),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
if setters.is_empty() {
|
|
||||||
return schema_expr;
|
|
||||||
}
|
|
||||||
|
|
||||||
quote! {
|
|
||||||
{
|
|
||||||
let schema = #schema_expr.into();
|
|
||||||
let mut schema_obj = gen.make_extensible(schema);
|
|
||||||
let mut metadata = schema_obj.metadata();
|
|
||||||
#(#setters)*
|
|
||||||
schemars::schema::Schema::Object(schema_obj)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl SchemaMetadata {
|
||||||
|
pub fn from_doc_attrs(attrs: &[Attribute]) -> SchemaMetadata {
|
||||||
|
let (title, description) = attr::get_title_and_desc_from_doc(attrs);
|
||||||
|
SchemaMetadata {
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn apply_to_schema(&self, schema_expr: TokenStream) -> TokenStream {
|
||||||
|
let setters = self.make_setters();
|
||||||
|
|
||||||
|
if setters.is_empty() {
|
||||||
|
return schema_expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
{
|
||||||
|
let mut schema = #schema_expr.into();
|
||||||
|
gen.make_extensible(&mut schema);
|
||||||
|
let mut metadata = schema.metadata();
|
||||||
|
#(#setters)*
|
||||||
|
schemars::schema::Schema::Object(schema)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_setters(self: &SchemaMetadata) -> Vec<TokenStream> {
|
||||||
|
let mut setters = Vec::<TokenStream>::new();
|
||||||
|
|
||||||
|
if let Some(title) = &self.title {
|
||||||
|
setters.push(quote! {
|
||||||
|
metadata.title = Some(#title.to_owned());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if let Some(description) = &self.description {
|
||||||
|
setters.push(quote! {
|
||||||
|
metadata.description = Some(#description.to_owned());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.read_only {
|
||||||
|
setters.push(quote! {
|
||||||
|
metadata.read_only = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if self.write_only {
|
||||||
|
setters.push(quote! {
|
||||||
|
metadata.write_only = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
match (&self.default, &self.skip_default_if) {
|
||||||
|
(Some(default), Some(skip_if)) => setters.push(quote! {
|
||||||
|
{
|
||||||
|
let default = #default;
|
||||||
|
if !#skip_if(&default) {
|
||||||
|
metadata.default = schemars::_serde_json::value::to_value(default).ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
(Some(default), None) => setters.push(quote! {
|
||||||
|
metadata.default = schemars::_serde_json::value::to_value(#default).ok();
|
||||||
|
}),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
setters
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue