use proc_macro2::TokenStream;
use quote::format_ident;
use quote::quote;
use crate::emit::doc_attr;
use crate::emit::emit_type;
use crate::error::Result;
use crate::ir::Alias;
use crate::ir::DefaultValue;
use crate::ir::Deprecation;
use crate::ir::Enum;
use crate::ir::EnumKind;
use crate::ir::Field;
use crate::ir::ForeignDerives;
use crate::ir::IntegerVariant;
use crate::ir::Item;
use crate::ir::RustType;
use crate::ir::StringVariant;
use crate::ir::Struct;
use crate::ir::UnionVariant;
use crate::naming::RustIdent;
#[derive(Debug, Clone, Copy)]
pub(crate) struct ModelDerives {
pub serde: SerdeDerives,
pub foreign: ForeignDerives,
}
impl ModelDerives {
pub(crate) fn both() -> Self {
return Self {
serde: SerdeDerives::both(),
foreign: ForeignDerives::default(),
};
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct SerdeDerives {
pub serialize: bool,
pub deserialize: bool,
}
impl SerdeDerives {
pub(crate) fn both() -> Self {
return Self {
serialize: true,
deserialize: true,
};
}
}
pub(crate) fn emit_item(item: &Item, derives: ModelDerives) -> Result<TokenStream> {
let tokens = match item {
Item::Struct(strukt) => emit_struct(strukt, derives)?,
Item::Enum(enom) => emit_enum(enom, derives)?,
Item::Alias(alias) => emit_alias(alias)?,
};
return Ok(tokens);
}
fn derive_attr(derives: ModelDerives) -> TokenStream {
let mut parts: Vec<TokenStream> = Vec::new();
if derives.serde.serialize {
parts.push(quote! { serde::Serialize });
}
if derives.serde.deserialize {
parts.push(quote! { serde::Deserialize });
}
if derives.foreign.debug {
parts.push(quote! { Debug });
}
if derives.foreign.clone {
parts.push(quote! { Clone });
}
if derives.foreign.partial_eq {
parts.push(quote! { PartialEq });
}
if parts.is_empty() {
return quote! {};
}
return quote! {
#[derive(#(#parts),*)]
};
}
pub(crate) fn plain_derive_attr(requested: ForeignDerives, allowed: ForeignDerives) -> TokenStream {
return derive_attr(ModelDerives {
serde: SerdeDerives {
serialize: false,
deserialize: false,
},
foreign: requested.intersect(allowed),
});
}
pub(crate) const DEBUG_AND_CLONE: ForeignDerives = ForeignDerives {
debug: true,
clone: true,
partial_eq: false,
};
pub(crate) const DEBUG_CLONE_AND_EQ: ForeignDerives = ForeignDerives {
debug: true,
clone: true,
partial_eq: true,
};
fn deprecated_attr(deprecated: &Option<Deprecation>) -> TokenStream {
return match deprecated {
None => quote! {},
Some(Deprecation { note: None }) => quote! { #[deprecated] },
Some(Deprecation { note: Some(note) }) => quote! { #[deprecated(note = #note)] },
};
}
pub(crate) fn emit_struct(strukt: &Struct, derives: ModelDerives) -> Result<TokenStream> {
let name = strukt.name.to_token();
let doc = doc_attr(&strukt.doc);
let deprecated = deprecated_attr(&strukt.deprecated);
let serde = derives.serde;
let derive_attr = derive_attr(derives);
let has_serde = serde.serialize || serde.deserialize;
let mut fields = Vec::with_capacity(strukt.fields.len());
let mut defaults = Vec::new();
for field in &strukt.fields {
fields.push(emit_field(field, serde, &strukt.name)?);
if let (Some(value), true) = (&field.default, serde.deserialize) {
defaults.push(emit_default_fn(field, value)?);
}
if serde.deserialize && super::constraints::is_checked(field) {
defaults.push(super::constraints::emit_validate_fn(field)?);
}
}
let defaults = if defaults.is_empty() {
quote! {}
} else {
quote! { impl #name { #(#defaults)* } }
};
let additional = match &strukt.additional_properties {
Some(element) => {
let ty = emit_type(element)?;
let flatten = if has_serde {
quote! { #[serde(flatten)] }
} else {
quote! {}
};
quote! {
#flatten
pub additional_properties: std::collections::HashMap<String, #ty>,
}
}
None => quote! {},
};
let deny_unknown = if strukt.deny_unknown_fields && serde.deserialize {
quote! { #[serde(deny_unknown_fields)] }
} else {
quote! {}
};
return Ok(quote! {
#doc
#derive_attr
#deny_unknown
#deprecated
pub struct #name {
#(#fields)*
#additional
}
#defaults
});
}
fn default_fn_name(field: &Field) -> proc_macro2::Ident {
return format_ident!("default_{}", field.name.logical());
}
fn emit_default_fn(field: &Field, value: &DefaultValue) -> Result<TokenStream> {
let name = default_fn_name(field);
let ty = emit_type(&field.ty)?;
let expr = emit_default_value(value, &field.ty)?;
let doc = doc_attr(&Some(format!(
"The `default` the document gives `{}`.",
field.name.logical()
)));
return Ok(quote! {
#doc
fn #name() -> #ty {
#expr
}
});
}
fn emit_default_value(value: &DefaultValue, ty: &RustType) -> Result<TokenStream> {
match ty {
RustType::Option(inner) => {
let inner = emit_default_value(value, inner)?;
return Ok(quote! { Some(#inner) });
}
RustType::Boxed(inner) => {
let inner = emit_default_value(value, inner)?;
return Ok(quote! { Box::new(#inner) });
}
_ => {}
}
let expr = match value {
DefaultValue::Str(text) => quote! { #text.to_owned() },
DefaultValue::Int(number) => {
let literal = proc_macro2::Literal::i64_unsuffixed(*number);
quote! { #literal }
}
DefaultValue::UInt(number) => {
let literal = proc_macro2::Literal::u64_unsuffixed(*number);
quote! { #literal }
}
DefaultValue::Float(number) => {
let literal = proc_macro2::Literal::f64_unsuffixed(*number);
quote! { #literal }
}
DefaultValue::Bool(flag) => quote! { #flag },
DefaultValue::Variant(variant) => {
let owner = emit_type(ty)?;
let variant = variant.to_token();
quote! { #owner::#variant }
}
DefaultValue::Empty => quote! { Default::default() },
};
return Ok(expr);
}
fn emit_field(field: &Field, serde: SerdeDerives, owner: &RustIdent) -> Result<TokenStream> {
let has_serde = serde.serialize || serde.deserialize;
let name = field.name.to_token();
let ty = emit_type(&field.ty)?;
let doc = doc_attr(&field.doc);
let deprecated = deprecated_attr(&field.deprecated);
let mut metas: Vec<TokenStream> = Vec::new();
if field.serde_skip {
metas.push(quote! { skip });
} else {
if let Some(rename) = &field.rename {
metas.push(quote! { rename = #rename });
}
let omit_empty = field.omit_empty.unwrap_or(!field.required);
if omit_empty && field.ty.is_option() {
metas.push(quote! { skip_serializing_if = "Option::is_none" });
}
if field.default.is_some() && serde.deserialize {
let path = format!("{}::{}", owner.to_token(), default_fn_name(field));
metas.push(quote! { default = #path });
}
if serde.deserialize && super::constraints::is_checked(field) {
let path = format!("{}::{}", owner.to_token(), super::constraints::validate_fn_name(field));
metas.push(quote! { deserialize_with = #path });
if field.ty.is_option() && field.default.is_none() {
metas.push(quote! { default });
}
}
}
let serde_attr = if !has_serde || metas.is_empty() {
quote! {}
} else {
quote! { #[serde(#(#metas),*)] }
};
return Ok(quote! {
#doc
#serde_attr
#deprecated
pub #name: #ty,
});
}
pub(crate) fn emit_enum(enom: &Enum, derives: ModelDerives) -> Result<TokenStream> {
let name = enom.name.to_token();
let doc = doc_attr(&enom.doc);
let deprecated = deprecated_attr(&enom.deprecated);
let derive_attr = derive_attr(derives);
let tokens = match &enom.kind {
EnumKind::Strings(variants) => {
let rendered = variants.iter().map(emit_string_variant);
let rendered: Vec<TokenStream> = rendered.collect();
quote! {
#doc
#derive_attr
#deprecated
pub enum #name {
#(#rendered)*
}
}
}
EnumKind::Integers { repr, variants } => {
let repr_ty = emit_type(repr)?;
let rendered: Vec<TokenStream> = variants.iter().map(emit_integer_variant).collect();
let mut convert = Vec::new();
if derives.serde.deserialize {
let text = format!("{repr_ty}");
convert.push(quote! { try_from = #text });
}
if derives.serde.serialize {
let text = format!("{repr_ty}");
convert.push(quote! { into = #text });
}
let convert_attr = if convert.is_empty() {
quote! {}
} else {
quote! { #[serde(#(#convert),*)] }
};
let conversions = emit_integer_conversions(&enom.name, repr, variants)?;
quote! {
#doc
#derive_attr
#convert_attr
#[repr(#repr_ty)]
#deprecated
pub enum #name {
#(#rendered)*
}
#conversions
}
}
EnumKind::Union(variants) => {
let mut rendered = Vec::with_capacity(variants.len());
for variant in variants {
rendered.push(emit_union_variant(variant)?);
}
quote! {
#doc
#derive_attr
#[serde(untagged)]
#deprecated
pub enum #name {
#(#rendered)*
}
}
}
};
return Ok(tokens);
}
fn emit_integer_variant(variant: &IntegerVariant) -> TokenStream {
let name = variant.name.to_token();
let doc = doc_attr(&variant.doc);
let value = proc_macro2::Literal::i64_unsuffixed(variant.value);
return quote! {
#doc
#name = #value,
};
}
fn emit_integer_conversions(name: &RustIdent, repr: &RustType, variants: &[IntegerVariant]) -> Result<TokenStream> {
let ident = name.to_token();
let repr_ty = emit_type(repr)?;
let label = name.logical();
let mut to_number = Vec::with_capacity(variants.len());
let mut from_number = Vec::with_capacity(variants.len());
for variant in variants {
let variant_ident = variant.name.to_token();
let value = proc_macro2::Literal::i64_unsuffixed(variant.value);
to_number.push(quote! { #ident::#variant_ident => #value, });
from_number.push(quote! { #value => Ok(#ident::#variant_ident), });
}
return Ok(quote! {
impl From<#ident> for #repr_ty {
fn from(value: #ident) -> Self {
return match value {
#(#to_number)*
};
}
}
impl TryFrom<#repr_ty> for #ident {
type Error = String;
fn try_from(value: #repr_ty) -> Result<Self, Self::Error> {
return match value {
#(#from_number)*
other => Err(format!("`{}` is not a value of `{}`", other, #label)),
};
}
}
});
}
fn emit_string_variant(variant: &StringVariant) -> TokenStream {
let name = variant.name.to_token();
let doc = doc_attr(&variant.doc);
let serde_attr = match &variant.rename {
Some(rename) => quote! { #[serde(rename = #rename)] },
None => quote! {},
};
return quote! {
#doc
#serde_attr
#name,
};
}
fn emit_union_variant(variant: &UnionVariant) -> Result<TokenStream> {
let name = variant.name.to_token();
let ty = emit_type(&variant.ty)?;
return Ok(quote! {
#name(#ty),
});
}
fn emit_alias(alias: &Alias) -> Result<TokenStream> {
let name = alias.name.to_token();
let ty = emit_type(&alias.ty)?;
let doc = doc_attr(&alias.doc);
let deprecated = deprecated_attr(&alias.deprecated);
return Ok(quote! {
#doc
#deprecated
pub type #name = #ty;
});
}
#[cfg(test)]
mod tests {
use super::*;
use crate::naming::Case;
use crate::naming::to_ident;
fn widget_with_a_default() -> Struct {
return Struct {
name: to_ident("Widget", Case::Pascal),
doc: None,
deprecated: None,
fields: vec![Field {
name: to_ident("count", Case::Snake),
rename: None,
doc: None,
deprecated: None,
ty: RustType::I64,
required: false,
omit_empty: None,
serde_skip: false,
default: Some(DefaultValue::Int(10)),
constraints: None,
}],
additional_properties: None,
deny_unknown_fields: false,
};
}
fn rendered(serde: SerdeDerives) -> String {
let derives = ModelDerives {
serde,
foreign: ForeignDerives {
debug: true,
clone: true,
partial_eq: true,
},
};
return emit_struct(&widget_with_a_default(), derives)
.expect("this struct renders")
.to_string();
}
#[test]
fn a_default_function_needs_the_deserialize_derive() {
let cases = [
(
SerdeDerives {
serialize: true,
deserialize: true,
},
true,
),
(
SerdeDerives {
serialize: false,
deserialize: true,
},
true,
),
(
SerdeDerives {
serialize: true,
deserialize: false,
},
false,
),
(
SerdeDerives {
serialize: false,
deserialize: false,
},
false,
),
];
for (serde, is_emitted) in cases {
let code = rendered(serde);
assert_eq!(code.contains("default_count"), is_emitted, "{code}");
}
}
}