use inflections::Inflect;
use quote::{format_ident, quote, ToTokens};
use syn::Ident;
use crate::parser::{JsonMacroInput, JsonStruct, JsonValue};
pub fn generate_structs(
json_struct: &JsonMacroInput,
base_name: &Ident,
) -> (proc_macro2::TokenStream, Vec<proc_macro2::TokenStream>) {
let mut all_structs = Vec::new();
let mut fields = Vec::new();
let mut derives = vec![quote!(::std::clone::Clone)];
if json_struct.flags.debug {
derives.push(quote!(::std::fmt::Debug));
}
derives.extend(json_struct.flags.custom_derives.iter().map(|d| quote!(#d)));
for (key, value) in &json_struct.content.entries {
let field_name = format_ident!("{}", sanitize_identifier(key));
let (field_type, _) = match value {
JsonValue::Str(_) => (quote!(String), Vec::<proc_macro2::TokenStream>::new()),
JsonValue::Number(_) => (quote!(f64), Vec::new()),
JsonValue::Boolean(_) => (quote!(bool), Vec::new()),
JsonValue::Array(arr) => {
let (elem_type, _) = infer_array_type(arr);
(quote!(Vec<#elem_type>), Vec::new())
}
JsonValue::Object(obj) => {
let nested_name = format_ident!("{}{}", base_name, key.to_pascal_case());
let json_content = JsonStruct {
entries: obj.clone(),
};
let nested_macro_input = JsonMacroInput {
struct_name: json_struct.struct_name.clone(),
flags: json_struct.flags.clone(),
content: json_content,
};
let (nested_struct, nested_structs) =
generate_structs(&nested_macro_input, &nested_name);
all_structs.extend(nested_structs);
all_structs.push(nested_struct.clone());
(
format_ident!("{}", nested_name).into_token_stream(),
Vec::new(),
)
}
JsonValue::Null => (quote!(Option<::serde_json::Value>), Vec::new()),
};
let field = if json_struct.flags.use_serde_alias {
quote! {
#[serde(alias = #key)]
#field_name: #field_type
}
} else {
quote! {
#field_name: #field_type
}
};
fields.push(field);
}
let struct_name = base_name;
let style = json_struct
.clone()
.flags
.rename_all
.map(|style| Some(style.to_string()));
let main_struct = if let Some(rename_all_style) = style {
quote! {
#[derive(#(#derives),*, ::serde::Deserialize, ::serde::Serialize)]
#[serde(rename_all = #rename_all_style)]
struct #struct_name {
#(#fields),*
}
}
} else {
quote! {
#[derive(#(#derives),*, ::serde::Deserialize, ::serde::Serialize)]
struct #struct_name {
#(#fields),*
}
}
};
(main_struct, all_structs)
}
fn infer_array_type(
arr: &[JsonValue],
) -> (proc_macro2::TokenStream, Vec<proc_macro2::TokenStream>) {
if arr.is_empty() {
return (quote!(::serde_json::Value), Vec::new());
}
match &arr[0] {
JsonValue::Str(_) => (quote!(String), Vec::new()),
JsonValue::Number(_) => (quote!(f64), Vec::new()),
JsonValue::Boolean(_) => (quote!(bool), Vec::new()),
_ => (quote!(::serde_json::Value), Vec::new()),
}
}
fn sanitize_identifier(name: &str) -> String {
name.chars()
.map(|c| if c.is_alphanumeric() { c } else { '_' })
.collect::<String>()
.to_lowercase()
}