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}