1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
#![recursion_limit = "128"] extern crate proc_macro; #[macro_use] extern crate quote; extern crate proc_macro2; extern crate regex; extern crate syn; use proc_macro::TokenStream; use proc_macro2::Span; use regex::Regex; use syn::{DeriveInput, Field, Ident}; #[proc_macro_attribute] pub fn findable_by(args: TokenStream, input: TokenStream) -> TokenStream { let mut string_input = input.to_string(); let string_args = args.to_string(); let ast: DeriveInput = syn::parse(input).unwrap(); let fields: Vec<Field> = match ast.data { syn::Data::Enum(..) => panic!("#[findable_by] cannot be used with enums"), syn::Data::Union(..) => panic!("#[findable_by] cannot be used with unions"), syn::Data::Struct(ref body) => body.fields.iter().map(|f| f.clone()).collect(), }; let struct_attributes = string_args.replace(" ", "").replace("\"", ""); let struct_attributes: Vec<&str> = struct_attributes.split(",").collect(); let struct_name = ast.ident; let re = Regex::new(r###"#\[table_name = "(.*)"\]"###).unwrap(); for struct_attribute in struct_attributes { let func = gen_find_by_func( &struct_name.to_string(), &string_input.clone(), struct_attribute, &fields, ); string_input.push_str(&func); } re.replace_all(&string_input, "").parse().unwrap() } fn gen_find_by_func( struct_name: &str, string_input: &str, struct_attribute: &str, fields: &Vec<Field>, ) -> String { let field: Vec<&Field> = fields .iter() .filter(|f| f.ident.clone().unwrap().to_string() == struct_attribute) .collect(); if field.len() > 0 { let field = field[0]; let attr_type = &field.ty; let struct_name = Ident::new(&format!("{}", struct_name), Span::call_site()); let func_name = Ident::new(&format!("find_by_{}", struct_attribute), Span::call_site()); let all_func_name = Ident::new( &format!("find_all_by_{}", struct_attribute), Span::call_site(), ); let struct_attribute = Ident::new(&struct_attribute, Span::call_site()); let struct_attribute_col = Ident::new(&format!("{}_col", struct_attribute), Span::call_site()); let table_name = Ident::new( &get_table_name(string_input.to_string().clone()), Span::call_site(), ); let func = quote! { impl #struct_name { pub fn #func_name(attr: & #attr_type, conn: &PgConnection) -> Option<#struct_name> { use crate::schema::#table_name::dsl::#struct_attribute as #struct_attribute_col; match #table_name::table.filter(#struct_attribute_col.eq(attr)).first(conn) { Ok(res) => Some(res), Err(_) => None, } } pub fn #all_func_name(attr: & #attr_type, conn: &PgConnection) -> Result<Vec<#struct_name>, ::diesel::result::Error> { use crate::schema::#table_name::dsl::#struct_attribute as #struct_attribute_col; #table_name::table.filter(#struct_attribute_col.eq(attr)).get_results(conn) } } }; func.to_string() } else { panic!( "Attribute {} not found in {}", struct_attribute, struct_name ); } } fn get_table_name(input: String) -> String { let re = Regex::new(r###"#\[table_name = "(.*)"\]"###).unwrap(); let table_name_attr = input .lines() .skip_while(|line| !line.trim_start().starts_with("#[table_name =")) .next() .expect("Struct must be annotated with #[table_name = \"...\"]"); if let Some(table_name) = re.captures(table_name_attr).unwrap().get(1) { table_name.as_str().to_string() } else { panic!("Malformed table_name attribute"); } }