Implement basic client

next
Sayan Nandan 4 years ago
parent e4663a6206
commit 7434071509
No known key found for this signature in database
GPG Key ID: C31EFD7DDA12AEE0

11
Cargo.lock generated

@ -343,10 +343,6 @@ dependencies = [
"tokio", "tokio",
] ]
[[package]]
name = "terrabase-cli"
version = "0.1.0"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "0.2.21" version = "0.2.21"
@ -382,6 +378,13 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "tsh"
version = "0.1.0"
dependencies = [
"libcore",
]
[[package]] [[package]]
name = "unicode-xid" name = "unicode-xid"
version = "0.2.1" version = "0.2.1"

@ -1,5 +1,5 @@
[package] [package]
name = "terrabase-cli" name = "tsh"
version = "0.1.0" version = "0.1.0"
authors = ["Sayan Nandan <nandansayan@outlook.com>"] authors = ["Sayan Nandan <nandansayan@outlook.com>"]
edition = "2018" edition = "2018"
@ -7,3 +7,4 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
libcore = {path="../libcore"}

@ -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; use std::process;
/// `SET` command line argument const ARG_GET: &'static str = "GET";
const ARG_SET: &'static str = "set"; const ARG_SET: &'static str = "SET";
/// `GET` command line argument const ARG_UPDATE: &'static str = "UPDATE";
const ARG_GET: &'static str = "get"; const ARG_DEL: &'static str = "DEL";
/// `UPDATE` command line argument const ARG_EXIT: &'static str = "EXIT";
const ARG_UPDATE: &'static str = "update"; const ERR_MULTIPLE_GET: &'static str = "Multiple GETs aren't supported yet";
/// `EXIT` command line argument const ERR_MULTIPLE_SET: &'static str = "Multiple SETs aren't supported yet";
const ARG_EXIT: &'static str = "exit"; const ERR_MULTIPLE_UPDATE: &'static str = "Multiple UPDATEs aren't supported yet";
/// Error message when trying to get multiple keys at the same time (TEMP) const ERR_MULTIPLE_DEL: &'static str = "Multiple DELs aren't supported yet";
const ERR_GET_MULTIPLE: &'static str = "GET only supports fetching one key at a time"; const SELF_VERSION: Version = Version(0, 1, 0);
/// Error message when trying to set multiple keys at the same time (TEMP) const ADDR: &'static str = "127.0.0.1:2003";
const ERR_SET_MULTIPLE: &'static str = "SET only supports setting one key at a time"; pub const EXIT_ERROR: fn(error: &str) -> ! = |error| {
/// Error message when trying to update multiple keys at the same time (TEMP) eprintln!("error: {}", error);
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);
process::exit(0x100); process::exit(0x100);
}; };
/// ### Parse a `String` argument into a corresponding `FinalCommands` variant const NORMAL_ERROR: fn(error: &str) = |error| {
/// #### Errors eprintln!("error: {}", error);
/// 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<FinalCommands, ArgsError> {
let args: Vec<String> = 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()));
}
}
match actions[0] { pub fn run(args: String) {
Commands::GET => { let args: Vec<&str> = args.split_whitespace().collect();
// Now read next command match args[0].to_uppercase().as_str() {
if let Some(arg) = args.get(1) { ARG_GET => {
if let Some(key) = args.get(1) {
if args.get(2).is_none() { 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 { } else {
return Err(ArgsError::Other(ERR_GET_MULTIPLE)); NORMAL_ERROR(ERR_MULTIPLE_GET);
} }
} else { } else {
return Err(ArgsError::ExpectedMoreArgs); NORMAL_ERROR("Expected one more argument");
} }
} }
Commands::SET => { ARG_SET => {
// Now read next command if let Some(key) = args.get(1) {
if let (Some(key), Some(value)) = (args.get(1), args.get(2)) { if let Some(value) = args.get(2) {
if args.get(3).is_none() { if args.get(3).is_none() {
return Ok(FinalCommands::SET(KeyValue( send_query(QUERY_PACKET(
key.to_string(), SELF_VERSION,
value.to_string(), ARG_SET.to_owned(),
))); format!("{} {}", key, value),
))
} else {
NORMAL_ERROR(ERR_MULTIPLE_SET);
}
} else { } else {
return Err(ArgsError::Other(ERR_SET_MULTIPLE)); NORMAL_ERROR("Expected one more argument");
} }
} else { } else {
return Err(ArgsError::ExpectedMoreArgs); NORMAL_ERROR("Expected more arguments");
} }
} }
Commands::UPDATE => { ARG_UPDATE => {
if let (Some(key), Some(value)) = (args.get(1), args.get(2)) { if let Some(key) = args.get(1) {
if args.get(3).is_none() { if let Some(value) = args.get(2) {
return Ok(FinalCommands::UPDATE(KeyValue( 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(), key.to_string(),
value.to_string(), ));
)));
} else { } else {
return Err(ArgsError::Other(ERR_UPDATE_MULTIPLE)); NORMAL_ERROR(ERR_MULTIPLE_DEL);
} }
} else { } else {
return Err(ArgsError::ExpectedMoreArgs); NORMAL_ERROR("Expected one more argument");
} }
} }
ARG_EXIT => {
println!("Goodbye!");
process::exit(0x100)
}
_ => NORMAL_ERROR("Unknown command"),
} }
} }
#[cfg(test)] fn send_query(query: Vec<u8>) {
#[test] let mut binding = match TcpStream::connect(ADDR) {
fn test_argparse_valid_cmds() { Ok(b) => b,
let test_set_arg1 = "set sayan 100".to_owned(); Err(_) => EXIT_ERROR("Couldn't connect to Terrabase"),
let test_set_arg2 = "SET sayan 100".to_owned(); };
let test_set_arg3 = "SeT sayan 100".to_owned(); match binding.write(&query) {
let test_get_arg1 = "get sayan".to_owned(); Ok(_) => (),
let test_get_arg2 = "GET sayan".to_owned(); Err(_) => EXIT_ERROR("Couldn't read data from socket"),
let test_get_arg3 = "GeT sayan".to_owned(); }
let test_set_result: Result<FinalCommands, ArgsError> = Ok(FinalCommands::SET(KeyValue( let mut bufreader = BufReader::new(binding);
"sayan".to_owned(), let mut buf = String::with_capacity(DEF_R_META_BUFSIZE);
"100".to_owned(), match bufreader.read_line(&mut buf) {
))); Ok(_) => (),
let test_get_result: Result<FinalCommands, ArgsError> = Err(_) => EXIT_ERROR("Failed to read line from socket"),
Ok(FinalCommands::GET("sayan".to_owned())); }
assert_eq!(parse_args(test_get_arg1), test_get_result); let rmf = match ResultMetaframe::from_buffer(buf) {
assert_eq!(parse_args(test_get_arg2), test_get_result); Ok(mf) => mf,
assert_eq!(parse_args(test_get_arg3), test_get_result); Err(e) => {
assert_eq!(parse_args(test_set_arg1), test_set_result); NORMAL_ERROR(&e.to_string());
assert_eq!(parse_args(test_set_arg2), test_set_result); return;
assert_eq!(parse_args(test_set_arg3), test_set_result); }
} };
match &rmf.response {
#[cfg(test)] Okay(_) => (),
#[test] x @ _ => {
fn test_argparse_invalid_cmds() { NORMAL_ERROR(&x.to_string());
let test_multiple_get = "get sayan supersayan".to_owned(); return;
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<FinalCommands, ArgsError> = let mut data_buffer = vec![0; rmf.get_content_size()];
Err(ArgsError::Other(ERR_GET_MULTIPLE)); match bufreader.read(&mut data_buffer) {
let result_multiple_set: Result<FinalCommands, ArgsError> = Ok(_) => (),
Err(ArgsError::Other(ERR_SET_MULTIPLE)); Err(_) => EXIT_ERROR("Failed to read line from socket"),
let result_multiple_update: Result<FinalCommands, ArgsError> = }
Err(ArgsError::Other(ERR_UPDATE_MULTIPLE)); let df = match Dataframe::from_buffer(rmf.get_content_size(), data_buffer) {
assert_eq!(parse_args(test_multiple_get), result_multiple_get); Ok(d) => d,
assert_eq!(parse_args(test_multiple_set), result_multiple_set); Err(e) => {
assert_eq!(parse_args(test_multiple_update), result_multiple_update); 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);
}
}
} }

@ -29,7 +29,7 @@ fn main() {
println!("{}", MSG_WELCOME); println!("{}", MSG_WELCOME);
loop { loop {
let mut buffer = String::new(); let mut buffer = String::new();
print!("terrabase> "); print!("tsh> ");
match io::stdout().flush() { match io::stdout().flush() {
Ok(_) => (), Ok(_) => (),
Err(_) => argparse::EXIT_ERROR("Failed to flush output stream"), Err(_) => argparse::EXIT_ERROR("Failed to flush output stream"),
@ -38,13 +38,10 @@ fn main() {
Ok(_) => (), Ok(_) => (),
Err(_) => argparse::EXIT_ERROR("Failed to read line and append to buffer"), Err(_) => argparse::EXIT_ERROR("Failed to read line and append to buffer"),
}; };
let cmds = match argparse::parse_args(buffer) { if buffer.len() != 0 {
Ok(cmds) => cmds, argparse::run(buffer);
Err(e) => { } else {
eprintln!("{}", e); continue;
continue; }
}
};
println!("{:#?}", cmds);
} }
} }

@ -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 /// 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; 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 /// Constant function to generate a response packet
const RESPONSE_PACKET: fn(version: Version, respcode: u8, data: &str) -> Vec<u8> = pub const RESPONSE_PACKET: fn(version: Version, respcode: u8, data: &str) -> Vec<u8> =
|version, respcode, data| { |version, respcode, data| {
let res = format!( let res = format!(
"TP/{}.{}.{}/R/{}/{}\n{}", "TP/{}.{}.{}/R/{}/{}\n{}",
@ -68,8 +69,9 @@ const RESPONSE_PACKET: fn(version: Version, respcode: u8, data: &str) -> Vec<u8>
res.as_bytes().to_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 /// Constant function to generate a query packet
const QUERY_PACKET: fn(version: Version, method: String, data: String) -> Vec<u8> = pub const QUERY_PACKET: fn(version: Version, method: String, data: String) -> Vec<u8> =
|version, method, data| { |version, method, data| {
let res = format!( let res = format!(
"TP/{}.{}.{}/Q/{}/{}\n{}", "TP/{}.{}.{}/Q/{}/{}\n{}",
@ -138,6 +140,7 @@ impl Version {
} }
} }
#[derive(Debug)]
/// Response codes which are returned by the server /// Response codes which are returned by the server
pub enum ResponseCodes { pub enum ResponseCodes {
/// `0` : Okay /// `0` : Okay
@ -160,6 +163,32 @@ pub enum ResponseCodes {
CorruptPacket, 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 { impl ResponseCodes {
/// Instantiate a new `ResponseCodes` variant from an `u8` value /// Instantiate a new `ResponseCodes` variant from an `u8` value
/// ## Errors /// ## Errors
@ -341,6 +370,17 @@ fn benchmark_metaframe_parsing() {
b.print_stats(); 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 /// Errors that may occur when parsing a response packet from the server
pub enum ResultError { pub enum ResultError {
/// A standard response code used by the Terrapipe protocol /// A standard response code used by the Terrapipe protocol
@ -349,10 +389,11 @@ pub enum ResultError {
UnknownError(String), UnknownError(String),
} }
#[derive(Debug)]
/// A result metaframe /// A result metaframe
pub struct ResultMetaframe { pub struct ResultMetaframe {
content_size: usize, content_size: usize,
response: ResponseCodes, pub response: ResponseCodes,
} }
impl ResultMetaframe { impl ResultMetaframe {
@ -391,4 +432,7 @@ impl ResultMetaframe {
response, response,
}) })
} }
pub fn get_content_size(&self) -> usize {
self.content_size
}
} }

@ -64,6 +64,7 @@ impl Coretable {
Err(ResponseCodes::NotFound) Err(ResponseCodes::NotFound)
} }
} }
#[cfg(debug)]
pub fn print_debug_table(&self) { pub fn print_debug_table(&self) {
println!("{:#?}", *self.coremap.read().unwrap()); println!("{:#?}", *self.coremap.read().unwrap());
} }

@ -96,6 +96,7 @@ async fn execute_query(
}, },
_ => ResponseCodes::CorruptDataframe, _ => ResponseCodes::CorruptDataframe,
}; };
#[cfg(debug)]
handle.print_debug_table(); handle.print_debug_table();
stream.write(&result.response_bytes()).await.unwrap(); stream.write(&result.response_bytes()).await.unwrap();
} }
@ -107,6 +108,7 @@ async fn execute_query(
}, },
_ => ResponseCodes::CorruptDataframe, _ => ResponseCodes::CorruptDataframe,
}; };
#[cfg(debug)]
handle.print_debug_table(); handle.print_debug_table();
stream.write(&result.response_bytes()).await.unwrap(); stream.write(&result.response_bytes()).await.unwrap();
} }
@ -118,7 +120,9 @@ async fn execute_query(
}, },
_ => ResponseCodes::CorruptDataframe, _ => ResponseCodes::CorruptDataframe,
}; };
#[cfg(debug)]
handle.print_debug_table(); handle.print_debug_table();
stream.write(&result.response_bytes()).await.unwrap(); stream.write(&result.response_bytes()).await.unwrap();
} }
} }

Loading…
Cancel
Save