#[cfg_attr(feature = "cargo-clippy",
allow(redundant_field_names, suspicious_arithmetic_impl))]
pub mod raw;
use error::CellError;
use libc::{c_int, c_long, c_longlong, size_t};
use std::error::Error;
use std::iter;
use std::ptr;
use std::string;
use time;
#[derive(Clone, Copy, Debug)]
pub enum LogLevel {
Debug,
Notice,
Verbose,
Warning,
}
#[derive(Debug)]
pub enum Reply {
Array,
Error,
Integer(i64),
Nil,
String(String),
Unknown,
}
pub trait Command {
fn name(&self) -> &'static str;
fn run(&self, r: Redis, args: &[&str]) -> Result<(), CellError>;
fn str_flags(&self) -> &'static str;
}
impl Command {
pub fn harness(
command: &Command,
ctx: *mut raw::RedisModuleCtx,
argv: *mut *mut raw::RedisModuleString,
argc: c_int,
) -> raw::Status {
let r = Redis { ctx };
let args = parse_args(argv, argc).unwrap();
let str_args: Vec<&str> = args.iter().map(|s| s.as_str()).collect();
match command.run(r, str_args.as_slice()) {
Ok(_) => raw::Status::Ok,
Err(e) => {
raw::reply_with_error(
ctx,
format!("Cell error: {}\0", e.description()).as_ptr(),
);
raw::Status::Err
}
}
}
}
pub struct Redis {
ctx: *mut raw::RedisModuleCtx,
}
impl Redis {
pub fn call(&self, command: &str, args: &[&str]) -> Result<Reply, CellError> {
log_debug!(self, "{} [began] args = {:?}", command, args);
let format: String = iter::repeat("s").take(args.len()).collect();
let terminated_args: Vec<RedisString> =
args.iter().map(|s| self.create_string(s)).collect();
let raw_reply = match args.len() {
1 => {
raw::call1::call(
self.ctx,
format!("{}\0", command).as_ptr(),
format!("{}\0", format).as_ptr(),
terminated_args[0].str_inner,
)
}
2 => raw::call2::call(
self.ctx,
format!("{}\0", command).as_ptr(),
format!("{}\0", format).as_ptr(),
terminated_args[0].str_inner,
terminated_args[1].str_inner,
),
3 => raw::call3::call(
self.ctx,
format!("{}\0", command).as_ptr(),
format!("{}\0", format).as_ptr(),
terminated_args[0].str_inner,
terminated_args[1].str_inner,
terminated_args[2].str_inner,
),
_ => return Err(error!("Can't support that many CALL arguments")),
};
let reply_res = manifest_redis_reply(raw_reply);
raw::free_call_reply(raw_reply);
if let Ok(ref reply) = reply_res {
log_debug!(self, "{} [ended] result = {:?}", command, reply);
}
reply_res
}
pub fn coerce_integer(
&self,
reply_res: Result<Reply, CellError>,
) -> Result<Reply, CellError> {
match reply_res {
Ok(Reply::String(s)) => match s.parse::<i64>() {
Ok(n) => Ok(Reply::Integer(n)),
_ => Ok(Reply::String(s)),
},
_ => reply_res,
}
}
pub fn create_string(&self, s: &str) -> RedisString {
RedisString::create(self.ctx, s)
}
pub fn log(&self, level: LogLevel, message: &str) {
raw::log(
self.ctx,
format!("{:?}\0", level).to_lowercase().as_ptr(),
format!("{}\0", message).as_ptr(),
);
}
pub fn log_debug(&self, message: &str) {
self.log(LogLevel::Notice, message);
}
pub fn open_key(&self, key: &str) -> RedisKey {
RedisKey::open(self.ctx, key)
}
pub fn open_key_writable(&self, key: &str) -> RedisKeyWritable {
RedisKeyWritable::open(self.ctx, key)
}
pub fn reply_array(&self, len: i64) -> Result<(), CellError> {
handle_status(
raw::reply_with_array(self.ctx, len as c_long),
"Could not reply with long",
)
}
pub fn reply_integer(&self, integer: i64) -> Result<(), CellError> {
handle_status(
raw::reply_with_long_long(self.ctx, integer as c_longlong),
"Could not reply with longlong",
)
}
pub fn reply_string(&self, message: &str) -> Result<(), CellError> {
let redis_str = self.create_string(message);
handle_status(
raw::reply_with_string(self.ctx, redis_str.str_inner),
"Could not reply with string",
)
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum KeyMode {
Read,
ReadWrite,
}
#[derive(Debug)]
pub struct RedisKey {
ctx: *mut raw::RedisModuleCtx,
key_inner: *mut raw::RedisModuleKey,
key_str: RedisString,
}
impl RedisKey {
fn open(ctx: *mut raw::RedisModuleCtx, key: &str) -> RedisKey {
let key_str = RedisString::create(ctx, key);
let key_inner = raw::open_key(ctx, key_str.str_inner, to_raw_mode(KeyMode::Read));
RedisKey {
ctx,
key_inner,
key_str,
}
}
pub fn is_null(&self) -> bool {
let null_key: *mut raw::RedisModuleKey = ptr::null_mut();
self.key_inner == null_key
}
pub fn read(&self) -> Result<Option<String>, CellError> {
let val = if self.is_null() {
None
} else {
Some(read_key(self.key_inner)?)
};
Ok(val)
}
}
impl Drop for RedisKey {
fn drop(&mut self) {
raw::close_key(self.key_inner);
}
}
pub struct RedisKeyWritable {
ctx: *mut raw::RedisModuleCtx,
key_inner: *mut raw::RedisModuleKey,
#[allow(dead_code)]
key_str: RedisString,
}
impl RedisKeyWritable {
fn open(ctx: *mut raw::RedisModuleCtx, key: &str) -> RedisKeyWritable {
let key_str = RedisString::create(ctx, key);
let key_inner =
raw::open_key(ctx, key_str.str_inner, to_raw_mode(KeyMode::ReadWrite));
RedisKeyWritable {
ctx,
key_inner,
key_str,
}
}
pub fn is_empty(&self) -> Result<bool, CellError> {
match self.read()? {
Some(s) => match s.as_str() {
"" => Ok(true),
_ => Ok(false),
},
_ => Ok(false),
}
}
pub fn read(&self) -> Result<Option<String>, CellError> {
Ok(Some(read_key(self.key_inner)?))
}
pub fn set_expire(&self, expire: time::Duration) -> Result<(), CellError> {
match raw::set_expire(self.key_inner, expire.num_milliseconds()) {
raw::Status::Ok => Ok(()),
raw::Status::Err => Err(error!("Error while setting key expire")),
}
}
pub fn write(&self, val: &str) -> Result<(), CellError> {
let val_str = RedisString::create(self.ctx, val);
match raw::string_set(self.key_inner, val_str.str_inner) {
raw::Status::Ok => Ok(()),
raw::Status::Err => Err(error!("Error while setting key")),
}
}
}
impl Drop for RedisKeyWritable {
fn drop(&mut self) {
raw::close_key(self.key_inner);
}
}
#[derive(Debug)]
pub struct RedisString {
ctx: *mut raw::RedisModuleCtx,
str_inner: *mut raw::RedisModuleString,
}
impl RedisString {
fn create(ctx: *mut raw::RedisModuleCtx, s: &str) -> RedisString {
let str_inner = raw::create_string(ctx, format!("{}\0", s).as_ptr(), s.len());
RedisString { ctx, str_inner }
}
}
impl Drop for RedisString {
fn drop(&mut self) {
raw::free_string(self.ctx, self.str_inner);
}
}
fn handle_status(status: raw::Status, message: &str) -> Result<(), CellError> {
match status {
raw::Status::Ok => Ok(()),
raw::Status::Err => Err(error!(message)),
}
}
fn manifest_redis_reply(
reply: *mut raw::RedisModuleCallReply,
) -> Result<Reply, CellError> {
match raw::call_reply_type(reply) {
raw::ReplyType::Integer => Ok(Reply::Integer(raw::call_reply_integer(reply))),
raw::ReplyType::Nil => Ok(Reply::Nil),
raw::ReplyType::String => {
let mut length: size_t = 0;
let bytes = raw::call_reply_string_ptr(reply, &mut length);
from_byte_string(bytes, length)
.map(Reply::String)
.map_err(CellError::from)
}
raw::ReplyType::Unknown => Ok(Reply::Unknown),
raw::ReplyType::Error => Err(error!("Redis replied with an error.")),
other => Err(error!("Don't yet handle Redis type: {:?}", other)),
}
}
fn manifest_redis_string(
redis_str: *mut raw::RedisModuleString,
) -> Result<String, string::FromUtf8Error> {
let mut length: size_t = 0;
let bytes = raw::string_ptr_len(redis_str, &mut length);
from_byte_string(bytes, length)
}
fn parse_args(
argv: *mut *mut raw::RedisModuleString,
argc: c_int,
) -> Result<Vec<String>, string::FromUtf8Error> {
let mut args: Vec<String> = Vec::with_capacity(argc as usize);
for i in 0..argc {
let redis_str = unsafe { *argv.offset(i as isize) };
args.push(manifest_redis_string(redis_str)?);
}
Ok(args)
}
fn from_byte_string(
byte_str: *const u8,
length: size_t,
) -> Result<String, string::FromUtf8Error> {
let mut vec_str: Vec<u8> = Vec::with_capacity(length as usize);
for j in 0..length {
let byte: u8 = unsafe { *byte_str.offset(j as isize) };
vec_str.insert(j, byte);
}
String::from_utf8(vec_str)
}
fn read_key(key: *mut raw::RedisModuleKey) -> Result<String, string::FromUtf8Error> {
let mut length: size_t = 0;
from_byte_string(
raw::string_dma(key, &mut length, raw::KeyMode::READ),
length,
)
}
fn to_raw_mode(mode: KeyMode) -> raw::KeyMode {
match mode {
KeyMode::Read => raw::KeyMode::READ,
KeyMode::ReadWrite => raw::KeyMode::READ | raw::KeyMode::WRITE,
}
}