use std::iter;
use crate::context::{BaseModule, BuilderConfig, BuilderItemConfig, Context};
use crate::types::objects::{FieldDefinition, ObjectDefinition};
use proc_macro2::TokenStream;
use quote::quote;
pub fn generate(ctx: &Context, base_module: BaseModule, def: &ObjectDefinition) -> TokenStream {
let docs = ctx.docs(def.docs());
let name = ctx.type_name(def.type_name().name());
let mut type_attrs = vec![quote!(#[serde(crate = "conjure_object::serde")])];
let mut derives = vec![
"Debug",
"Clone",
"conjure_object::serde::Serialize",
"conjure_object::serde::Deserialize",
];
if def.fields().iter().any(|v| ctx.has_double(v.type_())) {
derives.push("conjure_object::private::DeriveWith");
type_attrs.push(quote!(#[derive_with(PartialEq, Eq, PartialOrd, Ord, Hash)]));
} else {
derives.push("PartialEq");
derives.push("Eq");
derives.push("PartialOrd");
derives.push("Ord");
derives.push("Hash");
}
if def.fields().iter().all(|v| ctx.is_copy(v.type_())) {
derives.push("Copy");
}
let derives = derives.iter().map(|s| s.parse::<TokenStream>().unwrap());
type_attrs.insert(0, quote!(#[derive(#(#derives),*)]));
if ctx.public_fields() {
type_attrs.push(quote!(#[non_exhaustive]));
}
let field_attrs = def.fields().iter().map(|s| {
let builder_attr = field_builder_attr(ctx, base_module, def, s);
let serde_attr = serde_field_attr(ctx, def, s);
let educe_attr = if ctx.is_double(s.type_()) {
quote! {
#[derive_with(with = conjure_object::private::DoubleWrapper)]
}
} else {
quote!()
};
let (docs, deprecated) = if ctx.public_fields() {
(ctx.docs(s.docs()), ctx.deprecated(s.deprecated()))
} else {
(quote!(), quote!())
};
quote! {
#builder_attr
#serde_attr
#educe_attr
#docs
#deprecated
}
});
let pub_ = if ctx.public_fields() {
quote!(pub)
} else {
quote!()
};
let pub_ = iter::repeat(pub_);
let fields = def.fields().iter().map(|f| ctx.field_name(f.field_name()));
let boxed_types = &def
.fields()
.iter()
.map(|s| ctx.boxed_rust_type(base_module, def.type_name(), s.type_()))
.collect::<Vec<_>>();
let constructor = generate_constructor(ctx, base_module, def);
let accessors = def.fields().iter().map(|s| {
if ctx.public_fields() {
return quote!();
}
let docs = ctx.docs(s.docs());
let deprecated = ctx.deprecated(s.deprecated());
let name = ctx.field_name(s.field_name());
let ret_type = ctx.borrowed_rust_type(base_module, def.type_name(), s.type_());
let borrow = ctx.borrow_rust_type(quote!(self.#name), s.type_());
quote!(
#docs
#deprecated
#[inline]
pub fn #name(&self) -> #ret_type {
#borrow
}
)
});
quote! {
#docs
#(#type_attrs)*
#[conjure_object::private::staged_builder::staged_builder]
#[builder(
crate = conjure_object::private::staged_builder,
update,
inline,
)]
pub struct #name {
#(
#field_attrs
#pub_ #fields: #boxed_types,
)*
}
impl #name {
#constructor
#(#accessors)*
}
}
}
fn generate_constructor(
ctx: &Context,
base_module: BaseModule,
def: &ObjectDefinition,
) -> TokenStream {
let required_args = def
.fields()
.iter()
.filter(|f| ctx.is_required(f.type_()))
.collect::<Vec<_>>();
if required_args.len() > 3 {
return quote!();
}
let new = if def.fields().iter().any(|f| **f.field_name() == "new") {
quote!(new_)
} else {
quote!(new)
};
let arguments = required_args.iter().map(|f| {
let name = ctx.field_name(f.field_name());
let ty = match ctx.builder_config(base_module, def.type_name(), f.type_()) {
BuilderConfig::Normal => ctx.rust_type(base_module, def.type_name(), f.type_()),
BuilderConfig::Into => {
let into = ctx.into_ident(def.type_name());
let ty = ctx.rust_type(base_module, def.type_name(), f.type_());
quote!(impl #into<#ty>)
}
BuilderConfig::Custom { type_, .. } => type_,
BuilderConfig::List { .. } | BuilderConfig::Set { .. } | BuilderConfig::Map { .. } => {
unreachable!()
}
};
quote!(#name: #ty)
});
let setters = required_args.iter().map(|f| {
let field = ctx.field_name(f.field_name());
quote!(.#field(#field))
});
quote! {
#[inline]
pub fn #new(#(#arguments,)*) -> Self {
Self::builder()
#(#setters)*
.build()
}
}
}
fn serde_field_attr(ctx: &Context, def: &ObjectDefinition, field: &FieldDefinition) -> TokenStream {
let mut parts = vec![];
let name = &field.field_name().0;
parts.push(quote!(rename = #name));
if !ctx.serialize_empty_collections() {
if let Some(is_empty) = ctx.is_empty_method(def.type_name(), field.type_()) {
parts.push(quote!(skip_serializing_if = #is_empty));
}
}
if !ctx.is_required(field.type_()) {
parts.push(quote!(default));
}
quote!(#[serde(#(#parts),*)])
}
fn field_builder_attr(
ctx: &Context,
base_module: BaseModule,
def: &ObjectDefinition,
field: &FieldDefinition,
) -> TokenStream {
let mut inner = match ctx.builder_config(base_module, def.type_name(), field.type_()) {
BuilderConfig::Normal => quote!(),
BuilderConfig::Into => quote!(into),
BuilderConfig::Custom { type_, convert } => {
quote!(custom(type = #type_, convert = #convert))
}
BuilderConfig::List { item } => {
let item = builder_item_attr(item);
quote!(list(item(#item)))
}
BuilderConfig::Set { item } => {
let item = builder_item_attr(item);
quote!(set(item(#item)))
}
BuilderConfig::Map { key, value } => {
let key = builder_item_attr(key);
let value = builder_item_attr(value);
quote!(map(key(#key), value(#value)))
}
};
if !ctx.is_required(field.type_()) {
inner = quote!(default, #inner);
}
if inner.is_empty() {
quote!()
} else {
quote!(#[builder(#inner)])
}
}
fn builder_item_attr(config: BuilderItemConfig) -> TokenStream {
match config {
BuilderItemConfig::Normal { type_ } => quote!(type = #type_),
BuilderItemConfig::Into { type_ } => quote!(type = #type_, into),
BuilderItemConfig::Custom { type_, convert } => {
quote!(custom(type = #type_, convert = #convert))
}
}
}