use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::Ident;
use crate::parse::{FieldCategory, FieldMeta, LayerMeta};
pub(crate) fn gen_structs(
opt_name: &Ident,
fields: &[FieldMeta],
layer: &LayerMeta,
doc_attrs: &[syn::Attribute],
) -> TokenStream {
let resolved_name = layer
.resolved_name
.clone()
.unwrap_or_else(|| format_ident!("Resolved{}", opt_name));
let resolved_fields = gen_resolved_fields(fields);
let field_names = gen_field_names(fields);
let doc_tokens: Vec<_> = doc_attrs.iter().collect();
quote! {
#(#doc_tokens)*
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
#[non_exhaustive]
pub struct #resolved_name {
#resolved_fields
}
impl #opt_name {
pub const FIELD_NAMES: &[&str] = &[#field_names];
}
}
}
fn gen_resolved_fields(fields: &[FieldMeta]) -> TokenStream {
let field_tokens: Vec<TokenStream> = fields
.iter()
.map(|f| {
let ident = &f.ident;
let docs: Vec<_> = f.doc_attrs.iter().collect();
match &f.category {
FieldCategory::Option => {
let inner = extract_option_inner(&f.ty);
quote! {
#(#docs)*
pub #ident: #inner,
}
}
FieldCategory::SoftOption => {
let inner = extract_option_inner(&f.ty);
quote! {
#(#docs)*
pub #ident: Option<#inner>,
}
}
FieldCategory::Nested { resolved_ty } => {
quote! {
#(#docs)*
pub #ident: #resolved_ty,
}
}
}
})
.collect();
quote! { #(#field_tokens)* }
}
fn gen_field_names(fields: &[FieldMeta]) -> TokenStream {
let entries: Vec<TokenStream> = fields
.iter()
.map(|f| {
if let Some(ref rename) = f.serde_rename {
quote! { #rename, }
} else {
let name = f.ident.to_string();
quote! { #name, }
}
})
.collect();
quote! { #(#entries)* }
}
fn extract_option_inner(ty: &syn::Type) -> syn::Type {
if let syn::Type::Path(type_path) = ty
&& let Some(seg) = type_path.path.segments.last()
&& seg.ident == "Option"
&& let syn::PathArguments::AngleBracketed(args) = &seg.arguments
&& let Some(syn::GenericArgument::Type(inner)) = args.args.first()
{
return inner.clone();
}
ty.clone()
}