use syn::{DeriveInput, ItemTrait, Data, Fields, Type};
use std::fs::OpenOptions;
use std::io::Write;
fn psl_file() -> std::fs::File {
OpenOptions::new()
.create(true)
.append(true)
.open("schema.psl")
.unwrap()
}
fn convert_type(ty: &Type) -> String {
match ty {
Type::Path(p) => {
let last = p.path.segments.last().unwrap();
let ident = last.ident.to_string();
if ident == "Option" {
if let syn::PathArguments::AngleBracketed(args) = &last.arguments {
if let Some(syn::GenericArgument::Type(inner)) = args.args.first() {
return format!("{}?", convert_type(inner));
}
}
} else if ident == "Vec" {
if let syn::PathArguments::AngleBracketed(args) = &last.arguments {
if let Some(syn::GenericArgument::Type(inner)) = args.args.first() {
let inner_ty = convert_type(inner);
if inner_ty == "u8" {
return "bytes".to_string();
}
return format!("{}[]", inner_ty);
}
}
} else if ident == "HashMap" || ident == "BTreeMap" {
if let syn::PathArguments::AngleBracketed(args) = &last.arguments {
let mut iter = args.args.iter();
let key_ty = if let Some(syn::GenericArgument::Type(t)) = iter.next() {
convert_type(t)
} else {
"any".to_string()
};
let val_ty = if let Some(syn::GenericArgument::Type(t)) = iter.next() {
convert_type(t)
} else {
"any".to_string()
};
return format!("map<{}, {}>", key_ty, val_ty);
}
} else if let syn::PathArguments::AngleBracketed(args) = &last.arguments {
let inner_types: Vec<String> = args.args.iter()
.filter_map(|a| {
if let syn::GenericArgument::Type(t) = a {
Some(convert_type(t))
} else {
None
}
})
.collect();
if !inner_types.is_empty() {
return format!("{}<{}>", ident, inner_types.join(", "));
}
}
match ident.as_str() {
"String" => "string".to_string(),
"i32" | "i64" | "u32" | "u64" => "int".to_string(),
"f32" | "f64" => "float".to_string(),
"bool" => "boolean".to_string(),
"SystemTime" => "timestamp".to_string(),
"u8" => "u8".to_string(),
other => other.to_string(),
}
},
_ => "any".to_string(),
}
}
pub fn write_psl_type(input: &DeriveInput) {
let mut file = psl_file();
let name = &input.ident;
let mut decorators = Vec::new();
for attr in &input.attrs {
if attr.path.is_ident("collection") {
decorators.push("@collection");
}
}
for dec in decorators {
writeln!(file, "{}", dec).unwrap();
}
writeln!(file, "message {} {{", name).unwrap();
if let Data::Struct(data) = &input.data {
if let Fields::Named(fields) = &data.fields {
for f in &fields.named {
let fname = f.ident.as_ref().unwrap();
let ftype = convert_type(&f.ty);
writeln!(file, " {}: {};", fname, ftype).unwrap();
}
}
}
writeln!(file, "}}\n").unwrap();
}
pub fn write_psl_enum(input: &DeriveInput) {
let mut file = psl_file();
let name = &input.ident;
writeln!(file, "enum {} {{", name).unwrap();
if let Data::Enum(data) = &input.data {
for variant in &data.variants {
let vname = &variant.ident;
match &variant.fields {
Fields::Unit => {
writeln!(file, " {},", vname).unwrap();
}
Fields::Named(fields) => {
writeln!(file, " {} {{", vname).unwrap();
for f in &fields.named {
let fname = f.ident.as_ref().unwrap();
let ftype = convert_type(&f.ty);
writeln!(file, " {}: {};", fname, ftype).unwrap();
}
writeln!(file, " }},").unwrap();
}
Fields::Unnamed(fields) => {
if fields.unnamed.len() == 1 {
let ftype = convert_type(&fields.unnamed.first().unwrap().ty);
writeln!(file, " {}({}),", vname, ftype).unwrap();
}
}
}
}
}
writeln!(file, "}}\n").unwrap();
}
pub fn write_psl_error(input: &DeriveInput) {
let mut file = psl_file();
let name = &input.ident;
writeln!(file, "error {} {{", name).unwrap();
if let Data::Enum(data) = &input.data {
for variant in &data.variants {
let vname = &variant.ident;
match &variant.fields {
Fields::Unit => {
writeln!(file, " {},", vname).unwrap();
}
Fields::Named(fields) => {
writeln!(file, " {} {{", vname).unwrap();
for f in &fields.named {
let fname = f.ident.as_ref().unwrap();
let ftype = convert_type(&f.ty);
writeln!(file, " {}: {};", fname, ftype).unwrap();
}
writeln!(file, " }},").unwrap();
}
Fields::Unnamed(fields) => {
if fields.unnamed.len() == 1 {
let ftype = convert_type(&fields.unnamed.first().unwrap().ty);
writeln!(file, " {}({}),", vname, ftype).unwrap();
}
}
}
}
}
writeln!(file, "}}\n").unwrap();
}
pub fn write_psl_service(input: &ItemTrait) {
let mut file = psl_file();
let name = &input.ident;
writeln!(file, "service {} {{", name).unwrap();
for item in &input.items {
if let syn::TraitItem::Method(method) = item {
let mname = &method.sig.ident;
for attr in &method.attrs {
if attr.path.is_ident("auth") {
if let Ok(syn::Meta::List(meta_list)) = attr.parse_meta() {
if let Some(syn::NestedMeta::Lit(syn::Lit::Str(lit))) = meta_list.nested.first() {
writeln!(file, " @auth(\"{}\")", lit.value()).unwrap();
}
}
}
}
let mut args = Vec::new();
for arg in &method.sig.inputs {
if let syn::FnArg::Typed(pat_type) = arg {
if let syn::Pat::Ident(pat_ident) = pat_type.pat.as_ref() {
let arg_name = pat_ident.ident.to_string();
let arg_type = convert_type(&pat_type.ty);
args.push(format!("{}: {}", arg_name, arg_type));
}
}
}
let args_str = args.join(", ");
let ret_str = match &method.sig.output {
syn::ReturnType::Default => String::new(),
syn::ReturnType::Type(_, ty) => {
if let Type::Path(p) = ty.as_ref() {
let last = p.path.segments.last().unwrap();
if last.ident == "Result" {
if let syn::PathArguments::AngleBracketed(args) = &last.arguments {
let mut iter = args.args.iter();
let ok_type = if let Some(syn::GenericArgument::Type(t)) = iter.next() {
convert_type(t)
} else {
"void".to_string()
};
let err_type = if let Some(syn::GenericArgument::Type(t)) = iter.next() {
convert_type(t)
} else {
String::new()
};
if err_type.is_empty() {
format!(" -> {}", ok_type)
} else {
format!(" -> {} ! {}", ok_type, err_type)
}
} else {
String::new()
}
} else {
format!(" -> {}", convert_type(ty))
}
} else {
format!(" -> {}", convert_type(ty))
}
}
};
writeln!(file, " rpc {}({}){};", mname, args_str, ret_str).unwrap();
}
}
writeln!(file, "}}\n").unwrap();
}