meilisearch_index_setting_macro/
lib.rs1use convert_case::{Case, Casing};
2use proc_macro2::Ident;
3use quote::quote;
4use structmeta::{Flag, StructMeta};
5use syn::{parse_macro_input, spanned::Spanned};
6
7#[derive(Clone, StructMeta, Default)]
8struct FieldAttrs {
9 primary_key: Flag,
10 displayed: Flag,
11 searchable: Flag,
12 distinct: Flag,
13 filterable: Flag,
14 sortable: Flag,
15}
16
17#[proc_macro_derive(IndexConfig, attributes(index_config))]
18pub fn generate_index_settings(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
19 let ast = parse_macro_input!(input as syn::DeriveInput);
20
21 let fields: &syn::Fields = match ast.data {
22 syn::Data::Struct(ref data) => &data.fields,
23 _ => {
24 return proc_macro::TokenStream::from(
25 syn::Error::new(ast.ident.span(), "Applicable only to struct").to_compile_error(),
26 );
27 }
28 };
29
30 let struct_ident = &ast.ident;
31
32 let index_config_implementation = get_index_config_implementation(struct_ident, fields);
33 proc_macro::TokenStream::from(quote! {
34 #index_config_implementation
35 })
36}
37
38fn get_index_config_implementation(
39 struct_ident: &Ident,
40 fields: &syn::Fields,
41) -> proc_macro2::TokenStream {
42 let mut primary_key_attribute = String::new();
43 let mut distinct_key_attribute = String::new();
44 let mut displayed_attributes = vec![];
45 let mut searchable_attributes = vec![];
46 let mut filterable_attributes = vec![];
47 let mut sortable_attributes = vec![];
48
49 let index_name = struct_ident
50 .to_string()
51 .from_case(Case::UpperCamel)
52 .to_case(Case::Snake);
53
54 let mut primary_key_found = false;
55 let mut distinct_found = false;
56
57 for field in fields {
58 let attrs = field
59 .attrs
60 .iter()
61 .filter(|attr| attr.path().is_ident("index_config"))
62 .map(|attr| attr.parse_args::<FieldAttrs>().unwrap())
63 .collect::<Vec<_>>()
64 .first()
65 .cloned()
66 .unwrap_or_default();
67
68 if attrs.primary_key.value() {
70 if primary_key_found {
71 return syn::Error::new(
72 field.span(),
73 "Only one field can be marked as primary key",
74 )
75 .to_compile_error();
76 }
77 primary_key_attribute = field.ident.clone().unwrap().to_string();
78 primary_key_found = true;
79 }
80
81 if attrs.distinct.value() {
83 if distinct_found {
84 return syn::Error::new(field.span(), "Only one field can be marked as distinct")
85 .to_compile_error();
86 }
87 distinct_key_attribute = field.ident.clone().unwrap().to_string();
88 distinct_found = true;
89 }
90
91 if attrs.displayed.value() {
92 displayed_attributes.push(field.ident.clone().unwrap().to_string());
93 }
94
95 if attrs.searchable.value() {
96 searchable_attributes.push(field.ident.clone().unwrap().to_string());
97 }
98
99 if attrs.filterable.value() {
100 filterable_attributes.push(field.ident.clone().unwrap().to_string());
101 }
102
103 if attrs.sortable.value() {
104 sortable_attributes.push(field.ident.clone().unwrap().to_string());
105 }
106 }
107
108 let primary_key_token: proc_macro2::TokenStream = if primary_key_attribute.is_empty() {
109 quote! {
110 ::std::option::Option::None
111 }
112 } else {
113 quote! {
114 ::std::option::Option::Some(#primary_key_attribute)
115 }
116 };
117
118 let display_attr_tokens =
119 get_settings_token_for_list(&displayed_attributes, "with_displayed_attributes");
120 let sortable_attr_tokens =
121 get_settings_token_for_list(&sortable_attributes, "with_sortable_attributes");
122 let filterable_attr_tokens =
123 get_settings_token_for_list(&filterable_attributes, "with_filterable_attributes");
124 let searchable_attr_tokens =
125 get_settings_token_for_list(&searchable_attributes, "with_searchable_attributes");
126 let distinct_attr_token = get_settings_token_for_string_for_some_string(
127 &distinct_key_attribute,
128 "with_distinct_attribute",
129 );
130
131 quote! {
132 #[::meilisearch_sdk::macro_helper::async_trait(?Send)]
133 impl ::meilisearch_sdk::documents::IndexConfig for #struct_ident {
134 const INDEX_STR: &'static str = #index_name;
135
136 fn generate_settings() -> ::meilisearch_sdk::settings::Settings {
137 ::meilisearch_sdk::settings::Settings::new()
138 #display_attr_tokens
139 #sortable_attr_tokens
140 #filterable_attr_tokens
141 #searchable_attr_tokens
142 #distinct_attr_token
143 }
144
145 async fn generate_index<Http: ::meilisearch_sdk::request::HttpClient>(client: &::meilisearch_sdk::client::Client<Http>) -> std::result::Result<::meilisearch_sdk::indexes::Index<Http>, ::meilisearch_sdk::tasks::Task> {
146 return client.create_index(#index_name, #primary_key_token)
147 .await.unwrap()
148 .wait_for_completion(&client, ::std::option::Option::None, ::std::option::Option::None)
149 .await.unwrap()
150 .try_make_index(&client);
151 }
152 }
153 }
154}
155
156fn get_settings_token_for_list(
157 field_name_list: &[String],
158 method_name: &str,
159) -> proc_macro2::TokenStream {
160 let string_attributes = field_name_list.iter().map(|attr| {
161 quote! {
162 #attr
163 }
164 });
165 let method_ident = Ident::new(method_name, proc_macro2::Span::call_site());
166
167 if field_name_list.is_empty() {
168 quote! {
169 .#method_ident(::std::iter::empty::<&str>())
170 }
171 } else {
172 quote! {
173 .#method_ident([#(#string_attributes),*])
174 }
175 }
176}
177
178fn get_settings_token_for_string_for_some_string(
179 field_name: &String,
180 method_name: &str,
181) -> proc_macro2::TokenStream {
182 let method_ident = Ident::new(method_name, proc_macro2::Span::call_site());
183
184 if field_name.is_empty() {
185 proc_macro2::TokenStream::new()
186 } else {
187 quote! {
188 .#method_ident(::std::option::Option::Some(#field_name))
189 }
190 }
191}