use serde::{Deserialize, Serialize}; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use std::error::Error; use tokio::net::TcpStream; use colored::Colorize; use rustyline::{Editor, error::ReadlineError}; use std::fs; use toml::Value; #[derive(Debug, Serialize, Deserialize)] enum Command { Insert { key: String, value: serde_json::Value }, Get { key: String }, Update { key: String, value: serde_json::Value }, Delete { key: String }, BeginTransaction, CommitTransaction, RollbackTransaction, CreateIndex { field: String }, DropIndex { field: String }, } #[derive(Debug, Serialize, Deserialize)] enum Response { Success(Option), Error(String), } async fn send_command(cmd: Command) -> Result> { let config_content = fs::read_to_string("../futriix.config.toml")?; let config: Value = toml::from_str(&config_content)?; let ip = config["client"]["ip"].as_str().unwrap_or("127.0.0.1"); let port = config["client"]["port"].as_integer().unwrap_or(8080) as u16; let addr = format!("{}:{}", ip, port); let mut stream = TcpStream::connect(&addr).await?; let cmd_bytes = rmp_serde::to_vec(&cmd)?; let len = cmd_bytes.len() as u32; stream.write_all(&len.to_be_bytes()).await?; stream.write_all(&cmd_bytes).await?; let mut len_buf = [0u8; 4]; stream.read_exact(&mut len_buf).await?; let len = u32::from_be_bytes(len_buf) as usize; let mut buf = vec![0u8; len]; stream.read_exact(&mut buf).await?; Ok(rmp_serde::from_slice(&buf)?) } #[tokio::main] async fn main() -> Result<(), Box> { println!(); println!("{}", "Futriix CLI Client".bright_cyan()); println!("Type 'help' for available commands"); let mut rl = Editor::<()>::new()?; if rl.load_history("futriix-cli-history.txt").is_err() { println!("No previous history."); } loop { let readline = rl.readline("futriix> "); match readline { Ok(line) => { rl.add_history_entry(&line); if line.trim().is_empty() { continue; } let parts: Vec<&str> = line.split_whitespace().collect(); match parts[0] { "insert" | "i" => { if parts.len() < 3 { println!("{}", "Usage: insert ".red()); continue; } let key = parts[1].to_string(); let value_str = parts[2..].join(" "); match serde_json::from_str(&value_str) { Ok(value) => match send_command(Command::Insert { key, value }).await { Ok(Response::Success(_)) => { println!("{}", "Insert successful".bright_green()); println!(); }, Ok(Response::Error(e)) => println!("{}", e.bold().red()), Err(e) => println!("{}: {}", "Connection error".red(), e), }, Err(e) => println!("{}: {}", "Invalid JSON".red(), e), } }, "get" | "g" => { if parts.len() != 2 { println!("{}", "Usage: get ".red()); continue; } match send_command(Command::Get { key: parts[1].to_string() }).await { Ok(Response::Success(Some(value))) => println!("{}", serde_json::to_string_pretty(&value)?), Ok(Response::Success(None)) => println!("{}", "Error: Key not found".bold().red()), Ok(Response::Error(e)) => println!("{}", e.bold().red()), Err(e) => println!("{}: {}", "Connection error".red(), e), } }, "update" | "u" => { if parts.len() < 3 { println!("{}", "Usage: update ".red()); continue; } let key = parts[1].to_string(); let value_str = parts[2..].join(" "); match serde_json::from_str(&value_str) { Ok(value) => match send_command(Command::Update { key, value }).await { Ok(Response::Success(_)) => println!("{}", "Update successful".bright_green()), Ok(Response::Error(e)) => println!("{}", e.bold().red()), Err(e) => println!("{}: {}", "Connection error".red(), e), }, Err(e) => println!("{}: {}", "Invalid JSON".red(), e), } }, "delete" | "d" => { if parts.len() != 2 { println!("{}", "Usage: delete ".red()); continue; } match send_command(Command::Delete { key: parts[1].to_string() }).await { Ok(Response::Success(_)) => println!("{}", "Delete successful".bright_green()), Ok(Response::Error(e)) => println!("{}", e.bold().red()), Err(e) => println!("{}: {}", "Connection error".red(), e), } }, "begin" => match send_command(Command::BeginTransaction).await { Ok(Response::Success(_)) => println!("{}", "Transaction started".bright_green()), Ok(Response::Error(e)) => println!("{}", e.bold().red()), Err(e) => println!("{}: {}", "Connection error".red(), e), }, "commit" => match send_command(Command::CommitTransaction).await { Ok(Response::Success(_)) => println!("{}", "Transaction committed".bright_green()), Ok(Response::Error(e)) => println!("{}", e.bold().red()), Err(e) => println!("{}: {}", "Connection error".red(), e), }, "rollback" => match send_command(Command::RollbackTransaction).await { Ok(Response::Success(_)) => println!("{}", "Transaction rolled back".bright_green()), Ok(Response::Error(e)) => println!("{}", e.bold().red()), Err(e) => println!("{}: {}", "Connection error".red(), e), }, "create_index" => { if parts.len() != 2 { println!("{}", "Usage: create_index ".red()); continue; } match send_command(Command::CreateIndex { field: parts[1].to_string() }).await { Ok(Response::Success(_)) => println!("{}", format!("Index created on field '{}'", parts[1]).bright_green()), Ok(Response::Error(e)) => println!("{}", e.bold().red()), Err(e) => println!("{}: {}", "Connection error".red(), e), } }, "drop_index" => { if parts.len() != 2 { println!("{}", "Usage: drop_index ".red()); continue; } match send_command(Command::DropIndex { field: parts[1].to_string() }).await { Ok(Response::Success(_)) => println!("{}", format!("Index dropped on field '{}'", parts[1]).bright_green()), Ok(Response::Error(e)) => println!("{}", e.bold().red()), Err(e) => println!("{}: {}", "Connection error".red(), e), } }, "help" | "h" => { println!("Available commands:"); println!(" insert|i - Insert document"); println!(" get|g - Get document"); println!(" update|u - Update document"); println!(" delete|d - Delete document"); println!(" begin - Start transaction"); println!(" commit - Commit transaction"); println!(" rollback - Rollback transaction"); println!(" create_index - Create index"); println!(" drop_index - Drop index"); println!(" help|h - Show this help"); println!(" exit|quit|q - Exit the client"); }, "exit" | "quit" | "q" => break, _ => println!("{}: Unknown command. Type 'help' for available commands.", "Error".red()), } }, Err(ReadlineError::Interrupted) => break, Err(ReadlineError::Eof) => break, Err(err) => { println!("{}: {:?}", "Error".red(), err); break; } } } rl.save_history("futriix-cli-history.txt")?; Ok(()) }