Skip to content

Commit

Permalink
add RESP3 new APIs (#280)
Browse files Browse the repository at this point in the history
The PR adds Resp3 support for both, return result from module command and getting result as resp3 on `ctx.call_ext` (when resp3 option is used).

Tests was added to verify both new functionalities.

The PR also refactor the `CallReply` API introduced on #290. Instead of having 2 types of `CallReply` we now have one type such that the root call reply created with `'static` lifetime indicating that the user can hold it for as long as it want. Internal `CallReply` can outlive its father.
As part of the refactoring, the `CallReply` was changed to be more rust friendly. `CallReply` was renamed to `CallResult = Result<CallReply, ErrorCallReply>`, `CallReply` is an enum which allows to check the `CallReply` type using a `match` statement.
  • Loading branch information
gkorland authored Apr 5, 2023
1 parent deeb441 commit d79071c
Show file tree
Hide file tree
Showing 15 changed files with 1,129 additions and 199 deletions.
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ crate-type = ["cdylib"]
name = "stream"
crate-type = ["cdylib"]

[[example]]
name = "response"
crate-type = ["cdylib"]

[dependencies]
bitflags = "1.2"
libc = "0.2"
Expand Down
51 changes: 42 additions & 9 deletions examples/call.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
#[macro_use]
extern crate redis_module;

use redis_module::raw::*;
use redis_module::{
CallOptionsBuilder, CallReply, Context, RedisError, RedisResult, RedisString, RootCallReply,
CallOptionResp, CallOptionsBuilder, CallReply, CallResult, Context, RedisError, RedisResult,
RedisString,
};

fn call_test(ctx: &Context, _: Vec<RedisString>) -> RedisResult {
Expand Down Expand Up @@ -66,15 +66,48 @@ fn call_test(ctx: &Context, _: Vec<RedisString>) -> RedisResult {
}

let call_options = CallOptionsBuilder::new().script_mode().errors_as_replies();
let res: RootCallReply = ctx.call_ext::<&[&str; 0], _>("SHUTDOWN", &call_options.build(), &[]);
if res.get_type() != ReplyType::Error {
let res: CallResult = ctx.call_ext::<&[&str; 0], _>("SHUTDOWN", &call_options.build(), &[]);
if let Err(err) = res {
let error_msg = err.to_string().unwrap();
if !error_msg.contains("not allow") {
return Err(RedisError::String(format!(
"Failed to verify error messages, expected error message to contain 'not allow', error message: '{error_msg}'",
)));
}
} else {
return Err(RedisError::Str("Failed to set script mode on call_ext"));
}
let error_msg = res.get_string().unwrap();
if !error_msg.contains("not allow") {
return Err(RedisError::String(format!(
"Failed to verify error messages, expected error message to contain 'not allow', error message: '{error_msg}'",
)));

// test resp3 on call_ext
let call_options = CallOptionsBuilder::new()
.script_mode()
.resp_3(CallOptionResp::Resp3)
.errors_as_replies()
.build();
ctx.call_ext::<_, CallResult>("HSET", &call_options, &["x", "foo", "bar"])
.map_err(|e| -> RedisError { e.into() })?;
let res: CallReply = ctx
.call_ext::<_, CallResult>("HGETALL", &call_options, &["x"])
.map_err(|e| -> RedisError { e.into() })?;
if let CallReply::Map(map) = res {
let res = map.iter().fold(Vec::new(), |mut vec, (key, val)| {
if let CallReply::String(key) = key.unwrap() {
vec.push(key.to_string().unwrap());
}
if let CallReply::String(val) = val.unwrap() {
vec.push(val.to_string().unwrap());
}
vec
});
if res != vec!["foo".to_string(), "bar".to_string()] {
return Err(RedisError::String(
"Reply of hgetall does not match expected value".into(),
));
}
} else {
return Err(RedisError::String(
"Did not get a set type on hgetall".into(),
));
}

Ok("pass".into())
Expand Down
74 changes: 74 additions & 0 deletions examples/response.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#[macro_use]
extern crate redis_module;

use redis_module::{
redisvalue::RedisValueKey, Context, NextArg, RedisError, RedisResult, RedisString, RedisValue,
};
use std::collections::{HashMap, HashSet};

fn map_mget(ctx: &Context, args: Vec<RedisString>) -> RedisResult {
if args.len() < 2 {
return Err(RedisError::WrongArity);
}

let mut args = args.into_iter().skip(1);
let key_name = args.next_arg()?;

let fields: Vec<RedisString> = args.collect();

let key = ctx.open_key(&key_name);
let values = key.hash_get_multi(&fields)?;
let res = match values {
None => RedisValue::Null,
Some(values) => {
let mut map: HashMap<RedisValueKey, RedisValue> = HashMap::with_capacity(fields.len());
for (field, value) in values.into_iter() {
map.insert(
RedisValueKey::BulkRedisString(field),
RedisValue::BulkRedisString(value),
);
}
RedisValue::Map(map)
}
};

Ok(res)
}

fn map_unique(ctx: &Context, args: Vec<RedisString>) -> RedisResult {
if args.len() < 2 {
return Err(RedisError::WrongArity);
}

let mut args = args.into_iter().skip(1);
let key_name = args.next_arg()?;

let fields: Vec<RedisString> = args.collect();

let key = ctx.open_key(&key_name);
let values = key.hash_get_multi(&fields)?;
let res = match values {
None => RedisValue::Null,
Some(values) => {
let mut set: HashSet<RedisValueKey> = HashSet::new();
for (_, value) in values.into_iter() {
set.insert(RedisValueKey::BulkRedisString(value));
}
RedisValue::Set(set)
}
};

Ok(res)
}

//////////////////////////////////////////////////////

redis_module! {
name: "response",
version: 1,
data_types: [],
commands: [
["map.mget", map_mget, "readonly", 1, 1, 1],
["map.unique", map_unique, "readonly", 1, 1, 1],
],
}
4 changes: 2 additions & 2 deletions examples/string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ redis_module! {
version: 1,
data_types: [],
commands: [
["string.set", string_set, "", 1, 1, 1],
["string.get", string_get, "", 1, 1, 1],
["string.set", string_set, "write fast deny-oom", 1, 1, 1],
["string.get", string_get, "readonly", 1, 1, 1],
],
}
Loading

0 comments on commit d79071c

Please sign in to comment.