diff --git a/schemars/tests/docs.rs b/schemars/tests/docs.rs index bdae83a..df07f4e 100644 --- a/schemars/tests/docs.rs +++ b/schemars/tests/docs.rs @@ -66,3 +66,22 @@ fn doc_comments_struct_ref_siblings() -> TestResult { fn doc_comments_enum() -> TestResult { test_default_generated_schema::("doc_comments_enum") } + +/// # OverrideDocs struct +/// This description should be overridden +#[derive(Debug, JsonSchema)] +#[schemars(description = "New description")] +pub struct OverrideDocs { + /// # Overridden + #[schemars(title = "My integer", description = "This is an i32")] + pub my_int: i32, + /// # Overridden + /// Also overridden + #[schemars(title = "", description = "")] + pub my_undocumented_bool: bool, +} + +#[test] +fn doc_comments_override() -> TestResult { + test_default_generated_schema::("doc_comments_override") +} diff --git a/schemars/tests/expected/doc_comments_override.json b/schemars/tests/expected/doc_comments_override.json new file mode 100644 index 0000000..83dce79 --- /dev/null +++ b/schemars/tests/expected/doc_comments_override.json @@ -0,0 +1,21 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "OverrideDocs struct", + "description": "New description", + "type": "object", + "required": [ + "my_int", + "my_undocumented_bool" + ], + "properties": { + "my_int": { + "title": "My integer", + "description": "This is an i32", + "type": "integer", + "format": "int32" + }, + "my_undocumented_bool": { + "type": "boolean" + } + } +} \ No newline at end of file diff --git a/schemars_derive/src/ast/from_serde.rs b/schemars_derive/src/ast/from_serde.rs index 60d2898..0d9add3 100644 --- a/schemars_derive/src/ast/from_serde.rs +++ b/schemars_derive/src/ast/from_serde.rs @@ -27,6 +27,8 @@ impl<'a> FromSerde for Container<'a> { data: Data::from_serde(errors, serde.data)?, generics: serde.generics, original: serde.original, + // FIXME this allows with/schema_with attribute on containers + attrs: Attrs::new(&serde.original.attrs, errors), }) } } diff --git a/schemars_derive/src/ast/mod.rs b/schemars_derive/src/ast/mod.rs index a388ac8..a394acd 100644 --- a/schemars_derive/src/ast/mod.rs +++ b/schemars_derive/src/ast/mod.rs @@ -11,6 +11,7 @@ pub struct Container<'a> { pub data: Data<'a>, pub generics: &'a syn::Generics, pub original: &'a syn::DeriveInput, + pub attrs: Attrs, } pub enum Data<'a> { diff --git a/schemars_derive/src/attr/mod.rs b/schemars_derive/src/attr/mod.rs index 6e36946..4957e1c 100644 --- a/schemars_derive/src/attr/mod.rs +++ b/schemars_derive/src/attr/mod.rs @@ -1,7 +1,6 @@ mod doc; mod schemars_to_serde; -pub use doc::get_title_and_desc_from_doc; pub use schemars_to_serde::process_serde_attrs; use proc_macro2::{Group, Span, TokenStream, TokenTree}; @@ -16,6 +15,7 @@ pub struct Attrs { pub with: Option, pub title: Option, pub description: Option, + pub deprecated: bool, // TODO pub example: Option, } @@ -27,14 +27,17 @@ pub enum WithAttr { impl Attrs { pub fn new(attrs: &[syn::Attribute], errors: &Ctxt) -> Self { - let (title, description) = doc::get_title_and_desc_from_doc(attrs); - Attrs { - title, - description, - ..Attrs::default() - } - .populate(attrs, "schemars", false, errors) - .populate(attrs, "serde", true, errors) + let mut result = Attrs::default() + .populate(attrs, "schemars", false, errors) + .populate(attrs, "serde", true, errors); + + result.deprecated = attrs.iter().any(|a| a.path.is_ident("deprecated")); + + let (doc_title, doc_description) = doc::get_title_and_desc_from_doc(attrs); + result.title = result.title.or(doc_title); + result.description = result.description.or(doc_description); + + result } fn populate( @@ -90,6 +93,24 @@ impl Attrs { } } + Meta(NameValue(m)) if m.path.is_ident("title") => { + if let Ok(title) = get_lit_str(errors, attr_type, "title", &m.lit) { + match self.title { + Some(_) => duplicate_error(m), + None => self.title = Some(title.value()), + } + } + } + + Meta(NameValue(m)) if m.path.is_ident("description") => { + if let Ok(description) = get_lit_str(errors, attr_type, "description", &m.lit) { + match self.description { + Some(_) => duplicate_error(m), + None => self.description = Some(description.value()), + } + } + } + Meta(_meta_item) => { // TODO uncomment this for 0.8.0 (breaking change) // https://github.com/GREsau/schemars/issues/18 diff --git a/schemars_derive/src/metadata.rs b/schemars_derive/src/metadata.rs index fc5e0ca..88ee2df 100644 --- a/schemars_derive/src/metadata.rs +++ b/schemars_derive/src/metadata.rs @@ -1,19 +1,19 @@ use crate::attr; +use attr::Attrs; use proc_macro2::{Ident, Span, TokenStream}; use quote::{ToTokens, TokenStreamExt}; -use syn::Attribute; #[derive(Debug, Clone, Default)] -pub struct SchemaMetadata { - pub title: Option, - pub description: Option, +pub struct SchemaMetadata<'a> { + pub title: Option<&'a str>, + pub description: Option<&'a str>, pub deprecated: bool, pub read_only: bool, pub write_only: bool, pub default: Option, } -impl ToTokens for SchemaMetadata { +impl ToTokens for SchemaMetadata<'_> { fn to_tokens(&self, tokens: &mut TokenStream) { let setters = self.make_setters(); if setters.is_empty() { @@ -30,14 +30,12 @@ impl ToTokens for SchemaMetadata { } } -impl SchemaMetadata { - pub fn from_attrs(attrs: &[Attribute]) -> SchemaMetadata { - let (title, description) = attr::get_title_and_desc_from_doc(attrs); - let deprecated = attrs.iter().any(|a| a.path.is_ident("deprecated")); +impl<'a> SchemaMetadata<'a> { + pub fn from_attrs(attrs: &'a Attrs) -> Self { SchemaMetadata { - title, - description, - deprecated, + title: attrs.title.as_deref().and_then(none_if_empty), + description: attrs.description.as_deref().and_then(none_if_empty), + deprecated: attrs.deprecated, ..Default::default() } } @@ -51,7 +49,7 @@ impl SchemaMetadata { } } - fn make_setters(self: &SchemaMetadata) -> Vec { + fn make_setters(&self) -> Vec { let mut setters = Vec::::new(); if let Some(title) = &self.title { @@ -91,3 +89,11 @@ impl SchemaMetadata { setters } } + +fn none_if_empty<'a>(s: &'a str) -> Option<&'a 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 6b312fe..6dc532d 100644 --- a/schemars_derive/src/schema_exprs.rs +++ b/schemars_derive/src/schema_exprs.rs @@ -13,7 +13,7 @@ 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.original.attrs); + let doc_metadata = SchemaMetadata::from_attrs(&cont.attrs); doc_metadata.apply_to_schema(schema_expr) } @@ -121,7 +121,7 @@ fn expr_for_external_tagged_enum<'a>( ..Default::default() })), }); - let doc_metadata = SchemaMetadata::from_attrs(&variant.original.attrs); + let doc_metadata = SchemaMetadata::from_attrs(&variant.attrs); doc_metadata.apply_to_schema(schema_expr) })); @@ -160,7 +160,7 @@ fn expr_for_internal_tagged_enum<'a>( ..Default::default() })), }); - let doc_metadata = SchemaMetadata::from_attrs(&variant.original.attrs); + 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) { @@ -182,7 +182,7 @@ fn expr_for_internal_tagged_enum<'a>( fn expr_for_untagged_enum<'a>(variants: impl Iterator>) -> TokenStream { let schemas = variants.map(|variant| { let schema_expr = expr_for_untagged_enum_variant(variant); - let doc_metadata = SchemaMetadata::from_attrs(&variant.original.attrs); + let doc_metadata = SchemaMetadata::from_attrs(&variant.attrs); doc_metadata.apply_to_schema(schema_expr) }); @@ -240,7 +240,7 @@ fn expr_for_adjacent_tagged_enum<'a>( })), }); - let doc_metadata = SchemaMetadata::from_attrs(&variant.original.attrs); + let doc_metadata = SchemaMetadata::from_attrs(&variant.attrs); doc_metadata.apply_to_schema(outer_schema) }); @@ -334,7 +334,7 @@ fn expr_for_struct(fields: &[Field], cattrs: Option<&serde_attr::Container>) -> read_only: field.serde_attrs.skip_deserializing(), write_only: field.serde_attrs.skip_serializing(), default, - ..SchemaMetadata::from_attrs(&field.original.attrs) + ..SchemaMetadata::from_attrs(&field.attrs) }; let (ty, type_def) = type_for_schema(field, type_defs.len());