Skip to content

Commit

Permalink
fix #207 add support for RedisModule_GetCurrentCommandName (#208)
Browse files Browse the repository at this point in the history
* fix #207 add support for RedisModule_GetCurrentCommandName

* current_command_name returns Result + add test

* Fix test ports, remove temp debug code for redis 6.2.5

* Fix test_command_name for redis<6.2.5

Co-authored-by: oshadmi <[email protected]>
  • Loading branch information
gkorland and oshadmi authored Feb 16, 2022
1 parent d6aef18 commit 53f494b
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 59 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ required-features = ["experimental-api"]
[[example]]
name = "test_helper"
crate-type = ["cdylib"]
required-features = ["test"]
required-features = ["test","experimental-api"]

[[example]]
name = "info"
Expand Down
22 changes: 0 additions & 22 deletions examples/hello.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
#[macro_use]
extern crate redis_module;

use redis_module::InfoContext;
use redis_module::Status;

use redis_module::{Context, RedisError, RedisResult, RedisString};
fn hello_mul(_: &Context, args: Vec<RedisString>) -> RedisResult {
if args.len() < 2 {
Expand All @@ -24,32 +21,13 @@ fn hello_mul(_: &Context, args: Vec<RedisString>) -> RedisResult {
Ok(response.into())
}

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

let msg = args.get(1).unwrap();

ctx.reply_error_string(msg.try_as_str().unwrap());
Ok(().into())
}

fn add_info(ctx: &InfoContext, _for_crash_report: bool) {
if ctx.add_info_section(Some("hello")) == Status::Ok {
ctx.add_info_field_str("field", "hello_value");
}
}

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

redis_module! {
name: "hello",
version: 1,
data_types: [],
info: add_info,
commands: [
["hello.mul", hello_mul, "", 0, 0, 0],
["hello.err", hello_err, "", 0, 0, 0],
],
}
36 changes: 26 additions & 10 deletions examples/test_helper.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
#[macro_use]
extern crate redis_module;

use redis_module::{Context, RedisResult, RedisString};
use redis_module::InfoContext;
use redis_module::Status;
use redis_module::{Context, RedisError, RedisResult, RedisString};

fn test_helper_version(ctx: &Context, _args: Vec<RedisString>) -> RedisResult {
let ver = ctx.get_redis_version()?;
Expand All @@ -18,25 +20,39 @@ fn test_helper_version_rm_call(ctx: &Context, _args: Vec<RedisString>) -> RedisR
Ok(response.into())
}

//////////////////////////////////////////////////////
fn test_helper_command_name(ctx: &Context, _args: Vec<RedisString>) -> RedisResult {
Ok(ctx.current_command_name()?.into())
}

#[cfg(not(feature = "test"))]
redis_module! {
name: "test_helper",
version: 1,
data_types: [],
commands: [
["test_helper.version", test_helper_version, "", 0, 0, 0],
],
fn test_helper_err(ctx: &Context, args: Vec<RedisString>) -> RedisResult {
if args.len() < 1 {
return Err(RedisError::WrongArity);
}

let msg = args.get(1).unwrap();

ctx.reply_error_string(msg.try_as_str().unwrap());
Ok(().into())
}

fn add_info(ctx: &InfoContext, _for_crash_report: bool) {
if ctx.add_info_section(Some("test_helper")) == Status::Ok {
ctx.add_info_field_str("field", "test_helper_value");
}
}

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

#[cfg(feature = "test")]
redis_module! {
name: "test_helper",
version: 1,
data_types: [],
info: add_info,
commands: [
["test_helper.version", test_helper_version, "", 0, 0, 0],
["test_helper._version_rm_call", test_helper_version_rm_call, "", 0, 0, 0],
["test_helper.name", test_helper_command_name, "", 0, 0, 0],
["test_helper.err", test_helper_err, "", 0, 0, 0],
],
}
51 changes: 36 additions & 15 deletions src/context/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ use crate::{add_info_field_long_long, add_info_field_str, raw, utils, Status};
use crate::{add_info_section, LogLevel};
use crate::{RedisError, RedisResult, RedisString, RedisValue};

#[cfg(feature = "experimental-api")]
use std::ffi::CStr;

#[cfg(feature = "experimental-api")]
mod timer;

Expand Down Expand Up @@ -294,7 +297,19 @@ impl Context {
unsafe { raw::notify_keyspace_event(self.ctx, event_type, event, keyname) }
}

/// Returns the redis version either by calling ``RedisModule_GetServerVersion`` API,
#[cfg(feature = "experimental-api")]
pub fn current_command_name(&self) -> Result<String, RedisError> {
unsafe {
match raw::RedisModule_GetCurrentCommandName {
Some(cmd) => Ok(CStr::from_ptr(cmd(self.ctx)).to_str().unwrap().to_string()),
None => Err(RedisError::Str(
"API RedisModule_GetCurrentCommandName is not available",
)),
}
}
}

/// Returns the redis version either by calling RedisModule_GetServerVersion API,
/// Or if it is not available, by calling "info server" API and parsing the reply
pub fn get_redis_version(&self) -> Result<Version, RedisError> {
self.get_redis_version_internal(false)
Expand All @@ -306,6 +321,22 @@ impl Context {
self.get_redis_version_internal(true)
}

pub fn version_from_info(info: RedisValue) -> Result<Version, RedisError> {
if let RedisValue::SimpleString(info_str) = info {
if let Some(ver) = utils::get_regexp_captures(
info_str.as_str(),
r"(?m)\bredis_version:([0-9]+)\.([0-9]+)\.([0-9]+)\b",
) {
return Ok(Version {
major: ver[0].parse::<c_int>().unwrap(),
minor: ver[1].parse::<c_int>().unwrap(),
patch: ver[2].parse::<c_int>().unwrap(),
});
}
}
Err(RedisError::Str("Error getting redis_version"))
}

#[allow(clippy::not_unsafe_ptr_arg_deref)]
fn get_redis_version_internal(&self, force_use_rm_call: bool) -> Result<Version, RedisError> {
match unsafe { raw::RedisModule_GetServerVersion } {
Expand All @@ -315,21 +346,11 @@ impl Context {
}
_ => {
// Call "info server"
if let Ok(RedisValue::SimpleString(s)) = self.call("info", &["server"]) {
if let Some(ver) = utils::get_regexp_captures(
s.as_str(),
r"(?m)\bredis_version:([0-9]+)\.([0-9]+)\.([0-9]+)\b",
) {
return Ok(Version {
major: ver[0].parse::<c_int>().unwrap(),
minor: ver[1].parse::<c_int>().unwrap(),
patch: ver[2].parse::<c_int>().unwrap(),
});
}
if let Ok(info) = self.call("info", &["server"]) {
Context::version_from_info(info)
} else {
Err(RedisError::Str("Error calling \"info server\""))
}
Err(RedisError::Str(
"Error getting redis_version from \"info server\" call",
))
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/include/redismodule.h
Original file line number Diff line number Diff line change
Expand Up @@ -838,6 +838,7 @@ REDISMODULE_API int (*RedisModule_AuthenticateClientWithUser)(RedisModuleCtx *ct
REDISMODULE_API int (*RedisModule_DeauthenticateAndCloseClient)(RedisModuleCtx *ctx, uint64_t client_id) REDISMODULE_ATTR;
REDISMODULE_API RedisModuleString * (*RedisModule_GetClientCertificate)(RedisModuleCtx *ctx, uint64_t id) REDISMODULE_ATTR;
REDISMODULE_API int *(*RedisModule_GetCommandKeys)(RedisModuleCtx *ctx, RedisModuleString **argv, int argc, int *num_keys) REDISMODULE_ATTR;
REDISMODULE_API const char *(*RedisModule_GetCurrentCommandName)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_RegisterDefragFunc)(RedisModuleCtx *ctx, RedisModuleDefragFunc func) REDISMODULE_ATTR;
REDISMODULE_API void *(*RedisModule_DefragAlloc)(RedisModuleDefragCtx *ctx, void *ptr) REDISMODULE_ATTR;
REDISMODULE_API RedisModuleString *(*RedisModule_DefragRedisModuleString)(RedisModuleDefragCtx *ctx, RedisModuleString *str) REDISMODULE_ATTR;
Expand Down Expand Up @@ -1110,6 +1111,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
REDISMODULE_GET_API(AuthenticateClientWithUser);
REDISMODULE_GET_API(GetClientCertificate);
REDISMODULE_GET_API(GetCommandKeys);
REDISMODULE_GET_API(GetCurrentCommandName);
REDISMODULE_GET_API(RegisterDefragFunc);
REDISMODULE_GET_API(DefragAlloc);
REDISMODULE_GET_API(DefragRedisModuleString);
Expand Down
59 changes: 48 additions & 11 deletions tests/integration.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
use crate::utils::{get_redis_connection, start_redis_server_with_module};
use anyhow::Context;
use anyhow::Result;
use redis::RedisError;

use utils::{get_redis_connection, start_redis_server_with_module};

mod utils;

#[test]
Expand Down Expand Up @@ -74,38 +73,76 @@ fn test_test_helper_version() -> Result<()> {
Ok(())
}

#[cfg(feature = "experimental-api")]
#[test]
fn test_hello_info() -> Result<()> {
fn test_command_name() -> Result<()> {
use redis_module::RedisValue;

let port: u16 = 6482;
let _guards = vec![start_redis_server_with_module("hello", port)
let _guards = vec![start_redis_server_with_module("test_helper", port)
.with_context(|| "failed to start redis server")?];
let mut con =
get_redis_connection(port).with_context(|| "failed to connect to redis server")?;

// Call the tested command
let res: Result<String, RedisError> = redis::cmd("test_helper.name").query(&mut con);

// The expected result is according to redis version
let info: String = redis::cmd("info")
.arg(&["server"])
.query(&mut con)
.with_context(|| "failed to run test_helper.name")?;

if let Ok(ver) = redis_module::Context::version_from_info(RedisValue::SimpleString(info)) {
if ver.major > 6
|| (ver.major == 6 && ver.minor > 2)
|| (ver.major == 6 && ver.minor == 2 && ver.patch >= 5)
{
assert_eq!(res.unwrap(), String::from("test_helper.name"));
} else {
assert!(res
.err()
.unwrap()
.to_string()
.contains("RedisModule_GetCurrentCommandName is not available"));
}
}

Ok(())
}

#[test]
fn test_test_helper_info() -> Result<()> {
let port: u16 = 6483;
let _guards = vec![start_redis_server_with_module("test_helper", port)
.with_context(|| "failed to start redis server")?];
let mut con =
get_redis_connection(port).with_context(|| "failed to connect to redis server")?;

let res: String = redis::cmd("INFO")
.arg("HELLO")
.arg("TEST_HELPER")
.query(&mut con)
.with_context(|| "failed to run INFO HELLO")?;
assert!(res.contains("hello_field:hello_value"));
.with_context(|| "failed to run INFO TEST_HELPER")?;
assert!(res.contains("test_helper_field:test_helper_value"));

Ok(())
}

#[allow(unused_must_use)]
#[test]
fn test_hello_err() -> Result<()> {
let port: u16 = 6483;
fn test_test_helper_err() -> Result<()> {
let port: u16 = 6484;
let _guards = vec![start_redis_server_with_module("hello", port)
.with_context(|| "failed to start redis server")?];
let mut con =
get_redis_connection(port).with_context(|| "failed to connect to redis server")?;

// Make sure embedded nulls do not cause a crash
redis::cmd("hello.err")
redis::cmd("test_helper.err")
.arg(&["\x00\x00"])
.query::<()>(&mut con);

redis::cmd("hello.err")
redis::cmd("test_helper.err")
.arg(&["no crash\x00"])
.query::<()>(&mut con);

Expand Down

0 comments on commit 53f494b

Please sign in to comment.