use proc_macro::TokenStream;
use proc_macro_crate::{FoundCrate, crate_name};
use proc_macro2::Span;
use quote::quote;
use syn::{Data, Fields, Ident};
pub fn generate_to_json(ast: &syn::DeriveInput) -> TokenStream {
let name = &ast.ident;
let data = &ast.data;
let crate_name = match crate_name("s2json") {
Ok(FoundCrate::Itself) => "s2json".to_string(),
Ok(FoundCrate::Name(name)) => name,
Err(_) => "s2json_core".to_string(), };
let s2json_core = Ident::new(&crate_name, Span::call_site());
let fields = match data {
Data::Struct(data_struct) => &data_struct.fields,
_ => panic!("Unsupported data type"),
};
let (from_mvalue, into_mvalue) = generate_mvalue_conversions(fields);
let gener = quote! {
#[doc(hidden)]
#[allow(
non_upper_case_globals,
unused_attributes,
unused_qualifications,
clippy::absolute_paths,
)]
const _: () = {
#[allow(unused_extern_crates, clippy::useless_attribute)]
extern crate #s2json_core as _s2json_core;
#[allow(unused_extern_crates, clippy::useless_attribute)]
extern crate alloc;
use _s2json_core::*;
#[automatically_derived]
impl From<JSONProperties> for #name {
fn from(m: JSONProperties) -> Self {
#from_mvalue
}
}
#[automatically_derived]
impl From<&JSONProperties> for #name {
fn from(m: &JSONProperties) -> Self {
#from_mvalue
}
}
#[automatically_derived]
impl From<#name> for JSONProperties {
fn from(value: #name) -> JSONProperties {
#into_mvalue
}
}
#[automatically_derived]
impl From<JSONValue> for #name {
fn from(value: JSONValue) -> Self {
match value {
JSONValue::Object(v) => v.into(),
_ => #name::default(),
}
}
}
#[automatically_derived]
impl From<&JSONValue> for #name {
fn from(value: &JSONValue) -> Self {
match value {
JSONValue::Object(v) => v.into(),
_ => #name::default(),
}
}
}
#[automatically_derived]
impl From<#name> for JSONValue {
fn from(value: #name) -> JSONValue {
JSONValue::Object(value.into())
}
}
#[automatically_derived]
impl JSONPropertiesCompatible for #name {}
};
};
gener.into()
}
fn generate_mvalue_conversions(
fields: &Fields,
) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
let mut from_assignments = vec![];
let mut into_insertions = vec![];
for field in fields.iter() {
let field_name = field.ident.as_ref().unwrap();
let field_str = field_name.to_string();
let field_ty = &field.ty;
let is_option = if let syn::Type::Path(type_path) = field_ty {
type_path.path.segments.last().unwrap().ident == "Option"
} else {
false
};
if is_option {
from_assignments.push(quote! {
#field_name: m.get(#field_str)
.map(|v| match v {
JSONValue::Primitive(PrimitiveValue::Null) => None,
other => Some(other.into()),
})
.unwrap_or(None)
});
} else {
from_assignments.push(quote! {
#field_name: m.get(#field_str).map(Into::into).unwrap_or_default()
});
}
into_insertions.push(quote! {
map.insert(#field_str.into(), value.#field_name.into());
});
}
let from_mvalue = quote! {
Self {
#(#from_assignments),*
}
};
let into_mvalue = quote! {
let mut map = JSONProperties::new();
#(#into_insertions)*
map
};
(from_mvalue, into_mvalue)
}