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
use proc_macro2::{Ident, Span}; use syn; use diagnostic_shim::*; use field::*; use meta::*; use resolved_at_shim::*; pub struct Model { pub name: syn::Ident, pub primary_key_names: Vec<syn::Ident>, table_name_from_attribute: Option<syn::Ident>, fields: Vec<Field>, } impl Model { pub fn from_item(item: &syn::DeriveInput) -> Result<Self, Diagnostic> { let table_name_from_attribute = MetaItem::with_name(&item.attrs, "table_name").map(|m| m.expect_ident_value()); let primary_key_names = MetaItem::with_name(&item.attrs, "primary_key") .map(|m| { Ok(m.nested()? .map(|m| m.expect_path().segments.first().unwrap().ident.clone()) .collect()) }) .unwrap_or_else(|| Ok(vec![Ident::new("id", Span::call_site())]))?; let fields = fields_from_item_data(&item.data)?; Ok(Self { name: item.ident.clone(), table_name_from_attribute, primary_key_names, fields, }) } pub fn table_name(&self) -> syn::Ident { self.table_name_from_attribute.clone().unwrap_or_else(|| { syn::Ident::new( &infer_table_name(&self.name.to_string()), self.name.span().resolved_at(Span::call_site()), ) }) } pub fn dummy_mod_name(&self, trait_name: &str) -> syn::Ident { let name = format!("_impl_{}_for_{}", trait_name, self.name).to_lowercase(); Ident::new(&name, Span::call_site()) } pub fn fields(&self) -> &[Field] { &self.fields } pub fn find_column(&self, column_name: &syn::Ident) -> Result<&Field, Diagnostic> { self.fields() .iter() .find(|f| &f.column_name() == column_name) .ok_or_else(|| { column_name .span() .error(format!("No field with column name {}", column_name)) }) } pub fn has_table_name_attribute(&self) -> bool { self.table_name_from_attribute.is_some() } } pub fn camel_to_snake(name: &str) -> String { let mut result = String::with_capacity(name.len()); result.push_str(&name[..1].to_lowercase()); for character in name[1..].chars() { if character.is_uppercase() { result.push('_'); for lowercase in character.to_lowercase() { result.push(lowercase); } } else { result.push(character); } } result } fn infer_table_name(name: &str) -> String { let mut result = camel_to_snake(name); result.push('s'); result } fn fields_from_item_data(data: &syn::Data) -> Result<Vec<Field>, Diagnostic> { use syn::Data::*; let struct_data = match *data { Struct(ref d) => d, _ => return Err(Span::call_site().error("This derive can only be used on structs")), }; Ok(struct_data .fields .iter() .enumerate() .map(|(i, f)| Field::from_struct_field(f, i)) .collect()) } #[test] fn infer_table_name_pluralizes_and_downcases() { assert_eq!("foos", &infer_table_name("Foo")); assert_eq!("bars", &infer_table_name("Bar")); } #[test] fn infer_table_name_properly_handles_underscores() { assert_eq!("foo_bars", &infer_table_name("FooBar")); assert_eq!("foo_bar_bazs", &infer_table_name("FooBarBaz")); }