cleu_orm_derive/
lib.rs

1//! # Cleu ORM - Derive
2
3#![allow(clippy::shadow_reuse)]
4
5mod utils;
6
7use quote::{format_ident, quote};
8use syn::{parse_macro_input, spanned::Spanned, Data, DataStruct, DeriveInput, Fields};
9use utils::*;
10
11/// Implements [cleu_orm::TableParams].
12#[proc_macro_derive(TableParams, attributes(cleu_orm))]
13pub fn table_params(ts: proc_macro::TokenStream) -> proc_macro::TokenStream {
14  let input = parse_macro_input!(ts as DeriveInput);
15  do_table_params(input).unwrap_or_else(|err| err.to_compile_error()).into()
16}
17
18fn do_table_params(input: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
19  let input_span = input.span();
20
21  let table_struct_name = input.ident.to_string();
22  let (error_ty, table_name_alias, table_name) =
23    container_attrs(&input.attrs, input_span, &table_struct_name)?;
24
25  let fields = if let Data::Struct(DataStruct { fields: Fields::Named(elem), .. }) = input.data {
26    elem.named
27  } else {
28    return Err(syn::Error::new(input_span, "Table element must be a structure with named fields"));
29  };
30
31  let associations_ty_name = format_ident!("{}ParamsAssociationsTy", table_struct_name);
32  let fields_ty_name = format_ident!("{}ParamsFieldsTy", table_struct_name);
33  let table_params_struct_name = format_ident!("{}Params", table_struct_name);
34  let table_struct_ty = input.ident;
35
36  let does_not_have_associations = fields.iter().all(|elem| elem.attrs.is_empty());
37
38  let fields_with_attrs = fields.iter().filter(|elem| !elem.attrs.is_empty());
39  let fields_without_attrs = fields.iter().filter(|elem| elem.attrs.is_empty());
40
41  let (association_exprs, association_types) = if does_not_have_associations {
42    (
43      vec![quote! { cleu_orm::NoAssociation::new() }],
44      vec![quote! { cleu_orm::NoAssociation<#error_ty> }],
45    )
46  } else {
47    let association_exprs = fields_with_attrs.clone().filter_map(|elem| {
48      let snake_case = elem.ident.as_ref().map(|i| i.to_string())?;
49      let a = if let Some("_") = snake_case.get(..1) {
50        snake_case.get(1..).unwrap_or_default()
51      } else {
52        &snake_case
53      };
54      let associated_table_params_struct_name = format_ident!("{}Params", to_camel_case(a));
55
56      let params = group_params(elem.attrs.get(0)?).ok()?;
57
58      let mut iter = params.1.into_iter();
59
60      let (from_id_ident, from_id_expr) = iter.next()?;
61      if from_id_ident != "from_id" {
62        return None;
63      }
64
65      let (to_id_ident, to_id_expr) = iter.next()?;
66      if to_id_ident != "to_id" {
67        return None;
68      }
69
70      Some(quote! {
71        (#associated_table_params_struct_name::new({
72          incrementing_suffix = incrementing_suffix.wrapping_add(1);
73          incrementing_suffix
74        }),
75        cleu_orm::Association::new(#from_id_expr, #to_id_expr))
76      })
77    });
78
79    let association_types = fields_with_attrs.clone().filter_map(|elem| {
80      let snake_case = elem.ident.as_ref().map(|i| i.to_string())?;
81      let a = if let Some("_") = snake_case.get(..1) {
82        snake_case.get(1..).unwrap_or_default()
83      } else {
84        &snake_case
85      };
86      let associated_table_params_struct_name = format_ident!("{}Params", to_camel_case(a));
87
88      Some(quote! { (#associated_table_params_struct_name, cleu_orm::Association) })
89    });
90
91    (association_exprs.collect(), association_types.collect())
92  };
93
94  let field_exprs =
95    fields_without_attrs.clone().filter_map(|elem| elem.ident.as_ref().map(|i| i.to_string()));
96  let field_types = fields_without_attrs.map(|elem| &elem.ty);
97
98  Ok(quote! {
99    #[automatically_derived]
100    type #associations_ty_name = (
101      #( #association_types, )*
102    );
103    #[automatically_derived]
104    type #fields_ty_name = (
105      #( cleu_orm::Field<#field_types>, )*
106    );
107
108    #[automatically_derived]
109    pub struct #table_params_struct_name(
110      #associations_ty_name,
111      #fields_ty_name,
112      u8
113    );
114
115    #[automatically_derived]
116    impl #table_params_struct_name {
117      #[inline]
118      pub const fn new(suffix: u8) -> Self {
119        let mut incrementing_suffix = suffix;
120        Self(
121          (
122            #( #association_exprs, )*
123          ),
124          (
125            #( cleu_orm::Field::new(#field_exprs), )*
126          ),
127          suffix
128        )
129      }
130    }
131
132    #[automatically_derived]
133    impl cleu_orm::TableParams for #table_params_struct_name {
134      type Associations = #associations_ty_name;
135      type Error = #error_ty;
136      type Fields = #fields_ty_name;
137      type Table = #table_struct_ty;
138
139      #[inline]
140      fn associations(&self) -> &Self::Associations {
141        &self.0
142      }
143
144      #[inline]
145      fn fields(&self) -> &Self::Fields {
146        &self.1
147      }
148
149      #[inline]
150      fn id_field(&self) -> &str {
151        self.1.0.name()
152      }
153
154      #[inline]
155      fn suffix(&self) -> u8 {
156        self.2
157      }
158
159      #[inline]
160      fn table_name() -> &'static str {
161        #table_name
162      }
163
164      #[inline]
165      fn table_name_alias() -> Option<&'static str> {
166        #table_name_alias
167      }
168    }
169  })
170}