1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
extern crate proc_macro;

use proc_macro::TokenStream;

mod rcmd;
mod rtypedef;
mod rwrap;

/// A wrapper of module command func.
///
/// It's can have five attrs.
/// ```
/// #[rcmd("hello.leftpad", "", 0, 0, 0)]
/// #[rcmd("hello.leftpad")]
/// #[rcmd("helloacl.authglobal", "no-auth")]
/// #[rcmd("hello.hcopy", "write deny-oom", 1, 1, 1)]
/// ```
///
/// The first attr command name, it is required.
///
/// The second attr is  'strflags' specify the behavior of the command and should
/// be passed as a C string composed of space separated words, like for
/// example "write deny-oom". The set of flags are:
/// * **"write"**:     The command may modify the data set (it may also read
///                    from it).
/// * **"readonly"**:  The command returns data from keys but never writes.
/// * **"admin"**:     The command is an administrative command (may change
///                    replication or perform similar tasks).
/// * **"deny-oom"**:  The command may use additional memory and should be
///                    denied during out of memory conditions.
/// * **"deny-script"**:   Don't allow this command in Lua scripts.
/// * **"allow-loading"**: Allow this command while the server is loading data.
///                        Only commands not interacting with the data set
///                        should be allowed to run in this mode. If not sure
///                        don't use this flag.
/// * **"pubsub"**:    The command publishes things on Pub/Sub channels.
/// * **"random"**:    The command may have different outputs even starting
///                    from the same input arguments and key values.
/// * **"allow-stale"**: The command is allowed to run on slaves that don't
///                      serve stale data. Don't use if you don't know what
///                      this means.
/// * **"no-monitor"**: Don't propagate the command on monitor. Use this if
///                     the command has sensible data among the arguments.
/// * **"no-slowlog"**: Don't log this command in the slowlog. Use this if
///                     the command has sensible data among the arguments.
/// * **"fast"**:      The command time complexity is not greater
///                    than O(log(N)) where N is the size of the collection or
///                    anything else representing the normal scalability
///                    issue with the command.
/// * **"getkeys-api"**: The command implements the interface to return
///                      the arguments that are keys. Used when start/stop/step
///                      is not enough because of the command syntax.
/// * **"no-cluster"**: The command should not register in Redis Cluster
///                     since is not designed to work with it because, for
///                     example, is unable to report the position of the
///                     keys, programmatically creates key names, or any
///                     other reason.
/// * **"no-auth"**:    This command can be run by an un-authenticated client.
///                     Normally this is used by a command that is used
///                     to authenticate a client.
///
/// The las three attrs means first_key, last_key and key_step.
///
/// ```rust,no_run
/// #[rcmd("hello.simple", "readonly", 0, 0, 0)]
/// fn hello_simple(ctx: &mut Context, _args: Vec<RStr>) -> RResult {
///     let db = ctx.get_select_db();
///     Ok(db.into())
/// }
/// ```
///
/// This macro expands above code:
/// ```rust,no_run
/// fn hello_simple(ctx: &mut Context, _args: Vec<RStr>) -> RResult {
///     let db = ctx.get_select_db();
///     Ok(db.into())
/// }
/// extern "C" fn hello_simple_c(
///     ctx: *mut iredismodule::raw::RedisModuleCtx,
///     argv: *mut *mut iredismodule::raw::RedisModuleString,
///     argc: std::os::raw::c_int,
/// ) -> std::os::raw::c_int {
///     use iredismodule::FromPtr;
///     let mut context = iredismodule::context::Context::from_ptr(ctx);
///     let response = hello_simple(&mut context, iredismodule::parse_args(argv, argc));
///     context.reply(response);
///     iredismodule::raw::REDISMODULE_OK as std::os::raw::c_int
/// }
/// fn hello_simple_cmd(
///     ctx: &mut iredismodule::context::Context,
/// ) -> Result<(), iredismodule::error::Error> {
///     ctx.create_cmd(
///         "hello.simple",
///         hello_simple_c,
///         "readonly",
///         0usize,
///         0usize,
///         0usize,
///     )
/// }
/// ```
/// The `hello_simple` fn is the origin fn.
///
/// The `hello_simple_c` fn is a c wrapper function which will be apply to ffi or callback.
///
/// The `hello_simple_cmd` fn is entrypoint to register command.
///
/// The `***_cmd` fn will be applyed to `define_macro`
/// ```rust,no_run
/// define_module! {
///     name: "simple",
///     version: 1,
///     data_types: [],
///     init_funcs: [],
///     commands: [
///         hello_simple_cmd, // <- used here
///     ]
/// }
#[proc_macro_attribute]
pub fn rcmd(attr: TokenStream, input: TokenStream) -> TokenStream {
    rcmd::rcmd(attr, input)
}

