use crate::provider::parse::{Def, InternalMashinType, TsType};
use inflector::Inflector;
use std::collections::HashMap;
use syn::{ext::IdentExt, Attribute, Fields, Lit, Meta, NestedMeta};
macro_rules! variant_instance {
( $variant:path, $iterator:expr ) => {
$iterator
.filter_map(
|val| {
if let $variant(ref f1, ref f2) = *val {
Some((f1, f2))
} else {
None
}
},
)
.next()
};
}
pub fn process_struct(
def: &mut Def,
key: usize,
mashin_ty: InternalMashinType,
) -> Result<(), String> {
let input = def.item.content.clone().expect("Checked by def parser").1[key].clone();
match &input {
syn::Item::Struct(syn::ItemStruct {
ident, attrs, fields: Fields::Named(fields), ..
}) => {
let fields = &fields.named;
let name = ident;
let mut fmap = HashMap::new();
let mut typescript: Vec<String> = vec![];
let serde_attrs = get_serde_attrs(attrs);
for field in fields.iter() {
let mut ident = field
.ident
.as_ref()
.expect("Field without ident")
.unraw()
.to_string();
match field.ty {
syn::Type::Path(ref ty) => {
let segment = &ty.path.segments.first().unwrap();
let ty = segment.ident.to_string();
fmap.insert(ident.clone(), ty);
},
syn::Type::Reference(ref ty) => {
assert!(ty.mutability.is_none());
assert!(ty.lifetime.is_some());
match *ty.elem {
syn::Type::Path(ref ty) => {
let segment = &ty.path.segments.first().unwrap();
let ty = segment.ident.to_string();
fmap.insert(ident.clone(), ty);
},
_ => unimplemented!(),
}
},
_ => unimplemented!(),
};
ident = ident.to_camel_case();
for attr in &serde_attrs {
if let Some(i) = attr.transform(&ident) {
ident = i;
}
}
let doc_str = get_docs(&field.attrs);
typescript.push(format!("{} {}: {};", doc_str, ident, types_to_ts(&field.ty)));
}
let doc_str = get_docs(attrs);
def.type_defs.insert(
name.to_string(),
TsType {
doc: doc_str,
name: name.to_string(),
typescript: typescript.join("\n"),
mashin_ty,
is_enum: false,
},
);
Ok(())
},
syn::Item::Enum(syn::ItemEnum { ident, attrs, variants, .. }) => {
let name = &ident;
let mut typescript: Vec<String> = vec![];
for variant in variants {
let mut variant_fields: Vec<String> = vec![];
let fields = &variant.fields;
let serde_attrs = get_serde_attrs(attrs);
for field in fields {
let mut ident = field
.ident
.as_ref()
.expect("Field without ident")
.unraw()
.to_string();
for attr in &serde_attrs {
if let Some(i) = attr.transform(&ident) {
ident = i;
}
}
let doc_str = get_docs(&field.attrs);
variant_fields.push(format!(
"{} {}: {};",
doc_str,
ident,
types_to_ts(&field.ty)
));
}
let mut ident = variant.ident.to_string();
for attr in &serde_attrs {
if let Some(i) = attr.transform(&ident) {
ident = i;
}
}
let doc_str = get_docs(&variant.attrs);
let variant_str = if !variant_fields.is_empty() {
let tag_content =
variant_instance!(SerdeAttr::TagAndContent, serde_attrs.iter());
match tag_content {
None => {
format!(
"{} {{ {}: {{\n {}\n}} }}",
doc_str,
&ident,
variant_fields.join("\n")
)
},
Some((tag, content)) => {
format!(
"{} {{ {}: \"{}\", {}: {{ {} }} }}",
doc_str,
&tag,
&ident,
&content,
variant_fields.join("\n")
)
},
}
} else {
format!("{} \"{}\"", doc_str, &ident)
};
typescript.push(variant_str);
}
let doc_str = get_docs(attrs);
def.type_defs.insert(
name.to_string(),
TsType {
doc: doc_str,
name: name.to_string(),
typescript: typescript.join(" |\n"),
mashin_ty,
is_enum: true,
},
);
Ok(())
},
_ => unimplemented!(),
}
}
fn types_to_ts(ty: &syn::Type) -> String {
match ty {
syn::Type::Array(_) => String::from("any"),
syn::Type::Ptr(_) => String::from("any"),
syn::Type::Reference(ref ty) => types_to_ts(&ty.elem),
syn::Type::Path(ref ty) => {
let segment = &ty.path.segments.last().unwrap();
let ty = segment.ident.to_string();
let mut generics: Vec<String> = vec![];
let generic_params = &segment.arguments;
match generic_params {
syn::PathArguments::AngleBracketed(args) =>
for p in &args.args {
let ty = match p {
syn::GenericArgument::Type(ty) => types_to_ts(ty),
syn::GenericArgument::Lifetime(_) => continue,
_ => unimplemented!(),
};
generics.push(ty);
},
&syn::PathArguments::None => {},
_ => unimplemented!(),
};
match ty.as_ref() {
"Option" =>
format!("{} | undefined | null", rs_to_ts(generics.first().unwrap().as_ref())),
_ =>
if !generics.is_empty() {
let root_ty = rs_to_ts(&ty);
let generic_str =
generics.iter().map(|g| rs_to_ts(g)).collect::<Vec<&str>>().join(", ");
format!("{}<{}>", root_ty, generic_str)
} else {
rs_to_ts(&ty).to_string()
},
}
},
_ => unimplemented!(),
}
}
#[derive(Debug)]
pub enum SerdeAttr {
RenameAll(String),
TagAndContent(String, String),
}
impl SerdeAttr {
pub fn transform(&self, s: &str) -> Option<String> {
match self {
SerdeAttr::RenameAll(t) => match t.as_ref() {
"lowercase" => Some(s.to_lowercase()),
"UPPERCASE" => Some(s.to_uppercase()),
"camelCase" => Some(s.to_camel_case()),
"snake_case" => Some(s.to_snake_case()),
"PascalCase" => Some(s.to_pascal_case()),
"SCREAMING_SNAKE_CASE" => Some(s.to_screaming_snake_case()),
_ => panic!("Invalid attribute value: {}", s),
},
_ => None,
}
}
}
pub fn get_serde_attrs(attrs: &[Attribute]) -> Vec<SerdeAttr> {
attrs
.iter()
.filter(|i| i.path.is_ident("serde"))
.flat_map(|attr| match attr.parse_meta() {
Ok(Meta::List(l)) => l.nested.iter().find_map(|meta| match meta {
NestedMeta::Meta(Meta::NameValue(v)) => match v.path.get_ident() {
Some(id) => match id.to_string().as_ref() {
"rename_all" => match &v.lit {
Lit::Str(s) => Some(SerdeAttr::RenameAll(s.value())),
_ => None,
},
"tag" => match &v.lit {
Lit::Str(s) => {
let tag = s.value();
let lit = l.nested.iter().find_map(|meta| match meta {
NestedMeta::Meta(Meta::NameValue(v)) => {
match v.path.is_ident("content") {
true => Some(&v.lit),
false => None,
}
},
_ => None,
});
match lit {
Some(Lit::Str(s)) => {
let content = s.value();
Some(SerdeAttr::TagAndContent(tag, content))
},
_ => panic!("Missing `content` attribute with `tag`."),
}
},
_ => None,
},
"content" => match &v.lit {
Lit::Str(s) => {
let content = s.value();
let lit = l.nested.iter().find_map(|meta| match meta {
NestedMeta::Meta(Meta::NameValue(v)) => {
match v.path.is_ident("tag") {
true => Some(&v.lit),
false => None,
}
},
_ => None,
});
match lit {
Some(Lit::Str(s)) => {
let tag = s.value();
Some(SerdeAttr::TagAndContent(tag, content))
},
_ => panic!("Missing `tag` attribute with `content`."),
}
},
_ => None,
},
_ => None,
},
_ => None,
},
_ => None,
}),
_ => None,
})
.collect::<Vec<_>>()
}
pub fn get_docs(attrs: &Vec<Attribute>) -> String {
let mut doc: Vec<String> = vec![];
for attr in attrs {
if let Ok(Meta::NameValue(meta)) = attr.parse_meta() {
if !meta.path.is_ident("doc") {
continue
}
if let Lit::Str(lit) = meta.lit {
doc.push(lit.value());
}
}
}
if !doc.is_empty() {
format!("/**\n *{}\n **/\n", doc.join("\n *"))
} else {
String::new()
}
}
fn rs_to_ts(ty: &str) -> &str {
match ty {
"i8" => "number",
"i16" => "number",
"i32" => "number",
"i64" => "number",
"u8" => "number",
"u16" => "number",
"u32" => "number",
"u64" => "number",
"usize" => "number",
"bool" => "boolean",
"String" => "string",
"str" => "string",
"f32" => "number",
"f64" => "number",
"HashMap" => "Record",
"Vec" => "Array",
"HashSet" => "Array",
"Value" => "any",
a => a,
}
}