Update futriix-cli/src/main.rs

This commit is contained in:
Григорий Сафронов 2025-06-15 19:36:13 +00:00
parent 3d64b3442e
commit 2b88fe0a72

View File

@ -1,33 +1,43 @@
use serde::{Deserialize, Serialize};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use std::error::Error;
use tokio::net::TcpStream;
use colored::Colorize; use colored::Colorize;
use rustyline::{Editor, error::ReadlineError}; use rustyline::Editor;
use rustyline::error::ReadlineError;
use serde::{Deserialize, Serialize};
use serde_json;
use std::error::Error;
use std::fs; use std::fs;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
use toml::Value; use toml::Value;
#[derive(Debug, Serialize, Deserialize)] mod server {
enum Command { use serde::{Deserialize, Serialize};
Insert { key: String, value: serde_json::Value }, use serde_json;
Get { key: String },
Update { key: String, value: serde_json::Value }, #[derive(Debug, Serialize, Deserialize)]
Delete { key: String }, pub enum Command {
BeginTransaction, Insert { key: String, value: serde_json::Value },
CommitTransaction, Get { key: String },
RollbackTransaction, Update { key: String, value: serde_json::Value },
CreateIndex { field: String }, Delete { key: String },
DropIndex { field: String }, BeginTransaction,
CommitTransaction,
RollbackTransaction,
CreateIndex { field: String },
DropIndex { field: String },
SysExec { script_name: String },
}
#[derive(Debug, Serialize, Deserialize)]
pub enum Response {
Success(Option<serde_json::Value>),
Error(String),
}
} }
#[derive(Debug, Serialize, Deserialize)] use server::{Command, Response};
enum Response {
Success(Option<serde_json::Value>),
Error(String),
}
async fn send_command(cmd: Command) -> Result<Response, Box<dyn Error>> { async fn send_command(cmd: Command) -> Result<Response, Box<dyn std::error::Error>> {
let config_content = fs::read_to_string("../futriix.config.toml")?; let config_content = fs::read_to_string("futriix.config.toml")?;
let config: Value = toml::from_str(&config_content)?; let config: Value = toml::from_str(&config_content)?;
let ip = config["client"]["ip"].as_str().unwrap_or("127.0.0.1"); let ip = config["client"]["ip"].as_str().unwrap_or("127.0.0.1");
@ -58,8 +68,9 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("Type 'help' for available commands"); println!("Type 'help' for available commands");
let mut rl = Editor::<()>::new()?; let mut rl = Editor::<()>::new()?;
if rl.load_history("futriix-cli-history.txt").is_err() { if rl.load_history("futriix-history.txt").is_err() {
println!("No previous history."); println!("No previous history.");
println!();
} }
loop { loop {
@ -73,7 +84,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
} }
let parts: Vec<&str> = line.split_whitespace().collect(); let parts: Vec<&str> = line.split_whitespace().collect();
match parts[0] { match parts[0].to_lowercase().as_str() {
"insert" | "i" => { "insert" | "i" => {
if parts.len() < 3 { if parts.len() < 3 {
println!("{}", "Usage: insert <key> <json_value>".red()); println!("{}", "Usage: insert <key> <json_value>".red());
@ -94,13 +105,20 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
} }
}, },
"get" | "g" => { "get" | "g" => {
if parts.len() != 2 { if parts.len() < 2 {
println!("{}", "Usage: get <key>".red()); println!("{}", "Usage: get <key>".red());
continue; continue;
} }
match send_command(Command::Get { key: parts[1].to_string() }).await { let key = parts[1].to_string();
Ok(Response::Success(Some(value))) => println!("{}", serde_json::to_string_pretty(&value)?), match send_command(Command::Get { key }).await {
Ok(Response::Success(None)) => println!("{}", "Error: Key not found".bold().red()), 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!();
},
Ok(Response::Error(e)) => println!("{}", e.bold().red()), Ok(Response::Error(e)) => println!("{}", e.bold().red()),
Err(e) => println!("{}: {}", "Connection error".red(), e), Err(e) => println!("{}: {}", "Connection error".red(), e),
} }
@ -114,7 +132,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let value_str = parts[2..].join(" "); let value_str = parts[2..].join(" ");
match serde_json::from_str(&value_str) { match serde_json::from_str(&value_str) {
Ok(value) => match send_command(Command::Update { key, value }).await { Ok(value) => match send_command(Command::Update { key, value }).await {
Ok(Response::Success(_)) => println!("{}", "Update successful".bright_green()), Ok(Response::Success(_)) => {
println!("{}", "Update successful".bright_green());
println!();
},
Ok(Response::Error(e)) => println!("{}", e.bold().red()), Ok(Response::Error(e)) => println!("{}", e.bold().red()),
Err(e) => println!("{}: {}", "Connection error".red(), e), Err(e) => println!("{}: {}", "Connection error".red(), e),
}, },
@ -122,68 +143,117 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
} }
}, },
"delete" | "d" => { "delete" | "d" => {
if parts.len() != 2 { if parts.len() < 2 {
println!("{}", "Usage: delete <key>".red()); println!("{}", "Usage: delete <key>".red());
continue; continue;
} }
match send_command(Command::Delete { key: parts[1].to_string() }).await { let key = parts[1].to_string();
Ok(Response::Success(_)) => println!("{}", "Delete successful".bright_green()), match send_command(Command::Delete { key }).await {
Ok(Response::Success(_)) => {
println!("{}", "Delete successful".bright_green());
println!();
},
Ok(Response::Error(e)) => println!("{}", e.bold().red()), Ok(Response::Error(e)) => println!("{}", e.bold().red()),
Err(e) => println!("{}: {}", "Connection error".red(), e), Err(e) => println!("{}: {}", "Connection error".red(), e),
} }
}, },
"begin" => match send_command(Command::BeginTransaction).await { "begin" | "transaction" | "tx" => {
Ok(Response::Success(_)) => println!("{}", "Transaction started".bright_green()), match send_command(Command::BeginTransaction).await {
Ok(Response::Error(e)) => println!("{}", e.bold().red()), Ok(Response::Success(_)) => {
Err(e) => println!("{}: {}", "Connection error".red(), e), 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 { "commit" => {
Ok(Response::Success(_)) => println!("{}", "Transaction committed".bright_green()), match send_command(Command::CommitTransaction).await {
Ok(Response::Error(e)) => println!("{}", e.bold().red()), Ok(Response::Success(_)) => {
Err(e) => println!("{}: {}", "Connection error".red(), e), println!("{}", "Transaction committed".bright_green());
println!();
},
Ok(Response::Error(e)) => println!("{}", e.bold().red()),
Err(e) => println!("{}: {}", "Connection error".red(), e),
}
}, },
"rollback" => match send_command(Command::RollbackTransaction).await { "rollback" => {
Ok(Response::Success(_)) => println!("{}", "Transaction rolled back".bright_green()), match send_command(Command::RollbackTransaction).await {
Ok(Response::Error(e)) => println!("{}", e.bold().red()), Ok(Response::Success(_)) => {
Err(e) => println!("{}: {}", "Connection error".red(), e), println!("{}", "Transaction rolled back".bright_green());
println!();
},
Ok(Response::Error(e)) => println!("{}", e.bold().red()),
Err(e) => println!("{}: {}", "Connection error".red(), e),
}
}, },
"create_index" => { "createindex" => {
if parts.len() != 2 { if parts.len() < 2 {
println!("{}", "Usage: create_index <field_name>".red()); println!("{}", "Usage: createindex <field>".red());
continue; continue;
} }
match send_command(Command::CreateIndex { field: parts[1].to_string() }).await { let field = parts[1].to_string();
Ok(Response::Success(_)) => println!("{}", format!("Index created on field '{}'", parts[1]).bright_green()), match send_command(Command::CreateIndex { field }).await {
Ok(Response::Success(_)) => {
println!("{}", "Index created".bright_green());
println!();
},
Ok(Response::Error(e)) => println!("{}", e.bold().red()), Ok(Response::Error(e)) => println!("{}", e.bold().red()),
Err(e) => println!("{}: {}", "Connection error".red(), e), Err(e) => println!("{}: {}", "Connection error".red(), e),
} }
}, },
"drop_index" => { "dropindex" => {
if parts.len() != 2 { if parts.len() < 2 {
println!("{}", "Usage: drop_index <field_name>".red()); println!("{}", "Usage: dropindex <field>".red());
continue; continue;
} }
match send_command(Command::DropIndex { field: parts[1].to_string() }).await { let field = parts[1].to_string();
Ok(Response::Success(_)) => println!("{}", format!("Index dropped on field '{}'", parts[1]).bright_green()), match send_command(Command::DropIndex { field }).await {
Ok(Response::Success(_)) => {
println!("{}", "Index dropped".bright_green());
println!();
},
Ok(Response::Error(e)) => println!("{}", e.bold().red()), Ok(Response::Error(e)) => println!("{}", e.bold().red()),
Err(e) => println!("{}: {}", "Connection error".red(), e), Err(e) => println!("{}: {}", "Connection error".red(), e),
} }
}, },
"help" | "h" => { "sysexec" => {
if parts.len() < 2 {
println!("{}", "Usage: sysexec <script_name>".red());
continue;
}
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!();
},
Ok(Response::Error(e)) => println!("{}", e.bold().red()),
Err(e) => println!("{}: {}", "Connection error".red(), e),
}
},
"help" => {
println!("Available commands:"); println!("Available commands:");
println!(" insert|i <key> <json_value> - Insert document"); println!(" insert <key> <json_value> (or i) - Insert data");
println!(" get|g <key> - Get document"); println!(" get <key> (or g) - Get data");
println!(" update|u <key> <json_value> - Update document"); println!(" update <key> <json_value> (or u) - Update data");
println!(" delete|d <key> - Delete document"); println!(" delete <key> (or d) - Delete data");
println!(" begin - Start transaction"); println!(" begin (or transaction, tx) - Start transaction");
println!(" commit - Commit transaction"); println!(" commit - Commit transaction");
println!(" rollback - Rollback transaction"); println!(" rollback - Rollback transaction");
println!(" create_index <field> - Create index"); println!(" createindex <field> - Create index");
println!(" drop_index <field> - Drop index"); println!(" dropindex <field> - Drop index");
println!(" help|h - Show this help"); println!(" sysexec <script_name> - Execute system script");
println!(" exit|quit|q - Exit the client"); println!(" exit (or quit) - Exit client");
println!(" help - Show this help");
println!();
}, },
"exit" | "quit" | "q" => break, "exit" | "quit" => break,
_ => println!("{}: Unknown command. Type 'help' for available commands.", "Error".red()), _ => println!("{}: Unknown command. Type 'help' for available commands.", "Error".red()),
} }
}, },
@ -196,6 +266,6 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
} }
} }
rl.save_history("futriix-cli-history.txt")?; rl.save_history("futriix-history.txt")?;
Ok(()) Ok(())
} }