/// This macro will be used to define a module type.
///
/// It must be used in `impl TypeMethod for T`.
///
/// It have two attr value.
///
/// * **name**: A 9 characters data type name that MUST be unique in the Redis
///   Modules ecosystem. Be creative... and there will be no collisions. Use
///   the charset A-Z a-z 9-0, plus the two "-_" characters. A good
///   idea is to use, for example `<typename>-<vendor>`. For example
///   "tree-AntZ" may mean "Tree data structure by @antirez". To use both
///   lower case and upper case letters helps in order to prevent collisions.
/// * **encver**: Encoding version, which is, the version of the serialization
///   that a module used in order to persist data. As long as the "name"
///   matches, the RDB loading will be dispatched to the type callbacks
///   whatever 'encver' is used, however the module can understand if
///   the encoding it must load are of an older version of the module.
///   For example the module "tree-AntZ" initially used encver=0. Later
///   after an upgrade, it started to serialize data in a different format
///   and to register the type with encver=1. However this module may
///   still load old data produced by an older version if the rdb_load
///   callback is able to check the encver value and act accordingly.
///   The encver must be a positive value between 0 and 1023.
///
/// ```rust,no_run
/// #[rtypedef("hellotype", 0)]
/// impl TypeMethod for HelloTypeNode {
///     fn rdb_load(io: &mut IO, encver: u32) -> Option<Box<Self>> {
///         if encver != 0 {
///             return None;
///         }
///         let elements = io.load_unsigned();
///         let mut hto = Self::new();
///         for _ in 0..elements {
///             let ele = io.load_signed();
///             hto.push(ele);
///         }
///         Some(Box::new(hto))
///     }
///     fn rdb_save(&self, io: &mut IO) {
///         let eles: Vec<&i64> = self.iter().collect();
///         io.save_unsigned(eles.len() as u64);
///         eles.iter().for_each(|v| io.save_signed(**v));
///     }
///     fn free(_: Box<Self>) {}
///     fn aof_rewrite<T: AsRef<str>>(&self, io: &mut IO, key: T) {
///         let eles: Vec<&i64> = self.iter().collect();
///         let keyname = key.as_ref();
///         eles.iter().for_each(|v| {
///             io.emit_aof(
///                 "HELLOTYPE.INSERT",
///                 &[keyname, &v.to_string()],
///             )
///         })
///     }
///     fn mem_usage(&self) -> usize {
///         std::mem::size_of::<Self>() * self.len()
///     }
///     fn digest(&self, digest: &mut Digest) {
///         let eles: Vec<&i64> = self.iter().collect();
///         eles.iter().for_each(|v| digest.add_long_long(**v));
///         digest.end_sequeue();
///     }
/// }
/// ```
///
/// The macro will generate static variable which repersent the data type. The variabe name
/// is generated by switching to uppercase and replace "-" with "_".
///
/// The methods of trait will be expand to extern "C" fn and will be used to set the
/// value of RedisModuleTypeMethods fields.
///
/// For example. The macro will generate `hellotype_rdb_save` based on method `rdb_save`.
/// ```rust,no_run
/// unsafe extern "C" fn hellotype_rdb_save(
///     rdb: *mut iredismodule::raw::RedisModuleIO,
///     value: *mut std::os::raw::c_void,
/// ) {
///     use iredismodule::FromPtr;
///     let mut io = iredismodule::io::IO::from_ptr(rdb);
///     let hto = &*(value as *mut HelloTypeNode);
///     hto.rdb_save(&mut io)
/// }
/// ```
/// If the method is ommited, the value will be set none in construct `RedisModuleTypeMethods`.
///
/// ```rust,no_run
/// pub static HELLOTYPE: iredismodule::rtype::RType<HelloTypeNode> = iredismodule::rtype::RType::new(
///     "hellotype",
///     0i32,
///     iredismodule::raw::RedisModuleTypeMethods {
///         version: iredismodule::raw::REDISMODULE_TYPE_METHOD_VERSION as u64,
///         rdb_load: Some(hellotype_rdb_load),
///         rdb_save: Some(hellotype_rdb_save),
///         aof_rewrite: Some(hellotype_aof_rewrite),
///         mem_usage: Some(hellotype_mem_usage),
///         free: Some(hellotype_free),
///         digest: Some(hellotype_digest),
///         aux_load: None,
///         aux_save: None,
///         aux_save_triggers: HelloTypeNode::AUX_SAVE_TRIGGERS as i32,
///     },
/// );
///
/// Finally, use `define_macro` to register that data type.
/// ```rust,no_run
/// define_module! {
///     name: "hellotype",
///     version: 1,
///     data_types: [
///         HELLOTYPE,
///     ],
///     init_funcs: [],
///     commands: [
///         ...
///     ],
/// }
/// ```
#[proc_macro_attribute]
pub fn rtypedef(attr: TokenStream, input: TokenStream) -> TokenStream {
    rtypedef::rtypedef(attr, input)
}

