valkey_module_macros/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3use syn::ItemFn;
4
5mod command;
6mod info_section;
7mod valkey_value;
8
9/// This proc macro allow to specify that the follow function is a Valkey 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/// * 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 Valkey 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 Valkey finds the location from where it needs to start looking for keys,
38///      Valkey 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(ValkeyValue::SimpleStringStatic("OK"))
79/// }
80/// ```
81///
82/// **Notice**, by default Valkey 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 Valkey can raise a cross slot error when needed.
83#[proc_macro_attribute]
84pub fn command(attr: TokenStream, item: TokenStream) -> TokenStream {
85    command::valkey_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(valkey_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(valkey_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(valkey_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(valkey_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 client connects or disconnects on the server.
177/// The function must accept a [Context] and [ClientChangeSubEvent].
178///
179/// Example:
180///
181/// ```rust,no_run,ignore
182/// #[client_changed_event_handler]
183/// fn client_changed_event_handler(ctx: &Context, values: ClientChangeSubEvent) { ... }
184/// ```
185#[proc_macro_attribute]
186pub fn client_changed_event_handler(_attr: TokenStream, item: TokenStream) -> TokenStream {
187    let ast: ItemFn = match syn::parse(item) {
188        Ok(res) => res,
189        Err(e) => return e.to_compile_error().into(),
190    };
191    let gen = quote! {
192        #[linkme::distributed_slice(valkey_module::server_events::CLIENT_CHANGED_SERVER_EVENTS_LIST)]
193        #ast
194    };
195    gen.into()
196}
197
198/// Proc macro which is set on a function that need to be called whenever a shutdown event happens.
199/// The function must accept a [Context] and [u64].
200///
201/// Example:
202///
203/// ```rust,no_run,ignore
204/// #[shutdown_event_handler]
205/// fn shutdown_event_handler(ctx: &Context, subevent: u64) { ... }
206/// ```
207#[proc_macro_attribute]
208pub fn shutdown_event_handler(_attr: TokenStream, item: TokenStream) -> TokenStream {
209    let ast: ItemFn = match syn::parse(item) {
210        Ok(res) => res,
211        Err(e) => return e.to_compile_error().into(),
212    };
213    let gen = quote! {
214        #[linkme::distributed_slice(valkey_module::server_events::SHUTDOWN_SERVER_EVENT_LIST)]
215        #ast
216    };
217    gen.into()
218}
219
220/// Proc macro which is set on a function that need to be called whenever a configuration change
221/// event is happening. The function must accept a [Context] and [&[&str]] that contains the names
222/// of the configiration values that was changed.
223///
224/// Example:
225///
226/// ```rust,no_run,ignore
227/// #[config_changed_event_handler]
228/// fn configuration_changed_event_handler(ctx: &Context, values: &[&str]) { ... }
229/// ```
230#[proc_macro_attribute]
231pub fn config_changed_event_handler(_attr: TokenStream, item: TokenStream) -> TokenStream {
232    let ast: ItemFn = match syn::parse(item) {
233        Ok(res) => res,
234        Err(e) => return e.to_compile_error().into(),
235    };
236    let gen = quote! {
237        #[linkme::distributed_slice(valkey_module::server_events::CONFIG_CHANGED_SERVER_EVENTS_LIST)]
238        #ast
239    };
240    gen.into()
241}
242
243/// Proc macro which is set on a function that need to be called on Valkey cron.
244/// The function must accept a [Context] and [u64] that represent the cron hz.
245///
246/// Example:
247///
248/// ```rust,no_run,ignore
249/// #[cron_event_handler]
250/// fn cron_event_handler(ctx: &Context, hz: u64) { ... }
251/// ```
252#[proc_macro_attribute]
253pub fn cron_event_handler(_attr: TokenStream, item: TokenStream) -> TokenStream {
254    let ast: ItemFn = match syn::parse(item) {
255        Ok(res) => res,
256        Err(e) => return e.to_compile_error().into(),
257    };
258    let gen = quote! {
259        #[linkme::distributed_slice(valkey_module::server_events::CRON_SERVER_EVENTS_LIST)]
260        #ast
261    };
262    gen.into()
263}
264
265/// Proc macro which is set on a function that need to be called whenever a key event happened.
266/// The function must accept a [Context] and [KeyEventSubevent].
267/// Example:
268/// ```rust,no_run,ignore
269/// #[key_event_handler]
270/// fn key_event_handler(ctx: &Context, values: KeyEventSubevent) { ... }
271#[proc_macro_attribute]
272pub fn key_event_handler(_attr: TokenStream, item: TokenStream) -> TokenStream {
273    let ast: ItemFn = match syn::parse(item) {
274        Ok(res) => res,
275        Err(e) => return e.to_compile_error().into(),
276    };
277    let gen = quote! {
278        #[linkme::distributed_slice(valkey_module::server_events::KEY_SERVER_EVENTS_LIST)]
279        #ast
280    };
281    gen.into()
282}
283
284/// Proc macro which is set on a function that need to be called whenever a persistence event happened.
285/// The function must accept a [Context] and [PersistenceSubevent].
286/// Example:
287/// ```rust,no_run,ignore
288/// #[persistence_event_handler]
289/// fn persistence_event_handler(ctx: &Context, values: PersistenceSubevent) { ... }
290/// ```
291#[proc_macro_attribute]
292pub fn persistence_event_handler(_attr: TokenStream, item: TokenStream) -> TokenStream {
293    let ast: ItemFn = match syn::parse(item) {
294        Ok(res) => res,
295        Err(e) => return e.to_compile_error().into(),
296    };
297    let gen = quote! {
298        #[linkme::distributed_slice(valkey_module::server_events::PERSISTENCE_SERVER_EVENTS_LIST)]
299        #ast
300    };
301    gen.into()
302}
303
304
305
306/// The macro auto generate a [From] implementation that can convert the struct into [ValkeyValue].
307///
308/// Example:
309///
310/// ```rust,no_run,ignore
311/// #[derive(ValkeyValue)]
312/// struct ValkeyValueDeriveInner {
313///     i: i64,
314/// }
315///
316/// #[derive(ValkeyValue)]
317/// struct ValkeyValueDerive {
318///     i: i64,
319///     f: f64,
320///     s: String,
321///     u: usize,
322///     v: Vec<i64>,
323///     v2: Vec<ValkeyValueDeriveInner>,
324///     hash_map: HashMap<String, String>,
325///     hash_set: HashSet<String>,
326///     ordered_map: BTreeMap<String, ValkeyValueDeriveInner>,
327///     ordered_set: BTreeSet<String>,
328/// }
329///
330/// #[command(
331///     {
332///         flags: [ReadOnly, NoMandatoryKeys],
333///         arity: -1,
334///         key_spec: [
335///             {
336///                 notes: "test valkey 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 valkey_value_derive(_ctx: &Context, _args: Vec<RedisString>) -> RedisResult {
345///     Ok(ValkeyValueDerive {
346///         i: 10,
347///         f: 1.1,
348///         s: "s".to_owned(),
349///         u: 20,
350///         v: vec![1, 2, 3],
351///         v2: vec![
352///             ValkeyValueDeriveInner { i: 1 },
353///             ValkeyValueDeriveInner { i: 2 },
354///         ],
355///         hash_map: HashMap::from([("key".to_owned(), "val`".to_owned())]),
356///         hash_set: HashSet::from(["key".to_owned()]),
357///         ordered_map: BTreeMap::from([("key".to_owned(), ValkeyValueDeriveInner { i: 10 })]),
358///         ordered_set: BTreeSet::from(["key".to_owned()]),
359///     }
360///     .into())
361/// }
362/// ```
363///
364/// The [From] implementation generates a [ValkeyValue::OrderMap] such that the fields names
365/// are the map keys and the values are the result of running [Into] function on the field
366/// value and convert it into a [ValkeyValue].
367///
368/// The code above will generate the following reply (in resp3):
369///
370/// ```bash
371/// 127.0.0.1:6379> valkey_value_derive
372/// 1# "f" => (double) 1.1
373/// 2# "hash_map" => 1# "key" => "val"
374/// 3# "hash_set" => 1~ "key"
375/// 4# "i" => (integer) 10
376/// 5# "ordered_map" => 1# "key" => 1# "i" => (integer) 10
377/// 6# "ordered_set" => 1~ "key"
378/// 7# "s" => "s"
379/// 8# "u" => (integer) 20
380/// 9# "v" =>
381///    1) (integer) 1
382///    2) (integer) 2
383///    3) (integer) 3
384/// 10# "v2" =>
385///    1) 1# "i" => (integer) 1
386///    2) 1# "i" => (integer) 2
387/// ```
388///
389/// The derive proc macro can also be set on an Enum. In this case, the generated
390/// code will check the enum variant (using a match statement) and perform [Into]
391/// on the matched varient. This is usefull in case the command returns more than
392/// a single reply type and the reply type need to be decided at runtime.
393///
394/// It is possible to specify a field attribute that will define a specific behavior
395/// about the field. Supported attributes:
396///
397/// * flatten - indicate to inlines keys from a field into the parent struct.
398///
399/// Example:
400///
401/// ```rust,no_run,ignore
402/// #[derive(ValkeyValue)]
403/// struct ValkeyValueDeriveInner {
404///     i2: i64,
405/// }
406///
407/// #[derive(ValkeyValue)]
408/// struct ValkeyValueDerive {
409///     i1: i64,
410///     #[ValkeyValueAttr{flatten: true}]
411///     inner: ValkeyValueDeriveInner
412/// }
413///
414/// #[command(
415///     {
416///         flags: [ReadOnly, NoMandatoryKeys],
417///         arity: -1,
418///         key_spec: [
419///             {
420///                 notes: "test valkey value derive macro",
421///                 flags: [ReadOnly, Access],
422///                 begin_search: Index({ index : 0 }),
423///                 find_keys: Range({ last_key : 0, steps : 0, limit : 0 }),
424///             }
425///         ]
426///     }
427/// )]
428/// fn valkey_value_derive(_ctx: &Context, _args: Vec<RedisString>) -> RedisResult {
429///     Ok(ValkeyValueDerive {
430///         i1: 10,
431///         inner: ValkeyValueDeriveInner{ i2: 10 },
432///     }
433///     .into())
434/// }
435/// ```
436///
437/// The code above will generate the following reply (in resp3):
438///
439/// ```bash
440/// 127.0.0.1:6379> valkey_value_derive
441/// 1# "i1" => 10
442/// 2# "i2" => 10
443/// ```
444///
445#[proc_macro_derive(ValkeyValue, attributes(ValkeyValueAttr))]
446pub fn valkey_value(item: TokenStream) -> TokenStream {
447    valkey_value::valkey_value(item)
448}
449
450/// A procedural macro which registers this function as the custom
451/// `INFO` command handler. There might be more than one handler, each
452/// adding new information to the context.
453///
454/// Example:
455///
456/// ```rust,no_run,ignore
457/// #[info_command_handler]
458/// fn info_command_handler(
459///     ctx: &InfoContext,
460///     for_crash_report: bool) -> RedisResult
461/// {
462///     ctx.builder()
463///         .add_section("test_info")
464///         .field("test_field1", "test_value1")?
465///         .field("test_field2", "test_value2")?
466///         .build_section()?
467///         .build_info()?;
468///
469///     Ok(())
470/// }
471/// ```
472#[proc_macro_attribute]
473pub fn info_command_handler(_attr: TokenStream, item: TokenStream) -> TokenStream {
474    let ast: ItemFn = match syn::parse(item) {
475        Ok(res) => res,
476        Err(e) => return e.to_compile_error().into(),
477    };
478    let gen = quote! {
479        #[linkme::distributed_slice(valkey_module::server_events::INFO_COMMAND_HANDLER_LIST)]
480        #ast
481    };
482    gen.into()
483}
484
485/// Implements a corresponding [`From`] for this struct, to convert
486/// objects of this struct to an information object to be sent to the
487/// [`valkey_module::InfoContext`] as a reply.
488///
489/// Example:
490///
491/// ```rust,no_run,ignore
492/// #[derive(InfoSection)]
493/// struct Info {
494///     field_1: String,
495///     field_2: u64,
496///     dictionary_1: BTreeMap<String, String>,
497/// }
498/// ```
499///
500/// This procedural macro only implements an easy way to convert objects
501/// of this struct, it doesn't automatically do anything. To actually
502/// make use of this, we must return an object of this struct from the
503/// corresponding handler (`info` handler):
504///
505/// ```rust,no_run,ignore
506/// static mut INFO: Info = Info::new();
507///
508/// #[info_command_handler]
509/// fn info_command_handler(
510///     ctx: &InfoContext,
511///     _for_crash_report: bool) -> RedisResult
512/// {
513///     ctx.build_one_section(INFO)
514/// }
515/// ```
516///
517/// # Notes
518///
519/// 1. The name of the struct is taken "as is", so if it starts with
520/// a capital letter (written in the "Upper Camel Case"), like in this
521/// example - `Info`, then it will be compiled into a string prefixed
522/// with the module name, ending up being `"module_name_Info"`-named
523/// section. The fields of the struct are also prefixed with the module
524/// name, so the `field_1` will be prefixed with `module_name_` as well.
525/// 2. In dictionaries, the type of dictionaries supported varies,
526/// for now it is [`std::collections::BTreeMap`] and
527/// [`std::collections::HashMap`].
528/// 3. In dictionaries, the value type can be anything that can be
529/// converted into an object of type
530/// [`valkey_module::InfoContextBuilderFieldBottomLevelValue`], for
531/// example, a [`std::string::String`] or [`u64`]. Please, refer to
532/// [`valkey_module::InfoContextBuilderFieldBottomLevelValue`] for more
533/// information.
534#[proc_macro_derive(InfoSection)]
535pub fn info_section(item: TokenStream) -> TokenStream {
536    info_section::info_section(item)
537}