Skip to content

Commit 3c89a00

Browse files
committed
Refactor to use better Command wrapper
1 parent d6a9269 commit 3c89a00

File tree

10 files changed

+303
-330
lines changed

10 files changed

+303
-330
lines changed

examples/data_type.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/*
12
use std::os::raw::c_void;
23
34
extern crate libc;
@@ -228,3 +229,4 @@ fn parse_i64(arg: &str) -> Result<i64, Error> {
228229
arg.parse::<i64>()
229230
.map_err(|_| Error::generic(format!("Couldn't parse as integer: {}", arg).as_str()))
230231
}
232+
*/

examples/hello.rs

Lines changed: 59 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -1,132 +1,93 @@
1+
use std::ffi::CString;
2+
13
extern crate libc;
24

35
use libc::c_int;
46

57
extern crate redismodule;
68

7-
use redismodule::error::Error;
8-
use redismodule::CommandOld;
99
use redismodule::raw;
1010
use redismodule::Context;
11-
use redismodule::raw::module_init;
11+
use redismodule::{Command, RedisResult, RedisValue, RedisError};
1212

1313
const MODULE_NAME: &str = "hello";
14-
const MODULE_VERSION: c_int = 1;
15-
16-
17-
//////////////////////////////////////////////////////
18-
19-
struct HelloMulCommand;
20-
21-
impl CommandOld for HelloMulCommand {
22-
fn name() -> &'static str { "hello.mul" }
23-
24-
fn external_command() -> raw::CommandFunc { HelloMulCommand_Redis }
25-
26-
fn str_flags() -> &'static str { "write" }
27-
28-
// Run the command.
29-
fn run(r: Context, args: &[&str]) -> Result<(), Error> {
30-
if args.len() != 3 {
31-
return Err(Error::generic(format!(
32-
"Usage: {} <m1> <m2>", Self::name()
33-
).as_str()));
34-
}
14+
const MODULE_VERSION: u32 = 1;
3515

36-
// the first argument is command name (ignore it)
37-
let m1 = parse_i64(args[1])?;
38-
let m2 = parse_i64(args[2])?;
3916

40-
r.reply_array(3)?;
41-
r.reply_integer(m1)?;
42-
r.reply_integer(m2)?;
43-
r.reply_integer(m1 * m2)?;
44-
45-
Ok(())
17+
fn hello_mul(_: &Context, args: Vec<String>) -> RedisResult {
18+
if args.len() != 3 {
19+
return Err(RedisError::WrongArity);
4620
}
47-
}
48-
49-
#[allow(non_snake_case)]
50-
pub extern "C" fn HelloMulCommand_Redis(
51-
ctx: *mut raw::RedisModuleCtx,
52-
argv: *mut *mut raw::RedisModuleString,
53-
argc: c_int,
54-
) -> c_int {
55-
HelloMulCommand::execute(ctx, argv, argc).into()
56-
}
57-
58-
//////////////////////////////////////////////////////
59-
60-
struct HelloAddCommand;
6121

62-
impl CommandOld for HelloAddCommand {
63-
fn name() -> &'static str { "hello.add" }
22+
// TODO: Write generic RedisValue::parse method
23+
if let RedisValue::Integer(m1) = parse_integer(&args[1])? {
24+
if let RedisValue::Integer(m2) = parse_integer(&args[2])? {
25+
let result = m1 * m2;
6426

65-
fn external_command() -> raw::CommandFunc { HelloAddCommand_Redis }
66-
67-
fn str_flags() -> &'static str { "write" }
68-
69-
// Run the command.
70-
fn run(r: Context, args: &[&str]) -> Result<(), Error> {
71-
if args.len() != 3 {
72-
// FIXME: Use RedisModule_WrongArity instead?
73-
return Err(Error::generic(format!(
74-
"Usage: {} <m1> <m2>", Self::name()
75-
).as_str()));
27+
return Ok(RedisValue::Array(
28+
vec![m1, m2, result]
29+
.into_iter()
30+
.map(|v| RedisValue::Integer(v))
31+
.collect()));
7632
}
77-
78-
// the first argument is command name (ignore it)
79-
let m1 = parse_i64(args[1])?;
80-
let m2 = parse_i64(args[2])?;
81-
82-
r.reply_array(3)?;
83-
r.reply_integer(m1)?;
84-
r.reply_integer(m2)?;
85-
r.reply_integer(m1 + m2)?;
86-
87-
Ok(())
8833
}
89-
}
90-
91-
// TODO: Write a macro to generate these glue functions
92-
// TODO: Look at https://github.com/faineance/redismodule which has some macros
9334

94-
#[allow(non_snake_case)]
95-
pub extern "C" fn HelloAddCommand_Redis(
96-
ctx: *mut raw::RedisModuleCtx,
97-
argv: *mut *mut raw::RedisModuleString,
98-
argc: c_int,
99-
) -> c_int {
100-
HelloAddCommand::execute(ctx, argv, argc).into()
35+
Err(RedisError::String("Something went wrong"))
10136
}
10237

10338
//////////////////////////////////////////////////////
10439

105-
fn module_on_load(ctx: *mut raw::RedisModuleCtx) -> Result<(), &'static str> {
106-
module_init(ctx, MODULE_NAME, MODULE_VERSION)?;
107-
108-
HelloMulCommand::create(ctx)?;
109-
HelloAddCommand::create(ctx)?;
110-
111-
Ok(())
112-
}
113-
114-
#[allow(non_snake_case)]
11540
#[no_mangle]
41+
#[allow(non_snake_case)]
11642
pub extern "C" fn RedisModule_OnLoad(
11743
ctx: *mut raw::RedisModuleCtx,
11844
_argv: *mut *mut raw::RedisModuleString,
11945
_argc: c_int,
12046
) -> c_int {
47+
unsafe {
48+
//////////////////
49+
50+
let module_name = MODULE_NAME;
51+
let module_version = MODULE_VERSION;
52+
53+
let commands = [
54+
Command::new("hello.mul", hello_mul, "write"),
55+
];
56+
57+
//////////////////
58+
59+
let module_name = CString::new(module_name).unwrap();
60+
let module_version = module_version as c_int;
61+
62+
if raw::Export_RedisModule_Init(
63+
ctx,
64+
module_name.as_ptr(),
65+
module_version,
66+
raw::REDISMODULE_APIVER_1 as c_int,
67+
) == raw::Status::Err as _ { return raw::Status::Err as _; }
68+
69+
for command in &commands {
70+
let name = CString::new(command.name).unwrap();
71+
let flags = CString::new(command.flags).unwrap();
72+
let (firstkey, lastkey, keystep) = (1, 1, 1);
73+
74+
if raw::RedisModule_CreateCommand.unwrap()(
75+
ctx,
76+
name.as_ptr(),
77+
command.wrap_handler(),
78+
flags.as_ptr(),
79+
firstkey, lastkey, keystep,
80+
) == raw::Status::Err as _ { return raw::Status::Err as _; }
81+
}
12182

122-
if let Err(_) = module_on_load(ctx) {
123-
return raw::Status::Err.into()
83+
raw::Status::Ok as _
12484
}
125-
126-
raw::Status::Ok.into()
12785
}
12886

129-
fn parse_i64(arg: &str) -> Result<i64, Error> {
87+
fn parse_integer(arg: &str) -> RedisResult {
13088
arg.parse::<i64>()
131-
.map_err(|_| Error::generic(format!("Couldn't parse as integer: {}", arg).as_str()))
89+
.map_err(|_| RedisError::String("Couldn't parse as integer"))
90+
.map(|v| RedisValue::Integer(v))
91+
//Error::generic(format!("Couldn't parse as integer: {}", arg).as_str()))
13292
}
93+

src/command.rs

Lines changed: 31 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,77 +1,49 @@
1-
use std::ffi::CString;
2-
use std::os::raw::c_int;
1+
use std::slice;
2+
use std::mem;
33

44
use crate::raw;
5-
use crate::error::Error;
65
use crate::context::Context;
7-
use crate::parse_args;
6+
use crate::{RedisString, RedisResult};
87

9-
10-
/*
118
pub struct Command<F> where F: Fn(&Context, Vec<String>) -> RedisResult {
129
pub name: &'static str,
13-
pub handler: F,
1410
pub flags: &'static str,
11+
pub handler: F,
1512
}
16-
*/
17-
18-
type CommandFuncPtr = extern "C" fn(
19-
*mut raw::RedisModuleCtx,
20-
*mut *mut raw::RedisModuleString,
21-
c_int,
22-
) -> c_int;
23-
24-
25-
pub trait CommandOld {
26-
// Should return the name of the command to be registered.
27-
fn name() -> &'static str;
28-
29-
fn external_command() -> CommandFuncPtr;
3013

31-
// Should return any flags to be registered with the name as a string
32-
// separated list. See the Redis module API documentation for a complete
33-
// list of the ones that are available.
34-
fn str_flags() -> &'static str;
35-
36-
// Run the command.
37-
fn run(r: Context, args: &[&str]) -> Result<(), Error>;
38-
39-
/// Provides a basic wrapper for a command's implementation that parses
40-
/// arguments to Rust data types and handles the OK/ERR reply back to Redis.
41-
fn execute(
42-
ctx: *mut raw::RedisModuleCtx,
43-
argv: *mut *mut raw::RedisModuleString,
44-
argc: c_int,
45-
) -> raw::Status {
46-
let args = parse_args(argv, argc).unwrap();
47-
let str_args: Vec<&str> = args.iter().map(|s| s.as_str()).collect();
14+
impl<F> Command<F>
15+
where F: Fn(&Context, Vec<String>) -> RedisResult {
16+
pub fn new(name: &'static str, handler: F, flags: &'static str) -> Command<F> {
17+
Command {
18+
name,
19+
handler,
20+
flags,
21+
}
22+
}
4823

49-
let r = Context::new(ctx);
24+
pub fn wrap_handler(&self) -> raw::RedisModuleCmdFunc {
25+
extern "C" fn do_command<F: Fn(&Context, Vec<String>) -> RedisResult>(
26+
ctx: *mut raw::RedisModuleCtx,
27+
argv: *mut *mut raw::RedisModuleString,
28+
argc: libc::c_int,
29+
) -> i32 {
30+
unsafe {
31+
let cmd: *const F = &() as *const () as *const F;
32+
//let cmd: *const F = mem::transmute(&()); // equiv ^^
5033

51-
match Self::run(r, str_args.as_slice()) {
52-
Ok(_) => raw::Status::Ok,
53-
Err(e) => {
54-
let message = format!("Redis error: {}", e.to_string());
55-
let message = CString::new(message).unwrap();
34+
let context = Context::new(ctx);
5635

57-
raw::reply_with_error(
58-
ctx,
59-
message.as_ptr(),
60-
);
36+
let args: Vec<String> = slice::from_raw_parts(argv, argc as usize)
37+
.into_iter()
38+
.map(|a| RedisString::from_ptr(*a).expect("UTF8 encoding error in handler args").to_string())
39+
.collect();
6140

62-
raw::Status::Err
41+
context.reply((*cmd)(&context, args)) as _
6342
}
6443
}
65-
}
6644

67-
fn create(ctx: *mut raw::RedisModuleCtx) -> Result<(), &'static str> {
68-
raw::create_command(
69-
ctx,
70-
Self::name(),
71-
Self::external_command(),
72-
Self::str_flags(),
73-
0, 0, 0,
74-
)
45+
assert_eq!(mem::size_of::<F>(), 0);
46+
47+
Some(do_command::<F> as _)
7548
}
7649
}
77-

0 commit comments

Comments
 (0)