2025-06-14 14:23:36 +03:00
|
|
|
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 },
|
2025-06-14 21:10:37 +00:00
|
|
|
BeginTransaction,
|
|
|
|
CommitTransaction,
|
|
|
|
RollbackTransaction,
|
|
|
|
CreateIndex { field: String },
|
|
|
|
DropIndex { field: String },
|
2025-06-14 14:23:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
|
|
enum Response {
|
|
|
|
Success(Option<serde_json::Value>),
|
|
|
|
Error(String),
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn send_command(cmd: Command) -> Result<Response, Box<dyn Error>> {
|
|
|
|
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?;
|
|
|
|
|
2025-06-14 21:10:37 +00:00
|
|
|
Ok(rmp_serde::from_slice(&buf)?)
|
2025-06-14 14:23:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
#[tokio::main]
|
|
|
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
2025-06-14 21:10:37 +00:00
|
|
|
println!();
|
|
|
|
println!("{}", "Futriix CLI Client".bright_cyan());
|
2025-06-14 14:23:36 +03:00
|
|
|
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 <key> <json_value>".red());
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
let key = parts[1].to_string();
|
|
|
|
let value_str = parts[2..].join(" ");
|
|
|
|
match serde_json::from_str(&value_str) {
|
2025-06-14 21:10:37 +00:00
|
|
|
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),
|
2025-06-14 14:23:36 +03:00
|
|
|
},
|
2025-06-14 21:10:37 +00:00
|
|
|
Err(e) => println!("{}: {}", "Invalid JSON".red(), e),
|
2025-06-14 14:23:36 +03:00
|
|
|
}
|
|
|
|
},
|
|
|
|
"get" | "g" => {
|
|
|
|
if parts.len() != 2 {
|
|
|
|
println!("{}", "Usage: get <key>".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)?),
|
2025-06-14 21:10:37 +00:00
|
|
|
Ok(Response::Success(None)) => println!("{}", "Error: Key not found".bold().red()),
|
|
|
|
Ok(Response::Error(e)) => println!("{}", e.bold().red()),
|
2025-06-14 14:23:36 +03:00
|
|
|
Err(e) => println!("{}: {}", "Connection error".red(), e),
|
|
|
|
}
|
|
|
|
},
|
|
|
|
"update" | "u" => {
|
|
|
|
if parts.len() < 3 {
|
|
|
|
println!("{}", "Usage: update <key> <json_value>".red());
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
let key = parts[1].to_string();
|
|
|
|
let value_str = parts[2..].join(" ");
|
|
|
|
match serde_json::from_str(&value_str) {
|
2025-06-14 21:10:37 +00:00
|
|
|
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),
|
2025-06-14 14:23:36 +03:00
|
|
|
},
|
2025-06-14 21:10:37 +00:00
|
|
|
Err(e) => println!("{}: {}", "Invalid JSON".red(), e),
|
2025-06-14 14:23:36 +03:00
|
|
|
}
|
|
|
|
},
|
|
|
|
"delete" | "d" => {
|
|
|
|
if parts.len() != 2 {
|
|
|
|
println!("{}", "Usage: delete <key>".red());
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
match send_command(Command::Delete { key: parts[1].to_string() }).await {
|
2025-06-14 21:10:37 +00:00
|
|
|
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 <field_name>".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 <field_name>".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()),
|
2025-06-14 14:23:36 +03:00
|
|
|
Err(e) => println!("{}: {}", "Connection error".red(), e),
|
|
|
|
}
|
|
|
|
},
|
|
|
|
"help" | "h" => {
|
|
|
|
println!("Available commands:");
|
|
|
|
println!(" insert|i <key> <json_value> - Insert document");
|
|
|
|
println!(" get|g <key> - Get document");
|
|
|
|
println!(" update|u <key> <json_value> - Update document");
|
|
|
|
println!(" delete|d <key> - Delete document");
|
2025-06-14 21:10:37 +00:00
|
|
|
println!(" begin - Start transaction");
|
|
|
|
println!(" commit - Commit transaction");
|
|
|
|
println!(" rollback - Rollback transaction");
|
|
|
|
println!(" create_index <field> - Create index");
|
|
|
|
println!(" drop_index <field> - Drop index");
|
2025-06-14 14:23:36 +03:00
|
|
|
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(())
|
2025-06-14 21:10:37 +00:00
|
|
|
}
|