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 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
#![warn(rust_2018_idioms)] #[derive(Clone, Debug)] struct Params { internal: bool, } impl Default for Params { fn default() -> Self { Self { internal: false, } } } impl syn::parse::Parse for Params { fn parse(input: syn::parse::ParseStream<'_>) -> syn::parse::Result<Self> { let content; syn::parenthesized!(content in input); let internal = match content.parse::<syn::Ident>() { Ok(internal) => internal == "internal", Err(_) => false, }; Ok(Params { internal, }) } } #[proc_macro_derive(Entity, attributes(entity))] pub fn entity_derive( input: proc_macro::TokenStream, ) -> proc_macro::TokenStream { let ast = syn::parse(input).unwrap(); impl_entity_macro(&ast) } fn impl_entity_macro(ast: &syn::DeriveInput) -> proc_macro::TokenStream { let attribute = ast.attrs.iter().find(|a| { a.path.segments.len() == 1 && a.path.segments[0].ident == "entity" }); let parameters = match attribute { Some(attribute) => { syn::parse2(attribute.tokens.clone()) .expect("Invalid entity attribute!") }, None => Params::default(), }; let fields = match ast.data { syn::Data::Struct(ref s) => &s.fields, _ => unimplemented!(), }; let from_body = fields.iter().map(|field| { let name = &field.ident; quote::quote! { #name: tuple.get(stringify!(#name)) } }); let get_body = fields.iter().map(|field| { let name = &field.ident; let ty = &field.ty; if is_option(ty) { quote::quote! { stringify!(#name) => match self.#name { Some(ref value) => Some(value), None => None, } } } else { quote::quote! { stringify!(#name) => Some(&self.#name) } } }); let name = &ast.ident; let elephantry = if parameters.internal { quote::quote! { crate } } else { quote::quote! { elephantry } }; let gen = quote::quote! { impl #elephantry::Entity for #name { fn from(tuple: &#elephantry::Tuple<'_>) -> Self { Self { #(#from_body, )* } } fn get(&self, field: &str) -> Option<&dyn #elephantry::ToSql> { match field { #(#get_body, )* _ => None, } } } }; gen.into() } fn is_option(ty: &syn::Type) -> bool { let typepath = match ty { syn::Type::Path(typepath) => typepath, _ => unimplemented!(), }; typepath.path.leading_colon.is_none() && typepath.path.segments.len() == 1 && typepath.path.segments.iter().next().unwrap().ident == "Option" }