Add #[schemars(bound = ...)] attribute

Based on https://github.com/GREsau/schemars/pull/162

Co-authored-by: teozkr <teo@nullable.se>
This commit is contained in:
Graham Esau 2022-08-14 13:58:33 +01:00 committed by Graham Esau
parent 6ada120cd3
commit 104dccca50
7 changed files with 76 additions and 14 deletions

29
schemars/tests/bound.rs Normal file
View file

@ -0,0 +1,29 @@
mod util;
use std::marker::PhantomData;
use schemars::JsonSchema;
use util::*;
struct MyIterator;
impl Iterator for MyIterator {
type Item = String;
fn next(&mut self) -> Option<Self::Item> {
unimplemented!()
}
}
// The default trait bounds would require T to implement JsonSchema,
// which MyIterator does not.
#[derive(JsonSchema)]
#[schemars(bound = "T::Item: JsonSchema", rename = "MyContainer")]
pub struct MyContainer<T> where T: Iterator {
pub associated: T::Item,
pub generic: PhantomData<T>,
}
#[test]
fn manual_bound_set() -> TestResult {
test_default_generated_schema::<MyContainer<MyIterator>>("bound")
}

View file

@ -0,0 +1,17 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "MyContainer",
"type": "object",
"required": [
"associated",
"generic"
],
"properties": {
"associated": {
"type": "string"
},
"generic": {
"type": "null"
}
}
}

View file

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

View file

@ -9,7 +9,7 @@ pub struct Container<'a> {
pub ident: syn::Ident,
pub serde_attrs: serde_derive_internals::attr::Container,
pub data: Data<'a>,
pub generics: &'a syn::Generics,
pub generics: syn::Generics,
pub original: &'a syn::DeriveInput,
pub attrs: Attrs,
}

View file

@ -27,6 +27,7 @@ pub struct Attrs {
pub examples: Vec<syn::Path>,
pub repr: Option<syn::Type>,
pub crate_name: Option<syn::Path>,
pub is_renamed: bool
}
#[derive(Debug)]
@ -152,6 +153,10 @@ impl Attrs {
}
}
Meta(NameValue(m)) if m.path.is_ident("rename") => {
self.is_renamed = true
}
Meta(NameValue(m)) if m.path.is_ident("crate") && attr_type == "schemars" => {
if let Ok(p) = parse_lit_into_path(errors, attr_type, "crate", &m.lit) {
if self.crate_name.is_some() {
@ -196,6 +201,7 @@ impl Attrs {
examples,
repr: None,
crate_name: None,
is_renamed: _,
} if examples.is_empty() => true,
_ => false,
}

View file

@ -20,6 +20,7 @@ pub(crate) static SERDE_KEYWORDS: &[&str] = &[
"flatten",
"remote",
"transparent",
"bound",
// Special cases - `with`/`serialize_with` are passed to serde but not copied from schemars attrs to serde attrs.
// This is because we want to preserve any serde attribute's `serialize_with` value to determine whether the field's
// default value should be serialized. We also check the `with` value on schemars/serde attrs e.g. to support deriving
@ -56,9 +57,9 @@ fn process_serde_field_attrs<'a>(ctxt: &Ctxt, fields: impl Iterator<Item = &'a m
}
fn process_attrs(ctxt: &Ctxt, attrs: &mut Vec<Attribute>) {
// Remove #[serde(...)] attributes (some may be re-added later)
let (serde_attrs, other_attrs): (Vec<_>, Vec<_>) =
attrs.drain(..).partition(|at| at.path.is_ident("serde"));
*attrs = other_attrs;
let schemars_attrs: Vec<_> = attrs
@ -66,6 +67,7 @@ fn process_attrs(ctxt: &Ctxt, attrs: &mut Vec<Attribute>) {
.filter(|at| at.path.is_ident("schemars"))
.collect();
// Copy appropriate #[schemars(...)] attributes to #[serde(...)] attributes
let (mut serde_meta, mut schemars_meta_names): (Vec<_>, HashSet<_>) = schemars_attrs
.iter()
.flat_map(|at| get_meta_items(&ctxt, at))
@ -85,6 +87,7 @@ fn process_attrs(ctxt: &Ctxt, attrs: &mut Vec<Attribute>) {
schemars_meta_names.insert("skip_deserializing".to_string());
}
// Re-add #[serde(...)] attributes that weren't overridden by #[schemars(...)] attributes
for meta in serde_attrs
.into_iter()
.flat_map(|at| get_meta_items(&ctxt, &at))

View file

@ -36,11 +36,11 @@ fn derive_json_schema(
mut input: syn::DeriveInput,
repr: bool,
) -> Result<TokenStream, Vec<syn::Error>> {
add_trait_bounds(&mut input.generics);
attr::process_serde_attrs(&mut input)?;
let cont = Container::from_ast(&input)?;
let mut cont = Container::from_ast(&input)?;
add_trait_bounds(&mut cont);
let crate_alias = cont.attrs.crate_name.as_ref().map(|path| {
quote_spanned! {path.span()=>
use #path as schemars;
@ -84,9 +84,8 @@ fn derive_json_schema(
}
let mut schema_base_name = cont.name();
let schema_is_renamed = *type_name != schema_base_name;
if !schema_is_renamed {
if !cont.attrs.is_renamed {
if let Some(path) = cont.serde_attrs.remote() {
if let Some(segment) = path.segments.last() {
schema_base_name = segment.ident.to_string();
@ -94,12 +93,13 @@ fn derive_json_schema(
}
}
// FIXME improve handling of generic type params which may not implement JsonSchema
let type_params: Vec<_> = cont.generics.type_params().map(|ty| &ty.ident).collect();
let schema_name = if type_params.is_empty() {
let schema_name = if type_params.is_empty() || (cont.attrs.is_renamed && !schema_base_name.contains('{')) {
quote! {
#schema_base_name.to_owned()
}
} else if schema_is_renamed {
} else if cont.attrs.is_renamed {
let mut schema_name_fmt = schema_base_name;
for tp in &type_params {
schema_name_fmt.push_str(&format!("{{{}:.0}}", tp));
@ -141,10 +141,17 @@ fn derive_json_schema(
})
}
fn add_trait_bounds(generics: &mut syn::Generics) {
for param in &mut generics.params {
if let syn::GenericParam::Type(ref mut type_param) = *param {
type_param.bounds.push(parse_quote!(schemars::JsonSchema));
fn add_trait_bounds(cont: &mut Container) {
if let Some(bounds) = cont.serde_attrs.ser_bound() {
let where_clause = cont.generics.make_where_clause();
where_clause.predicates.extend(bounds.iter().cloned());
} else {
// No explicit trait bounds specified, assume the Rust convention of adding the trait to each type parameter
// TODO consider also adding trait bound to associated types when used as fields - I think Serde does this?
for param in &mut cont.generics.params {
if let syn::GenericParam::Type(ref mut type_param) = *param {
type_param.bounds.push(parse_quote!(schemars::JsonSchema));
}
}
}
}