redis_module_ext_macros/
redis_command.rs

1use darling::FromMeta;
2use darling::ast::NestedMeta;
3use proc_macro2::{Span, TokenStream};
4
5use crate::utils::{collect_to_vec, format_command_ident, opt_to_tokens, repeated_parse, str_to_cstr};
6
7#[derive(darling::FromMeta, Debug)]
8struct Args {
9    name: syn::LitStr,
10    #[darling(default)]
11    summary: Option<syn::LitStr>,
12    #[darling(default)]
13    complexity: Option<syn::LitStr>,
14    #[darling(default)]
15    since: Option<syn::LitStr>,
16    #[darling(default, multiple)]
17    history: Vec<History>,
18    #[darling(default)]
19    tips: Option<syn::LitStr>,
20    #[darling(default)]
21    arity: Option<syn::Expr>,
22    #[darling(default, with = repeated_parse)]
23    flags: Vec<syn::Ident>,
24    #[darling(default, with = repeated_parse)]
25    enterprise_flags: Vec<syn::Ident>,
26    #[darling(default, multiple)]
27    key_spec: Vec<KeySpec>,
28    #[darling(default, multiple)]
29    arg: Vec<CommandArg>,
30}
31
32#[derive(darling::FromMeta, Debug)]
33struct History {
34    #[darling(default)]
35    since: Option<syn::LitStr>,
36    #[darling(default)]
37    changes: Option<syn::LitStr>,
38}
39
40#[derive(darling::FromMeta, Debug)]
41struct KeySpec {
42    #[darling(default)]
43    notes: Option<syn::LitStr>,
44    #[darling(default, with = repeated_parse)]
45    flags: Vec<syn::Ident>,
46    begin_search: BeginSearch,
47    #[darling(default)]
48    find_keys: Option<FindKeys>,
49}
50
51#[derive(darling::FromMeta, Debug)]
52struct CommandArg {
53    name: syn::LitStr,
54    #[darling(default)]
55    kind: Option<syn::Ident>,
56    #[darling(default)]
57    key_spec_index: Option<syn::LitInt>,
58    #[darling(default)]
59    token: Option<syn::LitStr>,
60    #[darling(default)]
61    summary: Option<syn::LitStr>,
62    #[darling(default)]
63    since: Option<syn::LitStr>,
64    #[darling(default)]
65    flags: Option<syn::LitInt>,
66    #[darling(default)]
67    deprecated_since: Option<syn::LitStr>,
68    #[darling(default, multiple)]
69    arg: Vec<CommandArg>,
70    #[darling(default)]
71    display_text: Option<syn::LitStr>,
72}
73
74#[derive(darling::FromMeta, Debug)]
75enum BeginSearch {
76    Index(i32),
77    Keyword(BeginSearchKeyword),
78}
79
80#[derive(darling::FromMeta, Debug)]
81struct BeginSearchKeyword {
82    keyword: syn::LitStr,
83    start_from: i32,
84}
85
86#[derive(darling::FromMeta, Debug)]
87enum FindKeys {
88    Range(FindKeysRange),
89    Keynum(FindKeysKeynum),
90}
91
92#[derive(darling::FromMeta, Debug)]
93struct FindKeysRange {
94    last_key: i32,
95    steps: i32,
96    limit: i32,
97}
98
99#[derive(darling::FromMeta, Debug)]
100struct FindKeysKeynum {
101    key_num_idx: i32,
102    first_key: i32,
103    key_step: i32,
104}
105
106pub fn macro_impl(attr: TokenStream, item: TokenStream) -> syn::Result<TokenStream> {
107    let attr_args = NestedMeta::parse_meta_list(attr)?;
108
109    let macro_args = Args::from_list(&attr_args)?;
110
111    let item: syn::ItemFn = syn::parse2(item)?;
112
113    let mut struct_ident = format_command_ident(&item.sig.ident);
114    struct_ident.set_span(Span::call_site());
115    let vis = &item.vis;
116    let (imp, ty, wh) = item.sig.generics.split_for_impl();
117
118    let name_cstr = str_to_cstr(&macro_args.name)?;
119    let summary = opt_to_tokens(macro_args.summary.as_ref().map(str_to_cstr).transpose()?);
120    let complexity = opt_to_tokens(macro_args.complexity.as_ref().map(str_to_cstr).transpose()?);
121    let since = opt_to_tokens(macro_args.since.as_ref().map(str_to_cstr).transpose()?);
122    let tips = opt_to_tokens(macro_args.tips.as_ref().map(str_to_cstr).transpose()?);
123    let arity = macro_args.arity.unwrap_or_else(|| syn::parse_quote!(-1));
124    let history = macro_args
125        .history
126        .iter()
127        .map(|history| {
128            let since = opt_to_tokens(history.since.as_ref().map(str_to_cstr).transpose()?);
129            let changes = opt_to_tokens(history.changes.as_ref().map(str_to_cstr).transpose()?);
130            Ok(quote::quote! {
131                ::redis_module_ext::command::RedisModuleCommandHistoryEntry {
132                    since: #since.map(::std::borrow::Cow::Borrowed),
133                    changes: #changes.map(::std::borrow::Cow::Borrowed),
134                }
135            })
136        })
137        .collect::<syn::Result<Vec<_>>>()?;
138
139    let key_specs = macro_args
140        .key_spec
141        .iter()
142        .map(|key_spec| {
143            let notes = opt_to_tokens(key_spec.notes.as_ref().map(str_to_cstr).transpose()?);
144            let begin_search = match &key_spec.begin_search {
145                BeginSearch::Index(idx) => quote::quote!(::redis_module_ext::command::KeySpecBeginSearch::Index(#idx)),
146                BeginSearch::Keyword(BeginSearchKeyword { keyword, start_from }) => {
147                    let keyword = str_to_cstr(keyword)?;
148                    quote::quote!(::redis_module_ext::command::KeySpecBeginSearch::Keyword {
149                        keyword: ::std::borrow::Cow::Borrowed(#keyword.into()),
150                        start_from: #start_from,
151                    })
152                }
153            };
154            let find_keys = match &key_spec.find_keys {
155                None => quote::quote!(::core::option::Option::None),
156                Some(FindKeys::Keynum(FindKeysKeynum {
157                    first_key,
158                    key_num_idx,
159                    key_step,
160                })) => quote::quote! {
161                    ::core::option::Option::Some(::redis_module_ext::command::KeySpecFindKeys::Keynum {
162                        keynum_idx: #key_num_idx,
163                        first_key: #first_key,
164                        key_step: #key_step,
165                    })
166                },
167                Some(FindKeys::Range(FindKeysRange { last_key, steps, limit })) => quote::quote! {
168                    ::core::option::Option::Some(::redis_module_ext::command::KeySpecFindKeys::Range {
169                        last_key: #last_key,
170                        key_step: #steps,
171                        limit: #limit,
172                    })
173                },
174            };
175
176            let flags = collect_to_vec(
177                key_spec
178                    .flags
179                    .iter()
180                    .map(|ident| quote::quote!(redis_module_ext::command::KeySpecFlag::#ident)),
181            );
182
183            Ok(quote::quote! {
184                redis_module_ext::command::RedisModuleCommandKeySpec {
185                    notes: #notes.map(::std::borrow::Cow::Borrowed),
186                    flags: #flags,
187                    begin_search: #begin_search,
188                    find_keys: #find_keys,
189                }
190            })
191        })
192        .collect::<syn::Result<Vec<_>>>()?;
193
194    fn convert_arg(arg: &CommandArg) -> syn::Result<TokenStream> {
195        let neg_1 = syn::LitInt::new("-1", Span::call_site());
196        let zero = syn::LitInt::new("0", Span::call_site());
197        let name = str_to_cstr(&arg.name)?;
198        let kind = arg.kind.clone().unwrap_or_else(|| quote::format_ident!("String"));
199        let key_spec_idx = arg.key_spec_index.as_ref().unwrap_or(&neg_1);
200        let token = opt_to_tokens(arg.token.as_ref().map(str_to_cstr).transpose()?);
201        let summary = opt_to_tokens(arg.summary.as_ref().map(str_to_cstr).transpose()?);
202        let since = opt_to_tokens(arg.since.as_ref().map(str_to_cstr).transpose()?);
203        let deprecated_since = opt_to_tokens(arg.deprecated_since.as_ref().map(str_to_cstr).transpose()?);
204        let display_text = opt_to_tokens(arg.display_text.as_ref().map(str_to_cstr).transpose()?);
205        let flags = arg.flags.as_ref().unwrap_or(&zero);
206        let sub_args = arg.arg.iter().map(convert_arg).collect::<syn::Result<Vec<_>>>()?;
207        let sub_args = collect_to_vec(sub_args);
208
209        Ok(quote::quote! {{
210            ::redis_module_ext::command::RedisModuleCommandArg {
211                name: ::std::borrow::Cow::Borrowed((#name)),
212                kind: ::redis_module_ext::command::RedisModuleCommandArgKind::#kind,
213                key_spec_idx: (#key_spec_idx),
214                token: (#token).map(::std::borrow::Cow::Borrowed),
215                summary: (#summary).map(::std::borrow::Cow::Borrowed),
216                since: (#since).map(::std::borrow::Cow::Borrowed),
217                flags: (#flags),
218                deprecated_since: (#deprecated_since).map(::std::borrow::Cow::Borrowed),
219                sub_args: #sub_args,
220                display_text: (#display_text).map(::std::borrow::Cow::Borrowed),
221            }
222        }})
223    }
224
225    let args = macro_args.arg.iter().map(convert_arg).collect::<syn::Result<Vec<_>>>()?;
226
227    let fn_generics = item.sig.generics.params.iter().filter_map(|param| match param {
228        syn::GenericParam::Const(c) => Some(&c.ident),
229        syn::GenericParam::Type(ty) => Some(&ty.ident),
230        _ => None,
231    });
232
233    let marker_ty = item
234        .sig
235        .generics
236        .lifetimes()
237        .map(|lt| {
238            let lt = &lt.lifetime;
239            quote::quote!(&#lt ())
240        })
241        .chain(item.sig.generics.type_params().map(|ty| {
242            let ident = &ty.ident;
243            quote::quote!(#ident)
244        }));
245
246    let history = collect_to_vec(history);
247    let key_specs = collect_to_vec(key_specs);
248    let args = collect_to_vec(args);
249
250    let syn::Signature {
251        constness,
252        asyncness,
253        unsafety,
254        abi,
255        fn_token,
256        ident,
257        inputs,
258        variadic,
259        output,
260        ..
261    } = &item.sig;
262
263    let inputs = inputs.pairs();
264
265    let flags = collect_to_vec(macro_args.flags.iter().map(|ident| {
266        quote::quote! {
267            ::redis_module_ext::command::CommandFlag::#ident
268        }
269    }));
270
271    let enterprise_flags = if !macro_args.enterprise_flags.is_empty() {
272        let enterprise_flags = macro_args.enterprise_flags.iter().map(|ident| {
273            quote::quote! {
274                ::redis_module_ext::command::CommandFlag::#ident
275            }
276        });
277
278        quote::quote! {
279            if ctx.is_enterprise() {
280                flags.extend([#(#enterprise_flags),*]);
281            }
282        }
283    } else {
284        quote::quote!()
285    };
286
287    Ok(quote::quote! {
288        #item
289
290        #[doc(hidden)]
291        #[allow(non_camel_case_types)]
292        #vis struct #struct_ident #imp #wh {
293            marker: ::core::marker::PhantomData<(#(#marker_ty),*)>,
294        }
295
296        impl #imp ::redis_module_ext::command::RedisCommand for #struct_ident #ty #wh {
297            const NAME: &'static ::std::ffi::CStr = #name_cstr;
298
299            fn flags(ctx: &::redis_module_ext::redis::Context) -> Vec<::redis_module_ext::command::CommandFlag> {
300                let mut flags = #flags;
301                #enterprise_flags
302                flags
303            }
304
305            fn command_info(_: &::redis_module_ext::redis::Context) -> ::redis_module_ext::command::RedisModuleCommandInfo {
306                ::redis_module_ext::command::RedisModuleCommandInfo {
307                    summary: (#summary).map(::std::borrow::Cow::Borrowed),
308                    complexity: (#complexity).map(::std::borrow::Cow::Borrowed),
309                    since: (#since).map(::std::borrow::Cow::Borrowed),
310                    history: #history,
311                    tips: (#tips).map(::std::borrow::Cow::Borrowed),
312                    arity: (#arity),
313                    key_specs: #key_specs,
314                    args: #args,
315                }
316            }
317
318            #[allow(unused_mut)]
319            #constness #asyncness #unsafety #abi #fn_token invoke(#(#inputs)*, #variadic) #output {
320                #ident :: <#(#fn_generics),*> (ctx, args)
321            }
322        }
323    })
324}