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 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
use proc_macro2::{Ident, Literal, TokenStream}; use quote::{quote, ToTokens}; use syn::{DeriveInput, Lit, Meta, MetaList, NestedMeta}; enum AttributeType { Table, Pk, Unknown, } struct PkItem { column_name: Literal, field_name: Ident, } impl ToTokens for PkItem { fn to_tokens(&self, tokens: &mut TokenStream) { let (col_name, field_name) = (&self.column_name, &self.field_name); tokens.extend(quote! { (#col_name, &self.#field_name) }); } } struct DbDataAttributes { table: Option<String>, pk: Option<Vec<PkItem>>, } impl From<&Ident> for AttributeType { fn from(ident: &Ident) -> Self { let str = format!("{}", ident); match str.as_str() { "orma_table" => AttributeType::Table, "orma_pk" => AttributeType::Pk, _ => AttributeType::Unknown, } } } pub fn impl_dbdata_macro(ast: &DeriveInput) -> TokenStream { let name = &ast.ident; let attrs = field_attrs(ast); let table = attrs.table.unwrap_or_else(|| format!("{}", name)); let pk = attrs.pk.unwrap_or_else(|| vec![]); let gen = quote! { impl orma::DbData for #name { fn table_name() -> &'static str { #table } fn pk_filter(&self) -> Vec<(&str, &(dyn orma::ToSql + Sync))> { vec![#(#pk),*] } } }; gen } impl DbDataAttributes { fn default() -> Self { Self { table: None, pk: None, } } } fn attribute_string_val(meta_list: &MetaList) -> String { let name_token = meta_list.nested.first().unwrap(); match name_token { NestedMeta::Lit(lit) => { if let Lit::Str(lit_str) = lit { lit_str.value() } else { panic!("table attribute should be a string") } } _ => panic!("no value defined for macro attr"), } } fn lit_string(lit: &Lit) -> String { if let Lit::Str(lit_str) = lit { lit_str.value() } else { panic!("Invalid attr syntax {:?}", lit) } } fn kv_pk(kv_nested_meta: &NestedMeta) -> PkItem { match kv_nested_meta { NestedMeta::Meta(meta) => match meta { Meta::NameValue(name_value) => PkItem { field_name: name_value.path.get_ident().unwrap().clone(), column_name: Literal::string(&lit_string(&name_value.lit)), }, _ => panic!("Invalid attr syntax {:?}", kv_nested_meta), }, _ => panic!("Invalid attr syntax {:?}", kv_nested_meta), } } fn attribute_pk(meta_list: &MetaList) -> Vec<PkItem> { meta_list .nested .iter() .map(|kv_nested_meta| kv_pk(kv_nested_meta)) .collect() } fn field_attrs(ast: &DeriveInput) -> DbDataAttributes { let mut ctx = DbDataAttributes::default(); for attr in &ast.attrs { let attr: Meta = attr.parse_meta().unwrap(); if let Meta::List(meta_list) = attr { let attr_type = AttributeType::from(meta_list.path.get_ident().unwrap()); match attr_type { AttributeType::Table => ctx.table = Some(attribute_string_val(&meta_list)), AttributeType::Pk => ctx.pk = Some(attribute_pk(&meta_list)), _ => (), } }; } ctx } #[cfg(test)] mod tests { use super::*; use proc_macro2::Span; use std::str::FromStr; #[test] fn test_pk_to_token() { let pk = PkItem { column_name: Literal::string("column_name"), field_name: Ident::new("field_name", Span::call_site()), }; let expected = TokenStream::from_str("(\"column_name\", &self.field_name)").unwrap(); assert_eq!(format!("{}", expected), format!("{}", pk.to_token_stream())); } }