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:
parent
6ada120cd3
commit
104dccca50
7 changed files with 76 additions and 14 deletions
29
schemars/tests/bound.rs
Normal file
29
schemars/tests/bound.rs
Normal 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")
|
||||
}
|
17
schemars/tests/expected/bound.json
Normal file
17
schemars/tests/expected/bound.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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),
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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,12 +141,19 @@ fn derive_json_schema(
|
|||
})
|
||||
}
|
||||
|
||||
fn add_trait_bounds(generics: &mut syn::Generics) {
|
||||
for param in &mut generics.params {
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn compile_error(errors: Vec<syn::Error>) -> TokenStream {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue