Skip to content

Instantly share code, notes, and snippets.

@rightfold
Created October 1, 2016 12:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rightfold/00cb9558f0a7a3c483fd4b86a980eadb to your computer and use it in GitHub Desktop.
Save rightfold/00cb9558f0a7a3c483fd4b86a980eadb to your computer and use it in GitHub Desktop.
use std::ffi::CString;
use std::io;
use std::slice;
use libc::{c_char, c_int, c_longlong, c_void, size_t};
pub struct Redis(*mut c_void);
impl Redis {
pub fn connect(host: &str, port: u16) -> io::Result<Self> {
let c_host = try!(CString::new(host));
let c_redis = unsafe { redis_connect(c_host.into_raw(), port as c_int) };
if c_redis.is_null() {
Err(io::Error::new(io::ErrorKind::Other, "redis"))
} else {
Ok(Redis(c_redis))
}
}
pub fn command(&mut self, parts: &[&[u8]]) -> io::Result<ReplyBox> {
let c_parts: Vec<*const c_char> =
parts.iter()
.map(|p| p.as_ptr() as *const c_char)
.collect();
let c_part_lens: Vec<size_t> =
parts.iter()
.map(|p| p.len())
.collect();
let c_reply = unsafe {
redisCommandArgv(
self.0,
parts.len() as c_int,
c_parts.as_ptr(),
c_part_lens.as_ptr()
)
};
if c_reply.is_null() {
Err(io::Error::new(io::ErrorKind::Other, "redis"))
} else {
Ok(ReplyBox(ReplyRef(c_reply)))
}
}
}
impl Drop for Redis {
fn drop(&mut self) {
unsafe { redisFree(self.0); }
}
}
pub struct ReplyRef(*mut c_void);
impl ReplyRef {
pub fn deref<'a>(&'a self) -> Reply<'a> {
unsafe {
match redis_reply_type(self.0) {
0 => {
let slice = slice::from_raw_parts(
redis_reply_str(self.0) as *const u8,
redis_reply_len(self.0)
);
Reply::Status(slice)
},
1 => {
let slice = slice::from_raw_parts(
redis_reply_str(self.0) as *const u8,
redis_reply_len(self.0)
);
Reply::Error(slice)
},
2 => Reply::Integer(redis_reply_integer(self.0)),
3 => Reply::Nil,
4 => {
let slice = slice::from_raw_parts(
redis_reply_str(self.0) as *const u8,
redis_reply_len(self.0)
);
Reply::String(slice)
},
5 => {
let slice = slice::from_raw_parts(
redis_reply_element(self.0) as *const ReplyRef,
redis_reply_elements(self.0)
);
Reply::Array(slice)
},
_ => Reply::Unknown,
}
}
}
}
pub struct ReplyBox(ReplyRef);
impl ReplyBox {
pub fn deref<'a>(&'a self) -> Reply<'a> {
self.0.deref()
}
}
impl Drop for ReplyBox {
fn drop(&mut self) {
unsafe { freeReplyObject((self.0).0); }
}
}
pub enum Reply<'a> {
Status(&'a [u8]),
Error(&'a [u8]),
Integer(i64),
Nil,
String(&'a [u8]),
Array(&'a [ReplyRef]),
Unknown,
}
extern {
fn redis_connect(ip: *const c_char, port: c_int) -> *mut c_void;
fn redisFree(redis: *mut c_void);
fn redisCommandArgv(
redis: *mut c_void,
argc: c_int,
argv: *const *const c_char,
argvlen: *const size_t
) -> *mut c_void;
fn redis_reply_type(reply: *mut c_void) -> c_int;
fn redis_reply_integer(reply: *mut c_void) -> c_longlong;
fn redis_reply_len(reply: *mut c_void) -> size_t;
fn redis_reply_str(reply: *mut c_void) -> *const c_char;
fn redis_reply_elements(reply: *mut c_void) -> size_t;
fn redis_reply_element(reply: *mut c_void) -> *const *mut c_void;
fn freeReplyObject(reply: *mut c_void);
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_connect() {
Redis::connect("127.0.0.1", 6379).unwrap();
}
#[test]
fn test_command() {
let mut redis = Redis::connect("127.0.0.1", 6379).unwrap();
let reply = redis.command(&[b"EVAL", b"return 42", b"0"]).unwrap();
assert!(match reply.deref() {
Reply::Integer(42) => true,
_ => false,
});
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment