#[macro_use]
extern crate bitflags;
extern crate libc;
extern crate time;
#[macro_use]
mod macros;
pub mod cell;
pub mod error;
mod redis;
use cell::store;
use error::CellError;
use libc::c_int;
use redis::Command;
use redis::raw;
const MODULE_NAME: &str = "redis-cell";
const MODULE_VERSION: c_int = 1;
struct ThrottleCommand {}
impl Command for ThrottleCommand {
fn name(&self) -> &'static str {
"cl.throttle"
}
fn run(&self, r: redis::Redis, args: &[&str]) -> Result<(), CellError> {
if args.len() != 5 && args.len() != 6 {
return Err(error!(
"Usage: {} <key> <max_burst> <count per period> \
<period> [<quantity>]",
self.name()
));
}
let key = args[1];
let max_burst = parse_i64(args[2])?;
let count = parse_i64(args[3])?;
let period = parse_i64(args[4])?;
let quantity = match args.get(5) {
Some(n) => parse_i64(n)?,
None => 1,
};
let mut store = store::InternalRedisStore::new(&r);
let rate = cell::Rate::per_period(count, time::Duration::seconds(period));
let mut limiter = cell::RateLimiter::new(
&mut store,
&cell::RateQuota {
max_burst,
max_rate: rate,
},
);
let (throttled, rate_limit_result) = limiter.rate_limit(key, quantity)?;
r.reply_array(5)?;
r.reply_integer(if throttled { 1 } else { 0 })?;
r.reply_integer(rate_limit_result.limit)?;
r.reply_integer(rate_limit_result.remaining)?;
r.reply_integer(rate_limit_result.retry_after.num_seconds())?;
r.reply_integer(rate_limit_result.reset_after.num_seconds())?;
Ok(())
}
fn str_flags(&self) -> &'static str {
"write"
}
}
#[allow(non_snake_case)]
#[allow(unused_variables)]
#[no_mangle]
pub extern "C" fn Throttle_RedisCommand(
ctx: *mut raw::RedisModuleCtx,
argv: *mut *mut raw::RedisModuleString,
argc: c_int,
) -> raw::Status {
Command::harness(&ThrottleCommand {}, ctx, argv, argc)
}
#[allow(non_snake_case)]
#[allow(unused_variables)]
#[no_mangle]
pub extern "C" fn RedisModule_OnLoad(
ctx: *mut raw::RedisModuleCtx,
argv: *mut *mut raw::RedisModuleString,
argc: c_int,
) -> raw::Status {
if raw::init(
ctx,
format!("{}\0", MODULE_NAME).as_ptr(),
MODULE_VERSION,
raw::REDISMODULE_APIVER_1,
) == raw::Status::Err
{
return raw::Status::Err;
}
let command = ThrottleCommand {};
if raw::create_command(
ctx,
format!("{}\0", command.name()).as_ptr(),
Some(Throttle_RedisCommand),
format!("{}\0", command.str_flags()).as_ptr(),
0,
0,
0,
) == raw::Status::Err
{
return raw::Status::Err;
}
raw::Status::Ok
}
fn parse_i64(arg: &str) -> Result<i64, CellError> {
arg.parse::<i64>()
.map_err(|_| error!("Couldn't parse as integer: {}", arg))
}