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(¯o_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 = <.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}