Allow overriding title/desc from doc comments (#13)

This commit is contained in:
Graham Esau 2020-05-17 11:19:48 +01:00
parent 42e3c8fd7f
commit 1b42dc7e3e
7 changed files with 98 additions and 28 deletions

View file

@ -66,3 +66,22 @@ fn doc_comments_struct_ref_siblings() -> TestResult {
fn doc_comments_enum() -> TestResult { fn doc_comments_enum() -> TestResult {
test_default_generated_schema::<MyEnum>("doc_comments_enum") test_default_generated_schema::<MyEnum>("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::<OverrideDocs>("doc_comments_override")
}

View file

@ -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"
}
}
}

View file

@ -27,6 +27,8 @@ impl<'a> FromSerde for Container<'a> {
data: Data::from_serde(errors, serde.data)?, data: Data::from_serde(errors, serde.data)?,
generics: serde.generics, generics: serde.generics,
original: serde.original, original: serde.original,
// FIXME this allows with/schema_with attribute on containers
attrs: Attrs::new(&serde.original.attrs, errors),
}) })
} }
} }

View file

@ -11,6 +11,7 @@ pub struct Container<'a> {
pub data: Data<'a>, pub data: Data<'a>,
pub generics: &'a syn::Generics, pub generics: &'a syn::Generics,
pub original: &'a syn::DeriveInput, pub original: &'a syn::DeriveInput,
pub attrs: Attrs,
} }
pub enum Data<'a> { pub enum Data<'a> {

View file

@ -1,7 +1,6 @@
mod doc; mod doc;
mod schemars_to_serde; mod schemars_to_serde;
pub use doc::get_title_and_desc_from_doc;
pub use schemars_to_serde::process_serde_attrs; pub use schemars_to_serde::process_serde_attrs;
use proc_macro2::{Group, Span, TokenStream, TokenTree}; use proc_macro2::{Group, Span, TokenStream, TokenTree};
@ -16,6 +15,7 @@ pub struct Attrs {
pub with: Option<WithAttr>, pub with: Option<WithAttr>,
pub title: Option<String>, pub title: Option<String>,
pub description: Option<String>, pub description: Option<String>,
pub deprecated: bool,
// TODO pub example: Option<syn::Path>, // TODO pub example: Option<syn::Path>,
} }
@ -27,14 +27,17 @@ pub enum WithAttr {
impl Attrs { impl Attrs {
pub fn new(attrs: &[syn::Attribute], errors: &Ctxt) -> Self { pub fn new(attrs: &[syn::Attribute], errors: &Ctxt) -> Self {
let (title, description) = doc::get_title_and_desc_from_doc(attrs); let mut result = Attrs::default()
Attrs {
title,
description,
..Attrs::default()
}
.populate(attrs, "schemars", false, errors) .populate(attrs, "schemars", false, errors)
.populate(attrs, "serde", true, 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( 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) => { Meta(_meta_item) => {
// TODO uncomment this for 0.8.0 (breaking change) // TODO uncomment this for 0.8.0 (breaking change)
// https://github.com/GREsau/schemars/issues/18 // https://github.com/GREsau/schemars/issues/18

View file

@ -1,19 +1,19 @@
use crate::attr; use crate::attr;
use attr::Attrs;
use proc_macro2::{Ident, Span, TokenStream}; use proc_macro2::{Ident, Span, TokenStream};
use quote::{ToTokens, TokenStreamExt}; use quote::{ToTokens, TokenStreamExt};
use syn::Attribute;
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct SchemaMetadata { pub struct SchemaMetadata<'a> {
pub title: Option<String>, pub title: Option<&'a str>,
pub description: Option<String>, pub description: Option<&'a str>,
pub deprecated: bool, pub deprecated: bool,
pub read_only: bool, pub read_only: bool,
pub write_only: bool, pub write_only: bool,
pub default: Option<TokenStream>, pub default: Option<TokenStream>,
} }
impl ToTokens for SchemaMetadata { impl ToTokens for SchemaMetadata<'_> {
fn to_tokens(&self, tokens: &mut TokenStream) { fn to_tokens(&self, tokens: &mut TokenStream) {
let setters = self.make_setters(); let setters = self.make_setters();
if setters.is_empty() { if setters.is_empty() {
@ -30,14 +30,12 @@ impl ToTokens for SchemaMetadata {
} }
} }
impl SchemaMetadata { impl<'a> SchemaMetadata<'a> {
pub fn from_attrs(attrs: &[Attribute]) -> SchemaMetadata { pub fn from_attrs(attrs: &'a Attrs) -> Self {
let (title, description) = attr::get_title_and_desc_from_doc(attrs);
let deprecated = attrs.iter().any(|a| a.path.is_ident("deprecated"));
SchemaMetadata { SchemaMetadata {
title, title: attrs.title.as_deref().and_then(none_if_empty),
description, description: attrs.description.as_deref().and_then(none_if_empty),
deprecated, deprecated: attrs.deprecated,
..Default::default() ..Default::default()
} }
} }
@ -51,7 +49,7 @@ impl SchemaMetadata {
} }
} }
fn make_setters(self: &SchemaMetadata) -> Vec<TokenStream> { fn make_setters(&self) -> Vec<TokenStream> {
let mut setters = Vec::<TokenStream>::new(); let mut setters = Vec::<TokenStream>::new();
if let Some(title) = &self.title { if let Some(title) = &self.title {
@ -91,3 +89,11 @@ impl SchemaMetadata {
setters setters
} }
} }
fn none_if_empty<'a>(s: &'a str) -> Option<&'a str> {
if s.is_empty() {
None
} else {
Some(s)
}
}

View file

@ -13,7 +13,7 @@ pub fn expr_for_container(cont: &Container) -> TokenStream {
Data::Enum(variants) => expr_for_enum(variants, &cont.serde_attrs), 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) doc_metadata.apply_to_schema(schema_expr)
} }
@ -121,7 +121,7 @@ fn expr_for_external_tagged_enum<'a>(
..Default::default() ..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) doc_metadata.apply_to_schema(schema_expr)
})); }));
@ -160,7 +160,7 @@ fn expr_for_internal_tagged_enum<'a>(
..Default::default() ..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); let tag_schema = doc_metadata.apply_to_schema(tag_schema);
match expr_for_untagged_enum_variant_for_flatten(&variant) { 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<Item = &'a Variant<'a>>) -> TokenStream { fn expr_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 = expr_for_untagged_enum_variant(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) 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) 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(), read_only: field.serde_attrs.skip_deserializing(),
write_only: field.serde_attrs.skip_serializing(), write_only: field.serde_attrs.skip_serializing(),
default, default,
..SchemaMetadata::from_attrs(&field.original.attrs) ..SchemaMetadata::from_attrs(&field.attrs)
}; };
let (ty, type_def) = type_for_schema(field, type_defs.len()); let (ty, type_def) = type_for_schema(field, type_defs.len());