diff --git a/Cargo.lock b/Cargo.lock index 0ffc053e..33870b2f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -343,10 +343,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "terrabase-cli" -version = "0.1.0" - [[package]] name = "tokio" version = "0.2.21" @@ -382,6 +378,13 @@ dependencies = [ "syn", ] +[[package]] +name = "tsh" +version = "0.1.0" +dependencies = [ + "libcore", +] + [[package]] name = "unicode-xid" version = "0.2.1" diff --git a/cli/Cargo.toml b/cli/Cargo.toml index c9b64972..1fc3f6f1 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "terrabase-cli" +name = "tsh" version = "0.1.0" authors = ["Sayan Nandan "] edition = "2018" @@ -7,3 +7,4 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +libcore = {path="../libcore"} \ No newline at end of file diff --git a/cli/src/argparse.rs b/cli/src/argparse.rs index 3099f84c..0dabb4fa 100644 --- a/cli/src/argparse.rs +++ b/cli/src/argparse.rs @@ -19,195 +19,160 @@ * */ -use std::fmt; +use libcore::terrapipe::DEF_R_META_BUFSIZE; +use libcore::terrapipe::{Dataframe, ResponseCodes::*, ResultMetaframe, Version, QUERY_PACKET}; +use std::io::{BufRead, BufReader, Read, Write}; +use std::net::TcpStream; use std::process; -/// `SET` command line argument -const ARG_SET: &'static str = "set"; -/// `GET` command line argument -const ARG_GET: &'static str = "get"; -/// `UPDATE` command line argument -const ARG_UPDATE: &'static str = "update"; -/// `EXIT` command line argument -const ARG_EXIT: &'static str = "exit"; -/// Error message when trying to get multiple keys at the same time (TEMP) -const ERR_GET_MULTIPLE: &'static str = "GET only supports fetching one key at a time"; -/// Error message when trying to set multiple keys at the same time (TEMP) -const ERR_SET_MULTIPLE: &'static str = "SET only supports setting one key at a time"; -/// Error message when trying to update multiple keys at the same time (TEMP) -const ERR_UPDATE_MULTIPLE: &'static str = "UPDATE only supports updating one key at a time"; - -/// Representation for a key/value pair -#[derive(Debug, PartialEq)] -pub struct KeyValue(Key, String); - -/// `Key` an alias for `String` -pub type Key = String; - -/// Directly parsed commands from the command line -#[derive(Debug, PartialEq)] -pub enum Commands { - /// A `GET` command - GET, - /// A `SET` command - SET, - /// An `UPDATE` command - UPDATE, -} - -/// Prepared commands that can be executed -#[derive(Debug, PartialEq)] -pub enum FinalCommands { - /// Parsed `GET` command - GET(Key), - /// Parsed `SET` command - SET(KeyValue), - /// Parsed `UPDATE` command - UPDATE(KeyValue), -} - -/// Errors that may occur while parsing arguments -#[derive(Debug, PartialEq)] -pub enum ArgsError { - /// Expected more arguments - ExpectedMoreArgs, - /// Failed to fetch an argument - ArgGetError, - /// Unexpected argument - UndefinedArgError(String), - /// Other error - Other(&'static str), -} - -impl fmt::Display for ArgsError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use ArgsError::*; - match self { - ExpectedMoreArgs => write!(f, "error: Expected more arguments"), - ArgGetError => write!(f, "error: Failed to get argument"), - UndefinedArgError(arg) => write!(f, "error: Undefined argument '{}'", arg), - Other(e) => write!(f, "error: {}", e), - } - } -} - -/// Exits the process with an error message -pub const EXIT_ERROR: fn(&'static str) -> ! = |err| { - eprintln!("error: {}", err); +const ARG_GET: &'static str = "GET"; +const ARG_SET: &'static str = "SET"; +const ARG_UPDATE: &'static str = "UPDATE"; +const ARG_DEL: &'static str = "DEL"; +const ARG_EXIT: &'static str = "EXIT"; +const ERR_MULTIPLE_GET: &'static str = "Multiple GETs aren't supported yet"; +const ERR_MULTIPLE_SET: &'static str = "Multiple SETs aren't supported yet"; +const ERR_MULTIPLE_UPDATE: &'static str = "Multiple UPDATEs aren't supported yet"; +const ERR_MULTIPLE_DEL: &'static str = "Multiple DELs aren't supported yet"; +const SELF_VERSION: Version = Version(0, 1, 0); +const ADDR: &'static str = "127.0.0.1:2003"; +pub const EXIT_ERROR: fn(error: &str) -> ! = |error| { + eprintln!("error: {}", error); process::exit(0x100); }; -/// ### Parse a `String` argument into a corresponding `FinalCommands` variant -/// #### Errors -/// This returns an `Err(ArgsError)` if it fails to parse the arguments and the errors -/// can be displayed directly (i.e the errors implement the `fmt::Display` trait) -pub fn parse_args(args: String) -> Result { - let args: Vec = args - .split_whitespace() - .map(|v| v.to_lowercase().to_string()) - .collect(); - // HACK(@ohsayan) This is a temporary workaround we will need a proper parser - let primary_arg = match args.get(0) { - Some(arg) => arg, - None => { - return Err(ArgsError::ArgGetError); - } - }; - let mut actions = Vec::with_capacity(3); - match primary_arg.as_str() { - ARG_GET => actions.push(Commands::GET), - ARG_SET => actions.push(Commands::SET), - ARG_UPDATE => actions.push(Commands::UPDATE), - ARG_EXIT => { - println!("Goodbye!"); - process::exit(0x00); - } - _ => { - return Err(ArgsError::UndefinedArgError(primary_arg.to_owned())); - } - } +const NORMAL_ERROR: fn(error: &str) = |error| { + eprintln!("error: {}", error); +}; - match actions[0] { - Commands::GET => { - // Now read next command - if let Some(arg) = args.get(1) { +pub fn run(args: String) { + let args: Vec<&str> = args.split_whitespace().collect(); + match args[0].to_uppercase().as_str() { + ARG_GET => { + if let Some(key) = args.get(1) { if args.get(2).is_none() { - return Ok(FinalCommands::GET(arg.to_string())); + send_query(QUERY_PACKET( + SELF_VERSION, + ARG_GET.to_owned(), + key.to_string(), + )); } else { - return Err(ArgsError::Other(ERR_GET_MULTIPLE)); + NORMAL_ERROR(ERR_MULTIPLE_GET); } } else { - return Err(ArgsError::ExpectedMoreArgs); + NORMAL_ERROR("Expected one more argument"); } } - Commands::SET => { - // Now read next command - if let (Some(key), Some(value)) = (args.get(1), args.get(2)) { - if args.get(3).is_none() { - return Ok(FinalCommands::SET(KeyValue( - key.to_string(), - value.to_string(), - ))); + ARG_SET => { + if let Some(key) = args.get(1) { + if let Some(value) = args.get(2) { + if args.get(3).is_none() { + send_query(QUERY_PACKET( + SELF_VERSION, + ARG_SET.to_owned(), + format!("{} {}", key, value), + )) + } else { + NORMAL_ERROR(ERR_MULTIPLE_SET); + } } else { - return Err(ArgsError::Other(ERR_SET_MULTIPLE)); + NORMAL_ERROR("Expected one more argument"); } } else { - return Err(ArgsError::ExpectedMoreArgs); + NORMAL_ERROR("Expected more arguments"); } } - Commands::UPDATE => { - if let (Some(key), Some(value)) = (args.get(1), args.get(2)) { - if args.get(3).is_none() { - return Ok(FinalCommands::UPDATE(KeyValue( + ARG_UPDATE => { + if let Some(key) = args.get(1) { + if let Some(value) = args.get(2) { + if args.get(3).is_none() { + send_query(QUERY_PACKET( + SELF_VERSION, + ARG_UPDATE.to_owned(), + format!("{} {}", key, value), + )) + } else { + NORMAL_ERROR(ERR_MULTIPLE_UPDATE); + } + } else { + NORMAL_ERROR("Expected one more argument"); + } + } else { + NORMAL_ERROR("Expected more arguments"); + } + } + ARG_DEL => { + if let Some(key) = args.get(1) { + if args.get(2).is_none() { + send_query(QUERY_PACKET( + SELF_VERSION, + ARG_DEL.to_owned(), key.to_string(), - value.to_string(), - ))); + )); } else { - return Err(ArgsError::Other(ERR_UPDATE_MULTIPLE)); + NORMAL_ERROR(ERR_MULTIPLE_DEL); } } else { - return Err(ArgsError::ExpectedMoreArgs); + NORMAL_ERROR("Expected one more argument"); } } + ARG_EXIT => { + println!("Goodbye!"); + process::exit(0x100) + } + _ => NORMAL_ERROR("Unknown command"), } } -#[cfg(test)] -#[test] -fn test_argparse_valid_cmds() { - let test_set_arg1 = "set sayan 100".to_owned(); - let test_set_arg2 = "SET sayan 100".to_owned(); - let test_set_arg3 = "SeT sayan 100".to_owned(); - let test_get_arg1 = "get sayan".to_owned(); - let test_get_arg2 = "GET sayan".to_owned(); - let test_get_arg3 = "GeT sayan".to_owned(); - let test_set_result: Result = Ok(FinalCommands::SET(KeyValue( - "sayan".to_owned(), - "100".to_owned(), - ))); - let test_get_result: Result = - Ok(FinalCommands::GET("sayan".to_owned())); - assert_eq!(parse_args(test_get_arg1), test_get_result); - assert_eq!(parse_args(test_get_arg2), test_get_result); - assert_eq!(parse_args(test_get_arg3), test_get_result); - assert_eq!(parse_args(test_set_arg1), test_set_result); - assert_eq!(parse_args(test_set_arg2), test_set_result); - assert_eq!(parse_args(test_set_arg3), test_set_result); -} - -#[cfg(test)] -#[test] -fn test_argparse_invalid_cmds() { - let test_multiple_get = "get sayan supersayan".to_owned(); - let test_multiple_set = "set sayan 18 supersayan 118".to_owned(); - let test_multiple_update = "update sayan 19 supersayan 119".to_owned(); - let result_multiple_get: Result = - Err(ArgsError::Other(ERR_GET_MULTIPLE)); - let result_multiple_set: Result = - Err(ArgsError::Other(ERR_SET_MULTIPLE)); - let result_multiple_update: Result = - Err(ArgsError::Other(ERR_UPDATE_MULTIPLE)); - assert_eq!(parse_args(test_multiple_get), result_multiple_get); - assert_eq!(parse_args(test_multiple_set), result_multiple_set); - assert_eq!(parse_args(test_multiple_update), result_multiple_update); +fn send_query(query: Vec) { + let mut binding = match TcpStream::connect(ADDR) { + Ok(b) => b, + Err(_) => EXIT_ERROR("Couldn't connect to Terrabase"), + }; + match binding.write(&query) { + Ok(_) => (), + Err(_) => EXIT_ERROR("Couldn't read data from socket"), + } + let mut bufreader = BufReader::new(binding); + let mut buf = String::with_capacity(DEF_R_META_BUFSIZE); + match bufreader.read_line(&mut buf) { + Ok(_) => (), + Err(_) => EXIT_ERROR("Failed to read line from socket"), + } + let rmf = match ResultMetaframe::from_buffer(buf) { + Ok(mf) => mf, + Err(e) => { + NORMAL_ERROR(&e.to_string()); + return; + } + }; + match &rmf.response { + Okay(_) => (), + x @ _ => { + NORMAL_ERROR(&x.to_string()); + return; + } + } + let mut data_buffer = vec![0; rmf.get_content_size()]; + match bufreader.read(&mut data_buffer) { + Ok(_) => (), + Err(_) => EXIT_ERROR("Failed to read line from socket"), + } + let df = match Dataframe::from_buffer(rmf.get_content_size(), data_buffer) { + Ok(d) => d, + Err(e) => { + NORMAL_ERROR(&e.to_string()); + return; + } + }; + let res = df.deflatten(); + if res.len() == 0 { + return; + } else { + if res.len() == 1 { + println!("{}", res[0]); + } else { + println!("{:?}", res); + } + } } diff --git a/cli/src/main.rs b/cli/src/main.rs index 418eb559..7d83bdc7 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -29,7 +29,7 @@ fn main() { println!("{}", MSG_WELCOME); loop { let mut buffer = String::new(); - print!("terrabase> "); + print!("tsh> "); match io::stdout().flush() { Ok(_) => (), Err(_) => argparse::EXIT_ERROR("Failed to flush output stream"), @@ -38,13 +38,10 @@ fn main() { Ok(_) => (), Err(_) => argparse::EXIT_ERROR("Failed to read line and append to buffer"), }; - let cmds = match argparse::parse_args(buffer) { - Ok(cmds) => cmds, - Err(e) => { - eprintln!("{}", e); - continue; - } - }; - println!("{:#?}", cmds); + if buffer.len() != 0 { + argparse::run(buffer); + } else { + continue; + } } } diff --git a/libcore/src/terrapipe.rs b/libcore/src/terrapipe.rs index b762dd8b..606a6df7 100644 --- a/libcore/src/terrapipe.rs +++ b/libcore/src/terrapipe.rs @@ -53,8 +53,9 @@ pub const DEF_Q_META_BUFSIZE: usize = 46; /// and the maximum size of the response metaframe is 20 - the buffer size is kept at 40 pub const DEF_R_META_BUFSIZE: usize = 40; +// HACK(@ohsayan) This is a temporary workaround since `const fn`s don't support `match` yet /// Constant function to generate a response packet -const RESPONSE_PACKET: fn(version: Version, respcode: u8, data: &str) -> Vec = +pub const RESPONSE_PACKET: fn(version: Version, respcode: u8, data: &str) -> Vec = |version, respcode, data| { let res = format!( "TP/{}.{}.{}/R/{}/{}\n{}", @@ -68,8 +69,9 @@ const RESPONSE_PACKET: fn(version: Version, respcode: u8, data: &str) -> Vec res.as_bytes().to_vec() }; +// HACK(@ohsayan) This is a temporary workaround since `const fn`s don't support `match` yet /// Constant function to generate a query packet -const QUERY_PACKET: fn(version: Version, method: String, data: String) -> Vec = +pub const QUERY_PACKET: fn(version: Version, method: String, data: String) -> Vec = |version, method, data| { let res = format!( "TP/{}.{}.{}/Q/{}/{}\n{}", @@ -138,6 +140,7 @@ impl Version { } } +#[derive(Debug)] /// Response codes which are returned by the server pub enum ResponseCodes { /// `0` : Okay @@ -160,6 +163,32 @@ pub enum ResponseCodes { CorruptPacket, } +impl fmt::Display for ResponseCodes { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use ResponseCodes::*; + match self { + Okay(v) => { + if let Some(v) = v { + write!(f, "{}", v) + } else { + write!(f, "") + } + } + NotFound => write!(f, "The target could not be found"), + OverwriteError => write!(f, "Existing values cannot be overwritten"), + MethodNotAllowed => write!(f, "The method is not supported"), + InternalServerError => write!(f, "An internal server error occurred"), + InvalidMetaframe => write!(f, "The query had an invalid metaframe"), + CorruptDataframe => write!(f, "The query did not contain the required data"), + ProtocolVersionMismatch => write!( + f, + "The server doesn't support the protocol being used" + ), + CorruptPacket => write!(f, "The query did not have the required data"), + } + } +} + impl ResponseCodes { /// Instantiate a new `ResponseCodes` variant from an `u8` value /// ## Errors @@ -341,6 +370,17 @@ fn benchmark_metaframe_parsing() { b.print_stats(); } +impl fmt::Display for ResultError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use ResultError::*; + match self { + StandardError(r) => write!(f, "{}", r), + UnknownError(u) => write!(f, "The server responded with '{}'", u), + } + } +} + +#[derive(Debug)] /// Errors that may occur when parsing a response packet from the server pub enum ResultError { /// A standard response code used by the Terrapipe protocol @@ -349,10 +389,11 @@ pub enum ResultError { UnknownError(String), } +#[derive(Debug)] /// A result metaframe pub struct ResultMetaframe { content_size: usize, - response: ResponseCodes, + pub response: ResponseCodes, } impl ResultMetaframe { @@ -391,4 +432,7 @@ impl ResultMetaframe { response, }) } + pub fn get_content_size(&self) -> usize { + self.content_size + } } diff --git a/server/src/coredb.rs b/server/src/coredb.rs index 1c2489c7..2e9f7755 100644 --- a/server/src/coredb.rs +++ b/server/src/coredb.rs @@ -64,6 +64,7 @@ impl Coretable { Err(ResponseCodes::NotFound) } } + #[cfg(debug)] pub fn print_debug_table(&self) { println!("{:#?}", *self.coremap.read().unwrap()); } diff --git a/server/src/main.rs b/server/src/main.rs index abceeba8..e778c664 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -96,6 +96,7 @@ async fn execute_query( }, _ => ResponseCodes::CorruptDataframe, }; + #[cfg(debug)] handle.print_debug_table(); stream.write(&result.response_bytes()).await.unwrap(); } @@ -107,6 +108,7 @@ async fn execute_query( }, _ => ResponseCodes::CorruptDataframe, }; + #[cfg(debug)] handle.print_debug_table(); stream.write(&result.response_bytes()).await.unwrap(); } @@ -118,7 +120,9 @@ async fn execute_query( }, _ => ResponseCodes::CorruptDataframe, }; + #[cfg(debug)] handle.print_debug_table(); + stream.write(&result.response_bytes()).await.unwrap(); } }