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) }