From 338694a64e136f3d92a8d77c17bcf62376762fb2 Mon Sep 17 00:00:00 2001 From: h7x4 Date: Tue, 20 Aug 2024 17:46:43 +0200 Subject: [PATCH] Add more `--json` flags --- src/cli/database_command.rs | 43 ++++++--- src/cli/user_command.rs | 111 +++++++++++++++------- src/core/protocol/server_responses.rs | 127 ++++++++++++++++++++++++++ 3 files changed, 234 insertions(+), 47 deletions(-) diff --git a/src/cli/database_command.rs b/src/cli/database_command.rs index 4435257..b458870 100644 --- a/src/cli/database_command.rs +++ b/src/cli/database_command.rs @@ -15,7 +15,8 @@ use crate::{ parse_privilege_table_cli_arg, }, protocol::{ - print_create_databases_output_status, print_drop_databases_output_status, + print_create_databases_output_status, print_create_databases_output_status_json, + print_drop_databases_output_status, print_drop_databases_output_status_json, print_modify_database_privileges_output_status, ClientToServerMessageStream, Request, Response, }, @@ -102,49 +103,57 @@ pub enum DatabaseCommand { #[derive(Parser, Debug, Clone)] pub struct DatabaseCreateArgs { - /// The name of the database(s) to create. + /// The name of the database(s) to create #[arg(num_args = 1..)] name: Vec, + + /// Print the information as JSON + #[arg(short, long)] + json: bool, } #[derive(Parser, Debug, Clone)] pub struct DatabaseDropArgs { - /// The name of the database(s) to drop. + /// The name of the database(s) to drop #[arg(num_args = 1..)] name: Vec, + + /// Print the information as JSON + #[arg(short, long)] + json: bool, } #[derive(Parser, Debug, Clone)] pub struct DatabaseShowArgs { - /// The name of the database(s) to show. + /// The name of the database(s) to show #[arg(num_args = 0..)] name: Vec, - /// Whether to output the information in JSON format. + /// Print the information as JSON #[arg(short, long)] json: bool, } #[derive(Parser, Debug, Clone)] pub struct DatabaseShowPrivsArgs { - /// The name of the database(s) to show. + /// The name of the database(s) to show #[arg(num_args = 0..)] name: Vec, - /// Whether to output the information in JSON format. + /// Print the information as JSON #[arg(short, long)] json: bool, } #[derive(Parser, Debug, Clone)] pub struct DatabaseEditPrivsArgs { - /// The name of the database to edit privileges for. + /// The name of the database to edit privileges for pub name: Option, #[arg(short, long, value_name = "[DATABASE:]USER:PRIVILEGES", num_args = 0..)] pub privs: Vec, - /// Whether to output the information in JSON format. + /// Print the information as JSON #[arg(short, long)] pub json: bool, @@ -152,7 +161,7 @@ pub struct DatabaseEditPrivsArgs { #[arg(short, long)] pub editor: Option, - /// Disable interactive confirmation before saving changes. + /// Disable interactive confirmation before saving changes #[arg(short, long)] pub yes: bool, } @@ -192,7 +201,11 @@ async fn create_databases( server_connection.send(Request::Exit).await?; - print_create_databases_output_status(&result); + if args.json { + print_create_databases_output_status_json(&result); + } else { + print_create_databases_output_status(&result); + } Ok(()) } @@ -215,7 +228,11 @@ async fn drop_databases( server_connection.send(Request::Exit).await?; - print_drop_databases_output_status(&result); + if args.json { + print_drop_databases_output_status_json(&result); + } else { + print_drop_databases_output_status(&result); + }; Ok(()) } @@ -232,6 +249,8 @@ async fn show_databases( server_connection.send(message).await?; + // TODO: collect errors for json output. + let database_list = match server_connection.next().await { Some(Ok(Response::ListDatabases(databases))) => databases .into_iter() diff --git a/src/cli/user_command.rs b/src/cli/user_command.rs index a99e596..05ad92a 100644 --- a/src/cli/user_command.rs +++ b/src/cli/user_command.rs @@ -4,9 +4,11 @@ use dialoguer::{Confirm, Password}; use futures_util::{SinkExt, StreamExt}; use crate::core::protocol::{ - print_create_users_output_status, print_drop_users_output_status, - print_lock_users_output_status, print_set_password_output_status, - print_unlock_users_output_status, ClientToServerMessageStream, ListUsersError, Request, + print_create_users_output_status, print_create_users_output_status_json, + print_drop_users_output_status, print_drop_users_output_status_json, + print_lock_users_output_status, print_lock_users_output_status_json, + print_set_password_output_status, print_unlock_users_output_status, + print_unlock_users_output_status_json, ClientToServerMessageStream, ListUsersError, Request, Response, }; @@ -56,12 +58,22 @@ pub struct UserCreateArgs { /// Do not ask for a password, leave it unset #[clap(long)] no_password: bool, + + /// Print the information as JSON + /// + /// Note that this implies `--no-password`, since the command will become non-interactive. + #[arg(short, long)] + json: bool, } #[derive(Parser, Debug, Clone)] pub struct UserDeleteArgs { #[arg(num_args = 1..)] username: Vec, + + /// Print the information as JSON + #[arg(short, long)] + json: bool, } #[derive(Parser, Debug, Clone)] @@ -70,6 +82,10 @@ pub struct UserPasswdArgs { #[clap(short, long)] password_file: Option, + + /// Print the information as JSON + #[arg(short, long)] + json: bool, } #[derive(Parser, Debug, Clone)] @@ -77,7 +93,8 @@ pub struct UserShowArgs { #[arg(num_args = 0..)] username: Vec, - #[clap(short, long)] + /// Print the information as JSON + #[arg(short, long)] json: bool, } @@ -85,12 +102,20 @@ pub struct UserShowArgs { pub struct UserLockArgs { #[arg(num_args = 1..)] username: Vec, + + /// Print the information as JSON + #[arg(short, long)] + json: bool, } #[derive(Parser, Debug, Clone)] pub struct UserUnlockArgs { #[arg(num_args = 1..)] username: Vec, + + /// Print the information as JSON + #[arg(short, long)] + json: bool, } pub async fn handle_command( @@ -126,39 +151,43 @@ async fn create_users( response => return erroneous_server_response(response), }; - print_create_users_output_status(&result); + if args.json { + print_create_users_output_status_json(&result); + } else { + print_create_users_output_status(&result); - let successfully_created_users = result - .iter() - .filter_map(|(username, result)| result.as_ref().ok().map(|_| username)) - .collect::>(); + let successfully_created_users = result + .iter() + .filter_map(|(username, result)| result.as_ref().ok().map(|_| username)) + .collect::>(); - for username in successfully_created_users { - if !args.no_password - && Confirm::new() - .with_prompt(format!( - "Do you want to set a password for user '{}'?", - username - )) - .default(false) - .interact()? - { - let password = read_password_from_stdin_with_double_check(username)?; - let message = Request::PasswdUser(username.clone(), password); + for username in successfully_created_users { + if !args.no_password + && Confirm::new() + .with_prompt(format!( + "Do you want to set a password for user '{}'?", + username + )) + .default(false) + .interact()? + { + let password = read_password_from_stdin_with_double_check(username)?; + let message = Request::PasswdUser(username.clone(), password); - if let Err(err) = server_connection.send(message).await { - server_connection.close().await.ok(); - anyhow::bail!(err); - } - - match server_connection.next().await { - Some(Ok(Response::PasswdUser(result))) => { - print_set_password_output_status(&result, username) + if let Err(err) = server_connection.send(message).await { + server_connection.close().await.ok(); + anyhow::bail!(err); } - response => return erroneous_server_response(response), - } - println!(); + match server_connection.next().await { + Some(Ok(Response::PasswdUser(result))) => { + print_set_password_output_status(&result, username) + } + response => return erroneous_server_response(response), + } + + println!(); + } } } @@ -189,7 +218,11 @@ async fn drop_users( server_connection.send(Request::Exit).await?; - print_drop_users_output_status(&result); + if args.json { + print_drop_users_output_status_json(&result); + } else { + print_drop_users_output_status(&result); + } Ok(()) } @@ -351,7 +384,11 @@ async fn lock_users( server_connection.send(Request::Exit).await?; - print_lock_users_output_status(&result); + if args.json { + print_lock_users_output_status_json(&result); + } else { + print_lock_users_output_status(&result); + } Ok(()) } @@ -378,7 +415,11 @@ async fn unlock_users( server_connection.send(Request::Exit).await?; - print_unlock_users_output_status(&result); + if args.json { + print_unlock_users_output_status_json(&result); + } else { + print_unlock_users_output_status(&result); + } Ok(()) } diff --git a/src/core/protocol/server_responses.rs b/src/core/protocol/server_responses.rs index 4bb857d..185963b 100644 --- a/src/core/protocol/server_responses.rs +++ b/src/core/protocol/server_responses.rs @@ -3,6 +3,7 @@ use std::collections::BTreeMap; use indoc::indoc; use itertools::Itertools; use serde::{Deserialize, Serialize}; +use serde_json::json; use crate::{ core::{common::UnixUser, database_privileges::DatabasePrivilegeRowDiff}, @@ -141,6 +142,27 @@ pub fn print_create_databases_output_status(output: &CreateDatabasesOutput) { } } +pub fn print_create_databases_output_status_json(output: &CreateDatabasesOutput) { + let value = output + .iter() + .map(|(name, result)| match result { + Ok(()) => (name.to_owned(), json!({ "status": "success" })), + Err(err) => ( + name.clone(), + json!({ + "status": "error", + "error": err.to_error_message(name), + }), + ), + }) + .collect::>(); + println!( + "{}", + serde_json::to_string_pretty(&value) + .unwrap_or("Failed to serialize result to JSON".to_string()) + ); +} + impl CreateDatabaseError { pub fn to_error_message(&self, database_name: &str) -> String { match self { @@ -184,6 +206,27 @@ pub fn print_drop_databases_output_status(output: &DropDatabasesOutput) { } } +pub fn print_drop_databases_output_status_json(output: &DropDatabasesOutput) { + let value = output + .iter() + .map(|(name, result)| match result { + Ok(()) => (name.to_owned(), json!({ "status": "success" })), + Err(err) => ( + name.clone(), + json!({ + "status": "error", + "error": err.to_error_message(name), + }), + ), + }) + .collect::>(); + println!( + "{}", + serde_json::to_string_pretty(&value) + .unwrap_or("Failed to serialize result to JSON".to_string()) + ); +} + impl DropDatabaseError { pub fn to_error_message(&self, database_name: &str) -> String { match self { @@ -412,6 +455,27 @@ pub fn print_create_users_output_status(output: &CreateUsersOutput) { } } +pub fn print_create_users_output_status_json(output: &CreateUsersOutput) { + let value = output + .iter() + .map(|(name, result)| match result { + Ok(()) => (name.to_owned(), json!({ "status": "success" })), + Err(err) => ( + name.clone(), + json!({ + "status": "error", + "error": err.to_error_message(name), + }), + ), + }) + .collect::>(); + println!( + "{}", + serde_json::to_string_pretty(&value) + .unwrap_or("Failed to serialize result to JSON".to_string()) + ); +} + impl CreateUserError { pub fn to_error_message(&self, username: &str) -> String { match self { @@ -453,6 +517,27 @@ pub fn print_drop_users_output_status(output: &DropUsersOutput) { } } +pub fn print_drop_users_output_status_json(output: &DropUsersOutput) { + let value = output + .iter() + .map(|(name, result)| match result { + Ok(()) => (name.to_owned(), json!({ "status": "success" })), + Err(err) => ( + name.clone(), + json!({ + "status": "error", + "error": err.to_error_message(name), + }), + ), + }) + .collect::>(); + println!( + "{}", + serde_json::to_string_pretty(&value) + .unwrap_or("Failed to serialize result to JSON".to_string()) + ); +} + impl DropUserError { pub fn to_error_message(&self, username: &str) -> String { match self { @@ -531,6 +616,27 @@ pub fn print_lock_users_output_status(output: &LockUsersOutput) { } } +pub fn print_lock_users_output_status_json(output: &LockUsersOutput) { + let value = output + .iter() + .map(|(name, result)| match result { + Ok(()) => (name.to_owned(), json!({ "status": "success" })), + Err(err) => ( + name.clone(), + json!({ + "status": "error", + "error": err.to_error_message(name), + }), + ), + }) + .collect::>(); + println!( + "{}", + serde_json::to_string_pretty(&value) + .unwrap_or("Failed to serialize result to JSON".to_string()) + ); +} + impl LockUserError { pub fn to_error_message(&self, username: &str) -> String { match self { @@ -574,6 +680,27 @@ pub fn print_unlock_users_output_status(output: &UnlockUsersOutput) { } } +pub fn print_unlock_users_output_status_json(output: &UnlockUsersOutput) { + let value = output + .iter() + .map(|(name, result)| match result { + Ok(()) => (name.to_owned(), json!({ "status": "success" })), + Err(err) => ( + name.clone(), + json!({ + "status": "error", + "error": err.to_error_message(name), + }), + ), + }) + .collect::>(); + println!( + "{}", + serde_json::to_string_pretty(&value) + .unwrap_or("Failed to serialize result to JSON".to_string()) + ); +} + impl UnlockUserError { pub fn to_error_message(&self, username: &str) -> String { match self {