123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297 |
- use convert_case::{Case, Casing};
- use quote::{format_ident, quote};
- fn extract_doc_comment(attrs: &[syn::Attribute]) -> proc_macro2::TokenStream {
- attrs
- .iter()
- .flat_map(|a| match a.parse_meta() {
- Ok(syn::Meta::NameValue(mnv)) => {
- if mnv.path.is_ident("doc") {
- if let syn::Lit::Str(ls) = mnv.lit {
- let lsv = ls.value();
- return Some(quote! { Some(#lsv) });
- }
- }
- None
- }
- _ => None,
- })
- .next()
- .unwrap_or(quote! { None })
- }
- fn is_elided(attrs: &[syn::Attribute]) -> bool {
- attrs.iter().filter(|a| a.path.is_ident("elide")).count() > 0
- }
- fn is_unique(attrs: &[syn::Attribute]) -> bool {
- attrs.iter().filter(|a| a.path.is_ident("unique")).count() > 0
- }
- fn is_key(attrs: &[syn::Attribute]) -> bool {
- attrs.iter().filter(|a| a.path.is_ident("key")).count() > 0
- }
- pub fn derive(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
- let input: syn::DeriveInput = syn::parse_macro_input!(tokens);
- let parts = match input.data {
- syn::Data::Struct(syn::DataStruct {
- struct_token: _,
- fields: syn::Fields::Named(fields),
- semi_token: _,
- }) => fields
- .named
- .into_iter()
- .map(|f| (f.ident.unwrap(), f.ty, f.attrs))
- .collect::<Vec<_>>(),
- _ => panic!("Can only derive Entity on data structs with named fields!"),
- };
- let entity_ident = input.ident;
- let make_combined_name = |part: &(syn::Ident, syn::Type, _)| {
- format_ident!(
- "{}{}PartType",
- entity_ident,
- part.0.to_string().to_case(Case::UpperCamel)
- )
- };
- let make_part_list = |plist: &Vec<_>| match plist.len() {
- 0 => quote! { microrm::schema::entity::EmptyList<Self> },
- 1 => {
- let ty = make_combined_name(plist.first().as_ref().unwrap());
- quote! { #ty }
- }
- _ => {
- let tys = plist.iter().map(make_combined_name);
- quote! { ( #(#tys),* ) }
- }
- };
- let vis = input.vis;
- // collect list of unique parts
- let key_parts = parts
- .iter()
- .filter(|part| is_key(&part.2))
- .cloned()
- .collect::<Vec<_>>();
- let part_defs = parts.iter().map(|part| {
- let part_combined_name = make_combined_name(part);
- let part_base_ident = &part.0;
- let part_base_name = &part.0.to_string();
- let part_type = &part.1;
- let placeholder = format!("${}_{}", entity_ident, part_base_name);
- let unique = is_unique(&part.2);
- let doc = extract_doc_comment(&part.2);
- quote! {
- #[derive(Clone, Copy, Default)]
- #vis struct #part_combined_name;
- impl ::microrm::schema::entity::EntityPart for #part_combined_name {
- type Datum = #part_type;
- type Entity = #entity_ident;
- fn part_name() -> &'static str {
- #part_base_name
- }
- fn placeholder() -> &'static str {
- #placeholder
- }
- fn unique() -> bool {
- #unique
- }
- fn desc() -> Option<&'static str> {
- #doc
- }
- fn get_datum(from: &Self::Entity) -> &Self::Datum {
- &from.#part_base_ident
- }
- }
- }
- });
- let part_visit = parts.iter().map(|part| {
- let part_combined_name = make_combined_name(part);
- quote! {
- v.visit::<#part_combined_name>();
- }
- });
- let part_ref_visit = parts.iter().map(|part| {
- let part_combined_name = make_combined_name(part);
- let field = &part.0;
- quote! {
- v.visit_datum::<#part_combined_name>(&self.#field);
- }
- });
- let part_mut_visit = parts.iter().map(|part| {
- let part_combined_name = make_combined_name(part);
- let field = &part.0;
- quote! {
- v.visit_datum_mut::<#part_combined_name>(&mut self.#field);
- }
- });
- let part_names = parts.iter().map(|part| {
- let part_combined_name = make_combined_name(part);
- let part_camel_name = format_ident!("{}", part.0.to_string().to_case(Case::UpperCamel));
- quote! {
- pub const #part_camel_name : #part_combined_name = #part_combined_name;
- }
- });
- let build_struct = parts
- .iter()
- .enumerate()
- .map(|(i, part)| {
- let ident = &part.0;
- match parts.len() {
- 1 => {
- quote! {
- #ident: values
- }
- }
- _ => {
- let idx = syn::Index::from(i);
- quote! {
- #ident: values. #idx
- }
- }
- }
- })
- .collect::<Vec<_>>();
- let debug_fields = parts
- .iter()
- .filter(|part| !is_elided(&part.2))
- .map(|part| {
- let ident = &part.0;
- let field = ident.to_string();
- quote! {
- self . #ident . debug_field(#field, &mut ds);
- }
- })
- .collect::<Vec<_>>();
- let parts_list = make_part_list(&parts);
- let key_list = make_part_list(&key_parts);
- let entity_ident_str = entity_ident.to_string();
- let entity_name = entity_ident.to_string().to_case(Case::Snake);
- let id_ident = format_ident!("{}ID", entity_ident);
- quote! {
- #(#part_defs)*
- impl #entity_ident {
- #(#part_names)*
- }
- #[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
- #vis struct #id_ident (i64);
- impl ::microrm::schema::entity::EntityID for #id_ident {
- type Entity = #entity_ident;
- fn from_raw(raw: i64) -> Self { Self(raw) }
- fn into_raw(self) -> i64 { self.0 }
- }
- impl ::microrm::schema::entity::EntityPart for #id_ident {
- type Datum = Self;
- type Entity = #entity_ident;
- fn unique() -> bool { false }
- fn part_name() -> &'static str { "id" }
- fn placeholder() -> &'static str { "TODO" }
- fn desc() -> Option<&'static str> { None }
- fn get_datum(from: &Self::Entity) -> &Self::Datum {
- unreachable!()
- }
- }
- impl ::microrm::schema::datum::Datum for #id_ident {
- fn sql_type() -> &'static str {
- <i64 as ::microrm::schema::datum::Datum>::sql_type()
- }
- fn bind_to<'a>(&self, stmt: &mut ::microrm::db::StatementContext, index: i32) {
- <i64 as ::microrm::schema::datum::Datum>::bind_to(&self.0, stmt, index)
- }
- fn build_from<'a>(
- adata: ::microrm::schema::AssocData,
- stmt: &mut ::microrm::db::StatementRow,
- index: &mut i32,
- ) -> ::microrm::DBResult<Self>
- where
- Self: Sized,
- {
- Ok(Self(<i64 as ::microrm::schema::datum::Datum>::build_from(adata, stmt, index)?))
- }
- fn accept_discriminator(d: &mut impl ::microrm::schema::datum::DatumDiscriminator) where Self: Sized {
- d.visit_entity_id::<#entity_ident>();
- }
- fn accept_discriminator_ref(&self, d: &mut impl ::microrm::schema::datum::DatumDiscriminatorRef) where Self: Sized {
- d.visit_entity_id::<#entity_ident>(self);
- }
- }
- impl ::microrm::schema::datum::ConcreteDatum for #id_ident {}
- impl ::std::fmt::Debug for #entity_ident {
- fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> Result<(), ::std::fmt::Error> {
- use ::microrm::schema::datum::Datum;
- let mut ds = f.debug_struct(#entity_ident_str);
- #(#debug_fields)*
- ds.finish()
- }
- }
- impl ::microrm::schema::entity::Entity for #entity_ident {
- type Parts = #parts_list;
- type Keys = #key_list;
- type ID = #id_ident;
- fn build(values: <Self::Parts as ::microrm::schema::entity::EntityPartList>::DatumList) -> Self {
- Self {
- #(#build_struct),*
- }
- }
- fn entity_name() -> &'static str { #entity_name }
- fn accept_part_visitor(v: &mut impl ::microrm::schema::entity::EntityPartVisitor) {
- #(
- #part_visit
- );*
- }
- fn accept_part_visitor_ref(&self, v: &mut impl ::microrm::schema::entity::EntityPartVisitor) {
- #(
- #part_ref_visit
- );*
- }
- fn accept_part_visitor_mut(&mut self, v: &mut impl ::microrm::schema::entity::EntityPartVisitor) {
- #(
- #part_mut_visit
- );*
- }
- }
- }
- .into()
- }
|