Skip to main content

redis_module_macros/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3use syn::ItemFn;
4
5mod command;
6mod info_section;
7mod redis_value;
8
9/// This proc macro allow to specify that the follow function is a Redis command.
10/// The macro accept the following arguments that discribe the command properties:
11/// * name (optional) - The command name. in case not given, the function name will be taken.
12/// * flags - An array of [`command::RedisCommandFlags`].
13/// * enterprise_flags - An array of [`command::RedisEnterpriseCommandFlags`].
14/// * summary (optional) - Command summary
15/// * complexity (optional) - Command compexity
16/// * since (optional) - At which module version the command was first introduce
17/// * tips (optional) - Command tips for proxy, for more information please refer to https://redis.io/topics/command-tips
18/// * arity - Number of arguments, including the command name itself. A positive number specifies an exact number of arguments and a negative number
19///   specifies a minimum number of arguments.
20/// * key_spec - A list of specs representing how to find the keys that the command might touch. the following options are available:
21///    * notes (optional) - Some note about the key spec.
22///    * flags - List of flags reprenting how the keys are accessed, the following options are available:
23///       * Readonly - Read-Only. Reads the value of the key, but doesn't necessarily return it.
24///       * ReadWrite - Read-Write. Modifies the data stored in the value of the key or its metadata.
25///       * Overwrite - Overwrite. Overwrites the data stored in the value of the key.
26///       * Remove - Deletes the key.
27///       * Access - Returns, copies or uses the user data from the value of the key.
28///       * Update - Updates data to the value, new value may depend on the old value.
29///       * Insert - Adds data to the value with no chance of modification or deletion of existing data.
30///       * Delete - Explicitly deletes some content from the value of the key.
31///       * NotKey - The key is not actually a key, but should be routed in cluster mode as if it was a key.
32///       * Incomplete - The keyspec might not point out all the keys it should cover.
33///       * VariableFlags - Some keys might have different flags depending on arguments.
34///    * begin_search - Represents how Redis should start looking for keys.
35///      There are 2 possible options:
36///       * Index - start looking for keys from a given position.
37///       * Keyword - Search for a specific keyward and start looking for keys from this keyword
38///    * FindKeys - After Redis finds the location from where it needs to start looking for keys,
39///      Redis will start finding keys base on the information in this struct.
40///      There are 2 possible options:
41///       * Range - An object of three element `last_key`, `steps`, `limit`.
42///          * last_key - Index of the last key relative to the result of the
43///            begin search step. Can be negative, in which case it's not
44///            relative. -1 indicates the last argument, -2 one before the
45///            last and so on.
46///          * steps - How many arguments should we skip after finding a
47///            key, in order to find the next one.
48///          * limit - If `lastkey` is -1, we use `limit` to stop the search
49///            by a factor. 0 and 1 mean no limit. 2 means 1/2 of the
50///            remaining args, 3 means 1/3, and so on.
51///       * Keynum -  An object of 3 elements `keynumidx`, `firstkey`, `keystep`.
52///          * keynumidx - Index of the argument containing the number of
53///            keys to come, relative to the result of the begin search step.
54///          * firstkey - Index of the fist key relative to the result of the
55///            begin search step. (Usually it's just after `keynumidx`, in
56///            which case it should be set to `keynumidx + 1`.)
57///          * keystep - How many arguments should we skip after finding a
58///            key, in order to find the next one?
59///
60/// Example:
61/// The following example will register a command called `foo`.
62/// ```rust,no_run,ignore
63/// #[command(
64/// {
65///    name: "test",
66///    flags: [ReadOnly],
67///    arity: -2,
68///    key_spec: [
69///        {
70///            notes: "test command that define all the arguments at even possition as keys",
71///            flags: [ReadOnly, Access],
72///            begin_search: Keyword({ keyword : "foo", startfrom : 1 }),
73///            find_keys: Range({ last_key :- 1, steps : 2, limit : 0 }),
74///        }
75///    ]
76/// }
77/// )]
78/// fn test_command(_ctx: &Context, _args: Vec<RedisString>) -> RedisResult {
79///     Ok(RedisValue::SimpleStringStatic("OK"))
80/// }
81/// ```
82///
83/// **Notice**, by default Redis does not validate the command spec. User should validate the command keys on the module command code. The command spec is used for validation on cluster so Redis can raise a cross slot error when needed.
84#[proc_macro_attribute]
85pub fn command(attr: TokenStream, item: TokenStream) -> TokenStream {
86    command::redis_command(attr, item)
87}
88
89/// Proc macro which is set on a function that need to be called whenever the server role changes.
90/// The function must accept a [Context] and [ServerRole].
91///
92/// Example:
93///
94/// ```rust,no_run,ignore
95/// #[role_changed_event_handler]
96/// fn role_changed_event_handler(ctx: &Context, values: ServerRole) { ... }
97/// ```
98#[proc_macro_attribute]
99pub fn role_changed_event_handler(_attr: TokenStream, item: TokenStream) -> TokenStream {
100    let ast: ItemFn = match syn::parse(item) {
101        Ok(res) => res,
102        Err(e) => return e.to_compile_error().into(),
103    };
104    let gen = quote! {
105        #[linkme::distributed_slice(redis_module::server_events::ROLE_CHANGED_SERVER_EVENTS_LIST)]
106        #ast
107    };
108    gen.into()
109}
110
111/// Proc macro which is set on a function that need to be called whenever a loading event happened.
112/// The function must accept a [Context] and [LoadingSubevent].
113///
114/// Example:
115///
116/// ```rust,no_run,ignore
117/// #[loading_event_handler]
118/// fn loading_event_handler(ctx: &Context, values: LoadingSubevent) { ... }
119/// ```
120#[proc_macro_attribute]
121pub fn loading_event_handler(_attr: TokenStream, item: TokenStream) -> TokenStream {
122    let ast: ItemFn = match syn::parse(item) {
123        Ok(res) => res,
124        Err(e) => return e.to_compile_error().into(),
125    };
126    let gen = quote! {
127        #[linkme::distributed_slice(redis_module::server_events::LOADING_SERVER_EVENTS_LIST)]
128        #ast
129    };
130    gen.into()
131}
132
133/// Proc macro which is set on a function that need to be called whenever a flush event happened.
134/// The function must accept a [Context] and [FlushSubevent].
135///
136/// Example:
137///
138/// ```rust,no_run,ignore
139/// #[flush_event_handler]
140/// fn flush_event_handler(ctx: &Context, values: FlushSubevent) { ... }
141/// ```
142#[proc_macro_attribute]
143pub fn flush_event_handler(_attr: TokenStream, item: TokenStream) -> TokenStream {
144    let ast: ItemFn = match syn::parse(item) {
145        Ok(res) => res,
146        Err(e) => return e.to_compile_error().into(),
147    };
148    let gen = quote! {
149        #[linkme::distributed_slice(redis_module::server_events::FLUSH_SERVER_EVENTS_LIST)]
150        #ast
151    };
152    gen.into()
153}
154
155/// Proc macro which is set on a function that need to be called whenever a module is loaded or unloaded on the server.
156/// The function must accept a [Context] and [ModuleChangeSubevent].
157///
158/// Example:
159///
160/// ```rust,no_run,ignore
161/// #[module_changed_event_handler]
162/// fn module_changed_event_handler(ctx: &Context, values: ModuleChangeSubevent) { ... }
163/// ```
164#[proc_macro_attribute]
165pub fn module_changed_event_handler(_attr: TokenStream, item: TokenStream) -> TokenStream {
166    let ast: ItemFn = match syn::parse(item) {
167        Ok(res) => res,
168        Err(e) => return e.to_compile_error().into(),
169    };
170    let gen = quote! {
171        #[linkme::distributed_slice(redis_module::server_events::MODULE_CHANGED_SERVER_EVENTS_LIST)]
172        #ast
173    };
174    gen.into()
175}
176
177/// Proc macro which is set on a function that need to be called whenever a configuration change
178/// event is happening. The function must accept a [Context] and [&[&str]] that contains the names
179/// of the configiration values that was changed.
180///
181/// Example:
182///
183/// ```rust,no_run,ignore
184/// #[config_changed_event_handler]
185/// fn configuration_changed_event_handler(ctx: &Context, values: &[&str]) { ... }
186/// ```
187#[proc_macro_attribute]
188pub fn config_changed_event_handler(_attr: TokenStream, item: TokenStream) -> TokenStream {
189    let ast: ItemFn = match syn::parse(item) {
190        Ok(res) => res,
191        Err(e) => return e.to_compile_error().into(),
192    };
193    let gen = quote! {
194        #[linkme::distributed_slice(redis_module::server_events::CONFIG_CHANGED_SERVER_EVENTS_LIST)]
195        #ast
196    };
197    gen.into()
198}
199
200/// Proc macro which is set on a function that need to be called on Redis cron.
201/// The function must accept a [Context] and [u64] that represent the cron hz.
202///
203/// Example:
204///
205/// ```rust,no_run,ignore
206/// #[cron_event_handler]
207/// fn cron_event_handler(ctx: &Context, hz: u64) { ... }
208/// ```
209#[proc_macro_attribute]
210pub fn cron_event_handler(_attr: TokenStream, item: TokenStream) -> TokenStream {
211    let ast: ItemFn = match syn::parse(item) {
212        Ok(res) => res,
213        Err(e) => return e.to_compile_error().into(),
214    };
215    let gen = quote! {
216        #[linkme::distributed_slice(redis_module::server_events::CRON_SERVER_EVENTS_LIST)]
217        #ast
218    };
219    gen.into()
220}
221
222/// The macro auto generate a [From] implementation that can convert the struct into [RedisValue].
223///
224/// Example:
225///
226/// ```rust,no_run,ignore
227/// #[derive(RedisValue)]
228/// struct RedisValueDeriveInner {
229///     i: i64,
230/// }
231///
232/// #[derive(RedisValue)]
233/// struct RedisValueDerive {
234///     i: i64,
235///     f: f64,
236///     s: String,
237///     u: usize,
238///     v: Vec<i64>,
239///     v2: Vec<RedisValueDeriveInner>,
240///     hash_map: HashMap<String, String>,
241///     hash_set: HashSet<String>,
242///     ordered_map: BTreeMap<String, RedisValueDeriveInner>,
243///     ordered_set: BTreeSet<String>,
244/// }
245///
246/// #[command(
247///     {
248///         flags: [ReadOnly, NoMandatoryKeys],
249///         arity: -1,
250///         key_spec: [
251///             {
252///                 notes: "test redis value derive macro",
253///                 flags: [ReadOnly, Access],
254///                 begin_search: Index({ index : 0 }),
255///                 find_keys: Range({ last_key : 0, steps : 0, limit : 0 }),
256///             }
257///         ]
258///     }
259/// )]
260/// fn redis_value_derive(_ctx: &Context, _args: Vec<RedisString>) -> RedisResult {
261///     Ok(RedisValueDerive {
262///         i: 10,
263///         f: 1.1,
264///         s: "s".to_owned(),
265///         u: 20,
266///         v: vec![1, 2, 3],
267///         v2: vec![
268///             RedisValueDeriveInner { i: 1 },
269///             RedisValueDeriveInner { i: 2 },
270///         ],
271///         hash_map: HashMap::from([("key".to_owned(), "val`".to_owned())]),
272///         hash_set: HashSet::from(["key".to_owned()]),
273///         ordered_map: BTreeMap::from([("key".to_owned(), RedisValueDeriveInner { i: 10 })]),
274///         ordered_set: BTreeSet::from(["key".to_owned()]),
275///     }
276///     .into())
277/// }
278/// ```
279///
280/// The [From] implementation generates a [RedisValue::OrderMap] such that the fields names
281/// are the map keys and the values are the result of running [Into] function on the field
282/// value and convert it into a [RedisValue].
283///
284/// The code above will generate the following reply (in resp3):
285///
286/// ```bash
287/// 127.0.0.1:6379> redis_value_derive
288/// 1# "f" => (double) 1.1
289/// 2# "hash_map" => 1# "key" => "val"
290/// 3# "hash_set" => 1~ "key"
291/// 4# "i" => (integer) 10
292/// 5# "ordered_map" => 1# "key" => 1# "i" => (integer) 10
293/// 6# "ordered_set" => 1~ "key"
294/// 7# "s" => "s"
295/// 8# "u" => (integer) 20
296/// 9# "v" =>
297///    1) (integer) 1
298///    2) (integer) 2
299///    3) (integer) 3
300/// 10# "v2" =>
301///    1) 1# "i" => (integer) 1
302///    2) 1# "i" => (integer) 2
303/// ```
304///
305/// The derive proc macro can also be set on an Enum. In this case, the generated
306/// code will check the enum variant (using a match statement) and perform [Into]
307/// on the matched varient. This is usefull in case the command returns more than
308/// a single reply type and the reply type need to be decided at runtime.
309///
310/// It is possible to specify a field attribute that will define a specific behavior
311/// about the field. Supported attributes:
312///
313/// * flatten - indicate to inlines keys from a field into the parent struct.
314///
315/// Example:
316///
317/// ```rust,no_run,ignore
318/// #[derive(RedisValue)]
319/// struct RedisValueDeriveInner {
320///     i2: i64,
321/// }
322///
323/// #[derive(RedisValue)]
324/// struct RedisValueDerive {
325///     i1: i64,
326///     #[RedisValueAttr{flatten: true}]
327///     inner: RedisValueDeriveInner
328/// }
329///
330/// #[command(
331///     {
332///         flags: [ReadOnly, NoMandatoryKeys],
333///         arity: -1,
334///         key_spec: [
335///             {
336///                 notes: "test redis value derive macro",
337///                 flags: [ReadOnly, Access],
338///                 begin_search: Index({ index : 0 }),
339///                 find_keys: Range({ last_key : 0, steps : 0, limit : 0 }),
340///             }
341///         ]
342///     }
343/// )]
344/// fn redis_value_derive(_ctx: &Context, _args: Vec<RedisString>) -> RedisResult {
345///     Ok(RedisValueDerive {
346///         i1: 10,
347///         inner: RedisValueDeriveInner{ i2: 10 },
348///     }
349///     .into())
350/// }
351/// ```
352///
353/// The code above will generate the following reply (in resp3):
354///
355/// ```bash
356/// 127.0.0.1:6379> redis_value_derive
357/// 1# "i1" => 10
358/// 2# "i2" => 10
359/// ```
360///
361#[proc_macro_derive(RedisValue, attributes(RedisValueAttr))]
362pub fn redis_value(item: TokenStream) -> TokenStream {
363    redis_value::redis_value(item)
364}
365
366/// A procedural macro which registers this function as the custom
367/// `INFO` command handler. There might be more than one handler, each
368/// adding new information to the context.
369///
370/// Example:
371///
372/// ```rust,no_run,ignore
373/// #[info_command_handler]
374/// fn info_command_handler(
375///     ctx: &InfoContext,
376///     for_crash_report: bool) -> RedisResult
377/// {
378///     ctx.builder()
379///         .add_section("test_info")
380///         .field("test_field1", "test_value1")?
381///         .field("test_field2", "test_value2")?
382///         .build_section()?
383///         .build_info()?;
384///
385///     Ok(())
386/// }
387/// ```
388#[proc_macro_attribute]
389pub fn info_command_handler(_attr: TokenStream, item: TokenStream) -> TokenStream {
390    let ast: ItemFn = match syn::parse(item) {
391        Ok(res) => res,
392        Err(e) => return e.to_compile_error().into(),
393    };
394    let gen = quote! {
395        #[linkme::distributed_slice(redis_module::server_events::INFO_COMMAND_HANDLER_LIST)]
396        #ast
397    };
398    gen.into()
399}
400
401/// Implements a corresponding [`From`] for this struct, to convert
402/// objects of this struct to an information object to be sent to the
403/// [`redis_module::InfoContext`] as a reply.
404///
405/// Example:
406///
407/// ```rust,no_run,ignore
408/// #[derive(InfoSection)]
409/// struct Info {
410///     field_1: String,
411///     field_2: u64,
412///     dictionary_1: BTreeMap<String, String>,
413/// }
414/// ```
415///
416/// This procedural macro only implements an easy way to convert objects
417/// of this struct, it doesn't automatically do anything. To actually
418/// make use of this, we must return an object of this struct from the
419/// corresponding handler (`info` handler):
420///
421/// ```rust,no_run,ignore
422/// static mut INFO: Info = Info::new();
423///
424/// #[info_command_handler]
425/// fn info_command_handler(
426///     ctx: &InfoContext,
427///     _for_crash_report: bool) -> RedisResult
428/// {
429///     ctx.build_one_section(INFO)
430/// }
431/// ```
432///
433/// # Notes
434///
435/// 1. The name of the struct is taken "as is", so if it starts with
436///    a capital letter (written in the "Upper Camel Case"), like in this
437///    example - `Info`, then it will be compiled into a string prefixed
438///    with the module name, ending up being `"module_name_Info"`-named
439///    section. The fields of the struct are also prefixed with the module
440///    name, so the `field_1` will be prefixed with `module_name_` as well.
441/// 2. In dictionaries, the type of dictionaries supported varies,
442///    for now it is [`std::collections::BTreeMap`] and
443///    [`std::collections::HashMap`].
444/// 3. In dictionaries, the value type can be anything that can be
445///    converted into an object of type
446///    [`redis_module::InfoContextBuilderFieldBottomLevelValue`], for
447///    example, a [`std::string::String`] or [`u64`]. Please, refer to
448///    [`redis_module::InfoContextBuilderFieldBottomLevelValue`] for more
449///    information.
450#[proc_macro_derive(InfoSection)]
451pub fn info_section(item: TokenStream) -> TokenStream {
452    info_section::info_section(item)
453}
454
455/// Proc macro which is set on a function that need to be called whenever server performs defrag.
456/// The function must accept a [&DefragContext]. If defrag is not supported by the Redis version
457/// the function will never be called.
458///
459/// Example:
460///
461/// ```rust,no_run,ignore
462/// #[defrag_function]
463/// fn defrag(ctx: &DefragContext) { ... }
464/// ```
465#[proc_macro_attribute]
466pub fn defrag_function(_attr: TokenStream, item: TokenStream) -> TokenStream {
467    let ast: ItemFn = match syn::parse(item) {
468        Ok(res) => res,
469        Err(e) => return e.to_compile_error().into(),
470    };
471    let gen = quote! {
472        #[linkme::distributed_slice(redis_module::defrag::DEFRAG_FUNCTIONS_LIST)]
473        #ast
474    };
475    gen.into()
476}
477
478/// Proc macro which is set on a function that need to be called whenever server start performs defrag.
479/// The function must accept a [&DefragContext]. If defrag start event is not supported by the Redis version
480/// the function will never be called.
481///
482/// Example:
483///
484/// ```rust,no_run,ignore
485/// #[defrag_start_function]
486/// fn defrag_start(ctx: &DefragContext) { ... }
487/// ```
488#[proc_macro_attribute]
489pub fn defrag_start_function(_attr: TokenStream, item: TokenStream) -> TokenStream {
490    let ast: ItemFn = match syn::parse(item) {
491        Ok(res) => res,
492        Err(e) => return e.to_compile_error().into(),
493    };
494    let gen = quote! {
495        #[linkme::distributed_slice(redis_module::defrag::DEFRAG_START_FUNCTIONS_LIST)]
496        #ast
497    };
498    gen.into()
499}
500
501/// Proc macro which is set on a function that need to be called whenever server end performs defrag.
502/// The function must accept a [&DefragContext]. If defrag end event is not supported by the Redis version
503/// the function will never be called.
504///
505/// Example:
506///
507/// ```rust,no_run,ignore
508/// #[defrag_end_function]
509/// fn defrag_end(ctx: &DefragContext) { ... }
510/// ```
511#[proc_macro_attribute]
512pub fn defrag_end_function(_attr: TokenStream, item: TokenStream) -> TokenStream {
513    let ast: ItemFn = match syn::parse(item) {
514        Ok(res) => res,
515        Err(e) => return e.to_compile_error().into(),
516    };
517    let gen = quote! {
518        #[linkme::distributed_slice(redis_module::defrag::DEFRAG_END_FUNCTIONS_LIST)]
519        #ast
520    };
521    gen.into()
522}