1#![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#[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}