diff --git a/src/client/commands/show_db.rs b/src/client/commands/show_db.rs index 9a72087..939f6ab 100644 --- a/src/client/commands/show_db.rs +++ b/src/client/commands/show_db.rs @@ -1,14 +1,16 @@ use clap::Parser; use clap_complete::ArgValueCompleter; use futures_util::SinkExt; -use prettytable::{Cell, Row, Table}; use tokio_stream::StreamExt; use crate::{ client::commands::erroneous_server_response, core::{ completion::mysql_database_completer, - protocol::{ClientToServerMessageStream, Request, Response}, + protocol::{ + ClientToServerMessageStream, Request, Response, print_list_databases_output_status, + print_list_databases_output_status_json, + }, types::MySQLDatabase, }, }; @@ -40,25 +42,13 @@ pub async fn show_databases( server_connection.send(message).await?; - // TODO: collect errors for json output. - - let mut contained_errors = false; - let database_list = match server_connection.next().await { - Some(Ok(Response::ListDatabases(databases))) => databases - .into_iter() - .filter_map(|(database_name, result)| match result { - Ok(database_row) => Some(database_row), - Err(err) => { - contained_errors = true; - eprintln!("{}", err.to_error_message(&database_name)); - eprintln!("Skipping..."); - println!(); - None - } - }) - .collect::>(), + let databases = match server_connection.next().await { + Some(Ok(Response::ListDatabases(databases))) => databases, Some(Ok(Response::ListAllDatabases(database_list))) => match database_list { - Ok(list) => list, + Ok(list) => list + .into_iter() + .map(|db| (db.database.clone(), Ok(db))) + .collect(), Err(err) => { server_connection.send(Request::Exit).await?; return Err( @@ -72,19 +62,12 @@ pub async fn show_databases( server_connection.send(Request::Exit).await?; if args.json { - println!("{}", serde_json::to_string_pretty(&database_list)?); - } else if database_list.is_empty() { - println!("No databases to show."); + print_list_databases_output_status_json(&databases); } else { - let mut table = Table::new(); - table.add_row(Row::new(vec![Cell::new("Database")])); - for db in database_list { - table.add_row(row![db.database]); - } - table.printstd(); + print_list_databases_output_status(&databases); } - if args.fail && contained_errors { + if args.fail && databases.values().any(|res| res.is_err()) { std::process::exit(1); } diff --git a/src/client/commands/show_user.rs b/src/client/commands/show_user.rs index ecdde64..566efb9 100644 --- a/src/client/commands/show_user.rs +++ b/src/client/commands/show_user.rs @@ -1,4 +1,3 @@ -use anyhow::Context; use clap::Parser; use clap_complete::ArgValueCompleter; use futures_util::SinkExt; @@ -8,7 +7,10 @@ use crate::{ client::commands::erroneous_server_response, core::{ completion::mysql_user_completer, - protocol::{ClientToServerMessageStream, Request, Response}, + protocol::{ + ClientToServerMessageStream, Request, Response, print_list_users_output_status, + print_list_users_output_status_json, + }, types::MySQLUser, }, }; @@ -43,22 +45,13 @@ pub async fn show_users( anyhow::bail!(err); } - let mut contained_errors = false; let users = match server_connection.next().await { - Some(Ok(Response::ListUsers(users))) => users - .into_iter() - .filter_map(|(username, result)| match result { - Ok(user) => Some(user), - Err(err) => { - contained_errors = true; - eprintln!("{}", err.to_error_message(&username)); - eprintln!("Skipping..."); - None - } - }) - .collect::>(), + Some(Ok(Response::ListUsers(users))) => users, Some(Ok(Response::ListAllUsers(users))) => match users { - Ok(users) => users, + Ok(users) => users + .into_iter() + .map(|user| (user.user.clone(), Ok(user))) + .collect(), Err(err) => { server_connection.send(Request::Exit).await?; return Err( @@ -72,32 +65,12 @@ pub async fn show_users( server_connection.send(Request::Exit).await?; if args.json { - println!( - "{}", - serde_json::to_string_pretty(&users).context("Failed to serialize users to JSON")? - ); - } else if users.is_empty() { - println!("No users to show."); + print_list_users_output_status_json(&users); } else { - let mut table = prettytable::Table::new(); - table.add_row(row![ - "User", - "Password is set", - "Locked", - "Databases where user has privileges" - ]); - for user in users { - table.add_row(row![ - user.user, - user.has_password, - user.is_locked, - user.databases.join("\n") - ]); - } - table.printstd(); + print_list_users_output_status(&users); } - if args.fail && contained_errors { + if args.fail && users.values().any(|result| result.is_err()) { std::process::exit(1); } diff --git a/src/core/protocol/commands/list_databases.rs b/src/core/protocol/commands/list_databases.rs index 2c36a5b..9b9badc 100644 --- a/src/core/protocol/commands/list_databases.rs +++ b/src/core/protocol/commands/list_databases.rs @@ -1,6 +1,8 @@ use std::collections::BTreeMap; +use prettytable::{Cell, Row, Table}; use serde::{Deserialize, Serialize}; +use serde_json::json; use crate::{ core::{ @@ -22,6 +24,57 @@ pub enum ListDatabasesError { MySqlError(String), } +pub fn print_list_databases_output_status(output: &ListDatabasesResponse) { + let mut final_database_list: Vec<&DatabaseRow> = Vec::new(); + for (db_name, db_result) in output { + match db_result { + Ok(db_row) => final_database_list.push(db_row), + Err(err) => { + eprintln!("{}", err.to_error_message(db_name)); + eprintln!("Skipping..."); + } + } + } + + if final_database_list.is_empty() { + println!("No databases to show."); + } else { + let mut table = Table::new(); + table.add_row(Row::new(vec![Cell::new("Database")])); + for db in final_database_list { + table.add_row(row![db.database]); + } + table.printstd(); + } +} + +pub fn print_list_databases_output_status_json(output: &ListDatabasesResponse) { + let value = output + .iter() + .map(|(name, result)| match result { + Ok(_row) => ( + name.to_string(), + json!({ + "status": "success", + // NOTE: there will likely be more data to include here in the future + }), + ), + Err(err) => ( + name.to_string(), + 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 ListDatabasesError { pub fn to_error_message(&self, database_name: &MySQLDatabase) -> String { match self { diff --git a/src/core/protocol/commands/list_users.rs b/src/core/protocol/commands/list_users.rs index d75b0a1..cea9a84 100644 --- a/src/core/protocol/commands/list_users.rs +++ b/src/core/protocol/commands/list_users.rs @@ -1,6 +1,8 @@ use std::collections::BTreeMap; +use prettytable::Table; use serde::{Deserialize, Serialize}; +use serde_json::json; use crate::{ core::{ @@ -22,6 +24,72 @@ pub enum ListUsersError { MySqlError(String), } +pub fn print_list_users_output_status(output: &ListUsersResponse) { + let mut final_user_list: Vec<&DatabaseUser> = Vec::new(); + for (db_name, db_result) in output { + match db_result { + Ok(db_row) => final_user_list.push(db_row), + Err(err) => { + eprintln!("{}", err.to_error_message(db_name)); + eprintln!("Skipping..."); + } + } + } + + if final_user_list.is_empty() { + println!("No users to show."); + } else { + let mut table = Table::new(); + table.add_row(row![ + "User", + "Password is set", + "Locked", + "Databases where user has privileges" + ]); + for user in final_user_list { + table.add_row(row![ + user.user, + user.has_password, + user.is_locked, + user.databases.join("\n") + ]); + } + table.printstd(); + } +} + +pub fn print_list_users_output_status_json(output: &ListUsersResponse) { + let value = output + .iter() + .map(|(name, result)| match result { + Ok(row) => ( + name.to_string(), + json!({ + "status": "success", + "value": { + "user": row.user, + "has_password": row.has_password, + "is_locked": row.is_locked, + "databases": row.databases, + } + }), + ), + Err(err) => ( + name.to_string(), + 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 ListUsersError { pub fn to_error_message(&self, username: &MySQLUser) -> String { match self {