Skip to main content

iredismodule_macros/
lib.rs

1extern crate proc_macro;
2
3use proc_macro::TokenStream;
4
5mod rcmd;
6mod rtypedef;
7mod rwrap;
8
9/// A wrapper of module command func.
10///
11/// It's can have five attrs.
12/// ```
13/// #[rcmd("hello.leftpad", "", 0, 0, 0)]
14/// #[rcmd("hello.leftpad")]
15/// #[rcmd("helloacl.authglobal", "no-auth")]
16/// #[rcmd("hello.hcopy", "write deny-oom", 1, 1, 1)]
17/// ```
18///
19/// The first attr command name, it is required.
20///
21/// The second attr is  'strflags' specify the behavior of the command and should
22/// be passed as a C string composed of space separated words, like for
23/// example "write deny-oom". The set of flags are:
24/// * **"write"**:     The command may modify the data set (it may also read
25///                    from it).
26/// * **"readonly"**:  The command returns data from keys but never writes.
27/// * **"admin"**:     The command is an administrative command (may change
28///                    replication or perform similar tasks).
29/// * **"deny-oom"**:  The command may use additional memory and should be
30///                    denied during out of memory conditions.
31/// * **"deny-script"**:   Don't allow this command in Lua scripts.
32/// * **"allow-loading"**: Allow this command while the server is loading data.
33///                        Only commands not interacting with the data set
34///                        should be allowed to run in this mode. If not sure
35///                        don't use this flag.
36/// * **"pubsub"**:    The command publishes things on Pub/Sub channels.
37/// * **"random"**:    The command may have different outputs even starting
38///                    from the same input arguments and key values.
39/// * **"allow-stale"**: The command is allowed to run on slaves that don't
40///                      serve stale data. Don't use if you don't know what
41///                      this means.
42/// * **"no-monitor"**: Don't propagate the command on monitor. Use this if
43///                     the command has sensible data among the arguments.
44/// * **"no-slowlog"**: Don't log this command in the slowlog. Use this if
45///                     the command has sensible data among the arguments.
46/// * **"fast"**:      The command time complexity is not greater
47///                    than O(log(N)) where N is the size of the collection or
48///                    anything else representing the normal scalability
49///                    issue with the command.
50/// * **"getkeys-api"**: The command implements the interface to return
51///                      the arguments that are keys. Used when start/stop/step
52///                      is not enough because of the command syntax.
53/// * **"no-cluster"**: The command should not register in Redis Cluster
54///                     since is not designed to work with it because, for
55///                     example, is unable to report the position of the
56///                     keys, programmatically creates key names, or any
57///                     other reason.
58/// * **"no-auth"**:    This command can be run by an un-authenticated client.
59///                     Normally this is used by a command that is used
60///                     to authenticate a client.
61///
62/// The las three attrs means first_key, last_key and key_step.
63///
64/// ```rust,no_run
65/// #[rcmd("hello.simple", "readonly", 0, 0, 0)]
66/// fn hello_simple(ctx: &mut Context, _args: Vec<RStr>) -> RResult {
67///     let db = ctx.get_select_db();
68///     Ok(db.into())
69/// }
70/// ```
71///
72/// This macro expands above code:
73/// ```rust,no_run
74/// fn hello_simple(ctx: &mut Context, _args: Vec<RStr>) -> RResult {
75///     let db = ctx.get_select_db();
76///     Ok(db.into())
77/// }
78/// extern "C" fn hello_simple_c(
79///     ctx: *mut iredismodule::raw::RedisModuleCtx,
80///     argv: *mut *mut iredismodule::raw::RedisModuleString,
81///     argc: std::os::raw::c_int,
82/// ) -> std::os::raw::c_int {
83///     use iredismodule::FromPtr;
84///     let mut context = iredismodule::context::Context::from_ptr(ctx);
85///     let response = hello_simple(&mut context, iredismodule::parse_args(argv, argc));
86///     context.reply(response);
87///     iredismodule::raw::REDISMODULE_OK as std::os::raw::c_int
88/// }
89/// fn hello_simple_cmd(
90///     ctx: &mut iredismodule::context::Context,
91/// ) -> Result<(), iredismodule::error::Error> {
92///     ctx.create_cmd(
93///         "hello.simple",
94///         hello_simple_c,
95///         "readonly",
96///         0usize,
97///         0usize,
98///         0usize,
99///     )
100/// }
101/// ```
102/// The `hello_simple` fn is the origin fn.
103///
104/// The `hello_simple_c` fn is a c wrapper function which will be apply to ffi or callback.
105///
106/// The `hello_simple_cmd` fn is entrypoint to register command.
107///
108/// The `***_cmd` fn will be applyed to `define_macro`
109/// ```rust,no_run
110/// define_module! {
111///     name: "simple",
112///     version: 1,
113///     data_types: [],
114///     init_funcs: [],
115///     commands: [
116///         hello_simple_cmd, // <- used here
117///     ]
118/// }
119#[proc_macro_attribute]
120pub fn rcmd(attr: TokenStream, input: TokenStream) -> TokenStream {
121    rcmd::rcmd(attr, input)
122}
123
124/// This macro will be used to define a module type.
125///
126/// It must be used in `impl TypeMethod for T`.
127///
128/// It have two attr value.
129///
130/// * **name**: A 9 characters data type name that MUST be unique in the Redis
131///   Modules ecosystem. Be creative... and there will be no collisions. Use
132///   the charset A-Z a-z 9-0, plus the two "-_" characters. A good
133///   idea is to use, for example `<typename>-<vendor>`. For example
134///   "tree-AntZ" may mean "Tree data structure by @antirez". To use both
135///   lower case and upper case letters helps in order to prevent collisions.
136/// * **encver**: Encoding version, which is, the version of the serialization
137///   that a module used in order to persist data. As long as the "name"
138///   matches, the RDB loading will be dispatched to the type callbacks
139///   whatever 'encver' is used, however the module can understand if
140///   the encoding it must load are of an older version of the module.
141///   For example the module "tree-AntZ" initially used encver=0. Later
142///   after an upgrade, it started to serialize data in a different format
143///   and to register the type with encver=1. However this module may
144///   still load old data produced by an older version if the rdb_load
145///   callback is able to check the encver value and act accordingly.
146///   The encver must be a positive value between 0 and 1023.
147///
148/// ```rust,no_run
149/// #[rtypedef("hellotype", 0)]
150/// impl TypeMethod for HelloTypeNode {
151///     fn rdb_load(io: &mut IO, encver: u32) -> Option<Box<Self>> {
152///         if encver != 0 {
153///             return None;
154///         }
155///         let elements = io.load_unsigned();
156///         let mut hto = Self::new();
157///         for _ in 0..elements {
158///             let ele = io.load_signed();
159///             hto.push(ele);
160///         }
161///         Some(Box::new(hto))
162///     }
163///     fn rdb_save(&self, io: &mut IO) {
164///         let eles: Vec<&i64> = self.iter().collect();
165///         io.save_unsigned(eles.len() as u64);
166///         eles.iter().for_each(|v| io.save_signed(**v));
167///     }
168///     fn free(_: Box<Self>) {}
169///     fn aof_rewrite<T: AsRef<str>>(&self, io: &mut IO, key: T) {
170///         let eles: Vec<&i64> = self.iter().collect();
171///         let keyname = key.as_ref();
172///         eles.iter().for_each(|v| {
173///             io.emit_aof(
174///                 "HELLOTYPE.INSERT",
175///                 &[keyname, &v.to_string()],
176///             )
177///         })
178///     }
179///     fn mem_usage(&self) -> usize {
180///         std::mem::size_of::<Self>() * self.len()
181///     }
182///     fn digest(&self, digest: &mut Digest) {
183///         let eles: Vec<&i64> = self.iter().collect();
184///         eles.iter().for_each(|v| digest.add_long_long(**v));
185///         digest.end_sequeue();
186///     }
187/// }
188/// ```
189///
190/// The macro will generate static variable which repersent the data type. The variabe name
191/// is generated by switching to uppercase and replace "-" with "_".
192///
193/// The methods of trait will be expand to extern "C" fn and will be used to set the
194/// value of RedisModuleTypeMethods fields.
195///
196/// For example. The macro will generate `hellotype_rdb_save` based on method `rdb_save`.
197/// ```rust,no_run
198/// unsafe extern "C" fn hellotype_rdb_save(
199///     rdb: *mut iredismodule::raw::RedisModuleIO,
200///     value: *mut std::os::raw::c_void,
201/// ) {
202///     use iredismodule::FromPtr;
203///     let mut io = iredismodule::io::IO::from_ptr(rdb);
204///     let hto = &*(value as *mut HelloTypeNode);
205///     hto.rdb_save(&mut io)
206/// }
207/// ```
208/// If the method is ommited, the value will be set none in construct `RedisModuleTypeMethods`.
209///
210/// ```rust,no_run
211/// pub static HELLOTYPE: iredismodule::rtype::RType<HelloTypeNode> = iredismodule::rtype::RType::new(
212///     "hellotype",
213///     0i32,
214///     iredismodule::raw::RedisModuleTypeMethods {
215///         version: iredismodule::raw::REDISMODULE_TYPE_METHOD_VERSION as u64,
216///         rdb_load: Some(hellotype_rdb_load),
217///         rdb_save: Some(hellotype_rdb_save),
218///         aof_rewrite: Some(hellotype_aof_rewrite),
219///         mem_usage: Some(hellotype_mem_usage),
220///         free: Some(hellotype_free),
221///         digest: Some(hellotype_digest),
222///         aux_load: None,
223///         aux_save: None,
224///         aux_save_triggers: HelloTypeNode::AUX_SAVE_TRIGGERS as i32,
225///     },
226/// );
227///
228/// Finally, use `define_macro` to register that data type.
229/// ```rust,no_run
230/// define_module! {
231///     name: "hellotype",
232///     version: 1,
233///     data_types: [
234///         HELLOTYPE,
235///     ],
236///     init_funcs: [],
237///     commands: [
238///         ...
239///     ],
240/// }
241/// ```
242#[proc_macro_attribute]
243pub fn rtypedef(attr: TokenStream, input: TokenStream) -> TokenStream {
244    rtypedef::rtypedef(attr, input)
245}
246
247/// Wrap of all kind of ffi fn and callback.
248///
249/// The first attr value is a kind, it's point out what kind of function to be wrapped.
250///
251/// ## **free**  - wrap a free callback
252///
253/// ```rust,no_run
254/// #[rwrap("free")]
255/// fn helloblock_free(ctx: &mut Context, data: Box<String>) { }
256/// ```
257/// The code above will be expanded below
258/// ```rust,no_run
259/// extern "C" fn helloblock_free_c(
260///     ctx: *mut iredismodule::raw::RedisModuleCtx,
261///     data: *mut std::os::raw::c_void,
262/// ) {
263///     use iredismodule::FromPtr;
264///     let mut context = iredismodule::context::Context::from_ptr(ctx);
265///     let data = data as *mut String;
266///     let data = unsafe { Box::from_raw(data) };
267///     helloblock_free(&mut context, data);
268/// }
269/// fn helloblock_free(ctx: &mut Context, data: Box<String>) {}
270///
271/// ```
272/// ## **cmd** - wrap a call callback
273///
274/// ```rust,no_run
275/// #[rwrap("call")]
276/// fn helloblock_reply(ctx: &mut Context, _: Vec<RStr>) -> RResult {}
277/// ```
278/// The code above will be expanded below
279/// ```rust,no_run
280/// extern "C" fn helloblock_reply_c(
281///     ctx: *mut iredismodule::raw::RedisModuleCtx,
282///     argv: *mut *mut iredismodule::raw::RedisModuleString,
283///     argc: std::os::raw::c_int,
284/// ) -> std::os::raw::c_int {
285///     let args = iredismodule::parse_args(argv, argc);
286///     let mut context = iredismodule::context::Context::from_ptr(ctx);
287///     let result = helloblock_reply(&mut context, args);
288///     if result.is_err() {
289///         return iredismodule::raw::REDISMODULE_ERR as std::os::raw::c_int;
290///     }
291///     context.reply(result);
292///     return iredismodule::raw::REDISMODULE_OK as std::os::raw::c_int;
293/// }
294/// fn helloblock_reply(ctx: &mut Context, _: Vec<RStr>) -> RResult {
295/// ```
296#[proc_macro_attribute]
297pub fn rwrap(attr: TokenStream, input: TokenStream) -> TokenStream {
298    rwrap::rwrap(attr, input)
299}