rdb_pagination_derive/
lib.rs

1/*!
2# RDB Pagination Derive
3
4The provided crate offers a procedural macro for defining `OrderByOptions`. See the [`rdb-pagination`](https://crates.io/crates/rdb-pagination) crate.
5*/
6
7mod common;
8mod panic;
9
10use common::{meta_2_string, Join};
11use proc_macro::TokenStream;
12use quote::quote;
13use rdb_pagination_core::{Name, OrderBuilder, Relationship};
14use syn::{
15    parse::{Parse, ParseStream},
16    parse_macro_input,
17    punctuated::Punctuated,
18    spanned::Spanned,
19    Data, DeriveInput, Index, Meta, Token,
20};
21
22use crate::common::OrderByOption;
23
24fn derive_input_handler(ast: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
25    let mut table_name = None;
26    let mut join_list = Vec::new();
27
28    for attr in ast.attrs.iter() {
29        let path = attr.path();
30
31        if path.is_ident("orderByOptions") {
32            if let Meta::List(list) = &attr.meta {
33                let result =
34                    list.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?;
35
36                for meta in result {
37                    let path = meta.path();
38
39                    if let Some(ident) = path.get_ident() {
40                        match ident.to_string().as_str() {
41                            "name" => {
42                                if table_name.is_some() {
43                                    return Err(syn::Error::new(
44                                        ident.span(),
45                                        "`name` has been set",
46                                    ));
47                                }
48
49                                let name = meta_2_string(&meta)?;
50
51                                table_name = Some(name);
52                            },
53                            "join" => {
54                                if let Meta::List(list) = meta {
55                                    let join: Join = list.parse_args()?;
56
57                                    join_list.push(join);
58                                } else {
59                                    return Err(syn::Error::new(
60                                        ident.span(),
61                                        "`join` should be a list",
62                                    ));
63                                }
64                            },
65                            _ => {
66                                return Err(panic::sub_attributes_for_item(path.span()));
67                            },
68                        }
69                    } else {
70                        return Err(panic::sub_attributes_for_item(path.span()));
71                    }
72                }
73            } else {
74                return Err(syn::Error::new(
75                    path.span(),
76                    "the `orderByOptions` attribute should be a list",
77                ));
78            }
79        }
80    }
81
82    let mut token_stream = proc_macro2::TokenStream::new();
83
84    if let Some(table_name) = table_name {
85        let mut relationship = Relationship::new(Name::Dynamic(table_name.clone()));
86
87        for join in join_list.iter() {
88            if let Err(error) = relationship.join_check(
89                join.foreign.clone(),
90                join.primary.clone(),
91                join.real_table_name.clone(),
92            ) {
93                return Err(syn::Error::new(join.span, error));
94            }
95        }
96
97        if let Data::Struct(data) = ast.data {
98            let mut options = Vec::with_capacity(data.fields.len());
99
100            {
101                let mut order_builder: OrderBuilder<i16> =
102                    OrderBuilder::new(relationship, data.fields.len());
103
104                for (index, field) in data.fields.iter().enumerate() {
105                    let mut has_option = false;
106
107                    for attr in field.attrs.iter() {
108                        let path = attr.path();
109
110                        if path.is_ident("orderByOptions") {
111                            if has_option {
112                                return Err(syn::Error::new(
113                                    path.span(),
114                                    "`orderByOptions` has been set",
115                                ));
116                            }
117
118                            let order_by_option: OrderByOption = attr.parse_args()?;
119
120                            if let Err(error) = order_builder.add_order_option_check(
121                                order_by_option.table_column.clone(),
122                                order_by_option.unique,
123                            ) {
124                                return Err(syn::Error::new(order_by_option.span, error));
125                            }
126
127                            has_option = true;
128
129                            options.push((index, field, order_by_option));
130                        }
131                    }
132                }
133            }
134            // Get the identifier of the type.
135            let name = &ast.ident;
136
137            let options_len = options.len();
138
139            if options_len == 0 {
140                token_stream.extend(quote! {
141                    impl OrderByOptions for #name {}
142                });
143            } else {
144                let mut join_impl = proc_macro2::TokenStream::new();
145
146                for join in join_list {
147                    let foreign_table_name = join.foreign.0.as_ref();
148                    let foreign_column_name = join.foreign.1.as_ref();
149                    let primary_table_name = join.primary.0.as_ref();
150                    let primary_column_name = join.primary.1.as_ref();
151
152                    let real_table_name = if let Some(real_table_name) = join.real_table_name {
153                        let real_table_name = real_table_name.as_ref();
154
155                        quote!(Some(rdb_pagination_prelude::Name::Static(#real_table_name)))
156                    } else {
157                        quote!(None)
158                    };
159
160                    join_impl.extend(quote! {
161                        relationship.join(
162                            (rdb_pagination_prelude::Name::Static(#foreign_table_name), rdb_pagination_prelude::Name::Static(#foreign_column_name)),
163                            (rdb_pagination_prelude::Name::Static(#primary_table_name), rdb_pagination_prelude::Name::Static(#primary_column_name)),
164                            #real_table_name
165                        );
166                    });
167                }
168
169                let mut options_impl = proc_macro2::TokenStream::new();
170
171                for (index, field, option) in options {
172                    let table_name = option.table_column.0.as_ref();
173                    let column_name = option.table_column.1.as_ref();
174                    let unique = option.unique;
175
176                    let null_strategy =
177                        if let Some(nulls_first_or_last) = option.nulls_first_or_last {
178                            if nulls_first_or_last {
179                                quote!(rdb_pagination_prelude::NullStrategy::First)
180                            } else {
181                                quote!(rdb_pagination_prelude::NullStrategy::Last)
182                            }
183                        } else {
184                            quote!(rdb_pagination_prelude::NullStrategy::Default)
185                        };
186
187                    let order_method = if let Some(ident) = &field.ident {
188                        quote!(self.#ident)
189                    } else {
190                        let index = Index::from(index);
191
192                        quote!(self.#index)
193                    };
194
195                    options_impl.extend(quote! {
196                        order_builder.add_order_option(
197                            (rdb_pagination_prelude::Name::Static(#table_name), rdb_pagination_prelude::Name::Static(#column_name)),
198                            #unique,
199                            #null_strategy,
200                            #order_method,
201                        );
202                    });
203                }
204
205                let order_by_options_impl = quote! {
206                    impl OrderByOptions for #name {
207                        fn to_sql(&self) -> (::std::vec::Vec<rdb_pagination_prelude::SqlJoin>, ::std::vec::Vec<rdb_pagination_prelude::SqlOrderByComponent>) {
208                            let mut relationship = rdb_pagination_prelude::Relationship::new(rdb_pagination_prelude::Name::Static(#table_name));
209
210                            #join_impl
211
212                            let mut order_builder = rdb_pagination_prelude::OrderBuilder::new(relationship, #options_len);
213
214                            #options_impl
215
216                            order_builder.build()
217                        }
218                    }
219                };
220
221                token_stream.extend(order_by_options_impl);
222            }
223        } else {
224            return Err(syn::Error::new(
225                ast.ident.span(),
226                "should use a struct to implement `OrderByOptions`",
227            ));
228        }
229    }
230
231    Ok(token_stream)
232}
233
234#[proc_macro_derive(OrderByOptions, attributes(orderByOptions))]
235pub fn order_by_options_derive(input: TokenStream) -> TokenStream {
236    struct MyDeriveInput(proc_macro2::TokenStream);
237
238    impl Parse for MyDeriveInput {
239        #[inline]
240        fn parse(input: ParseStream) -> syn::Result<Self> {
241            let token_stream = derive_input_handler(input.parse::<DeriveInput>()?)?;
242
243            Ok(Self(token_stream))
244        }
245    }
246
247    // Parse the token stream
248    let derive_input = parse_macro_input!(input as MyDeriveInput);
249
250    derive_input.0.into()
251}