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}