123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210 |
- use proc_macro::TokenStream;
- use quote::{format_ident, quote};
- use syn::{parse_macro_input, DeriveInput};
- use convert_case::{Case, Casing};
- fn parse_microrm_ref(attrs: &[syn::Attribute]) -> proc_macro2::TokenStream {
- for attr in attrs {
- if attr.path.segments.is_empty() {
- continue;
- }
- if attr.tokens.is_empty() && attr.path.segments.last().unwrap().ident == "microrm_internal"
- {
- return quote! { crate };
- }
- }
- quote! { ::microrm }
- }
- fn parse_fk(attrs: &[syn::Attribute]) -> bool {
- for attr in attrs {
- if attr.path.segments.len() == 1 && attr.path.segments.last().unwrap().ident == "microrm_foreign" {
- return true
- }
- }
- false
- }
- /// Turns a serializable/deserializable struct into a microrm entity model.
- ///
- /// There are two important visible effects:
- /// - Provides an implementation of `microrm::model::Entity`
- /// - Defines a <struct-name>Columns enum
- ///
- /// Note that names are converted from CamelCase to snake_case and vice versa
- /// where applicable, so a struct named `TestModel` is given a table name `test_model`
- /// and a struct field named `field_name` is given a variant name of `FieldName`.
- ///
- /// The `#[microrm...]` attributes can be used to control the derivation somewhat.
- /// The following are understood for the Entity struct:
- /// - `#[microrm_internal]`: this is internal to the microrm crate (of extremely limited usefulness
- /// outside the microrm library)
- /// The following are understood on individual fields
- /// - `#[microrm_foreign]`: this is a foreign key (and the field must be of a type implementing `EntityID`)
- #[proc_macro_derive(Entity, attributes(microrm_internal, microrm_foreign))]
- pub fn derive_entity(tokens: TokenStream) -> TokenStream {
- let input = parse_macro_input!(tokens as DeriveInput);
- let microrm_ref = parse_microrm_ref(&input.attrs);
- let struct_name = &input.ident;
- let enum_name = format_ident!("{}Columns", &input.ident);
- let id_name = format_ident!("{}ID", &input.ident);
- let table_name = format!("{}", struct_name).to_case(Case::Snake);
- let st = match input.data {
- syn::Data::Struct(st) => st,
- _ => panic!("Can only use derive(Entity) on structs!"),
- };
- let fields = match st.fields {
- syn::Fields::Named(fields) => fields,
- _ => panic!("Can only use derive(Entity) on non-unit structs with named fields!"),
- };
- let mut variants = Vec::new();
- let mut field_names = Vec::new();
- let mut field_numbers = Vec::new();
- let mut value_references = Vec::new();
- let mut foreign_keys = Vec::new();
- let mut foreign_key_impls = Vec::new();
- for name in fields.named.iter() {
- let converted_case =
- format!("{}", name.ident.as_ref().unwrap().clone()).to_case(Case::UpperCamel);
- let converted_case = format_ident!("{}", converted_case);
- variants.push(converted_case.clone());
- let field_name = name.ident.as_ref().unwrap().clone();
- let field_name_str = format!("{}", field_name);
- field_names.push(quote! { Self::Column::#converted_case => #field_name_str });
- let nn = field_numbers.len() + 1;
- field_numbers.push(quote! { #nn => Self::#converted_case, });
- if parse_fk(&name.attrs) {
- let fk_struct_name = format_ident!("{}{}ForeignKey", struct_name, converted_case);
- let ty = &name.ty;
- foreign_keys.push(quote!{
- &#fk_struct_name { col: #enum_name::#converted_case }
- });
- foreign_key_impls.push(quote!{
- struct #fk_struct_name {
- col: #enum_name
- }
- impl #microrm_ref::model::EntityForeignKey<#enum_name> for #fk_struct_name {
- fn local_column(&self) -> &#enum_name { &self.col }
- fn foreign_table_name(&self) -> &'static str {
- <<#ty as #microrm_ref::model::EntityID>::Entity as #microrm_ref::model::Entity>::table_name()
- }
- fn foreign_column_name(&self) -> &'static str {
- "id"
- }
- }
- });
- }
- value_references.push(quote! { &self. #field_name });
- }
- let field_count = fields.named.iter().count();
- quote!{
- // Related types for #struct_name
- #[derive(Clone,Copy,PartialEq)]
- #[allow(unused)]
- #[repr(usize)]
- pub enum #enum_name {
- ID,
- #(#variants),*
- }
- #[derive(Debug,PartialEq,Clone,Copy,#microrm_ref::re_export::serde::Serialize,#microrm_ref::re_export::serde::Deserialize)]
- #[allow(unused)]
- pub struct #id_name (i64);
- // Implementations for related types
- impl #microrm_ref::model::EntityColumns for #enum_name {
- type Entity = #struct_name;
- }
- impl std::convert::From<usize> for #enum_name {
- fn from(i: usize) -> Self {
- match i {
- 0 => Self::ID,
- #(#field_numbers)*
- _ => {
- panic!("Given invalid usize to convert to column")
- },
- }
- }
- }
- impl #microrm_ref::model::EntityID for #id_name {
- type Entity = #struct_name;
- fn from_raw_id(raw: i64) -> Self { Self(raw) }
- fn raw_id(&self) -> i64 { self.0 }
- }
- impl #microrm_ref::re_export::rusqlite::ToSql for #id_name {
- fn to_sql(&self) -> #microrm_ref::re_export::rusqlite::Result<#microrm_ref::re_export::rusqlite::types::ToSqlOutput<'_>> {
- self.0.to_sql()
- }
- }
- // Implementations for #struct_name
- impl #microrm_ref::model::Entity for #struct_name {
- type Column = #enum_name;
- type ID = #id_name;
- fn table_name() -> &'static str { #table_name }
- fn column_count() -> usize {
- // +1 for ID column
- #field_count + 1
- }
- fn index(c: Self::Column) -> usize {
- c as usize
- }
- fn name(c: Self::Column) -> &'static str {
- match c {
- Self::Column::ID => "ID",
- #(#field_names),*
- }
- }
- fn values(&self) -> Vec<&dyn #microrm_ref::re_export::rusqlite::ToSql> {
- vec![ #(#value_references),* ]
- }
- fn foreign_keys() -> &'static [&'static dyn #microrm_ref::model::EntityForeignKey<Self::Column>] {
- &[#(#foreign_keys),*]
- }
- }
- // Foreign key struct implementations
- #(#foreign_key_impls)*
- }.into()
- }
- /// Marks a struct as able to be directly used in an Entity to correspond to a single database column.
- #[proc_macro_derive(Modelable, attributes(microrm_internal))]
- pub fn derive_modelable(tokens: TokenStream) -> TokenStream {
- let input = parse_macro_input!(tokens as DeriveInput);
- let microrm_ref = parse_microrm_ref(&input.attrs);
- let ident = input.ident;
- quote!{
- impl #microrm_ref::re_export::rusqlite::ToSql for #ident {
- fn to_sql(&self) -> #microrm_ref::re_export::rusqlite::Result<#microrm_ref::re_export::rusqlite::types::ToSqlOutput<'_>> {
- use #microrm_ref::re_export::rusqlite::types::{ToSqlOutput,Value};
- Ok(ToSqlOutput::Owned(Value::Text(#microrm_ref::re_export::serde_json::to_string(self).expect("can be serialized"))))
- }
- }
- }.into()
- }
|