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}