mod attrs;
mod fields;
use fields::{ Fields, Field };
use proc_macro::TokenStream;
use quote::{ quote, quote_spanned };
use syn::{ punctuated::Punctuated, parse::Parser };
use proc_macro2::{ TokenStream as TokenStream2, Span };
static CRATE_NAME_STR: &str = "seamless";
static VARIANT_DESCRIPTION: &str = "Variant tag";
#[derive(Debug)]
pub struct Attrs {
pub deserialize: bool,
pub serialize: bool
}
pub fn parse_top_attrs(attrs: TokenStream) -> Attrs {
let attrs = Punctuated::<syn::Ident,syn::Token![,]>::parse_terminated
.parse(attrs)
.expect("Invalid Api attributes provided");
let mut se = false;
let mut de = false;
for ident in attrs {
if ident == "Serialize" { se = true }
else if ident == "Deserialize" { de = true}
}
if !se && !de {
se = true;
de = true;
}
Attrs { serialize: se, deserialize: de }
}
pub fn parse_enum(e: syn::ItemEnum, attrs: Attrs) -> syn::Result<TokenStream2> {
let crate_name: syn::Ident = syn::Ident::new(CRATE_NAME_STR, Span::call_site());
let ident = e.ident.clone();
let top_level_attr_props = attrs::parse(&e.attrs)?;
let serde_tag = top_level_attr_props.tag.unwrap_or("kind".to_owned());
let top_level_docs = top_level_attr_props.docs;
let tuple_variants_not_allowed = ||
syn::Error::new_spanned(&ident, "Enum tuple variants are not allowed");
let unit_and_nonunit_cant_be_mixed = ||
syn::Error::new_spanned(&ident, "Unit enum fields can't be mixed with named fields");
let mut ts_impl_variants = vec![];
let mut seen_unit_fields = false;
let mut seen_nonunit_fields = false;
for variant in e.variants.iter() {
let variant_ident = &variant.ident;
let variant_ident_string = variant_ident.to_string();
let attr_props = attrs::parse(&variant.attrs)?;
let variant_docs = attr_props.docs;
let token_stream = match Fields::from_syn(variant.fields.clone())? {
Fields::Unnamed(..) => {
return Err(tuple_variants_not_allowed())
},
Fields::Unit => {
seen_unit_fields = true;
if seen_nonunit_fields { return Err(unit_and_nonunit_cant_be_mixed()) }
quote!{{
::#crate_name::api::ApiBodyInfo {
description: #variant_docs.to_owned(),
ty: ::#crate_name::api::ApiBodyType::StringLiteral{ literal: #variant_ident_string.to_owned() }
}
}}
},
Fields::Single(f) => {
seen_nonunit_fields = true;
if seen_unit_fields { return Err(unit_and_nonunit_cant_be_mixed()) }
let ty = &f.field.ty;
quote!{{
let mut s = <#ty as ::#crate_name::api::ApiBodyStruct>::api_body_struct_info();
s.struc.insert(#serde_tag.to_owned(), ::#crate_name::api::ApiBodyInfo {
description: #VARIANT_DESCRIPTION.to_owned(),
ty: ::#crate_name::api::ApiBodyType::StringLiteral{ literal: #variant_ident_string.to_owned() }
});
let mut t = ::#crate_name::api::ApiBodyInfo {
description: #variant_docs.to_owned(),
ty: ::#crate_name::api::ApiBodyType::Object{ keys: s.struc }
};
if t.description.len() == 0 { t.description = s.description }
t
}}
},
Fields::Named(fields) => {
seen_nonunit_fields = true;
if seen_unit_fields { return Err(unit_and_nonunit_cant_be_mixed()) }
let entries = fields.iter().map(|f| {
let name = f.field.ident.as_ref().unwrap().to_string();
let f = quote_field(f);
quote!{ m.insert(#name.to_owned(), #f); }
}).collect::<Vec<_>>();
quote!{{
let mut m = std::collections::HashMap::new();
m.insert(#serde_tag.to_owned(), ::#crate_name::api::ApiBodyInfo {
description: #VARIANT_DESCRIPTION.to_owned(),
ty: ::#crate_name::api::ApiBodyType::StringLiteral{ literal: #variant_ident_string.to_owned() }
});
#(#entries)*
::#crate_name::api::ApiBodyInfo {
description: #variant_docs.to_owned(),
ty: ::#crate_name::api::ApiBodyType::Object{ keys: m }
}
}}
}
};
ts_impl_variants.push(token_stream);
}
let serialize_toks = if attrs.serialize {
quote!{ #[derive(::#crate_name::serde::Serialize)] }
} else {
TokenStream2::new()
};
let deserialize_toks = if attrs.deserialize {
quote!{ #[derive(::#crate_name::serde::Deserialize)] }
} else {
TokenStream2::new()
};
let serde_tag_attr = if seen_nonunit_fields {
quote!{ #[serde(tag = #serde_tag)] }
} else {
TokenStream2::new()
};
let mut sanitized_e = e;
sanitized_e.attrs.retain(|attr| !attr.path.is_ident(attrs::NAME));
let serde_crate_path = format!("::{}::serde", crate_name);
Ok(quote!{
#serialize_toks
#deserialize_toks
#[serde(crate = #serde_crate_path)]
#serde_tag_attr
#sanitized_e
impl ::#crate_name::api::ApiBody for #ident {
fn api_body_info() -> ::#crate_name::api::ApiBodyInfo {
::#crate_name::api::ApiBodyInfo {
description: #top_level_docs.to_owned(),
ty: ::#crate_name::api::ApiBodyType::OneOf{
values:vec![ #(#ts_impl_variants),* ]
}
}
}
}
})
}
pub fn parse_struct(s: syn::ItemStruct, attrs: Attrs) -> syn::Result<TokenStream2> {
let crate_name: syn::Ident = syn::Ident::new(CRATE_NAME_STR, Span::call_site());
let ident = s.ident.clone();
let top_level_attr_props = attrs::parse(&s.attrs)?;
let top_level_docs = top_level_attr_props.docs;
let ts_impl = match Fields::from_syn(s.fields.clone())? {
Fields::Single(f) => {
let field_toks = quote_field(&f);
quote!{
impl ::#crate_name::api::ApiBody for #ident {
fn api_body_info() -> ::#crate_name::api::ApiBodyInfo {
let mut t = #field_toks;
let d = #top_level_docs;
if d.len() > 0 { t.description = d.to_owned() }
t
}
}
}
},
Fields::Unnamed(fields) => {
let types = fields.iter()
.map(quote_field)
.collect::<Vec<_>>();
quote!{
impl ::#crate_name::api::ApiBody for #ident {
fn api_body_info() -> ::#crate_name::api::ApiBodyInfo {
::#crate_name::api::ApiBodyInfo {
description: #top_level_docs.to_owned(),
ty: ::#crate_name::api::ApiBodyType::TupleOf {
values: vec![ #( #types ),* ]
}
}
}
}
}
},
Fields::Named(fields) => {
let entries = fields.iter().map(|f| {
let flatten = f.attr_props.flatten;
if flatten {
let ty = &f.field.ty;
quote!{{
let s = <#ty as ::#crate_name::api::ApiBodyStruct>::api_body_struct_info();
for (key, val) in s.struc.into_iter() { m.insert(key, val); }
}}
} else {
let name = f.field.ident.as_ref().unwrap().to_string();
let f = quote_field(&f);
quote!{ m.insert(#name.to_owned(), #f); }
}
}).collect::<Vec<_>>();
quote!{
impl ::#crate_name::api::ApiBodyStruct for #ident {
fn api_body_struct_info() -> ::#crate_name::api::ApiBodyStructInfo {
let mut m = std::collections::HashMap::new();
#(#entries)*
::#crate_name::api::ApiBodyStructInfo {
description: #top_level_docs.to_owned(),
struc: m
}
}
}
impl ::#crate_name::api::ApiBody for #ident {
fn api_body_info() -> ::#crate_name::api::ApiBodyInfo {
let s = <#ident as ::#crate_name::api::ApiBodyStruct>::api_body_struct_info();
::#crate_name::api::ApiBodyInfo {
description: s.description,
ty: ::#crate_name::api::ApiBodyType::Object { keys: s.struc }
}
}
}
}
},
Fields::Unit => {
quote_spanned!{s.ident.span() =>
compile_error!("TypeScript: unit structs are not supported")
}
}
};
let serialize_toks = if attrs.serialize {
quote!{ #[derive(::#crate_name::serde::Serialize)] }
} else {
TokenStream2::new()
};
let deserialize_toks = if attrs.deserialize {
quote!{ #[derive(::#crate_name::serde::Deserialize)] }
} else {
TokenStream2::new()
};
let mut sanitized_s = s;
for field in sanitized_s.fields.iter_mut() {
let attr_props = attrs::parse(&field.attrs)?;
field.attrs.retain(|attr| !attr.path.is_ident(attrs::NAME));
if attr_props.flatten {
let new_attr: syn::Attribute = syn::parse_quote!{ #[serde(flatten)] };
field.attrs.push(new_attr);
}
}
let serde_crate_path = format!("::{}::serde", crate_name);
Ok(quote!{
#serialize_toks
#deserialize_toks
#[serde(crate = #serde_crate_path)]
#sanitized_s
#ts_impl
})
}
fn quote_field(f: &Field) -> TokenStream2 {
let crate_name: syn::Ident = syn::Ident::new(CRATE_NAME_STR, Span::call_site());
let ty = &f.field.ty;
let docs = &f.attr_props.docs;
quote!{{
let mut t = <#ty as ::#crate_name::api::ApiBody>::api_body_info();
let d = #docs;
if d.len() > 0 { t.description = d.to_owned(); }
t
}}
}