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