database.rs 2.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
  1. use convert_case::{Case, Casing};
  2. use quote::{format_ident, quote};
  3. fn type_to_expression_context_type(ty: &syn::Type) -> proc_macro2::TokenStream {
  4. fn handle_path_segment(seg: &syn::PathSegment) -> proc_macro2::TokenStream {
  5. let ident = &seg.ident;
  6. let args = &seg.arguments;
  7. match seg.arguments.is_empty() {
  8. true => quote! { #ident },
  9. false => quote! { #ident :: #args },
  10. }
  11. }
  12. match ty {
  13. syn::Type::Path(path) => {
  14. let new_segments = path.path.segments.iter().map(handle_path_segment);
  15. quote! {
  16. #(#new_segments)::*
  17. }
  18. }
  19. _ => todo!(),
  20. }
  21. }
  22. pub fn derive(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
  23. let input: syn::DeriveInput = syn::parse_macro_input!(tokens);
  24. let items = if let syn::Data::Struct(syn::DataStruct {
  25. struct_token: _,
  26. fields: syn::Fields::Named(fields),
  27. semi_token: _,
  28. }) = input.data
  29. {
  30. fields
  31. .named
  32. .into_iter()
  33. .map(|f| (f.ident.unwrap(), f.ty))
  34. .collect::<Vec<_>>()
  35. } else {
  36. panic!("Can only derive Database on data structs with named fields!");
  37. };
  38. let db_ident = input.ident;
  39. let visit_items = items.iter().map(|field| {
  40. let item_combined_name = format_ident!(
  41. "{}{}ItemType",
  42. db_ident,
  43. field.0.to_string().to_case(Case::UpperCamel)
  44. );
  45. let item_base_name = &field.0.to_string();
  46. let item_type = &field.1;
  47. quote! {
  48. struct #item_combined_name;
  49. impl ::microrm::schema::DatabaseItem for #item_combined_name {
  50. fn item_key() -> &'static str { #item_base_name }
  51. fn dependency_keys() -> &'static [&'static str] { &[] } // TODO
  52. fn accept_entity_visitor(visitor: &mut impl ::microrm::schema::entity::EntityVisitor) {
  53. <#item_type as ::microrm::schema::DatabaseSpec>::accept_entity_visitor(visitor);
  54. }
  55. }
  56. v.visit::<#item_combined_name>();
  57. }
  58. });
  59. let build_method = items.iter().map(|field| {
  60. let item_name = &field.0;
  61. let item_type = type_to_expression_context_type(&field.1);
  62. quote! {
  63. #item_name : #item_type :: build(conn.clone())
  64. }
  65. });
  66. quote! {
  67. impl ::microrm::schema::Database for #db_ident {
  68. fn build(conn: ::microrm::db::Connection) -> Self where Self: Sized {
  69. Self { #(#build_method),* }
  70. }
  71. fn accept_item_visitor(v: &mut impl ::microrm::schema::DatabaseItemVisitor) {
  72. #(#visit_items)*
  73. }
  74. }
  75. }
  76. .into()
  77. }