use std::collections::BTreeSet;
use cratestack_core::{Field, Model, TypeArity};
use quote::quote;
use crate::shared::{
doc_attrs, ident, is_primary_key, is_server_only_field, rust_type_tokens,
rust_type_tokens_with_scope, scalar_model_fields,
};
pub(crate) fn generate_model_struct_only(
model: &Model,
model_names: &BTreeSet<&str>,
enum_names: &BTreeSet<&str>,
) -> proc_macro2::TokenStream {
let model_ident = ident(&model.name);
let docs = doc_attrs(&model.docs);
let scalar_fields = scalar_model_fields(model, model_names);
let fields = scalar_fields
.iter()
.map(|field| struct_field_definition(field, false, enum_names));
quote! {
#docs
#[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct #model_ident {
#(#fields)*
}
}
}
pub(crate) fn generate_primary_key_accessor_impl(model: &Model) -> proc_macro2::TokenStream {
let primary_key = match model.fields.iter().find(|field| is_primary_key(field)) {
Some(pk) => pk,
None => return quote! {},
};
let model_ident = ident(&model.name);
let pk_type = rust_type_tokens(&primary_key.ty);
let pk_field_ident = ident(&primary_key.name);
quote! {
impl ::cratestack::ModelPrimaryKey<#pk_type> for #model_ident {
fn primary_key(&self) -> #pk_type {
self.#pk_field_ident.clone()
}
}
}
}
pub(crate) fn generate_client_model_struct(
model: &Model,
model_names: &BTreeSet<&str>,
enum_names: &BTreeSet<&str>,
) -> proc_macro2::TokenStream {
let model_ident = ident(&model.name);
let docs = doc_attrs(&model.docs);
let scalar_fields = scalar_model_fields(model, model_names);
let fields = scalar_fields
.iter()
.map(|field| struct_field_definition(field, false, enum_names));
quote! {
#docs
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct #model_ident {
#(#fields)*
}
}
}
pub(crate) fn struct_field_definition(
field: &Field,
wrap_for_patch: bool,
enum_names: &BTreeSet<&str>,
) -> proc_macro2::TokenStream {
let field_ident = ident(&field.name);
let docs = doc_attrs(&field.docs);
let base_type = if enum_names.contains(field.ty.name.as_str()) {
let enum_ident = ident(&field.ty.name);
match field.ty.arity {
TypeArity::Required => quote! { super::types::#enum_ident },
TypeArity::Optional => quote! { Option<super::types::#enum_ident> },
TypeArity::List => quote! { Vec<super::types::#enum_ident> },
}
} else {
rust_type_tokens_with_scope(&field.ty, true)
};
let field_type = if wrap_for_patch {
quote! { Option<#base_type> }
} else {
base_type
};
let serde_attr = if is_server_only_field(field) {
quote! { #[serde(skip_serializing, default)] }
} else if matches!(field.ty.arity, TypeArity::Optional) && !wrap_for_patch {
quote! { #[serde(default)] }
} else {
quote! {}
};
quote! {
#docs
#serde_attr
pub #field_ident: #field_type,
}
}