Update src/main.rs
This commit is contained in:
parent
88e7c6c7b6
commit
7663c4277a
127
src/main.rs
127
src/main.rs
@ -1,18 +1,41 @@
|
|||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
use std::net::{TcpStream, ToSocketAddrs};
|
use std::net::TcpStream;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::env;
|
use std::env;
|
||||||
use colored::*;
|
use colored::Colorize;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
use std::process;
|
||||||
|
|
||||||
mod resp;
|
mod resp;
|
||||||
|
|
||||||
const DEFAULT_PORT: u16 = 9880;
|
const DEFAULT_PORT: u16 = 9880;
|
||||||
const PROMPT_NAME: &str = "futriix";
|
const PROMPT_NAME: &str = "futriix";
|
||||||
const CONNECTION_TIMEOUT: u64 = 5; // seconds
|
const CONNECTION_TIMEOUT_SECS: u64 = 2;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
// Parse command line arguments
|
let (host, port) = parse_args();
|
||||||
|
let addr = format!("{}:{}", host, port);
|
||||||
|
|
||||||
|
let stream = match TcpStream::connect_timeout(
|
||||||
|
&addr.parse().unwrap(),
|
||||||
|
Duration::from_secs(CONNECTION_TIMEOUT_SECS)
|
||||||
|
) {
|
||||||
|
Ok(stream) => stream,
|
||||||
|
Err(e) if e.kind() == io::ErrorKind::ConnectionRefused => {
|
||||||
|
eprintln!("{}", "Connection refused".red());
|
||||||
|
process::exit(1);
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Connection error: {}", e);
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("Connected to {}", addr.green());
|
||||||
|
run_repl_loop(stream, &host, port);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_args() -> (String, u16) {
|
||||||
let args: Vec<String> = env::args().collect();
|
let args: Vec<String> = env::args().collect();
|
||||||
let mut host = "127.0.0.1".to_string();
|
let mut host = "127.0.0.1".to_string();
|
||||||
let mut port = DEFAULT_PORT;
|
let mut port = DEFAULT_PORT;
|
||||||
@ -28,94 +51,63 @@ fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
(host, port)
|
||||||
|
}
|
||||||
|
|
||||||
// Try to connect to server
|
fn run_repl_loop(stream: TcpStream, host: &str, port: u16) {
|
||||||
let addr = format!("{}:{}", host, port);
|
|
||||||
let socket_addr = match format!("{}:{}", host, port).to_socket_addrs() {
|
|
||||||
Ok(mut addrs) => addrs.next().expect("No address found"),
|
|
||||||
Err(_e) => {
|
|
||||||
eprintln!("Failed to resolve address {}", addr);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let stream = match TcpStream::connect_timeout(&socket_addr, Duration::from_secs(CONNECTION_TIMEOUT)) {
|
|
||||||
Ok(s) => s,
|
|
||||||
Err(_e) => {
|
|
||||||
eprintln!("Connection to port {} refused", port);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
println!("Connected to {}", addr);
|
|
||||||
|
|
||||||
// Main REPL loop
|
|
||||||
let mut input = String::new();
|
let mut input = String::new();
|
||||||
loop {
|
loop {
|
||||||
// Print prompt
|
print_prompt(host, port);
|
||||||
print_prompt(&host, port);
|
|
||||||
|
|
||||||
// Read input
|
|
||||||
input.clear();
|
input.clear();
|
||||||
io::stdin().read_line(&mut input).expect("Failed to read input");
|
|
||||||
let input = input.trim();
|
if io::stdin().read_line(&mut input).is_err() {
|
||||||
|
eprintln!("{}", "Failed to read input".red());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let input = input.trim();
|
||||||
if input.is_empty() {
|
if input.is_empty() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle special commands
|
|
||||||
if input.eq_ignore_ascii_case("quit") || input.eq_ignore_ascii_case("exit") {
|
if input.eq_ignore_ascii_case("quit") || input.eq_ignore_ascii_case("exit") {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate command before sending
|
|
||||||
if !is_valid_command(input) {
|
if !is_valid_command(input) {
|
||||||
eprintln!("Error: Invalid command format");
|
eprintln!("{}", "Error: Invalid command format".red());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send command to server
|
|
||||||
match send_command(&stream, input) {
|
match send_command(&stream, input) {
|
||||||
Ok(response) => {
|
Ok(response) => print_response(&response),
|
||||||
print_response(&response);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("Error: {}", e.to_string().replace("KeyDB", "Futriix"));
|
if is_connection_error(&e) {
|
||||||
// Check if connection was lost
|
eprintln!("{}", "Connection error".red());
|
||||||
if e.kind() == io::ErrorKind::ConnectionAborted ||
|
|
||||||
e.kind() == io::ErrorKind::ConnectionReset {
|
|
||||||
eprintln!("Connection lost. Please restart the client.");
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
eprintln!("{}", format!("Error: {}", e.to_string().replace("KeyDB", "Futriix")).red());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_valid_command(cmd: &str) -> bool {
|
|
||||||
if cmd.is_empty() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if cmd.chars().any(|c| c.is_control()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd.split_whitespace().next().is_some()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn print_prompt(host: &str, port: u16) {
|
fn print_prompt(host: &str, port: u16) {
|
||||||
let prompt = format!("{}:{}:{}:~>", PROMPT_NAME, host, port);
|
let prompt = format!("{}:{}:{}:~>", PROMPT_NAME, host, port);
|
||||||
print!("{} ", prompt.green());
|
print!("{} ", prompt.green());
|
||||||
io::stdout().flush().unwrap();
|
io::stdout().flush().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_valid_command(cmd: &str) -> bool {
|
||||||
|
!cmd.is_empty() &&
|
||||||
|
!cmd.chars().any(|c| c.is_control()) &&
|
||||||
|
cmd.split_whitespace().next().is_some()
|
||||||
|
}
|
||||||
|
|
||||||
fn send_command(stream: &TcpStream, command: &str) -> io::Result<resp::Value> {
|
fn send_command(stream: &TcpStream, command: &str) -> io::Result<resp::Value> {
|
||||||
let parts: Vec<&str> = command.split_whitespace().collect();
|
let parts: Vec<&str> = command.split_whitespace().collect();
|
||||||
let mut resp_command = String::new();
|
let mut resp_command = format!("*{}\r\n", parts.len());
|
||||||
|
|
||||||
resp_command.push_str(&format!("*{}\r\n", parts.len()));
|
|
||||||
for part in parts {
|
for part in parts {
|
||||||
resp_command.push_str(&format!("${}\r\n{}\r\n", part.len(), part));
|
resp_command.push_str(&format!("${}\r\n{}\r\n", part.len(), part));
|
||||||
}
|
}
|
||||||
@ -130,10 +122,10 @@ fn send_command(stream: &TcpStream, command: &str) -> io::Result<resp::Value> {
|
|||||||
fn print_response(value: &resp::Value) {
|
fn print_response(value: &resp::Value) {
|
||||||
match value {
|
match value {
|
||||||
resp::Value::SimpleString(s) | resp::Value::BulkString(s) => {
|
resp::Value::SimpleString(s) | resp::Value::BulkString(s) => {
|
||||||
println!("{}", s.replace("KeyDB", "Futriix"))
|
println!("{}", s.replace("KeyDB", "Futriix"));
|
||||||
},
|
},
|
||||||
resp::Value::Error(e) => {
|
resp::Value::Error(e) => {
|
||||||
println!("(error) {}", e.replace("KeyDB", "Futriix"))
|
println!("{}", format!("(error) {}", e.replace("KeyDB", "Futriix")).red());
|
||||||
},
|
},
|
||||||
resp::Value::Integer(i) => println!("(integer) {}", i),
|
resp::Value::Integer(i) => println!("(integer) {}", i),
|
||||||
resp::Value::Array(arr) => {
|
resp::Value::Array(arr) => {
|
||||||
@ -141,17 +133,16 @@ fn print_response(value: &resp::Value) {
|
|||||||
print!("{}) ", i + 1);
|
print!("{}) ", i + 1);
|
||||||
print_response(item);
|
print_response(item);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
resp::Value::Null => println!("(nil)"),
|
resp::Value::Null => println!("(nil)"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
fn is_connection_error(error: &io::Error) -> bool {
|
||||||
mod tests {
|
matches!(
|
||||||
use super::*;
|
error.kind(),
|
||||||
|
io::ErrorKind::ConnectionAborted |
|
||||||
#[test]
|
io::ErrorKind::ConnectionReset |
|
||||||
fn test_print_prompt() {
|
io::ErrorKind::BrokenPipe
|
||||||
print_prompt("127.0.0.1", 9880);
|
)
|
||||||
}
|
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user