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,
},
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<String>,
/// 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<String>,
/// 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<String>,
/// 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<String>,
/// 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<String>,
#[arg(short, long, value_name = "[DATABASE:]USER:PRIVILEGES", num_args = 0..)]
pub privs: Vec<String>,
/// 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<String>,
/// 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()

View File

@ -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<String>,
/// 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<String>,
/// 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<String>,
#[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<String>,
/// Print the information as JSON
#[arg(short, long)]
json: bool,
}
#[derive(Parser, Debug, Clone)]
pub struct UserUnlockArgs {
#[arg(num_args = 1..)]
username: Vec<String>,
/// 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::<Vec<_>>();
let successfully_created_users = result
.iter()
.filter_map(|(username, result)| result.as_ref().ok().map(|_| username))
.collect::<Vec<_>>();
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(())
}

View File

@ -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::<serde_json::Map<_, _>>();
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::<serde_json::Map<_, _>>();
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::<serde_json::Map<_, _>>();
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::<serde_json::Map<_, _>>();
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::<serde_json::Map<_, _>>();
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::<serde_json::Map<_, _>>();
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 {