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
	
	 Graham Esau
						Graham Esau