271 lines
12 KiB
Rust
Raw Normal View History

2025-06-15 19:36:13 +00:00
use colored::Colorize;
use rustyline::Editor;
use rustyline::error::ReadlineError;
2025-06-14 14:23:36 +03:00
use serde::{Deserialize, Serialize};
2025-06-15 19:36:13 +00:00
use serde_json;
2025-06-14 14:23:36 +03:00
use std::error::Error;
use std::fs;
2025-06-15 19:36:13 +00:00
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
2025-06-14 14:23:36 +03:00
use toml::Value;
2025-06-15 19:36:13 +00:00
mod server {
use serde::{Deserialize, Serialize};
use serde_json;
#[derive(Debug, Serialize, Deserialize)]
pub 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 },
SysExec { script_name: String },
}
2025-06-14 14:23:36 +03:00
2025-06-15 19:36:13 +00:00
#[derive(Debug, Serialize, Deserialize)]
pub enum Response {
Success(Option<serde_json::Value>),
Error(String),
}
2025-06-14 14:23:36 +03:00
}
2025-06-15 19:36:13 +00:00
use server::{Command, Response};
async fn send_command(cmd: Command) -> Result<Response, Box<dyn std::error::Error>> {
let config_content = fs::read_to_string("futriix.config.toml")?;
2025-06-14 14:23:36 +03:00
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()?;
2025-06-15 19:36:13 +00:00
if rl.load_history("futriix-history.txt").is_err() {
2025-06-14 14:23:36 +03:00
println!("No previous history.");
2025-06-15 19:36:13 +00:00
println!();
2025-06-14 14:23:36 +03:00
}
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();
2025-06-15 19:36:13 +00:00
match parts[0].to_lowercase().as_str() {
2025-06-14 14:23:36 +03:00
"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" => {
2025-06-15 19:36:13 +00:00
if parts.len() < 2 {
2025-06-14 14:23:36 +03:00
println!("{}", "Usage: get <key>".red());
continue;
}
2025-06-15 19:36:13 +00:00
let key = parts[1].to_string();
match send_command(Command::Get { key }).await {
Ok(Response::Success(Some(value))) => {
println!("{}", serde_json::to_string_pretty(&value)?);
println!();
},
Ok(Response::Success(None)) => {
println!("{}", "Key found but no value returned".yellow());
println!();
},
2025-06-14 21:10:37 +00:00
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 {
2025-06-15 19:36:13 +00:00
Ok(Response::Success(_)) => {
println!("{}", "Update successful".bright_green());
println!();
},
2025-06-14 21:10:37 +00:00
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" => {
2025-06-15 19:36:13 +00:00
if parts.len() < 2 {
2025-06-14 14:23:36 +03:00
println!("{}", "Usage: delete <key>".red());
continue;
}
2025-06-15 19:36:13 +00:00
let key = parts[1].to_string();
match send_command(Command::Delete { key }).await {
Ok(Response::Success(_)) => {
println!("{}", "Delete successful".bright_green());
println!();
},
2025-06-14 21:10:37 +00:00
Ok(Response::Error(e)) => println!("{}", e.bold().red()),
Err(e) => println!("{}: {}", "Connection error".red(), e),
}
},
2025-06-15 19:36:13 +00:00
"begin" | "transaction" | "tx" => {
match send_command(Command::BeginTransaction).await {
Ok(Response::Success(_)) => {
println!("{}", "Transaction started".bright_green());
println!();
},
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());
println!();
},
Ok(Response::Error(e)) => println!("{}", e.bold().red()),
Err(e) => println!("{}: {}", "Connection error".red(), e),
}
2025-06-14 21:10:37 +00:00
},
2025-06-15 19:36:13 +00:00
"rollback" => {
match send_command(Command::RollbackTransaction).await {
Ok(Response::Success(_)) => {
println!("{}", "Transaction rolled back".bright_green());
println!();
},
Ok(Response::Error(e)) => println!("{}", e.bold().red()),
Err(e) => println!("{}: {}", "Connection error".red(), e),
}
2025-06-14 21:10:37 +00:00
},
2025-06-15 19:36:13 +00:00
"createindex" => {
if parts.len() < 2 {
println!("{}", "Usage: createindex <field>".red());
continue;
}
let field = parts[1].to_string();
match send_command(Command::CreateIndex { field }).await {
Ok(Response::Success(_)) => {
println!("{}", "Index created".bright_green());
println!();
},
Ok(Response::Error(e)) => println!("{}", e.bold().red()),
Err(e) => println!("{}: {}", "Connection error".red(), e),
}
2025-06-14 21:10:37 +00:00
},
2025-06-15 19:36:13 +00:00
"dropindex" => {
if parts.len() < 2 {
println!("{}", "Usage: dropindex <field>".red());
2025-06-14 21:10:37 +00:00
continue;
}
2025-06-15 19:36:13 +00:00
let field = parts[1].to_string();
match send_command(Command::DropIndex { field }).await {
Ok(Response::Success(_)) => {
println!("{}", "Index dropped".bright_green());
println!();
},
2025-06-14 21:10:37 +00:00
Ok(Response::Error(e)) => println!("{}", e.bold().red()),
Err(e) => println!("{}: {}", "Connection error".red(), e),
}
},
2025-06-15 19:36:13 +00:00
"sysexec" => {
if parts.len() < 2 {
println!("{}", "Usage: sysexec <script_name>".red());
2025-06-14 21:10:37 +00:00
continue;
}
2025-06-15 19:36:13 +00:00
let script_name = parts[1].to_string();
match send_command(Command::SysExec { script_name }).await {
Ok(Response::Success(Some(output))) => {
println!("{}", "Script output:".bright_green());
println!("{}", output);
println!();
},
Ok(Response::Success(None)) => {
println!("{}", "Script executed but no output".yellow());
println!();
},
2025-06-14 21:10:37 +00:00
Ok(Response::Error(e)) => println!("{}", e.bold().red()),
2025-06-14 14:23:36 +03:00
Err(e) => println!("{}: {}", "Connection error".red(), e),
}
},
2025-06-15 19:36:13 +00:00
"help" => {
2025-06-14 14:23:36 +03:00
println!("Available commands:");
2025-06-15 19:36:13 +00:00
println!(" insert <key> <json_value> (or i) - Insert data");
println!(" get <key> (or g) - Get data");
println!(" update <key> <json_value> (or u) - Update data");
println!(" delete <key> (or d) - Delete data");
println!(" begin (or transaction, tx) - Start transaction");
2025-06-14 21:10:37 +00:00
println!(" commit - Commit transaction");
println!(" rollback - Rollback transaction");
2025-06-15 19:36:13 +00:00
println!(" createindex <field> - Create index");
println!(" dropindex <field> - Drop index");
println!(" sysexec <script_name> - Execute system script");
println!(" exit (or quit) - Exit client");
println!(" help - Show this help");
println!();
2025-06-14 14:23:36 +03:00
},
2025-06-15 19:36:13 +00:00
"exit" | "quit" => break,
2025-06-14 14:23:36 +03:00
_ => 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;
}
}
}
2025-06-15 19:36:13 +00:00
rl.save_history("futriix-history.txt")?;
2025-06-14 14:23:36 +03:00
Ok(())
2025-06-14 21:10:37 +00:00
}