Add more `--json` flags

This commit is contained in:
Oystein Kristoffer Tveit 2024-08-20 17:46:43 +02:00
parent cdb1fb4181
commit 338694a64e
Signed by: oysteikt
GPG Key ID: 9F2F7D8250F35146
3 changed files with 234 additions and 47 deletions

View File

@ -15,7 +15,8 @@ use crate::{
parse_privilege_table_cli_arg, parse_privilege_table_cli_arg,
}, },
protocol::{ 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, print_modify_database_privileges_output_status, ClientToServerMessageStream, Request,
Response, Response,
}, },
@ -102,49 +103,57 @@ pub enum DatabaseCommand {
#[derive(Parser, Debug, Clone)] #[derive(Parser, Debug, Clone)]
pub struct DatabaseCreateArgs { pub struct DatabaseCreateArgs {
/// The name of the database(s) to create. /// The name of the database(s) to create
#[arg(num_args = 1..)] #[arg(num_args = 1..)]
name: Vec<String>, name: Vec<String>,
/// Print the information as JSON
#[arg(short, long)]
json: bool,
} }
#[derive(Parser, Debug, Clone)] #[derive(Parser, Debug, Clone)]
pub struct DatabaseDropArgs { pub struct DatabaseDropArgs {
/// The name of the database(s) to drop. /// The name of the database(s) to drop
#[arg(num_args = 1..)] #[arg(num_args = 1..)]
name: Vec<String>, name: Vec<String>,
/// Print the information as JSON
#[arg(short, long)]
json: bool,
} }
#[derive(Parser, Debug, Clone)] #[derive(Parser, Debug, Clone)]
pub struct DatabaseShowArgs { pub struct DatabaseShowArgs {
/// The name of the database(s) to show. /// The name of the database(s) to show
#[arg(num_args = 0..)] #[arg(num_args = 0..)]
name: Vec<String>, name: Vec<String>,
/// Whether to output the information in JSON format. /// Print the information as JSON
#[arg(short, long)] #[arg(short, long)]
json: bool, json: bool,
} }
#[derive(Parser, Debug, Clone)] #[derive(Parser, Debug, Clone)]
pub struct DatabaseShowPrivsArgs { pub struct DatabaseShowPrivsArgs {
/// The name of the database(s) to show. /// The name of the database(s) to show
#[arg(num_args = 0..)] #[arg(num_args = 0..)]
name: Vec<String>, name: Vec<String>,
/// Whether to output the information in JSON format. /// Print the information as JSON
#[arg(short, long)] #[arg(short, long)]
json: bool, json: bool,
} }
#[derive(Parser, Debug, Clone)] #[derive(Parser, Debug, Clone)]
pub struct DatabaseEditPrivsArgs { pub struct DatabaseEditPrivsArgs {
/// The name of the database to edit privileges for. /// The name of the database to edit privileges for
pub name: Option<String>, pub name: Option<String>,
#[arg(short, long, value_name = "[DATABASE:]USER:PRIVILEGES", num_args = 0..)] #[arg(short, long, value_name = "[DATABASE:]USER:PRIVILEGES", num_args = 0..)]
pub privs: Vec<String>, pub privs: Vec<String>,
/// Whether to output the information in JSON format. /// Print the information as JSON
#[arg(short, long)] #[arg(short, long)]
pub json: bool, pub json: bool,
@ -152,7 +161,7 @@ pub struct DatabaseEditPrivsArgs {
#[arg(short, long)] #[arg(short, long)]
pub editor: Option<String>, pub editor: Option<String>,
/// Disable interactive confirmation before saving changes. /// Disable interactive confirmation before saving changes
#[arg(short, long)] #[arg(short, long)]
pub yes: bool, pub yes: bool,
} }
@ -192,7 +201,11 @@ async fn create_databases(
server_connection.send(Request::Exit).await?; 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(()) Ok(())
} }
@ -215,7 +228,11 @@ async fn drop_databases(
server_connection.send(Request::Exit).await?; 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(()) Ok(())
} }
@ -232,6 +249,8 @@ async fn show_databases(
server_connection.send(message).await?; server_connection.send(message).await?;
// TODO: collect errors for json output.
let database_list = match server_connection.next().await { let database_list = match server_connection.next().await {
Some(Ok(Response::ListDatabases(databases))) => databases Some(Ok(Response::ListDatabases(databases))) => databases
.into_iter() .into_iter()

View File

@ -4,9 +4,11 @@ use dialoguer::{Confirm, Password};
use futures_util::{SinkExt, StreamExt}; use futures_util::{SinkExt, StreamExt};
use crate::core::protocol::{ use crate::core::protocol::{
print_create_users_output_status, print_drop_users_output_status, print_create_users_output_status, print_create_users_output_status_json,
print_lock_users_output_status, print_set_password_output_status, print_drop_users_output_status, print_drop_users_output_status_json,
print_unlock_users_output_status, ClientToServerMessageStream, ListUsersError, Request, 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, Response,
}; };
@ -56,12 +58,22 @@ pub struct UserCreateArgs {
/// Do not ask for a password, leave it unset /// Do not ask for a password, leave it unset
#[clap(long)] #[clap(long)]
no_password: bool, 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)] #[derive(Parser, Debug, Clone)]
pub struct UserDeleteArgs { pub struct UserDeleteArgs {
#[arg(num_args = 1..)] #[arg(num_args = 1..)]
username: Vec<String>, username: Vec<String>,
/// Print the information as JSON
#[arg(short, long)]
json: bool,
} }
#[derive(Parser, Debug, Clone)] #[derive(Parser, Debug, Clone)]
@ -70,6 +82,10 @@ pub struct UserPasswdArgs {
#[clap(short, long)] #[clap(short, long)]
password_file: Option<String>, password_file: Option<String>,
/// Print the information as JSON
#[arg(short, long)]
json: bool,
} }
#[derive(Parser, Debug, Clone)] #[derive(Parser, Debug, Clone)]
@ -77,7 +93,8 @@ pub struct UserShowArgs {
#[arg(num_args = 0..)] #[arg(num_args = 0..)]
username: Vec<String>, username: Vec<String>,
#[clap(short, long)] /// Print the information as JSON
#[arg(short, long)]
json: bool, json: bool,
} }
@ -85,12 +102,20 @@ pub struct UserShowArgs {
pub struct UserLockArgs { pub struct UserLockArgs {
#[arg(num_args = 1..)] #[arg(num_args = 1..)]
username: Vec<String>, username: Vec<String>,
/// Print the information as JSON
#[arg(short, long)]
json: bool,
} }
#[derive(Parser, Debug, Clone)] #[derive(Parser, Debug, Clone)]
pub struct UserUnlockArgs { pub struct UserUnlockArgs {
#[arg(num_args = 1..)] #[arg(num_args = 1..)]
username: Vec<String>, username: Vec<String>,
/// Print the information as JSON
#[arg(short, long)]
json: bool,
} }
pub async fn handle_command( pub async fn handle_command(
@ -126,39 +151,43 @@ async fn create_users(
response => return erroneous_server_response(response), 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 let successfully_created_users = result
.iter() .iter()
.filter_map(|(username, result)| result.as_ref().ok().map(|_| username)) .filter_map(|(username, result)| result.as_ref().ok().map(|_| username))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
for username in successfully_created_users { for username in successfully_created_users {
if !args.no_password if !args.no_password
&& Confirm::new() && Confirm::new()
.with_prompt(format!( .with_prompt(format!(
"Do you want to set a password for user '{}'?", "Do you want to set a password for user '{}'?",
username username
)) ))
.default(false) .default(false)
.interact()? .interact()?
{ {
let password = read_password_from_stdin_with_double_check(username)?; let password = read_password_from_stdin_with_double_check(username)?;
let message = Request::PasswdUser(username.clone(), password); let message = Request::PasswdUser(username.clone(), password);
if let Err(err) = server_connection.send(message).await { if let Err(err) = server_connection.send(message).await {
server_connection.close().await.ok(); server_connection.close().await.ok();
anyhow::bail!(err); anyhow::bail!(err);
}
match server_connection.next().await {
Some(Ok(Response::PasswdUser(result))) => {
print_set_password_output_status(&result, username)
} }
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?; 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(()) Ok(())
} }
@ -351,7 +384,11 @@ async fn lock_users(
server_connection.send(Request::Exit).await?; 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(()) Ok(())
} }
@ -378,7 +415,11 @@ async fn unlock_users(
server_connection.send(Request::Exit).await?; 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(()) Ok(())
} }

View File

@ -3,6 +3,7 @@ use std::collections::BTreeMap;
use indoc::indoc; use indoc::indoc;
use itertools::Itertools; use itertools::Itertools;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::json;
use crate::{ use crate::{
core::{common::UnixUser, database_privileges::DatabasePrivilegeRowDiff}, 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::<serde_json::Map<_, _>>();
println!(
"{}",
serde_json::to_string_pretty(&value)
.unwrap_or("Failed to serialize result to JSON".to_string())
);
}
impl CreateDatabaseError { impl CreateDatabaseError {
pub fn to_error_message(&self, database_name: &str) -> String { pub fn to_error_message(&self, database_name: &str) -> String {
match self { 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::<serde_json::Map<_, _>>();
println!(
"{}",
serde_json::to_string_pretty(&value)
.unwrap_or("Failed to serialize result to JSON".to_string())
);
}
impl DropDatabaseError { impl DropDatabaseError {
pub fn to_error_message(&self, database_name: &str) -> String { pub fn to_error_message(&self, database_name: &str) -> String {
match self { 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::<serde_json::Map<_, _>>();
println!(
"{}",
serde_json::to_string_pretty(&value)
.unwrap_or("Failed to serialize result to JSON".to_string())
);
}
impl CreateUserError { impl CreateUserError {
pub fn to_error_message(&self, username: &str) -> String { pub fn to_error_message(&self, username: &str) -> String {
match self { 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::<serde_json::Map<_, _>>();
println!(
"{}",
serde_json::to_string_pretty(&value)
.unwrap_or("Failed to serialize result to JSON".to_string())
);
}
impl DropUserError { impl DropUserError {
pub fn to_error_message(&self, username: &str) -> String { pub fn to_error_message(&self, username: &str) -> String {
match self { 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::<serde_json::Map<_, _>>();
println!(
"{}",
serde_json::to_string_pretty(&value)
.unwrap_or("Failed to serialize result to JSON".to_string())
);
}
impl LockUserError { impl LockUserError {
pub fn to_error_message(&self, username: &str) -> String { pub fn to_error_message(&self, username: &str) -> String {
match self { 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::<serde_json::Map<_, _>>();
println!(
"{}",
serde_json::to_string_pretty(&value)
.unwrap_or("Failed to serialize result to JSON".to_string())
);
}
impl UnlockUserError { impl UnlockUserError {
pub fn to_error_message(&self, username: &str) -> String { pub fn to_error_message(&self, username: &str) -> String {
match self { match self {