aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoromagdy <omar.professional8777@gmail.com>2025-07-20 16:29:34 +0300
committeromagdy <omar.professional8777@gmail.com>2025-07-20 16:29:34 +0300
commit846479e6bcf8238879546534b09e141b9bb668f8 (patch)
treeac2e65b5842bea095107c0a750843d0cf7bc74b4
parent38b649ea16d8ed053fd9222bfb9867e3432ee2a6 (diff)
downloadredis-rust-846479e6bcf8238879546534b09e141b9bb668f8.tar.xz
redis-rust-846479e6bcf8238879546534b09e141b9bb668f8.zip
feat: Added parsing of simple command line arguments and CONFIG GET command implementation
-rw-r--r--src/lib.rs44
-rw-r--r--src/main.rs27
-rw-r--r--src/resp_commands.rs42
-rw-r--r--src/resp_parser.rs6
-rw-r--r--tests/test_commands.rs5
5 files changed, 111 insertions, 13 deletions
diff --git a/src/lib.rs b/src/lib.rs
index bf4f302..060106b 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,5 +1,49 @@
+use std::{env, sync::Arc};
+
#[macro_use]
pub mod macros;
pub mod resp_commands;
pub mod resp_parser;
pub mod shared_cache;
+
+#[derive(Debug, Default)]
+pub struct Config {
+ pub dir: Option<String>,
+ pub dbfilename: Option<String>,
+}
+
+pub type SharedConfig = Arc<Option<Config>>;
+
+impl Config {
+ pub fn new() -> Result<Config, String> {
+ let args: Vec<String> = env::args().collect();
+
+ let mut dir = None;
+ let mut dbfilename = None;
+
+ let mut i = 1; // Skip program name
+ while i < args.len() {
+ match args[i].as_str() {
+ "--dir" => {
+ if i + 1 >= args.len() {
+ return Err("--dir requires a value".to_string());
+ }
+ dir = Some(args[i + 1].clone());
+ i += 2;
+ }
+ "--dbfilename" => {
+ if i + 1 >= args.len() {
+ return Err("--dbfilename requires a value".to_string());
+ }
+ dbfilename = Some(args[i + 1].clone());
+ i += 2;
+ }
+ _ => {
+ return Err(format!("Unknown argument: {}", args[i]));
+ }
+ }
+ }
+
+ Ok(Config { dir, dbfilename })
+ }
+}
diff --git a/src/main.rs b/src/main.rs
index 6fb16b8..b29e2ce 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,6 +1,7 @@
#![allow(unused_imports)]
use std::{
collections::HashMap,
+ env,
io::{Read, Write},
net::{TcpListener, TcpStream},
sync::{Arc, Mutex},
@@ -8,9 +9,12 @@ use std::{
time::{Duration, SystemTime, UNIX_EPOCH},
};
-use codecrafters_redis::resp_commands::RedisCommands;
-use codecrafters_redis::resp_parser::{parse, RespType};
use codecrafters_redis::shared_cache::*;
+use codecrafters_redis::{resp_commands::RedisCommands, Config};
+use codecrafters_redis::{
+ resp_parser::{parse, RespType},
+ SharedConfig,
+};
fn spawn_cleanup_thread(cache: SharedCache) {
let cache_clone = cache.clone();
@@ -30,7 +34,7 @@ fn spawn_cleanup_thread(cache: SharedCache) {
});
}
-fn handle_client(mut stream: TcpStream, cache: SharedCache) {
+fn handle_client(mut stream: TcpStream, cache: SharedCache, config: SharedConfig) {
let mut buffer = [0; 512];
loop {
@@ -41,7 +45,7 @@ fn handle_client(mut stream: TcpStream, cache: SharedCache) {
};
let parsed_resp = parse(&buffer).unwrap();
- let response = RedisCommands::from(parsed_resp.0).execute(cache.clone());
+ let response = RedisCommands::from(parsed_resp.0).execute(cache.clone(), config.clone());
// write respose back to the client
stream.write(&response).unwrap();
@@ -51,15 +55,28 @@ fn handle_client(mut stream: TcpStream, cache: SharedCache) {
fn main() -> std::io::Result<()> {
let listener = TcpListener::bind("127.0.0.1:6379").unwrap();
let cache: SharedCache = Arc::new(Mutex::new(HashMap::new()));
+ let mut config: SharedConfig = None.into();
spawn_cleanup_thread(cache.clone());
+ match Config::new() {
+ Ok(conf) => {
+ config = Arc::new(Some((conf)));
+ }
+ Err(e) => {
+ config = Arc::new(None);
+ eprintln!("Error: {}", e);
+ std::process::exit(1);
+ }
+ }
+
for stream in listener.incoming() {
match stream {
Ok(stream) => {
let cache_clone = cache.clone();
+ let config_clone = Arc::clone(&config);
thread::spawn(|| {
- handle_client(stream, cache_clone);
+ handle_client(stream, cache_clone, config_clone);
});
}
Err(e) => {
diff --git a/src/resp_commands.rs b/src/resp_commands.rs
index 4d01507..37273f8 100644
--- a/src/resp_commands.rs
+++ b/src/resp_commands.rs
@@ -1,6 +1,6 @@
use crate::{resp_parser::*, shared_cache::*};
-use std::collections::HashMap;
-use std::time::{Duration, SystemTime, UNIX_EPOCH};
+use crate::{Config, SharedConfig};
+use std::time::{SystemTime, UNIX_EPOCH};
#[derive(Debug, Clone)]
pub enum SetCondition {
@@ -118,11 +118,12 @@ pub enum RedisCommands {
ECHO(String),
GET(String),
SET(SetCommand),
+ CONFIG_GET(String),
Invalid,
}
impl RedisCommands {
- pub fn execute(self, cache: SharedCache) -> Vec<u8> {
+ pub fn execute(self, cache: SharedCache, config: SharedConfig) -> Vec<u8> {
match self {
RedisCommands::PING => resp!("PONG"),
RedisCommands::ECHO(echo_string) => resp!(echo_string),
@@ -193,6 +194,29 @@ impl RedisCommands {
None => return resp!(null),
}
}
+ RedisCommands::CONFIG_GET(s) => {
+ use RespType as RT;
+ let config = config.clone();
+ if let Some(conf) = config.as_ref() {
+ let dir = conf.dir.clone().unwrap();
+ let dbfilename = conf.dbfilename.clone().unwrap();
+ match s.as_str() {
+ "dir" => RT::Array(vec![
+ RT::BulkString(s.as_bytes().to_vec()),
+ RT::BulkString(dir.as_bytes().to_vec()),
+ ])
+ .to_resp_bytes(),
+ "dbfilename" => RT::Array(vec![
+ RT::BulkString(s.as_bytes().to_vec()),
+ RT::BulkString(dbfilename.as_bytes().to_vec()),
+ ])
+ .to_resp_bytes(),
+ _ => unreachable!(),
+ }
+ } else {
+ unreachable!()
+ }
+ }
RedisCommands::Invalid => todo!(),
}
}
@@ -347,6 +371,18 @@ impl From<RespType> for RedisCommands {
}
}
}
+ "CONFIG" => {
+ let Some(sub_command) = args.next() else {
+ return Self::Invalid;
+ };
+ let Some(key) = args.next() else {
+ return Self::Invalid;
+ };
+ if &sub_command.to_uppercase() == &"GET" {
+ return Self::CONFIG_GET(key);
+ }
+ Self::Invalid
+ }
_ => Self::Invalid,
}
}
diff --git a/src/resp_parser.rs b/src/resp_parser.rs
index 0563188..a00bc1b 100644
--- a/src/resp_parser.rs
+++ b/src/resp_parser.rs
@@ -587,11 +587,11 @@ impl RespType {
let len = arr.len();
let elements = arr
.iter()
- .map(|e| e.to_resp_bytes())
- .collect::<Vec<Vec<u8>>>();
+ .flat_map(|e| String::from_utf8(e.to_resp_bytes()))
+ .collect::<String>();
// TODO: Implement proper Display for elements because this will definitely not
// work
- format!("*{:?}\r\n{:?}", len, elements).into_bytes()
+ format!("*{}\r\n{}", len, elements).into_bytes()
}
// this is just a hack because the platform uses RESP2 in RESP3 it should be "_\r\n"
RespType::Null() => b"$-1\r\n".into(),
diff --git a/tests/test_commands.rs b/tests/test_commands.rs
index 10e6c9c..1031c8b 100644
--- a/tests/test_commands.rs
+++ b/tests/test_commands.rs
@@ -141,7 +141,7 @@ mod command_parser_tests {
/// Tests for the command execution logic in `RedisCommands::execute`.
mod command_execution_tests {
- use codecrafters_redis::resp_commands::RedisCommands;
+ use codecrafters_redis::{resp_commands::RedisCommands, Config};
use std::time::Duration;
use super::*;
@@ -149,7 +149,8 @@ mod command_execution_tests {
/// Helper to parse and execute a command against a cache.
fn run_command(cache: &SharedCache, args: &[&str]) -> Vec<u8> {
let command = RedisCommands::from(build_command_from_str_slice(args));
- command.execute(Arc::clone(cache))
+ let config = Arc::new(Some(Config::default()));
+ command.execute(Arc::clone(cache), config)
}
#[test]