use std::collections::HashSet;
use crate::{ast::*, attr::WithAttr, metadata::SchemaMetadata};
use proc_macro2::{Span, TokenStream};
use serde_derive_internals::ast::Style;
use serde_derive_internals::attr::{self as serde_attr, Default as SerdeDefault, TagType};
use syn::spanned::Spanned;
pub fn expr_for_container(cont: &Container) -> TokenStream {
let mut schema_expr = match &cont.data {
Data::Struct(Style::Unit, _) => expr_for_unit_struct(),
Data::Struct(Style::Newtype, fields) => expr_for_newtype_struct(&fields[0]),
Data::Struct(Style::Tuple, fields) => expr_for_tuple_struct(fields),
Data::Struct(Style::Struct, fields) => expr_for_struct(
fields,
cont.serde_attrs.default(),
cont.serde_attrs.deny_unknown_fields(),
),
Data::Enum(variants) => expr_for_enum(variants, &cont.serde_attrs),
};
cont.attrs.as_metadata().apply_to_schema(&mut schema_expr);
schema_expr
}
pub fn expr_for_repr(cont: &Container) -> Result<TokenStream, syn::Error> {
let repr_type = cont.attrs.repr.as_ref().ok_or_else(|| {
syn::Error::new(
Span::call_site(),
"JsonSchema_repr: missing #[repr(...)] attribute",
)
})?;
let variants = match &cont.data {
Data::Enum(variants) => variants,
_ => return Err(syn::Error::new(Span::call_site(), "oh no!")),
};
if let Some(non_unit_error) = variants.iter().find_map(|v| match v.style {
Style::Unit => None,
_ => Some(syn::Error::new(
v.original.span(),
"JsonSchema_repr: must be a unit variant",
)),
}) {
return Err(non_unit_error);
};
let enum_ident = &cont.ident;
let variant_idents = variants.iter().map(|v| &v.ident);
let mut schema_expr = schema_object(quote! {
instance_type: Some(schemars::schema::InstanceType::Integer.into()),
enum_values: Some(vec![#((#enum_ident::#variant_idents as #repr_type).into()),*]),
});
cont.attrs.as_metadata().apply_to_schema(&mut schema_expr);
Ok(schema_expr)
}
fn expr_for_field(field: &Field, allow_ref: bool) -> TokenStream {
let (ty, type_def) = type_for_field_schema(field);
let span = field.original.span();
let gen = quote!(gen);
let mut schema_expr = if field.validation_attrs.required() {
quote_spanned! {span=>
<#ty as schemars::JsonSchema>::_schemars_private_non_optional_json_schema(#gen)
}
} else if allow_ref {
quote_spanned! {span=>
#gen.subschema_for::<#ty>()
}
} else {
quote_spanned! {span=>
<#ty as schemars::JsonSchema>::json_schema(#gen)
}
};
prepend_type_def(type_def, &mut schema_expr);
field.validation_attrs.apply_to_schema(&mut schema_expr);
schema_expr
}
pub fn type_for_field_schema(field: &Field) -> (syn::Type, Option<TokenStream>) {
match &field.attrs.with {
None => (field.ty.to_owned(), None),
Some(with_attr) => type_for_schema(with_attr),
}
}
fn type_for_schema(with_attr: &WithAttr) -> (syn::Type, Option<TokenStream>) {
match with_attr {
WithAttr::Type(ty) => (ty.to_owned(), None),
WithAttr::Function(fun) => {
let ty_name = syn::Ident::new("_SchemarsSchemaWithFunction", Span::call_site());
let fn_name = fun.segments.last().unwrap().ident.to_string();
let type_def = quote_spanned! {fun.span()=>
struct #ty_name;
impl schemars::JsonSchema for #ty_name {
fn is_referenceable() -> bool {
false
}
fn schema_name() -> std::string::String {
#fn_name.to_string()
}
fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
#fun(gen)
}
}
};
(parse_quote!(#ty_name), Some(type_def))
}
}
}
fn expr_for_enum(variants: &[Variant], cattrs: &serde_attr::Container) -> TokenStream {
let deny_unknown_fields = cattrs.deny_unknown_fields();
let variants = variants
.iter()
.filter(|v| !v.serde_attrs.skip_deserializing());
match cattrs.tag() {
TagType::External => expr_for_external_tagged_enum(variants, deny_unknown_fields),
TagType::None => expr_for_untagged_enum(variants, deny_unknown_fields),
TagType::Internal { tag } => {
expr_for_internal_tagged_enum(variants, tag, deny_unknown_fields)
}
TagType::Adjacent { tag, content } => {
expr_for_adjacent_tagged_enum(variants, tag, content, deny_unknown_fields)
}
}
}
fn expr_for_external_tagged_enum<'a>(
variants: impl Iterator<Item = &'a Variant<'a>>,
deny_unknown_fields: bool,
) -> TokenStream {
let mut unique_names = HashSet::<String>::new();
let mut count = 0;
let (unit_variants, complex_variants): (Vec<_>, Vec<_>) = variants
.inspect(|v| {
unique_names.insert(v.name());
count += 1;
})
.partition(|v| v.is_unit() && v.attrs.is_default());
let unit_names = unit_variants.iter().map(|v| v.name());
let unit_schema = schema_object(quote! {
instance_type: Some(schemars::schema::InstanceType::String.into()),
enum_values: Some(vec![#(#unit_names.into()),*]),
});
if complex_variants.is_empty() {
return unit_schema;
}
let mut schemas = Vec::new();
if !unit_variants.is_empty() {
schemas.push(unit_schema);
}
schemas.extend(complex_variants.into_iter().map(|variant| {
let name = variant.name();
let mut schema_expr = if variant.is_unit() && variant.attrs.with.is_none() {
schema_object(quote! {
instance_type: Some(schemars::schema::InstanceType::String.into()),
enum_values: Some(vec![#name.into()]),
})
} else {
let sub_schema = expr_for_untagged_enum_variant(variant, deny_unknown_fields);
schema_object(quote! {
instance_type: Some(schemars::schema::InstanceType::Object.into()),
object: Some(Box::new(schemars::schema::ObjectValidation {
properties: {
let mut props = schemars::Map::new();
props.insert(#name.to_owned(), #sub_schema);
props
},
required: {
let mut required = schemars::Set::new();
required.insert(#name.to_owned());
required
},
additional_properties: Some(Box::new(false.into())),
..Default::default()
})),
})
};
variant
.attrs
.as_metadata()
.apply_to_schema(&mut schema_expr);
schema_expr
}));
variant_subschemas(unique_names.len() == count, schemas)
}
fn expr_for_internal_tagged_enum<'a>(
variants: impl Iterator<Item = &'a Variant<'a>>,
tag_name: &str,
deny_unknown_fields: bool,
) -> TokenStream {
let mut unique_names = HashSet::new();
let mut count = 0;
let set_additional_properties = if deny_unknown_fields {
quote! {
additional_properties: Some(Box::new(false.into())),
}
} else {
TokenStream::new()
};
let variant_schemas = variants
.map(|variant| {
unique_names.insert(variant.name());
count += 1;
let name = variant.name();
let type_schema = schema_object(quote! {
instance_type: Some(schemars::schema::InstanceType::String.into()),
enum_values: Some(vec![#name.into()]),
});
let mut tag_schema = schema_object(quote! {
instance_type: Some(schemars::schema::InstanceType::Object.into()),
object: Some(Box::new(schemars::schema::ObjectValidation {
properties: {
let mut props = schemars::Map::new();
props.insert(#tag_name.to_owned(), #type_schema);
props
},
required: {
let mut required = schemars::Set::new();
required.insert(#tag_name.to_owned());
required
},
#set_additional_properties
..Default::default()
})),
});
variant.attrs.as_metadata().apply_to_schema(&mut tag_schema);
if let Some(variant_schema) =
expr_for_untagged_enum_variant_for_flatten(variant, deny_unknown_fields)
{
tag_schema.extend(quote!(.flatten(#variant_schema)))
}
tag_schema
})
.collect();
variant_subschemas(unique_names.len() == count, variant_schemas)
}
fn expr_for_untagged_enum<'a>(
variants: impl Iterator<Item = &'a Variant<'a>>,
deny_unknown_fields: bool,
) -> TokenStream {
let schemas = variants
.map(|variant| {
let mut schema_expr = expr_for_untagged_enum_variant(variant, deny_unknown_fields);
variant
.attrs
.as_metadata()
.apply_to_schema(&mut schema_expr);
schema_expr
})
.collect();
variant_subschemas(false, schemas)
}
fn expr_for_adjacent_tagged_enum<'a>(
variants: impl Iterator<Item = &'a Variant<'a>>,
tag_name: &str,
content_name: &str,
deny_unknown_fields: bool,
) -> TokenStream {
let mut unique_names = HashSet::new();
let mut count = 0;
let schemas = variants
.map(|variant| {
unique_names.insert(variant.name());
count += 1;
let content_schema = if variant.is_unit() && variant.attrs.with.is_none() {
None
} else {
Some(expr_for_untagged_enum_variant(variant, deny_unknown_fields))
};
let (add_content_to_props, add_content_to_required) = content_schema
.map(|content_schema| {
(
quote!(props.insert(#content_name.to_owned(), #content_schema);),
quote!(required.insert(#content_name.to_owned());),
)
})
.unwrap_or_default();
let name = variant.name();
let tag_schema = schema_object(quote! {
instance_type: Some(schemars::schema::InstanceType::String.into()),
enum_values: Some(vec![#name.into()]),
});
let set_additional_properties = if deny_unknown_fields {
quote! {
additional_properties: Some(Box::new(false.into())),
}
} else {
TokenStream::new()
};
let mut outer_schema = schema_object(quote! {
instance_type: Some(schemars::schema::InstanceType::Object.into()),
object: Some(Box::new(schemars::schema::ObjectValidation {
properties: {
let mut props = schemars::Map::new();
props.insert(#tag_name.to_owned(), #tag_schema);
#add_content_to_props
props
},
required: {
let mut required = schemars::Set::new();
required.insert(#tag_name.to_owned());
#add_content_to_required
required
},
#set_additional_properties
..Default::default()
})),
});
variant
.attrs
.as_metadata()
.apply_to_schema(&mut outer_schema);
outer_schema
})
.collect();
variant_subschemas(unique_names.len() == count, schemas)
}
fn variant_subschemas(unique: bool, schemas: Vec<TokenStream>) -> TokenStream {
if unique {
schema_object(quote! {
subschemas: Some(Box::new(schemars::schema::SubschemaValidation {
one_of: Some(vec![#(#schemas),*]),
..Default::default()
})),
})
} else {
schema_object(quote! {
subschemas: Some(Box::new(schemars::schema::SubschemaValidation {
any_of: Some(vec![#(#schemas),*]),
..Default::default()
})),
})
}
}
fn expr_for_untagged_enum_variant(variant: &Variant, deny_unknown_fields: bool) -> TokenStream {
if let Some(with_attr) = &variant.attrs.with {
let (ty, type_def) = type_for_schema(with_attr);
let gen = quote!(gen);
let mut schema_expr = quote_spanned! {variant.original.span()=>
#gen.subschema_for::<#ty>()
};
prepend_type_def(type_def, &mut schema_expr);
return schema_expr;
}
match variant.style {
Style::Unit => expr_for_unit_struct(),
Style::Newtype => expr_for_field(&variant.fields[0], true),
Style::Tuple => expr_for_tuple_struct(&variant.fields),
Style::Struct => expr_for_struct(&variant.fields, &SerdeDefault::None, deny_unknown_fields),
}
}
fn expr_for_untagged_enum_variant_for_flatten(
variant: &Variant,
deny_unknown_fields: bool,
) -> Option<TokenStream> {
if let Some(with_attr) = &variant.attrs.with {
let (ty, type_def) = type_for_schema(with_attr);
let gen = quote!(gen);
let mut schema_expr = quote_spanned! {variant.original.span()=>
<#ty as schemars::JsonSchema>::json_schema(#gen)
};
prepend_type_def(type_def, &mut schema_expr);
return Some(schema_expr);
}
Some(match variant.style {
Style::Unit => return None,
Style::Newtype => expr_for_field(&variant.fields[0], false),
Style::Tuple => expr_for_tuple_struct(&variant.fields),
Style::Struct => expr_for_struct(&variant.fields, &SerdeDefault::None, deny_unknown_fields),
})
}
fn expr_for_unit_struct() -> TokenStream {
quote! {
gen.subschema_for::<()>()
}
}
fn expr_for_newtype_struct(field: &Field) -> TokenStream {
expr_for_field(field, true)
}
fn expr_for_tuple_struct(fields: &[Field]) -> TokenStream {
let fields: Vec<_> = fields
.iter()
.filter(|f| !f.serde_attrs.skip_deserializing())
.map(|f| expr_for_field(f, true))
.collect();
let len = fields.len() as u32;
quote! {
schemars::schema::Schema::Object(
schemars::schema::SchemaObject {
instance_type: Some(schemars::schema::InstanceType::Array.into()),
array: Some(Box::new(schemars::schema::ArrayValidation {
items: Some(vec![#(#fields),*].into()),
max_items: Some(#len),
min_items: Some(#len),
..Default::default()
})),
..Default::default()
})
}
}
fn expr_for_struct(
fields: &[Field],
default: &SerdeDefault,
deny_unknown_fields: bool,
) -> TokenStream {
let (flattened_fields, property_fields): (Vec<_>, Vec<_>) = fields
.iter()
.filter(|f| !f.serde_attrs.skip_deserializing() || !f.serde_attrs.skip_serializing())
.partition(|f| f.serde_attrs.flatten());
let set_container_default = match default {
SerdeDefault::None => None,
SerdeDefault::Default => Some(quote!(let container_default = Self::default();)),
SerdeDefault::Path(path) => Some(quote!(let container_default = #path();)),
};
let properties: Vec<_> = property_fields
.into_iter()
.map(|field| {
let name = field.name();
let default = field_default_expr(field, set_container_default.is_some());
let (ty, type_def) = type_for_field_schema(field);
let maybe_insert_required = match (&default, field.validation_attrs.required()) {
(Some(_), _) => TokenStream::new(),
(None, false) => {
quote! {
if !<#ty as schemars::JsonSchema>::_schemars_private_is_option() {
object_validation.required.insert(#name.to_owned());
}
}
}
(None, true) => quote! {
object_validation.required.insert(#name.to_owned());
},
};
let metadata = SchemaMetadata {
read_only: field.serde_attrs.skip_deserializing(),
write_only: field.serde_attrs.skip_serializing(),
default,
..field.attrs.as_metadata()
};
let gen = quote!(gen);
let mut schema_expr = if field.validation_attrs.required() {
quote_spanned! {ty.span()=>
<#ty as schemars::JsonSchema>::_schemars_private_non_optional_json_schema(#gen)
}
} else {
quote_spanned! {ty.span()=>
#gen.subschema_for::<#ty>()
}
};
metadata.apply_to_schema(&mut schema_expr);
field.validation_attrs.apply_to_schema(&mut schema_expr);
quote! {
{
#type_def
object_validation.properties.insert(#name.to_owned(), #schema_expr);
#maybe_insert_required
}
}
})
.collect();
let flattens: Vec<_> = flattened_fields
.into_iter()
.map(|field| {
let (ty, type_def) = type_for_field_schema(field);
let required = field.validation_attrs.required();
let args = quote!(gen, #required);
let mut schema_expr = quote_spanned! {ty.span()=>
schemars::_private::json_schema_for_flatten::<#ty>(#args)
};
prepend_type_def(type_def, &mut schema_expr);
schema_expr
})
.collect();
let set_additional_properties = if deny_unknown_fields {
quote! {
object_validation.additional_properties = Some(Box::new(false.into()));
}
} else {
TokenStream::new()
};
quote! {
{
#set_container_default
let mut schema_object = schemars::schema::SchemaObject {
instance_type: Some(schemars::schema::InstanceType::Object.into()),
..Default::default()
};
let object_validation = schema_object.object();
#set_additional_properties
#(#properties)*
schemars::schema::Schema::Object(schema_object)
#(.flatten(#flattens))*
}
}
}
fn field_default_expr(field: &Field, container_has_default: bool) -> Option<TokenStream> {
let field_default = field.serde_attrs.default();
if field.serde_attrs.skip_serializing() || (field_default.is_none() && !container_has_default) {
return None;
}
let ty = field.ty;
let default_expr = match field_default {
SerdeDefault::None => {
let member = &field.member;
quote!(container_default.#member)
}
SerdeDefault::Default => quote!(<#ty>::default()),
SerdeDefault::Path(path) => quote!(#path()),
};
let default_expr = match field.serde_attrs.skip_serializing_if() {
Some(skip_if) => {
quote! {
{
let default = #default_expr;
if #skip_if(&default) {
None
} else {
Some(default)
}
}
}
}
None => quote!(Some(#default_expr)),
};
Some(if let Some(ser_with) = field.serde_attrs.serialize_with() {
quote! {
{
struct _SchemarsDefaultSerialize<T>(T);
impl serde::Serialize for _SchemarsDefaultSerialize<#ty>
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer
{
#ser_with(&self.0, serializer)
}
}
#default_expr.map(|d| _SchemarsDefaultSerialize(d))
}
}
} else {
default_expr
})
}
fn schema_object(properties: TokenStream) -> TokenStream {
quote! {
schemars::schema::Schema::Object(
schemars::schema::SchemaObject {
#properties
..Default::default()
})
}
}
fn prepend_type_def(type_def: Option<TokenStream>, schema_expr: &mut TokenStream) {
if let Some(type_def) = type_def {
*schema_expr = quote! {
{
#type_def
#schema_expr
}
}
}
}