use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput, LitStr};
#[proc_macro_derive(JsonxConstructor, attributes(jsonx))]
pub fn derive_jsonx_constructor(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
expand(input)
.unwrap_or_else(syn::Error::into_compile_error)
.into()
}
fn expand(input: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
let ident = &input.ident;
let mut name: Option<LitStr> = None;
for attr in &input.attrs {
if !attr.path().is_ident("jsonx") {
continue;
}
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("name") {
name = Some(meta.value()?.parse()?);
Ok(())
} else {
Err(meta.error("unknown `jsonx` option; expected `name = \"...\"`"))
}
})?;
}
let name = name.unwrap_or_else(|| LitStr::new(&ident.to_string().to_lowercase(), ident.span()));
if !is_valid_ctor_name(&name.value()) {
return Err(syn::Error::new_spanned(
&name,
"constructor name must match `[A-Za-z_][0-9A-Za-z_]*`",
));
}
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let mut de_generics = input.generics.clone();
de_generics
.params
.insert(0, syn::parse_quote!('de));
let (de_impl_generics, _, _) = de_generics.split_for_impl();
Ok(quote! {
impl #impl_generics ::jsonx::JsonxConstructor for #ident #ty_generics #where_clause {
const TOKEN: &'static str = ::jsonx::ctor!(#name);
fn to_jsonx_arg(&self) -> ::std::string::String {
::std::string::ToString::to_string(self)
}
fn from_jsonx_arg(arg: &str) -> ::std::result::Result<Self, ::std::string::String> {
<Self as ::std::str::FromStr>::from_str(arg)
.map_err(|e| ::std::string::ToString::to_string(&e))
}
}
impl #impl_generics ::jsonx::__derive::serde::Serialize for #ident #ty_generics #where_clause {
fn serialize<__S>(&self, serializer: __S) -> ::std::result::Result<__S::Ok, __S::Error>
where
__S: ::jsonx::__derive::serde::Serializer,
{
::jsonx::constructor::serialize(self, serializer)
}
}
impl #de_impl_generics ::jsonx::__derive::serde::Deserialize<'de> for #ident #ty_generics #where_clause {
fn deserialize<__D>(deserializer: __D) -> ::std::result::Result<Self, __D::Error>
where
__D: ::jsonx::__derive::serde::Deserializer<'de>,
{
::jsonx::constructor::deserialize(deserializer)
}
}
})
}
fn is_valid_ctor_name(name: &str) -> bool {
let mut bytes = name.bytes();
match bytes.next() {
Some(b) if b.is_ascii_alphabetic() || b == b'_' => {}
_ => return false,
}
bytes.all(|b| b.is_ascii_alphanumeric() || b == b'_')
}