use crate::errors::ZystError;
use crate::response::ZystResponse;
use crate::types::{Command, CommandArgs, Db, DbValue, Key};
use regex::Regex;
pub async fn get_key(db: &Db, command: Command) -> Result<ZystResponse, ZystError> {
let key_name = match &command.args {
CommandArgs::SingleKey(key) => &key.clone(),
_ => return Err(ZystError::InvalidCommand),
};
let key = {
let db_read = db.read().await;
db_read.get(key_name).cloned() };
let key = match key {
Some(DbValue::StringKey(k)) => k,
None => return Ok(ZystResponse::Nil),
Some(_) => return Err(ZystError::WrongType),
};
if let Some(value) = &key.data {
let deleted = delete_expired_key(db, key.clone()).await;
if !deleted {
return Ok(ZystResponse::SimpleString(value.to_string()));
}
}
Ok(ZystResponse::Nil)
}
pub async fn set_key(db: &Db, command: Command) -> Result<ZystResponse, ZystError> {
let (key_name, value) = match command.args {
CommandArgs::KeyWithValue { key, value } => (key, value),
_ => return Err(ZystError::InvalidCommand),
};
let key = Key::new(key_name.clone(), Some(value.clone()), None);
db.write()
.await
.insert(key.name.clone(), DbValue::StringKey(key));
Ok(ZystResponse::Ok)
}
pub async fn delete_key(db: &Db, command: Command) -> Result<ZystResponse, ZystError> {
let keys = match &command.args {
CommandArgs::SingleKey(key) => vec![key.clone()],
CommandArgs::MultipleKeys(keys) => keys.to_vec(),
_ => return Err(ZystError::InvalidCommand),
};
let mut db_write = db.write().await;
let mut deleted_count = 0;
for key in keys {
if db_write.swap_remove(&key).is_some() {
deleted_count += 1;
}
}
Ok(ZystResponse::Int(deleted_count))
}
pub async fn incr(db: &Db, command: Command) -> Result<ZystResponse, ZystError> {
incr_decr(db, command, true).await
}
pub async fn decr(db: &Db, command: Command) -> Result<ZystResponse, ZystError> {
incr_decr(db, command, false).await
}
pub async fn incrby(db: &Db, command: Command) -> Result<ZystResponse, ZystError> {
let (key_name, by_str) = match &command.args {
CommandArgs::KeyWithValue { key, value } => (key.clone(), value.clone()),
_ => return Err(ZystError::InvalidCommand),
};
let by = match by_str.parse::<i64>() {
Ok(num) => num,
Err(_) => return Err(ZystError::NotInt),
};
let mut db_write = db.write().await;
let key = match db_write.get_mut(&key_name) {
Some(DbValue::StringKey(existing_key)) => existing_key,
None => {
db_write.insert(
key_name.clone(),
DbValue::StringKey(Key {
name: key_name.clone(),
data: Some("0".to_string()),
..Default::default()
}),
);
match db_write.get_mut(&key_name) {
Some(DbValue::StringKey(new_key)) => new_key,
_ => return Err(ZystError::DatabaseError),
}
}
Some(_) => return Err(ZystError::WrongType),
};
let num_str = key.data.as_deref().unwrap_or("0");
let num = match num_str.parse::<i64>() {
Ok(n) => n,
Err(_) => return Err(ZystError::NotInt),
};
let new_value = num + by;
key.data = Some(new_value.to_string());
Ok(ZystResponse::Int(new_value))
}
async fn incr_decr(db: &Db, command: Command, inc: bool) -> Result<ZystResponse, ZystError> {
let key_name = match command.args {
CommandArgs::SingleKey(key) => key,
_ => return Err(ZystError::InvalidCommand),
};
let mut db_write = db.write().await;
let key = match db_write.get_mut(&key_name) {
Some(DbValue::StringKey(key)) => key,
None => {
let key = Key::new(key_name.clone(), Some("0".to_string()), None);
db_write.insert(key_name.clone(), DbValue::StringKey(key));
match db_write.get_mut(&key_name) {
Some(DbValue::StringKey(key)) => key,
Some(DbValue::ListKey(_)) => return Err(ZystError::WrongType),
_ => return Err(ZystError::DatabaseError),
}
}
Some(_) => return Err(ZystError::WrongType),
};
let Ok(num) = key.data.as_deref().unwrap_or("0").parse::<i64>() else {
return Err(ZystError::NotInt);
};
let new_value = if inc { num + 1 } else { num - 1 };
key.data = Some(new_value.to_string());
Ok(ZystResponse::Int(new_value))
}
pub async fn get_keys(db: &Db, command: Command) -> Result<ZystResponse, ZystError> {
let pattern = match &command.args {
CommandArgs::SingleKey(key) => key,
_ => return Err(ZystError::InvalidCommand),
};
let regex_pattern = convert_redis_pattern_to_regex(pattern);
let re = match Regex::new(®ex_pattern) {
Ok(re) => re,
Err(_) => return Err(ZystError::RegexError),
};
let mut results = vec![];
let db_read = db.read().await;
for key in db_read.keys() {
if re.is_match(key) {
results.push(key.clone());
}
}
if results.is_empty() {
return Ok(ZystResponse::EmptyArray);
}
Ok(ZystResponse::List(results))
}
pub async fn exists(db: &Db, command: Command) -> Result<ZystResponse, ZystError> {
let keys = match &command.args {
CommandArgs::SingleKey(key) => vec![key.to_string()],
CommandArgs::MultipleKeys(keys) => keys.to_vec(),
_ => return Err(ZystError::InvalidCommand),
};
let db_read = db.read().await;
let nb_keys = keys.iter().filter(|key| db_read.contains_key(*key)).count() as i64;
Ok(ZystResponse::Int(nb_keys))
}
pub async fn expire(db: &Db, command: Command) -> Result<ZystResponse, ZystError> {
let (key_name, ttl) = match command.args {
CommandArgs::KeyWithValue { key, value } => (key, value),
_ => return Err(ZystError::InvalidCommand),
};
let ttl = ttl.parse::<i64>().map_err(|_| ZystError::TTL)?;
let mut db_write = db.write().await;
match db_write.get_mut(&key_name) {
Some(DbValue::StringKey(key)) => {
key.set_ttl(ttl);
Ok(ZystResponse::Int(1))
}
Some(DbValue::ListKey(key)) => {
key.set_ttl(ttl);
Ok(ZystResponse::Int(1))
}
Some(DbValue::SetKey(key)) => {
key.set_ttl(ttl);
Ok(ZystResponse::Int(1))
}
Some(DbValue::HashKey(key)) => {
key.set_ttl(ttl);
Ok(ZystResponse::Int(1))
}
None => Ok(ZystResponse::Int(0)),
}
}
pub async fn ttl(db: &Db, command: Command) -> Result<ZystResponse, ZystError> {
let key_name = match command.args {
CommandArgs::SingleKey(key) => key,
_ => return Err(ZystError::InvalidCommand),
};
let db_read = db.read().await;
let key = match db_read.get(&key_name) {
Some(DbValue::StringKey(key)) => key,
None => return Ok(ZystResponse::Int(-2)),
Some(_) => return Err(ZystError::WrongType),
};
Ok(ZystResponse::Int(key.get_ttl()))
}
pub fn convert_redis_pattern_to_regex(pattern: &str) -> String {
let mut regex_pattern = String::from("^");
for c in pattern.chars() {
match c {
'*' => regex_pattern.push_str(".*"),
'?' => regex_pattern.push('.'),
'[' => regex_pattern.push('['),
']' => regex_pattern.push(']'),
_ => regex_pattern.push_str(®ex::escape(&c.to_string())), }
}
regex_pattern.push('$');
regex_pattern
}
pub async fn delete_expired_key(db: &Db, key: Key) -> bool {
let mut db_write = db.write().await;
if key.is_expired() {
db_write.swap_remove(&key.name);
return true;
}
false
}