diff options
| -rw-r--r-- | src/lib.rs | 44 | ||||
| -rw-r--r-- | src/main.rs | 27 | ||||
| -rw-r--r-- | src/resp_commands.rs | 42 | ||||
| -rw-r--r-- | src/resp_parser.rs | 6 | ||||
| -rw-r--r-- | tests/test_commands.rs | 5 |
5 files changed, 111 insertions, 13 deletions
@@ -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] |