/// Wrap of all kind of ffi fn and callback.
///
/// The first attr value is a kind, it's point out what kind of function to be wrapped.
///
/// ## **free**  - wrap a free callback
///
/// ```rust,no_run
/// #[rwrap("free")]
/// fn helloblock_free(ctx: &mut Context, data: Box<String>) { }
/// ```
/// The code above will be expanded below
/// ```rust,no_run
/// extern "C" fn helloblock_free_c(
///     ctx: *mut iredismodule::raw::RedisModuleCtx,
///     data: *mut std::os::raw::c_void,
/// ) {
///     use iredismodule::FromPtr;
///     let mut context = iredismodule::context::Context::from_ptr(ctx);
///     let data = data as *mut String;
///     let data = unsafe { Box::from_raw(data) };
///     helloblock_free(&mut context, data);
/// }
/// fn helloblock_free(ctx: &mut Context, data: Box<String>) {}
///
/// ```
/// ## **cmd** - wrap a call callback
///
/// ```rust,no_run
/// #[rwrap("call")]
/// fn helloblock_reply(ctx: &mut Context, _: Vec<RStr>) -> RResult {}
/// ```
/// The code above will be expanded below
/// ```rust,no_run
/// extern "C" fn helloblock_reply_c(
///     ctx: *mut iredismodule::raw::RedisModuleCtx,
///     argv: *mut *mut iredismodule::raw::RedisModuleString,
///     argc: std::os::raw::c_int,
/// ) -> std::os::raw::c_int {
///     let args = iredismodule::parse_args(argv, argc);
///     let mut context = iredismodule::context::Context::from_ptr(ctx);
///     let result = helloblock_reply(&mut context, args);
///     if result.is_err() {
///         return iredismodule::raw::REDISMODULE_ERR as std::os::raw::c_int;
///     }
///     context.reply(result);
///     return iredismodule::raw::REDISMODULE_OK as std::os::raw::c_int;
/// }
/// fn helloblock_reply(ctx: &mut Context, _: Vec<RStr>) -> RResult {
/// ```
#[proc_macro_attribute]
pub fn rwrap(attr: TokenStream, input: TokenStream) -> TokenStream {
    rwrap::rwrap(attr, input)
}