Support #[serde(flatten)]ed structs
This commit is contained in:
parent
bd750714a0
commit
54cfd2ba0e
6 changed files with 104 additions and 36 deletions
|
@ -273,6 +273,8 @@ map_impl!(<K: Eq + core::hash::Hash, V, H: core::hash::BuildHasher> MakeSchema f
|
||||||
|
|
||||||
////////// OPTION //////////
|
////////// OPTION //////////
|
||||||
|
|
||||||
|
// TODO should a field with a default set also be considered nullable?
|
||||||
|
|
||||||
impl<T: MakeSchema> MakeSchema for Option<T> {
|
impl<T: MakeSchema> MakeSchema for Option<T> {
|
||||||
no_ref_schema!();
|
no_ref_schema!();
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate as schemars;
|
use crate as schemars;
|
||||||
use crate::MakeSchema;
|
use crate::{MakeSchema, MakeSchemaError, Result};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::collections::BTreeMap as Map;
|
use std::collections::BTreeMap as Map;
|
||||||
|
@ -30,6 +30,63 @@ impl From<SchemaRef> for Schema {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn extend<A, E: Extend<A>>(mut a: E, b: impl IntoIterator<Item = A>) -> E {
|
||||||
|
a.extend(b);
|
||||||
|
a
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Schema {
|
||||||
|
pub fn flatten(self, other: Self) -> Result {
|
||||||
|
let s1 = self.ensure_flattenable()?;
|
||||||
|
let s2 = other.ensure_flattenable()?;
|
||||||
|
Ok(Schema::Object(SchemaObject {
|
||||||
|
schema: s1.schema.or(s2.schema),
|
||||||
|
id: s1.id.or(s2.id),
|
||||||
|
title: s1.title.or(s2.title),
|
||||||
|
description: s1.description.or(s2.description),
|
||||||
|
items: s1.items.or(s2.items),
|
||||||
|
properties: extend(s1.properties, s2.properties),
|
||||||
|
required: extend(s1.required, s2.required),
|
||||||
|
definitions: extend(s1.definitions, s2.definitions),
|
||||||
|
extensions: extend(s1.extensions, s2.extensions),
|
||||||
|
// TODO do the following make sense?
|
||||||
|
instance_type: s1.instance_type.or(s2.instance_type),
|
||||||
|
enum_values: s1.enum_values.or(s2.enum_values),
|
||||||
|
all_of: s1.all_of.or(s2.all_of),
|
||||||
|
any_of: s1.any_of.or(s2.any_of),
|
||||||
|
one_of: s1.one_of.or(s2.one_of),
|
||||||
|
not: s1.not.or(s2.not),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ensure_flattenable(self) -> Result<SchemaObject> {
|
||||||
|
let s = match self {
|
||||||
|
Schema::Object(s) => s,
|
||||||
|
s => {
|
||||||
|
return Err(MakeSchemaError::new(
|
||||||
|
"Only schemas with type `object` can be flattened.",
|
||||||
|
s,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
match s.instance_type {
|
||||||
|
Some(SingleOrVec::Single(ref t)) if **t != InstanceType::Object => {
|
||||||
|
Err(MakeSchemaError::new(
|
||||||
|
"Only schemas with type `object` can be flattened.",
|
||||||
|
s.into(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
Some(SingleOrVec::Vec(ref t)) if !t.contains(&InstanceType::Object) => {
|
||||||
|
Err(MakeSchemaError::new(
|
||||||
|
"Only schemas with type `object` can be flattened.",
|
||||||
|
s.into(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
_ => Ok(s),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, MakeSchema)]
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, MakeSchema)]
|
||||||
pub struct SchemaRef {
|
pub struct SchemaRef {
|
||||||
#[serde(rename = "$ref")]
|
#[serde(rename = "$ref")]
|
||||||
|
@ -55,8 +112,8 @@ pub struct SchemaObject {
|
||||||
pub items: Option<SingleOrVec<Schema>>,
|
pub items: Option<SingleOrVec<Schema>>,
|
||||||
#[serde(skip_serializing_if = "Map::is_empty")]
|
#[serde(skip_serializing_if = "Map::is_empty")]
|
||||||
pub properties: Map<String, Schema>,
|
pub properties: Map<String, Schema>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||||
pub required: Option<Vec<String>>,
|
pub required: Vec<String>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub all_of: Option<Vec<Schema>>,
|
pub all_of: Option<Vec<Schema>>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
use schemars::{schema_for, MakeSchema};
|
use schemars::{schema::*, schema_for, MakeSchema};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
|
||||||
|
@ -32,8 +32,13 @@ struct Deep3 {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[ignore = "flattening is not yet implemented"]
|
|
||||||
fn flatten_schema() -> Result<(), Box<dyn Error>> {
|
fn flatten_schema() -> Result<(), Box<dyn Error>> {
|
||||||
assert_eq!(schema_for!(Flat)?, schema_for!(Deep1)?);
|
let flat = schema_for!(Flat)?;
|
||||||
|
let mut deep = schema_for!(Deep1)?;
|
||||||
|
match deep {
|
||||||
|
Schema::Object(ref mut o) => o.title = Some("Flat".to_owned()),
|
||||||
|
_ => assert!(false, "Schema was not object: {:?}", deep),
|
||||||
|
};
|
||||||
|
assert_eq!(flat, deep);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"SchemaObject": {
|
"SchemaObject": {
|
||||||
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"$id": {
|
"$id": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
@ -76,10 +77,6 @@
|
||||||
"items": {},
|
"items": {},
|
||||||
"nullable": true
|
"nullable": true
|
||||||
},
|
},
|
||||||
"extensions": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": true
|
|
||||||
},
|
|
||||||
"items": {
|
"items": {
|
||||||
"anyOf": [
|
"anyOf": [
|
||||||
{
|
{
|
||||||
|
@ -125,8 +122,7 @@
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
}
|
||||||
"nullable": true
|
|
||||||
},
|
},
|
||||||
"title": {
|
"title": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
@ -146,9 +142,11 @@
|
||||||
],
|
],
|
||||||
"nullable": true
|
"nullable": true
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"additionalProperties": true
|
||||||
},
|
},
|
||||||
"SchemaRef": {
|
"SchemaRef": {
|
||||||
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"$ref": {
|
"$ref": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
|
|
@ -38,6 +38,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"SchemaObject": {
|
"SchemaObject": {
|
||||||
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"$id": {
|
"$id": {
|
||||||
"anyOf": [
|
"anyOf": [
|
||||||
|
@ -112,10 +113,6 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"extensions": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": true
|
|
||||||
},
|
|
||||||
"items": {
|
"items": {
|
||||||
"anyOf": [
|
"anyOf": [
|
||||||
{
|
{
|
||||||
|
@ -156,18 +153,11 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": {
|
"required": {
|
||||||
"anyOf": [
|
|
||||||
{
|
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"type": "null"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"title": {
|
"title": {
|
||||||
"anyOf": [
|
"anyOf": [
|
||||||
{
|
{
|
||||||
|
@ -188,9 +178,11 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"additionalProperties": true
|
||||||
},
|
},
|
||||||
"SchemaRef": {
|
"SchemaRef": {
|
||||||
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"$ref": {
|
"$ref": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
|
|
@ -64,11 +64,11 @@ fn add_trait_bounds(generics: &mut Generics) {
|
||||||
|
|
||||||
fn wrap_schema_fields(schema_contents: TokenStream) -> TokenStream {
|
fn wrap_schema_fields(schema_contents: TokenStream) -> TokenStream {
|
||||||
quote! {
|
quote! {
|
||||||
Ok(schemars::schema::SchemaObject {
|
Ok(schemars::schema::Schema::Object(
|
||||||
|
schemars::schema::SchemaObject {
|
||||||
#schema_contents
|
#schema_contents
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}))
|
||||||
.into())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,6 +118,7 @@ fn schema_for_external_tagged_enum(variants: &[Variant]) -> TokenStream {
|
||||||
let name = variant.attrs.name().deserialize_name();
|
let name = variant.attrs.name().deserialize_name();
|
||||||
let sub_schema = schema_for_untagged_enum_variant(variant);
|
let sub_schema = schema_for_untagged_enum_variant(variant);
|
||||||
wrap_schema_fields(quote! {
|
wrap_schema_fields(quote! {
|
||||||
|
instance_type: Some(schemars::schema::InstanceType::Object.into()),
|
||||||
properties: {
|
properties: {
|
||||||
let mut props = std::collections::BTreeMap::new();
|
let mut props = std::collections::BTreeMap::new();
|
||||||
props.insert(#name.to_owned(), #sub_schema);
|
props.insert(#name.to_owned(), #sub_schema);
|
||||||
|
@ -162,18 +163,31 @@ fn schema_for_untagged_enum_variant(variant: &Variant) -> TokenStream {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn schema_for_struct(fields: &[Field]) -> TokenStream {
|
fn schema_for_struct(fields: &[Field]) -> TokenStream {
|
||||||
let recurse = fields.into_iter().map(|f| {
|
let (nested, flat): (Vec<_>, Vec<_>) = fields.iter().partition(|f| !f.attrs.flatten());
|
||||||
|
let recurse = nested.iter().map(|f| {
|
||||||
let name = f.attrs.name().deserialize_name();
|
let name = f.attrs.name().deserialize_name();
|
||||||
let ty = f.ty;
|
let ty = f.ty;
|
||||||
quote_spanned! {f.original.span()=>
|
quote_spanned! {f.original.span()=>
|
||||||
props.insert(#name.to_owned(), gen.subschema_for::<#ty>()?);
|
props.insert(#name.to_owned(), gen.subschema_for::<#ty>()?);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
wrap_schema_fields(quote! {
|
let schema = wrap_schema_fields(quote! {
|
||||||
|
instance_type: Some(schemars::schema::InstanceType::Object.into()),
|
||||||
properties: {
|
properties: {
|
||||||
let mut props = std::collections::BTreeMap::new();
|
let mut props = std::collections::BTreeMap::new();
|
||||||
#(#recurse)*
|
#(#recurse)*
|
||||||
props
|
props
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
|
let flattens = flat.iter().map(|f| {
|
||||||
|
let ty = f.ty;
|
||||||
|
quote_spanned! {f.original.span()=>
|
||||||
|
?.flatten(<#ty>::make_schema(gen)?)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
#schema #(#flattens)*
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue