From a0be0d3b92a1795ff2df7ba7f77ab7336f96cb98 Mon Sep 17 00:00:00 2001
From: h7x4 <h7x4@nani.wtf>
Date: Tue, 20 Aug 2024 17:46:43 +0200
Subject: [PATCH] Wrap database users and database names in newtypes

Also, use less cloning where possible
---
 src/cli/database_command.rs                   |  28 ++---
 .../mysql_admutils_compatibility/common.rs    |  11 +-
 .../mysql_dbadm.rs                            |  45 +++----
 .../mysql_useradm.rs                          |  51 +++-----
 src/cli/user_command.rs                       |  34 +++---
 src/core/common.rs                            |   2 +-
 src/core/database_privileges.rs               | 101 ++++++++--------
 src/core/protocol/request_response.rs         | 113 ++++++++++++++++--
 src/core/protocol/server_responses.rs         | 103 ++++++++--------
 src/main.rs                                   |   8 +-
 src/server/config.rs                          |   2 +-
 src/server/input_sanitization.rs              |   2 +-
 src/server/sql/database_operations.rs         |  41 ++++---
 .../sql/database_privilege_operations.rs      |  49 ++++----
 src/server/sql/user_operations.rs             |  25 ++--
 15 files changed, 355 insertions(+), 260 deletions(-)

diff --git a/src/cli/database_command.rs b/src/cli/database_command.rs
index b458870..868497b 100644
--- a/src/cli/database_command.rs
+++ b/src/cli/database_command.rs
@@ -17,8 +17,8 @@ use crate::{
         protocol::{
             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,
+            print_modify_database_privileges_output_status, ClientToServerMessageStream,
+            MySQLDatabase, Request, Response,
         },
     },
     server::sql::database_privilege_operations::{DatabasePrivilegeRow, DATABASE_PRIVILEGE_FIELDS},
@@ -105,7 +105,7 @@ pub enum DatabaseCommand {
 pub struct DatabaseCreateArgs {
     /// The name of the database(s) to create
     #[arg(num_args = 1..)]
-    name: Vec<String>,
+    name: Vec<MySQLDatabase>,
 
     /// Print the information as JSON
     #[arg(short, long)]
@@ -116,7 +116,7 @@ pub struct DatabaseCreateArgs {
 pub struct DatabaseDropArgs {
     /// The name of the database(s) to drop
     #[arg(num_args = 1..)]
-    name: Vec<String>,
+    name: Vec<MySQLDatabase>,
 
     /// Print the information as JSON
     #[arg(short, long)]
@@ -127,7 +127,7 @@ pub struct DatabaseDropArgs {
 pub struct DatabaseShowArgs {
     /// The name of the database(s) to show
     #[arg(num_args = 0..)]
-    name: Vec<String>,
+    name: Vec<MySQLDatabase>,
 
     /// Print the information as JSON
     #[arg(short, long)]
@@ -138,7 +138,7 @@ pub struct DatabaseShowArgs {
 pub struct DatabaseShowPrivsArgs {
     /// The name of the database(s) to show
     #[arg(num_args = 0..)]
-    name: Vec<String>,
+    name: Vec<MySQLDatabase>,
 
     /// Print the information as JSON
     #[arg(short, long)]
@@ -148,7 +148,7 @@ pub struct DatabaseShowPrivsArgs {
 #[derive(Parser, Debug, Clone)]
 pub struct DatabaseEditPrivsArgs {
     /// The name of the database to edit privileges for
-    pub name: Option<String>,
+    pub name: Option<MySQLDatabase>,
 
     #[arg(short, long, value_name = "[DATABASE:]USER:PRIVILEGES", num_args = 0..)]
     pub privs: Vec<String>,
@@ -191,7 +191,7 @@ async fn create_databases(
         anyhow::bail!("No database names provided");
     }
 
-    let message = Request::CreateDatabases(args.name.clone());
+    let message = Request::CreateDatabases(args.name.to_owned());
     server_connection.send(message).await?;
 
     let result = match server_connection.next().await {
@@ -218,7 +218,7 @@ async fn drop_databases(
         anyhow::bail!("No database names provided");
     }
 
-    let message = Request::DropDatabases(args.name.clone());
+    let message = Request::DropDatabases(args.name.to_owned());
     server_connection.send(message).await?;
 
     let result = match server_connection.next().await {
@@ -244,7 +244,7 @@ async fn show_databases(
     let message = if args.name.is_empty() {
         Request::ListDatabases(None)
     } else {
-        Request::ListDatabases(Some(args.name.clone()))
+        Request::ListDatabases(Some(args.name.to_owned()))
     };
 
     server_connection.send(message).await?;
@@ -301,7 +301,7 @@ async fn show_database_privileges(
     let message = if args.name.is_empty() {
         Request::ListPrivileges(None)
     } else {
-        Request::ListPrivileges(Some(args.name.clone()))
+        Request::ListPrivileges(Some(args.name.to_owned()))
     };
     server_connection.send(message).await?;
 
@@ -373,7 +373,7 @@ pub async fn edit_database_privileges(
     args: DatabaseEditPrivsArgs,
     mut server_connection: ClientToServerMessageStream,
 ) -> anyhow::Result<()> {
-    let message = Request::ListPrivileges(args.name.clone().map(|name| vec![name]));
+    let message = Request::ListPrivileges(args.name.to_owned().map(|name| vec![name]));
 
     server_connection.send(message).await?;
 
@@ -405,7 +405,7 @@ pub async fn edit_database_privileges(
     let privileges_to_change = if !args.privs.is_empty() {
         parse_privilege_tables_from_args(&args)?
     } else {
-        edit_privileges_with_editor(&privilege_data, args.name.as_deref())?
+        edit_privileges_with_editor(&privilege_data, args.name.as_ref())?
     };
 
     let diffs = diff_privileges(&privilege_data, &privileges_to_change);
@@ -471,7 +471,7 @@ fn parse_privilege_tables_from_args(
 
 fn edit_privileges_with_editor(
     privilege_data: &[DatabasePrivilegeRow],
-    database_name: Option<&str>,
+    database_name: Option<&MySQLDatabase>,
 ) -> anyhow::Result<Vec<DatabasePrivilegeRow>> {
     let unix_user = User::from_uid(getuid())
         .context("Failed to look up your UNIX username")
diff --git a/src/cli/mysql_admutils_compatibility/common.rs b/src/cli/mysql_admutils_compatibility/common.rs
index f2c7f19..d3d829d 100644
--- a/src/cli/mysql_admutils_compatibility/common.rs
+++ b/src/cli/mysql_admutils_compatibility/common.rs
@@ -1,4 +1,11 @@
+use crate::core::protocol::{MySQLDatabase, MySQLUser};
+
 #[inline]
-pub fn trim_to_32_chars(name: &str) -> String {
-    name.chars().take(32).collect()
+pub fn trim_db_name_to_32_chars(db_name: &MySQLDatabase) -> MySQLDatabase {
+    db_name.chars().take(32).collect::<String>().into()
+}
+
+#[inline]
+pub fn trim_user_name_to_32_chars(user_name: &MySQLUser) -> MySQLUser {
+    user_name.chars().take(32).collect::<String>().into()
 }
diff --git a/src/cli/mysql_admutils_compatibility/mysql_dbadm.rs b/src/cli/mysql_admutils_compatibility/mysql_dbadm.rs
index c028791..9c63fc2 100644
--- a/src/cli/mysql_admutils_compatibility/mysql_dbadm.rs
+++ b/src/cli/mysql_admutils_compatibility/mysql_dbadm.rs
@@ -9,7 +9,7 @@ use crate::{
         common::erroneous_server_response,
         database_command,
         mysql_admutils_compatibility::{
-            common::trim_to_32_chars,
+            common::trim_db_name_to_32_chars,
             error_messages::{
                 format_show_database_error_message, handle_create_database_error,
                 handle_drop_database_error,
@@ -20,7 +20,7 @@ use crate::{
         bootstrap::bootstrap_server_connection_and_drop_privileges,
         protocol::{
             create_client_to_server_message_stream, ClientToServerMessageStream,
-            GetDatabasesPrivilegeDataError, Request, Response,
+            GetDatabasesPrivilegeDataError, MySQLDatabase, Request, Response,
         },
     },
     server::sql::database_privilege_operations::DatabasePrivilegeRow,
@@ -120,27 +120,27 @@ pub enum Command {
 pub struct CreateArgs {
     /// The name of the DATABASE(s) to create.
     #[arg(num_args = 1..)]
-    name: Vec<String>,
+    name: Vec<MySQLDatabase>,
 }
 
 #[derive(Parser)]
 pub struct DatabaseDropArgs {
     /// The name of the DATABASE(s) to drop.
     #[arg(num_args = 1..)]
-    name: Vec<String>,
+    name: Vec<MySQLDatabase>,
 }
 
 #[derive(Parser)]
 pub struct DatabaseShowArgs {
     /// The name of the DATABASE(s) to show.
     #[arg(num_args = 0..)]
-    name: Vec<String>,
+    name: Vec<MySQLDatabase>,
 }
 
 #[derive(Parser)]
 pub struct EditPermArgs {
     /// The name of the DATABASE to edit permissions for.
-    pub database: String,
+    pub database: MySQLDatabase,
 }
 
 pub fn main() -> anyhow::Result<()> {
@@ -202,11 +202,7 @@ async fn create_databases(
     args: CreateArgs,
     mut server_connection: ClientToServerMessageStream,
 ) -> anyhow::Result<()> {
-    let database_names = args
-        .name
-        .iter()
-        .map(|name| trim_to_32_chars(name))
-        .collect();
+    let database_names = args.name.iter().map(trim_db_name_to_32_chars).collect();
 
     let message = Request::CreateDatabases(database_names);
     server_connection.send(message).await?;
@@ -232,11 +228,7 @@ async fn drop_databases(
     args: DatabaseDropArgs,
     mut server_connection: ClientToServerMessageStream,
 ) -> anyhow::Result<()> {
-    let database_names = args
-        .name
-        .iter()
-        .map(|name| trim_to_32_chars(name))
-        .collect();
+    let database_names = args.name.iter().map(trim_db_name_to_32_chars).collect();
 
     let message = Request::DropDatabases(database_names);
     server_connection.send(message).await?;
@@ -262,11 +254,8 @@ async fn show_databases(
     args: DatabaseShowArgs,
     mut server_connection: ClientToServerMessageStream,
 ) -> anyhow::Result<()> {
-    let database_names: Vec<String> = args
-        .name
-        .iter()
-        .map(|name| trim_to_32_chars(name))
-        .collect();
+    let database_names: Vec<MySQLDatabase> =
+        args.name.iter().map(trim_db_name_to_32_chars).collect();
 
     let message = if database_names.is_empty() {
         let message = Request::ListDatabases(None);
@@ -291,14 +280,16 @@ async fn show_databases(
 
     // NOTE: mysql-dbadm show has a quirk where valid database names
     //       for non-existent databases will report with no users.
-    let results: Vec<Result<(String, Vec<DatabasePrivilegeRow>), String>> = match response {
+    let results: Vec<Result<(MySQLDatabase, Vec<DatabasePrivilegeRow>), String>> = match response {
         Some(Ok(Response::ListPrivileges(result))) => result
             .into_iter()
-            .map(|(name, rows)| match rows.map(|rows| (name.clone(), rows)) {
-                Ok(rows) => Ok(rows),
-                Err(GetDatabasesPrivilegeDataError::DatabaseDoesNotExist) => Ok((name, vec![])),
-                Err(err) => Err(format_show_database_error_message(err, &name)),
-            })
+            .map(
+                |(name, rows)| match rows.map(|rows| (name.to_owned(), rows)) {
+                    Ok(rows) => Ok(rows),
+                    Err(GetDatabasesPrivilegeDataError::DatabaseDoesNotExist) => Ok((name, vec![])),
+                    Err(err) => Err(format_show_database_error_message(err, &name)),
+                },
+            )
             .collect(),
         response => return erroneous_server_response(response),
     };
diff --git a/src/cli/mysql_admutils_compatibility/mysql_useradm.rs b/src/cli/mysql_admutils_compatibility/mysql_useradm.rs
index cdc7254..fc1dabb 100644
--- a/src/cli/mysql_admutils_compatibility/mysql_useradm.rs
+++ b/src/cli/mysql_admutils_compatibility/mysql_useradm.rs
@@ -9,7 +9,7 @@ use crate::{
     cli::{
         common::erroneous_server_response,
         mysql_admutils_compatibility::{
-            common::trim_to_32_chars,
+            common::trim_user_name_to_32_chars,
             error_messages::{
                 handle_create_user_error, handle_drop_user_error, handle_list_users_error,
             },
@@ -19,7 +19,8 @@ use crate::{
     core::{
         bootstrap::bootstrap_server_connection_and_drop_privileges,
         protocol::{
-            create_client_to_server_message_stream, ClientToServerMessageStream, Request, Response,
+            create_client_to_server_message_stream, ClientToServerMessageStream, MySQLUser,
+            Request, Response,
         },
     },
     server::sql::user_operations::DatabaseUser,
@@ -83,28 +84,28 @@ pub enum Command {
 pub struct CreateArgs {
     /// The name of the USER(s) to create.
     #[arg(num_args = 1..)]
-    name: Vec<String>,
+    name: Vec<MySQLUser>,
 }
 
 #[derive(Parser)]
 pub struct DeleteArgs {
     /// The name of the USER(s) to delete.
     #[arg(num_args = 1..)]
-    name: Vec<String>,
+    name: Vec<MySQLUser>,
 }
 
 #[derive(Parser)]
 pub struct PasswdArgs {
     /// The name of the USER(s) to change the password for.
     #[arg(num_args = 1..)]
-    name: Vec<String>,
+    name: Vec<MySQLUser>,
 }
 
 #[derive(Parser)]
 pub struct ShowArgs {
     /// The name of the USER(s) to show.
     #[arg(num_args = 0..)]
-    name: Vec<String>,
+    name: Vec<MySQLUser>,
 }
 
 pub fn main() -> anyhow::Result<()> {
@@ -152,13 +153,9 @@ async fn create_user(
     args: CreateArgs,
     mut server_connection: ClientToServerMessageStream,
 ) -> anyhow::Result<()> {
-    let usernames = args
-        .name
-        .iter()
-        .map(|name| trim_to_32_chars(name))
-        .collect();
+    let db_users = args.name.iter().map(trim_user_name_to_32_chars).collect();
 
-    let message = Request::CreateUsers(usernames);
+    let message = Request::CreateUsers(db_users);
     server_connection.send(message).await?;
 
     let result = match server_connection.next().await {
@@ -182,13 +179,9 @@ async fn drop_users(
     args: DeleteArgs,
     mut server_connection: ClientToServerMessageStream,
 ) -> anyhow::Result<()> {
-    let usernames = args
-        .name
-        .iter()
-        .map(|name| trim_to_32_chars(name))
-        .collect();
+    let db_users = args.name.iter().map(trim_user_name_to_32_chars).collect();
 
-    let message = Request::DropUsers(usernames);
+    let message = Request::DropUsers(db_users);
     server_connection.send(message).await?;
 
     let result = match server_connection.next().await {
@@ -212,13 +205,9 @@ async fn passwd_users(
     args: PasswdArgs,
     mut server_connection: ClientToServerMessageStream,
 ) -> anyhow::Result<()> {
-    let usernames = args
-        .name
-        .iter()
-        .map(|name| trim_to_32_chars(name))
-        .collect();
+    let db_users = args.name.iter().map(trim_user_name_to_32_chars).collect();
 
-    let message = Request::ListUsers(Some(usernames));
+    let message = Request::ListUsers(Some(db_users));
     server_connection.send(message).await?;
 
     let response = match server_connection.next().await {
@@ -243,11 +232,11 @@ async fn passwd_users(
 
     for user in users {
         let password = read_password_from_stdin_with_double_check(&user.user)?;
-        let message = Request::PasswdUser(user.user.clone(), password);
+        let message = Request::PasswdUser(user.user.to_owned(), password);
         server_connection.send(message).await?;
         match server_connection.next().await {
             Some(Ok(Response::PasswdUser(result))) => match result {
-                Ok(()) => println!("Password updated for user '{}'.", user.user),
+                Ok(()) => println!("Password updated for user '{}'.", &user.user),
                 Err(_) => eprintln!(
                     "{}: Failed to update password for user '{}'.",
                     argv0, user.user,
@@ -266,16 +255,12 @@ async fn show_users(
     args: ShowArgs,
     mut server_connection: ClientToServerMessageStream,
 ) -> anyhow::Result<()> {
-    let usernames: Vec<_> = args
-        .name
-        .iter()
-        .map(|name| trim_to_32_chars(name))
-        .collect();
+    let db_users: Vec<_> = args.name.iter().map(trim_user_name_to_32_chars).collect();
 
-    let message = if usernames.is_empty() {
+    let message = if db_users.is_empty() {
         Request::ListUsers(None)
     } else {
-        Request::ListUsers(Some(usernames))
+        Request::ListUsers(Some(db_users))
     };
     server_connection.send(message).await?;
 
diff --git a/src/cli/user_command.rs b/src/cli/user_command.rs
index 05ad92a..d92ca34 100644
--- a/src/cli/user_command.rs
+++ b/src/cli/user_command.rs
@@ -8,8 +8,8 @@ use crate::core::protocol::{
     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,
+    print_unlock_users_output_status_json, ClientToServerMessageStream, ListUsersError, MySQLUser,
+    Request, Response,
 };
 
 use super::common::erroneous_server_response;
@@ -53,7 +53,7 @@ pub enum UserCommand {
 #[derive(Parser, Debug, Clone)]
 pub struct UserCreateArgs {
     #[arg(num_args = 1..)]
-    username: Vec<String>,
+    username: Vec<MySQLUser>,
 
     /// Do not ask for a password, leave it unset
     #[clap(long)]
@@ -69,7 +69,7 @@ pub struct UserCreateArgs {
 #[derive(Parser, Debug, Clone)]
 pub struct UserDeleteArgs {
     #[arg(num_args = 1..)]
-    username: Vec<String>,
+    username: Vec<MySQLUser>,
 
     /// Print the information as JSON
     #[arg(short, long)]
@@ -78,7 +78,7 @@ pub struct UserDeleteArgs {
 
 #[derive(Parser, Debug, Clone)]
 pub struct UserPasswdArgs {
-    username: String,
+    username: MySQLUser,
 
     #[clap(short, long)]
     password_file: Option<String>,
@@ -91,7 +91,7 @@ pub struct UserPasswdArgs {
 #[derive(Parser, Debug, Clone)]
 pub struct UserShowArgs {
     #[arg(num_args = 0..)]
-    username: Vec<String>,
+    username: Vec<MySQLUser>,
 
     /// Print the information as JSON
     #[arg(short, long)]
@@ -101,7 +101,7 @@ pub struct UserShowArgs {
 #[derive(Parser, Debug, Clone)]
 pub struct UserLockArgs {
     #[arg(num_args = 1..)]
-    username: Vec<String>,
+    username: Vec<MySQLUser>,
 
     /// Print the information as JSON
     #[arg(short, long)]
@@ -111,7 +111,7 @@ pub struct UserLockArgs {
 #[derive(Parser, Debug, Clone)]
 pub struct UserUnlockArgs {
     #[arg(num_args = 1..)]
-    username: Vec<String>,
+    username: Vec<MySQLUser>,
 
     /// Print the information as JSON
     #[arg(short, long)]
@@ -140,7 +140,7 @@ async fn create_users(
         anyhow::bail!("No usernames provided");
     }
 
-    let message = Request::CreateUsers(args.username.clone());
+    let message = Request::CreateUsers(args.username.to_owned());
     if let Err(err) = server_connection.send(message).await {
         server_connection.close().await.ok();
         anyhow::bail!(anyhow::Error::from(err).context("Failed to communicate with server"));
@@ -172,7 +172,7 @@ async fn create_users(
                     .interact()?
             {
                 let password = read_password_from_stdin_with_double_check(username)?;
-                let message = Request::PasswdUser(username.clone(), password);
+                let message = Request::PasswdUser(username.to_owned(), password);
 
                 if let Err(err) = server_connection.send(message).await {
                     server_connection.close().await.ok();
@@ -204,7 +204,7 @@ async fn drop_users(
         anyhow::bail!("No usernames provided");
     }
 
-    let message = Request::DropUsers(args.username.clone());
+    let message = Request::DropUsers(args.username.to_owned());
 
     if let Err(err) = server_connection.send(message).await {
         server_connection.close().await.ok();
@@ -227,7 +227,7 @@ async fn drop_users(
     Ok(())
 }
 
-pub fn read_password_from_stdin_with_double_check(username: &str) -> anyhow::Result<String> {
+pub fn read_password_from_stdin_with_double_check(username: &MySQLUser) -> anyhow::Result<String> {
     Password::new()
         .with_prompt(format!("New MySQL password for user '{}'", username))
         .with_confirmation(
@@ -243,7 +243,7 @@ async fn passwd_user(
     mut server_connection: ClientToServerMessageStream,
 ) -> anyhow::Result<()> {
     // TODO: create a "user" exists check" command
-    let message = Request::ListUsers(Some(vec![args.username.clone()]));
+    let message = Request::ListUsers(Some(vec![args.username.to_owned()]));
     if let Err(err) = server_connection.send(message).await {
         server_connection.close().await.ok();
         anyhow::bail!(err);
@@ -273,7 +273,7 @@ async fn passwd_user(
         read_password_from_stdin_with_double_check(&args.username)?
     };
 
-    let message = Request::PasswdUser(args.username.clone(), password);
+    let message = Request::PasswdUser(args.username.to_owned(), password);
 
     if let Err(err) = server_connection.send(message).await {
         server_connection.close().await.ok();
@@ -299,7 +299,7 @@ async fn show_users(
     let message = if args.username.is_empty() {
         Request::ListUsers(None)
     } else {
-        Request::ListUsers(Some(args.username.clone()))
+        Request::ListUsers(Some(args.username.to_owned()))
     };
 
     if let Err(err) = server_connection.send(message).await {
@@ -370,7 +370,7 @@ async fn lock_users(
         anyhow::bail!("No usernames provided");
     }
 
-    let message = Request::LockUsers(args.username.clone());
+    let message = Request::LockUsers(args.username.to_owned());
 
     if let Err(err) = server_connection.send(message).await {
         server_connection.close().await.ok();
@@ -401,7 +401,7 @@ async fn unlock_users(
         anyhow::bail!("No usernames provided");
     }
 
-    let message = Request::UnlockUsers(args.username.clone());
+    let message = Request::UnlockUsers(args.username.to_owned());
 
     if let Err(err) = server_connection.send(message).await {
         server_connection.close().await.ok();
diff --git a/src/core/common.rs b/src/core/common.rs
index 2d4045c..5b30e53 100644
--- a/src/core/common.rs
+++ b/src/core/common.rs
@@ -54,7 +54,7 @@ impl UnixUser {
 
         Ok(UnixUser {
             username: libc_user.name,
-            groups: groups.iter().map(|g| g.name.clone()).collect(),
+            groups: groups.iter().map(|g| g.name.to_owned()).collect(),
         })
     }
 
diff --git a/src/core/database_privileges.rs b/src/core/database_privileges.rs
index a92f40a..a731452 100644
--- a/src/core/database_privileges.rs
+++ b/src/core/database_privileges.rs
@@ -7,7 +7,10 @@ use std::{
     collections::{BTreeSet, HashMap},
 };
 
-use super::common::{rev_yn, yn};
+use super::{
+    common::{rev_yn, yn},
+    protocol::{MySQLDatabase, MySQLUser},
+};
 use crate::server::sql::database_privilege_operations::{
     DatabasePrivilegeRow, DATABASE_PRIVILEGE_FIELDS,
 };
@@ -35,8 +38,8 @@ pub fn diff(row1: &DatabasePrivilegeRow, row2: &DatabasePrivilegeRow) -> Databas
     debug_assert!(row1.db == row2.db && row1.user == row2.user);
 
     DatabasePrivilegeRowDiff {
-        db: row1.db.clone(),
-        user: row1.user.clone(),
+        db: row1.db.to_owned(),
+        user: row1.user.to_owned(),
         diff: DATABASE_PRIVILEGE_FIELDS
             .into_iter()
             .skip(2)
@@ -70,8 +73,8 @@ pub fn parse_privilege_table_cli_arg(arg: &str) -> anyhow::Result<DatabasePrivil
         anyhow::bail!("Username cannot be empty.");
     }
 
-    let db = parts[0].to_string();
-    let user = parts[1].to_string();
+    let db = parts[0].into();
+    let user = parts[1].into();
     let privs = parts[2].to_string();
 
     let mut result = DatabasePrivilegeRow {
@@ -165,11 +168,11 @@ const EDITOR_COMMENT: &str = r#"
 pub fn generate_editor_content_from_privilege_data(
     privilege_data: &[DatabasePrivilegeRow],
     unix_user: &str,
-    database_name: Option<&str>,
+    database_name: Option<&MySQLDatabase>,
 ) -> String {
     let example_user = format!("{}_user", unix_user);
     let example_db = database_name
-        .unwrap_or(&format!("{}_db", unix_user))
+        .unwrap_or(&format!("{}_db", unix_user).into())
         .to_string();
 
     // NOTE: `.max()`` fails when the iterator is empty.
@@ -206,8 +209,8 @@ pub fn generate_editor_content_from_privilege_data(
 
     let example_line = format_privileges_line_for_editor(
         &DatabasePrivilegeRow {
-            db: example_db,
-            user: example_user,
+            db: example_db.into(),
+            user: example_user.into(),
             select_priv: true,
             insert_priv: true,
             update_priv: true,
@@ -298,8 +301,8 @@ fn parse_privilege_row_from_editor(row: &str) -> PrivilegeRowParseResult {
     }
 
     let row = DatabasePrivilegeRow {
-        db: (*parts.first().unwrap()).to_owned(),
-        user: (*parts.get(1).unwrap()).to_owned(),
+        db: (*parts.first().unwrap()).into(),
+        user: (*parts.get(1).unwrap()).into(),
         select_priv: match parse_privilege_cell_from_editor(
             parts.get(2).unwrap(),
             DATABASE_PRIVILEGE_FIELDS[2],
@@ -423,8 +426,8 @@ pub fn parse_privilege_data_from_editor_content(
 /// The `User` and `Database` are the same for both instances.
 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord)]
 pub struct DatabasePrivilegeRowDiff {
-    pub db: String,
-    pub user: String,
+    pub db: MySQLDatabase,
+    pub user: MySQLUser,
     pub diff: BTreeSet<DatabasePrivilegeChange>,
 }
 
@@ -454,7 +457,7 @@ pub enum DatabasePrivilegesDiff {
 }
 
 impl DatabasePrivilegesDiff {
-    pub fn get_database_name(&self) -> &str {
+    pub fn get_database_name(&self) -> &MySQLDatabase {
         match self {
             DatabasePrivilegesDiff::New(p) => &p.db,
             DatabasePrivilegesDiff::Modified(p) => &p.db,
@@ -462,7 +465,7 @@ impl DatabasePrivilegesDiff {
         }
     }
 
-    pub fn get_user_name(&self) -> &str {
+    pub fn get_user_name(&self) -> &MySQLUser {
         match self {
             DatabasePrivilegesDiff::New(p) => &p.user,
             DatabasePrivilegesDiff::Modified(p) => &p.user,
@@ -478,34 +481,36 @@ pub fn diff_privileges(
     from: &[DatabasePrivilegeRow],
     to: &[DatabasePrivilegeRow],
 ) -> BTreeSet<DatabasePrivilegesDiff> {
-    let from_lookup_table: HashMap<(String, String), DatabasePrivilegeRow> = HashMap::from_iter(
-        from.iter()
-            .cloned()
-            .map(|p| ((p.db.clone(), p.user.clone()), p)),
-    );
+    let from_lookup_table: HashMap<(MySQLDatabase, MySQLUser), DatabasePrivilegeRow> =
+        HashMap::from_iter(
+            from.iter()
+                .cloned()
+                .map(|p| ((p.db.to_owned(), p.user.to_owned()), p)),
+        );
 
-    let to_lookup_table: HashMap<(String, String), DatabasePrivilegeRow> = HashMap::from_iter(
-        to.iter()
-            .cloned()
-            .map(|p| ((p.db.clone(), p.user.clone()), p)),
-    );
+    let to_lookup_table: HashMap<(MySQLDatabase, MySQLUser), DatabasePrivilegeRow> =
+        HashMap::from_iter(
+            to.iter()
+                .cloned()
+                .map(|p| ((p.db.to_owned(), p.user.to_owned()), p)),
+        );
 
     let mut result = BTreeSet::new();
 
     for p in to {
-        if let Some(old_p) = from_lookup_table.get(&(p.db.clone(), p.user.clone())) {
+        if let Some(old_p) = from_lookup_table.get(&(p.db.to_owned(), p.user.to_owned())) {
             let diff = diff(old_p, p);
             if !diff.diff.is_empty() {
                 result.insert(DatabasePrivilegesDiff::Modified(diff));
             }
         } else {
-            result.insert(DatabasePrivilegesDiff::New(p.clone()));
+            result.insert(DatabasePrivilegesDiff::New(p.to_owned()));
         }
     }
 
     for p in from {
-        if !to_lookup_table.contains_key(&(p.db.clone(), p.user.clone())) {
-            result.insert(DatabasePrivilegesDiff::Deleted(p.clone()));
+        if !to_lookup_table.contains_key(&(p.db.to_owned(), p.user.to_owned())) {
+            result.insert(DatabasePrivilegesDiff::Deleted(p.to_owned()));
         }
     }
 
@@ -593,8 +598,8 @@ mod tests {
         assert_eq!(
             result.ok(),
             Some(DatabasePrivilegeRow {
-                db: "db".to_owned(),
-                user: "user".to_owned(),
+                db: "db".into(),
+                user: "user".into(),
                 select_priv: true,
                 insert_priv: true,
                 update_priv: true,
@@ -613,8 +618,8 @@ mod tests {
         assert_eq!(
             result.ok(),
             Some(DatabasePrivilegeRow {
-                db: "db".to_owned(),
-                user: "user".to_owned(),
+                db: "db".into(),
+                user: "user".into(),
                 select_priv: false,
                 insert_priv: false,
                 update_priv: false,
@@ -633,8 +638,8 @@ mod tests {
         assert_eq!(
             result.ok(),
             Some(DatabasePrivilegeRow {
-                db: "db".to_owned(),
-                user: "user".to_owned(),
+                db: "db".into(),
+                user: "user".into(),
                 select_priv: true,
                 insert_priv: true,
                 update_priv: true,
@@ -668,8 +673,8 @@ mod tests {
     #[test]
     fn test_diff_privileges() {
         let row_to_be_modified = DatabasePrivilegeRow {
-            db: "db".to_owned(),
-            user: "user".to_owned(),
+            db: "db".into(),
+            user: "user".into(),
             select_priv: true,
             insert_priv: true,
             update_priv: true,
@@ -683,20 +688,20 @@ mod tests {
             references_priv: false,
         };
 
-        let mut row_to_be_deleted = row_to_be_modified.clone();
+        let mut row_to_be_deleted = row_to_be_modified.to_owned();
         "user2".clone_into(&mut row_to_be_deleted.user);
 
-        let from = vec![row_to_be_modified.clone(), row_to_be_deleted.clone()];
+        let from = vec![row_to_be_modified.to_owned(), row_to_be_deleted.to_owned()];
 
-        let mut modified_row = row_to_be_modified.clone();
+        let mut modified_row = row_to_be_modified.to_owned();
         modified_row.select_priv = false;
         modified_row.insert_priv = false;
         modified_row.index_priv = true;
 
-        let mut new_row = row_to_be_modified.clone();
+        let mut new_row = row_to_be_modified.to_owned();
         "user3".clone_into(&mut new_row.user);
 
-        let to = vec![modified_row.clone(), new_row.clone()];
+        let to = vec![modified_row.to_owned(), new_row.to_owned()];
 
         let diffs = diff_privileges(&from, &to);
 
@@ -705,8 +710,8 @@ mod tests {
             BTreeSet::from_iter(vec![
                 DatabasePrivilegesDiff::Deleted(row_to_be_deleted),
                 DatabasePrivilegesDiff::Modified(DatabasePrivilegeRowDiff {
-                    db: "db".to_owned(),
-                    user: "user".to_owned(),
+                    db: "db".into(),
+                    user: "user".into(),
                     diff: BTreeSet::from_iter(vec![
                         DatabasePrivilegeChange::YesToNo("select_priv".to_owned()),
                         DatabasePrivilegeChange::YesToNo("insert_priv".to_owned()),
@@ -722,8 +727,8 @@ mod tests {
     fn ensure_generated_and_parsed_editor_content_is_equal() {
         let permissions = vec![
             DatabasePrivilegeRow {
-                db: "db".to_owned(),
-                user: "user".to_owned(),
+                db: "db".into(),
+                user: "user".into(),
                 select_priv: true,
                 insert_priv: true,
                 update_priv: true,
@@ -737,8 +742,8 @@ mod tests {
                 references_priv: true,
             },
             DatabasePrivilegeRow {
-                db: "db2".to_owned(),
-                user: "user2".to_owned(),
+                db: "db".into(),
+                user: "user".into(),
                 select_priv: false,
                 insert_priv: false,
                 update_priv: false,
diff --git a/src/core/protocol/request_response.rs b/src/core/protocol/request_response.rs
index 2f194af..ef0d029 100644
--- a/src/core/protocol/request_response.rs
+++ b/src/core/protocol/request_response.rs
@@ -1,4 +1,9 @@
-use std::collections::BTreeSet;
+use std::{
+    collections::BTreeSet,
+    fmt::{Display, Formatter},
+    ops::{Deref, DerefMut},
+    str::FromStr,
+};
 
 use serde::{Deserialize, Serialize};
 use tokio::net::UnixStream;
@@ -31,21 +36,107 @@ pub fn create_client_to_server_message_stream(socket: UnixStream) -> ClientToSer
     tokio_serde::Framed::new(length_delimited, Bincode::default())
 }
 
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
+pub struct MySQLUser(String);
+
+impl FromStr for MySQLUser {
+    type Err = String;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        Ok(MySQLUser(s.to_string()))
+    }
+}
+
+impl Deref for MySQLUser {
+    type Target = String;
+
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}
+
+impl DerefMut for MySQLUser {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.0
+    }
+}
+
+impl Display for MySQLUser {
+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{}", self.0)
+    }
+}
+
+impl From<&str> for MySQLUser {
+    fn from(s: &str) -> Self {
+        MySQLUser(s.to_string())
+    }
+}
+
+impl From<String> for MySQLUser {
+    fn from(s: String) -> Self {
+        MySQLUser(s)
+    }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
+pub struct MySQLDatabase(String);
+
+impl FromStr for MySQLDatabase {
+    type Err = String;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        Ok(MySQLDatabase(s.to_string()))
+    }
+}
+
+impl Deref for MySQLDatabase {
+    type Target = String;
+
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}
+
+impl DerefMut for MySQLDatabase {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.0
+    }
+}
+
+impl Display for MySQLDatabase {
+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{}", self.0)
+    }
+}
+
+impl From<&str> for MySQLDatabase {
+    fn from(s: &str) -> Self {
+        MySQLDatabase(s.to_string())
+    }
+}
+
+impl From<String> for MySQLDatabase {
+    fn from(s: String) -> Self {
+        MySQLDatabase(s)
+    }
+}
+
 #[non_exhaustive]
 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
 pub enum Request {
-    CreateDatabases(Vec<String>),
-    DropDatabases(Vec<String>),
-    ListDatabases(Option<Vec<String>>),
-    ListPrivileges(Option<Vec<String>>),
+    CreateDatabases(Vec<MySQLDatabase>),
+    DropDatabases(Vec<MySQLDatabase>),
+    ListDatabases(Option<Vec<MySQLDatabase>>),
+    ListPrivileges(Option<Vec<MySQLDatabase>>),
     ModifyPrivileges(BTreeSet<DatabasePrivilegesDiff>),
 
-    CreateUsers(Vec<String>),
-    DropUsers(Vec<String>),
-    PasswdUser(String, String),
-    ListUsers(Option<Vec<String>>),
-    LockUsers(Vec<String>),
-    UnlockUsers(Vec<String>),
+    CreateUsers(Vec<MySQLUser>),
+    DropUsers(Vec<MySQLUser>),
+    PasswdUser(MySQLUser, String),
+    ListUsers(Option<Vec<MySQLUser>>),
+    LockUsers(Vec<MySQLUser>),
+    UnlockUsers(Vec<MySQLUser>),
 
     // Commit,
     Exit,
diff --git a/src/core/protocol/server_responses.rs b/src/core/protocol/server_responses.rs
index 185963b..c3fc061 100644
--- a/src/core/protocol/server_responses.rs
+++ b/src/core/protocol/server_responses.rs
@@ -13,6 +13,8 @@ use crate::{
     },
 };
 
+use super::{MySQLDatabase, MySQLUser};
+
 /// This enum is used to differentiate between database and user operations.
 /// Their output are very similar, but there are slight differences in the words used.
 #[derive(Debug, PartialEq, Eq, Clone, Copy)]
@@ -22,17 +24,17 @@ pub enum DbOrUser {
 }
 
 impl DbOrUser {
-    pub fn lowercased(&self) -> String {
+    pub fn lowercased(&self) -> &'static str {
         match self {
-            DbOrUser::Database => "database".to_string(),
-            DbOrUser::User => "user".to_string(),
+            DbOrUser::Database => "database",
+            DbOrUser::User => "user",
         }
     }
 
-    pub fn capitalized(&self) -> String {
+    pub fn capitalized(&self) -> &'static str {
         match self {
-            DbOrUser::Database => "Database".to_string(),
-            DbOrUser::User => "User".to_string(),
+            DbOrUser::Database => "Database",
+            DbOrUser::User => "User",
         }
     }
 }
@@ -73,6 +75,11 @@ impl OwnerValidationError {
     pub fn to_error_message(self, name: &str, db_or_user: DbOrUser) -> String {
         let user = UnixUser::from_enviroment();
 
+        let UnixUser { username, groups } = user.unwrap_or(UnixUser {
+            username: "???".to_string(),
+            groups: vec![],
+        });
+
         match self {
             OwnerValidationError::NoMatch => format!(
                 indoc! {r#"
@@ -88,11 +95,8 @@ impl OwnerValidationError {
                 name,
                 db_or_user.lowercased(),
                 db_or_user.lowercased(),
-                user.as_ref()
-                    .map(|u| u.username.clone())
-                    .unwrap_or("???".to_string()),
-                user.map(|u| u.groups)
-                    .unwrap_or_default()
+                username,
+                groups
                     .iter()
                     .map(|g| format!("  - {}", g))
                     .sorted()
@@ -118,7 +122,7 @@ pub enum OwnerValidationError {
     StringEmpty,
 }
 
-pub type CreateDatabasesOutput = BTreeMap<String, Result<(), CreateDatabaseError>>;
+pub type CreateDatabasesOutput = BTreeMap<MySQLDatabase, Result<(), CreateDatabaseError>>;
 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
 pub enum CreateDatabaseError {
     SanitizationError(NameValidationError),
@@ -146,9 +150,9 @@ 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" })),
+            Ok(()) => (name.to_string(), json!({ "status": "success" })),
             Err(err) => (
-                name.clone(),
+                name.to_string(),
                 json!({
                   "status": "error",
                   "error": err.to_error_message(name),
@@ -164,7 +168,7 @@ pub fn print_create_databases_output_status_json(output: &CreateDatabasesOutput)
 }
 
 impl CreateDatabaseError {
-    pub fn to_error_message(&self, database_name: &str) -> String {
+    pub fn to_error_message(&self, database_name: &MySQLDatabase) -> String {
         match self {
             CreateDatabaseError::SanitizationError(err) => {
                 err.to_error_message(database_name, DbOrUser::Database)
@@ -182,7 +186,7 @@ impl CreateDatabaseError {
     }
 }
 
-pub type DropDatabasesOutput = BTreeMap<String, Result<(), DropDatabaseError>>;
+pub type DropDatabasesOutput = BTreeMap<MySQLDatabase, Result<(), DropDatabaseError>>;
 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
 pub enum DropDatabaseError {
     SanitizationError(NameValidationError),
@@ -195,7 +199,10 @@ pub fn print_drop_databases_output_status(output: &DropDatabasesOutput) {
     for (database_name, result) in output {
         match result {
             Ok(()) => {
-                println!("Database '{}' dropped successfully.", database_name);
+                println!(
+                    "Database '{}' dropped successfully.",
+                    database_name.as_str()
+                );
             }
             Err(err) => {
                 println!("{}", err.to_error_message(database_name));
@@ -210,9 +217,9 @@ 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" })),
+            Ok(()) => (name.to_string(), json!({ "status": "success" })),
             Err(err) => (
-                name.clone(),
+                name.to_string(),
                 json!({
                   "status": "error",
                   "error": err.to_error_message(name),
@@ -228,7 +235,7 @@ pub fn print_drop_databases_output_status_json(output: &DropDatabasesOutput) {
 }
 
 impl DropDatabaseError {
-    pub fn to_error_message(&self, database_name: &str) -> String {
+    pub fn to_error_message(&self, database_name: &MySQLDatabase) -> String {
         match self {
             DropDatabaseError::SanitizationError(err) => {
                 err.to_error_message(database_name, DbOrUser::Database)
@@ -246,7 +253,7 @@ impl DropDatabaseError {
     }
 }
 
-pub type ListDatabasesOutput = BTreeMap<String, Result<DatabaseRow, ListDatabasesError>>;
+pub type ListDatabasesOutput = BTreeMap<MySQLDatabase, Result<DatabaseRow, ListDatabasesError>>;
 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
 pub enum ListDatabasesError {
     SanitizationError(NameValidationError),
@@ -256,7 +263,7 @@ pub enum ListDatabasesError {
 }
 
 impl ListDatabasesError {
-    pub fn to_error_message(&self, database_name: &str) -> String {
+    pub fn to_error_message(&self, database_name: &MySQLDatabase) -> String {
         match self {
             ListDatabasesError::SanitizationError(err) => {
                 err.to_error_message(database_name, DbOrUser::Database)
@@ -293,7 +300,7 @@ impl ListAllDatabasesError {
 //       no need to index by database name.
 
 pub type GetDatabasesPrivilegeData =
-    BTreeMap<String, Result<Vec<DatabasePrivilegeRow>, GetDatabasesPrivilegeDataError>>;
+    BTreeMap<MySQLDatabase, Result<Vec<DatabasePrivilegeRow>, GetDatabasesPrivilegeDataError>>;
 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
 pub enum GetDatabasesPrivilegeDataError {
     SanitizationError(NameValidationError),
@@ -303,7 +310,7 @@ pub enum GetDatabasesPrivilegeDataError {
 }
 
 impl GetDatabasesPrivilegeDataError {
-    pub fn to_error_message(&self, database_name: &str) -> String {
+    pub fn to_error_message(&self, database_name: &MySQLDatabase) -> String {
         match self {
             GetDatabasesPrivilegeDataError::SanitizationError(err) => {
                 err.to_error_message(database_name, DbOrUser::Database)
@@ -337,7 +344,7 @@ impl GetAllDatabasesPrivilegeDataError {
 }
 
 pub type ModifyDatabasePrivilegesOutput =
-    BTreeMap<(String, String), Result<(), ModifyDatabasePrivilegesError>>;
+    BTreeMap<(MySQLDatabase, MySQLUser), Result<(), ModifyDatabasePrivilegesError>>;
 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
 pub enum ModifyDatabasePrivilegesError {
     DatabaseSanitizationError(NameValidationError),
@@ -352,8 +359,8 @@ pub enum ModifyDatabasePrivilegesError {
 #[allow(clippy::enum_variant_names)]
 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
 pub enum DiffDoesNotApplyError {
-    RowAlreadyExists(String, String),
-    RowDoesNotExist(String, String),
+    RowAlreadyExists(MySQLDatabase, MySQLUser),
+    RowDoesNotExist(MySQLDatabase, MySQLUser),
     RowPrivilegeChangeDoesNotApply(DatabasePrivilegeRowDiff, DatabasePrivilegeRow),
 }
 
@@ -376,7 +383,7 @@ pub fn print_modify_database_privileges_output_status(output: &ModifyDatabasePri
 }
 
 impl ModifyDatabasePrivilegesError {
-    pub fn to_error_message(&self, database_name: &str, username: &str) -> String {
+    pub fn to_error_message(&self, database_name: &MySQLDatabase, username: &MySQLUser) -> String {
         match self {
             ModifyDatabasePrivilegesError::DatabaseSanitizationError(err) => {
                 err.to_error_message(database_name, DbOrUser::Database)
@@ -431,7 +438,7 @@ impl DiffDoesNotApplyError {
     }
 }
 
-pub type CreateUsersOutput = BTreeMap<String, Result<(), CreateUserError>>;
+pub type CreateUsersOutput = BTreeMap<MySQLUser, Result<(), CreateUserError>>;
 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
 pub enum CreateUserError {
     SanitizationError(NameValidationError),
@@ -459,9 +466,9 @@ 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" })),
+            Ok(()) => (name.to_string(), json!({ "status": "success" })),
             Err(err) => (
-                name.clone(),
+                name.to_string(),
                 json!({
                   "status": "error",
                   "error": err.to_error_message(name),
@@ -477,7 +484,7 @@ pub fn print_create_users_output_status_json(output: &CreateUsersOutput) {
 }
 
 impl CreateUserError {
-    pub fn to_error_message(&self, username: &str) -> String {
+    pub fn to_error_message(&self, username: &MySQLUser) -> String {
         match self {
             CreateUserError::SanitizationError(err) => {
                 err.to_error_message(username, DbOrUser::User)
@@ -493,7 +500,7 @@ impl CreateUserError {
     }
 }
 
-pub type DropUsersOutput = BTreeMap<String, Result<(), DropUserError>>;
+pub type DropUsersOutput = BTreeMap<MySQLUser, Result<(), DropUserError>>;
 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
 pub enum DropUserError {
     SanitizationError(NameValidationError),
@@ -521,9 +528,9 @@ 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" })),
+            Ok(()) => (name.to_string(), json!({ "status": "success" })),
             Err(err) => (
-                name.clone(),
+                name.to_string(),
                 json!({
                   "status": "error",
                   "error": err.to_error_message(name),
@@ -539,7 +546,7 @@ pub fn print_drop_users_output_status_json(output: &DropUsersOutput) {
 }
 
 impl DropUserError {
-    pub fn to_error_message(&self, username: &str) -> String {
+    pub fn to_error_message(&self, username: &MySQLUser) -> String {
         match self {
             DropUserError::SanitizationError(err) => err.to_error_message(username, DbOrUser::User),
             DropUserError::OwnershipError(err) => err.to_error_message(username, DbOrUser::User),
@@ -562,7 +569,7 @@ pub enum SetPasswordError {
     MySqlError(String),
 }
 
-pub fn print_set_password_output_status(output: &SetPasswordOutput, username: &str) {
+pub fn print_set_password_output_status(output: &SetPasswordOutput, username: &MySQLUser) {
     match output {
         Ok(()) => {
             println!("Password for user '{}' set successfully.", username);
@@ -575,7 +582,7 @@ pub fn print_set_password_output_status(output: &SetPasswordOutput, username: &s
 }
 
 impl SetPasswordError {
-    pub fn to_error_message(&self, username: &str) -> String {
+    pub fn to_error_message(&self, username: &MySQLUser) -> String {
         match self {
             SetPasswordError::SanitizationError(err) => {
                 err.to_error_message(username, DbOrUser::User)
@@ -591,7 +598,7 @@ impl SetPasswordError {
     }
 }
 
-pub type LockUsersOutput = BTreeMap<String, Result<(), LockUserError>>;
+pub type LockUsersOutput = BTreeMap<MySQLUser, Result<(), LockUserError>>;
 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
 pub enum LockUserError {
     SanitizationError(NameValidationError),
@@ -620,9 +627,9 @@ 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" })),
+            Ok(()) => (name.to_string(), json!({ "status": "success" })),
             Err(err) => (
-                name.clone(),
+                name.to_string(),
                 json!({
                   "status": "error",
                   "error": err.to_error_message(name),
@@ -638,7 +645,7 @@ pub fn print_lock_users_output_status_json(output: &LockUsersOutput) {
 }
 
 impl LockUserError {
-    pub fn to_error_message(&self, username: &str) -> String {
+    pub fn to_error_message(&self, username: &MySQLUser) -> String {
         match self {
             LockUserError::SanitizationError(err) => err.to_error_message(username, DbOrUser::User),
             LockUserError::OwnershipError(err) => err.to_error_message(username, DbOrUser::User),
@@ -655,7 +662,7 @@ impl LockUserError {
     }
 }
 
-pub type UnlockUsersOutput = BTreeMap<String, Result<(), UnlockUserError>>;
+pub type UnlockUsersOutput = BTreeMap<MySQLUser, Result<(), UnlockUserError>>;
 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
 pub enum UnlockUserError {
     SanitizationError(NameValidationError),
@@ -684,9 +691,9 @@ 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" })),
+            Ok(()) => (name.to_string(), json!({ "status": "success" })),
             Err(err) => (
-                name.clone(),
+                name.to_string(),
                 json!({
                   "status": "error",
                   "error": err.to_error_message(name),
@@ -702,7 +709,7 @@ pub fn print_unlock_users_output_status_json(output: &UnlockUsersOutput) {
 }
 
 impl UnlockUserError {
-    pub fn to_error_message(&self, username: &str) -> String {
+    pub fn to_error_message(&self, username: &MySQLUser) -> String {
         match self {
             UnlockUserError::SanitizationError(err) => {
                 err.to_error_message(username, DbOrUser::User)
@@ -721,7 +728,7 @@ impl UnlockUserError {
     }
 }
 
-pub type ListUsersOutput = BTreeMap<String, Result<DatabaseUser, ListUsersError>>;
+pub type ListUsersOutput = BTreeMap<MySQLUser, Result<DatabaseUser, ListUsersError>>;
 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
 pub enum ListUsersError {
     SanitizationError(NameValidationError),
@@ -731,7 +738,7 @@ pub enum ListUsersError {
 }
 
 impl ListUsersError {
-    pub fn to_error_message(&self, username: &str) -> String {
+    pub fn to_error_message(&self, username: &MySQLUser) -> String {
         match self {
             ListUsersError::SanitizationError(err) => {
                 err.to_error_message(username, DbOrUser::User)
diff --git a/src/main.rs b/src/main.rs
index 02671b8..3c2f574 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -153,10 +153,10 @@ fn handle_server_command(args: &Args) -> anyhow::Result<Option<()>> {
     match args.command {
         Command::Server(ref command) => {
             tokio_start_server(
-                args.server_socket_path.clone(),
-                args.config.clone(),
-                args.verbose.clone(),
-                command.clone(),
+                args.server_socket_path.to_owned(),
+                args.config.to_owned(),
+                args.verbose.to_owned(),
+                command.to_owned(),
             )?;
             Ok(Some(()))
         }
diff --git a/src/server/config.rs b/src/server/config.rs
index e973200..ef45353 100644
--- a/src/server/config.rs
+++ b/src/server/config.rs
@@ -118,7 +118,7 @@ pub fn read_config_from_path(config_path: Option<PathBuf>) -> anyhow::Result<Ser
 }
 
 fn log_config(config: &MysqlConfig) {
-    let mut display_config = config.clone();
+    let mut display_config = config.to_owned();
     display_config.password = display_config
         .password
         .as_ref()
diff --git a/src/server/input_sanitization.rs b/src/server/input_sanitization.rs
index bd6dd22..a16f69c 100644
--- a/src/server/input_sanitization.rs
+++ b/src/server/input_sanitization.rs
@@ -24,7 +24,7 @@ pub fn validate_ownership_by_unix_user(
     name: &str,
     user: &UnixUser,
 ) -> Result<(), OwnerValidationError> {
-    let prefixes = std::iter::once(user.username.clone())
+    let prefixes = std::iter::once(user.username.to_owned())
         .chain(user.groups.iter().cloned())
         .collect::<Vec<String>>();
 
diff --git a/src/server/sql/database_operations.rs b/src/server/sql/database_operations.rs
index 3c87725..ddd4566 100644
--- a/src/server/sql/database_operations.rs
+++ b/src/server/sql/database_operations.rs
@@ -5,6 +5,7 @@ use sqlx::MySqlConnection;
 
 use serde::{Deserialize, Serialize};
 
+use crate::core::protocol::MySQLDatabase;
 use crate::{
     core::{
         common::UnixUser,
@@ -42,7 +43,7 @@ pub(super) async fn unsafe_database_exists(
 }
 
 pub async fn create_databases(
-    database_names: Vec<String>,
+    database_names: Vec<MySQLDatabase>,
     unix_user: &UnixUser,
     connection: &mut MySqlConnection,
 ) -> CreateDatabasesOutput {
@@ -51,7 +52,7 @@ pub async fn create_databases(
     for database_name in database_names {
         if let Err(err) = validate_name(&database_name) {
             results.insert(
-                database_name.clone(),
+                database_name.to_owned(),
                 Err(CreateDatabaseError::SanitizationError(err)),
             );
             continue;
@@ -59,7 +60,7 @@ pub async fn create_databases(
 
         if let Err(err) = validate_ownership_by_unix_user(&database_name, unix_user) {
             results.insert(
-                database_name.clone(),
+                database_name.to_owned(),
                 Err(CreateDatabaseError::OwnershipError(err)),
             );
             continue;
@@ -68,14 +69,14 @@ pub async fn create_databases(
         match unsafe_database_exists(&database_name, &mut *connection).await {
             Ok(true) => {
                 results.insert(
-                    database_name.clone(),
+                    database_name.to_owned(),
                     Err(CreateDatabaseError::DatabaseAlreadyExists),
                 );
                 continue;
             }
             Err(err) => {
                 results.insert(
-                    database_name.clone(),
+                    database_name.to_owned(),
                     Err(CreateDatabaseError::MySqlError(err.to_string())),
                 );
                 continue;
@@ -101,7 +102,7 @@ pub async fn create_databases(
 }
 
 pub async fn drop_databases(
-    database_names: Vec<String>,
+    database_names: Vec<MySQLDatabase>,
     unix_user: &UnixUser,
     connection: &mut MySqlConnection,
 ) -> DropDatabasesOutput {
@@ -110,7 +111,7 @@ pub async fn drop_databases(
     for database_name in database_names {
         if let Err(err) = validate_name(&database_name) {
             results.insert(
-                database_name.clone(),
+                database_name.to_owned(),
                 Err(DropDatabaseError::SanitizationError(err)),
             );
             continue;
@@ -118,7 +119,7 @@ pub async fn drop_databases(
 
         if let Err(err) = validate_ownership_by_unix_user(&database_name, unix_user) {
             results.insert(
-                database_name.clone(),
+                database_name.to_owned(),
                 Err(DropDatabaseError::OwnershipError(err)),
             );
             continue;
@@ -127,14 +128,14 @@ pub async fn drop_databases(
         match unsafe_database_exists(&database_name, &mut *connection).await {
             Ok(false) => {
                 results.insert(
-                    database_name.clone(),
+                    database_name.to_owned(),
                     Err(DropDatabaseError::DatabaseDoesNotExist),
                 );
                 continue;
             }
             Err(err) => {
                 results.insert(
-                    database_name.clone(),
+                    database_name.to_owned(),
                     Err(DropDatabaseError::MySqlError(err.to_string())),
                 );
                 continue;
@@ -159,13 +160,21 @@ pub async fn drop_databases(
     results
 }
 
-#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, FromRow)]
+#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
 pub struct DatabaseRow {
-    pub database: String,
+    pub database: MySQLDatabase,
+}
+
+impl FromRow<'_, sqlx::mysql::MySqlRow> for DatabaseRow {
+    fn from_row(row: &sqlx::mysql::MySqlRow) -> Result<Self, sqlx::Error> {
+        Ok(DatabaseRow {
+            database: row.try_get::<String, _>("database")?.into(),
+        })
+    }
 }
 
 pub async fn list_databases(
-    database_names: Vec<String>,
+    database_names: Vec<MySQLDatabase>,
     unix_user: &UnixUser,
     connection: &mut MySqlConnection,
 ) -> ListDatabasesOutput {
@@ -174,7 +183,7 @@ pub async fn list_databases(
     for database_name in database_names {
         if let Err(err) = validate_name(&database_name) {
             results.insert(
-                database_name.clone(),
+                database_name.to_owned(),
                 Err(ListDatabasesError::SanitizationError(err)),
             );
             continue;
@@ -182,7 +191,7 @@ pub async fn list_databases(
 
         if let Err(err) = validate_ownership_by_unix_user(&database_name, unix_user) {
             results.insert(
-                database_name.clone(),
+                database_name.to_owned(),
                 Err(ListDatabasesError::OwnershipError(err)),
             );
             continue;
@@ -195,7 +204,7 @@ pub async fn list_databases(
           WHERE `SCHEMA_NAME` = ?
         "#,
         )
-        .bind(&database_name)
+        .bind(database_name.to_string())
         .fetch_optional(&mut *connection)
         .await
         .map_err(|err| ListDatabasesError::MySqlError(err.to_string()))
diff --git a/src/server/sql/database_privilege_operations.rs b/src/server/sql/database_privilege_operations.rs
index a9d1dc5..da40e07 100644
--- a/src/server/sql/database_privilege_operations.rs
+++ b/src/server/sql/database_privilege_operations.rs
@@ -28,7 +28,8 @@ use crate::{
         protocol::{
             DiffDoesNotApplyError, GetAllDatabasesPrivilegeData, GetAllDatabasesPrivilegeDataError,
             GetDatabasesPrivilegeData, GetDatabasesPrivilegeDataError,
-            ModifyDatabasePrivilegesError, ModifyDatabasePrivilegesOutput,
+            ModifyDatabasePrivilegesError, ModifyDatabasePrivilegesOutput, MySQLDatabase,
+            MySQLUser,
         },
     },
     server::{
@@ -63,8 +64,8 @@ pub const DATABASE_PRIVILEGE_FIELDS: [&str; 13] = [
 /// This struct represents the set of privileges for a single user on a single database.
 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord)]
 pub struct DatabasePrivilegeRow {
-    pub db: String,
-    pub user: String,
+    pub db: MySQLDatabase,
+    pub user: MySQLUser,
     pub select_priv: bool,
     pub insert_priv: bool,
     pub update_priv: bool,
@@ -115,8 +116,8 @@ fn get_mysql_row_priv_field(row: &MySqlRow, position: usize) -> Result<bool, sql
 impl FromRow<'_, MySqlRow> for DatabasePrivilegeRow {
     fn from_row(row: &MySqlRow) -> Result<Self, sqlx::Error> {
         Ok(Self {
-            db: try_get_with_binary_fallback(row, "Db")?,
-            user: try_get_with_binary_fallback(row, "User")?,
+            db: try_get_with_binary_fallback(row, "Db")?.into(),
+            user: try_get_with_binary_fallback(row, "User")?.into(),
             select_priv: get_mysql_row_priv_field(row, 2)?,
             insert_priv: get_mysql_row_priv_field(row, 3)?,
             update_priv: get_mysql_row_priv_field(row, 4)?,
@@ -163,8 +164,8 @@ async fn unsafe_get_database_privileges(
 // NOTE: this function is unsafe because it does no input validation.
 /// Get all users + privileges for a single database-user pair.
 pub async fn unsafe_get_database_privileges_for_db_user_pair(
-    database_name: &str,
-    user_name: &str,
+    database_name: &MySQLDatabase,
+    user_name: &MySQLUser,
     connection: &mut MySqlConnection,
 ) -> Result<Option<DatabasePrivilegeRow>, sqlx::Error> {
     let result = sqlx::query_as::<_, DatabasePrivilegeRow>(&format!(
@@ -174,8 +175,8 @@ pub async fn unsafe_get_database_privileges_for_db_user_pair(
             .map(|field| quote_identifier(field))
             .join(","),
     ))
-    .bind(database_name)
-    .bind(user_name)
+    .bind(database_name.as_str())
+    .bind(user_name.as_str())
     .fetch_optional(connection)
     .await;
 
@@ -192,7 +193,7 @@ pub async fn unsafe_get_database_privileges_for_db_user_pair(
 }
 
 pub async fn get_databases_privilege_data(
-    database_names: Vec<String>,
+    database_names: Vec<MySQLDatabase>,
     unix_user: &UnixUser,
     connection: &mut MySqlConnection,
 ) -> GetDatabasesPrivilegeData {
@@ -201,7 +202,7 @@ pub async fn get_databases_privilege_data(
     for database_name in database_names.iter() {
         if let Err(err) = validate_name(database_name) {
             results.insert(
-                database_name.clone(),
+                database_name.to_owned(),
                 Err(GetDatabasesPrivilegeDataError::SanitizationError(err)),
             );
             continue;
@@ -209,7 +210,7 @@ pub async fn get_databases_privilege_data(
 
         if let Err(err) = validate_ownership_by_unix_user(database_name, unix_user) {
             results.insert(
-                database_name.clone(),
+                database_name.to_owned(),
                 Err(GetDatabasesPrivilegeDataError::OwnershipError(err)),
             );
             continue;
@@ -220,7 +221,7 @@ pub async fn get_databases_privilege_data(
             .unwrap()
         {
             results.insert(
-                database_name.clone(),
+                database_name.to_owned(),
                 Err(GetDatabasesPrivilegeDataError::DatabaseDoesNotExist),
             );
             continue;
@@ -230,7 +231,7 @@ pub async fn get_databases_privilege_data(
             .await
             .map_err(|e| GetDatabasesPrivilegeDataError::MySqlError(e.to_string()));
 
-        results.insert(database_name.clone(), result);
+        results.insert(database_name.to_owned(), result);
     }
 
     debug_assert!(database_names.len() == results.len());
@@ -364,8 +365,8 @@ async fn validate_diff(
             if privilege_row.is_some() {
                 Err(ModifyDatabasePrivilegesError::DiffDoesNotApply(
                     DiffDoesNotApplyError::RowAlreadyExists(
-                        diff.get_user_name().to_string(),
-                        diff.get_database_name().to_string(),
+                        diff.get_database_name().to_owned(),
+                        diff.get_user_name().to_owned(),
                     ),
                 ))
             } else {
@@ -375,8 +376,8 @@ async fn validate_diff(
         DatabasePrivilegesDiff::Modified(_) if privilege_row.is_none() => {
             Err(ModifyDatabasePrivilegesError::DiffDoesNotApply(
                 DiffDoesNotApplyError::RowDoesNotExist(
-                    diff.get_user_name().to_string(),
-                    diff.get_database_name().to_string(),
+                    diff.get_database_name().to_owned(),
+                    diff.get_user_name().to_owned(),
                 ),
             ))
         }
@@ -390,7 +391,7 @@ async fn validate_diff(
 
             if error_exists {
                 Err(ModifyDatabasePrivilegesError::DiffDoesNotApply(
-                    DiffDoesNotApplyError::RowPrivilegeChangeDoesNotApply(row_diff.clone(), row),
+                    DiffDoesNotApplyError::RowPrivilegeChangeDoesNotApply(row_diff.to_owned(), row),
                 ))
             } else {
                 Ok(())
@@ -400,8 +401,8 @@ async fn validate_diff(
             if privilege_row.is_none() {
                 Err(ModifyDatabasePrivilegesError::DiffDoesNotApply(
                     DiffDoesNotApplyError::RowDoesNotExist(
-                        diff.get_user_name().to_string(),
-                        diff.get_database_name().to_string(),
+                        diff.get_database_name().to_owned(),
+                        diff.get_user_name().to_owned(),
                     ),
                 ))
             } else {
@@ -419,12 +420,12 @@ pub async fn apply_privilege_diffs(
     unix_user: &UnixUser,
     connection: &mut MySqlConnection,
 ) -> ModifyDatabasePrivilegesOutput {
-    let mut results: BTreeMap<(String, String), _> = BTreeMap::new();
+    let mut results: BTreeMap<(MySQLDatabase, MySQLUser), _> = BTreeMap::new();
 
     for diff in database_privilege_diffs {
         let key = (
-            diff.get_database_name().to_string(),
-            diff.get_user_name().to_string(),
+            diff.get_database_name().to_owned(),
+            diff.get_user_name().to_owned(),
         );
         if let Err(err) = validate_name(diff.get_database_name()) {
             results.insert(
diff --git a/src/server/sql/user_operations.rs b/src/server/sql/user_operations.rs
index a003ffb..468b47b 100644
--- a/src/server/sql/user_operations.rs
+++ b/src/server/sql/user_operations.rs
@@ -7,18 +7,17 @@ use serde::{Deserialize, Serialize};
 use sqlx::prelude::*;
 use sqlx::MySqlConnection;
 
-use crate::server::common::try_get_with_binary_fallback;
 use crate::{
     core::{
         common::UnixUser,
         protocol::{
             CreateUserError, CreateUsersOutput, DropUserError, DropUsersOutput, ListAllUsersError,
             ListAllUsersOutput, ListUsersError, ListUsersOutput, LockUserError, LockUsersOutput,
-            SetPasswordError, SetPasswordOutput, UnlockUserError, UnlockUsersOutput,
+            MySQLUser, SetPasswordError, SetPasswordOutput, UnlockUserError, UnlockUsersOutput,
         },
     },
     server::{
-        common::create_user_group_matching_regex,
+        common::{create_user_group_matching_regex, try_get_with_binary_fallback},
         input_sanitization::{quote_literal, validate_name, validate_ownership_by_unix_user},
     },
 };
@@ -52,7 +51,7 @@ async fn unsafe_user_exists(
 }
 
 pub async fn create_database_users(
-    db_users: Vec<String>,
+    db_users: Vec<MySQLUser>,
     unix_user: &UnixUser,
     connection: &mut MySqlConnection,
 ) -> CreateUsersOutput {
@@ -98,7 +97,7 @@ pub async fn create_database_users(
 }
 
 pub async fn drop_database_users(
-    db_users: Vec<String>,
+    db_users: Vec<MySQLUser>,
     unix_user: &UnixUser,
     connection: &mut MySqlConnection,
 ) -> DropUsersOutput {
@@ -144,7 +143,7 @@ pub async fn drop_database_users(
 }
 
 pub async fn set_password_for_database_user(
-    db_user: &str,
+    db_user: &MySQLUser,
     password: &str,
     unix_user: &UnixUser,
     connection: &mut MySqlConnection,
@@ -219,7 +218,7 @@ async fn database_user_is_locked_unsafe(
 }
 
 pub async fn lock_database_users(
-    db_users: Vec<String>,
+    db_users: Vec<MySQLUser>,
     unix_user: &UnixUser,
     connection: &mut MySqlConnection,
 ) -> LockUsersOutput {
@@ -279,7 +278,7 @@ pub async fn lock_database_users(
 }
 
 pub async fn unlock_database_users(
-    db_users: Vec<String>,
+    db_users: Vec<MySQLUser>,
     unix_user: &UnixUser,
     connection: &mut MySqlConnection,
 ) -> UnlockUsersOutput {
@@ -342,7 +341,7 @@ pub async fn unlock_database_users(
 /// This can be extended if we need more information in the future.
 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
 pub struct DatabaseUser {
-    pub user: String,
+    pub user: MySQLUser,
     #[serde(skip)]
     pub host: String,
     pub has_password: bool,
@@ -353,7 +352,7 @@ pub struct DatabaseUser {
 impl FromRow<'_, sqlx::mysql::MySqlRow> for DatabaseUser {
     fn from_row(row: &sqlx::mysql::MySqlRow) -> Result<Self, sqlx::Error> {
         Ok(Self {
-            user: try_get_with_binary_fallback(row, "User")?,
+            user: try_get_with_binary_fallback(row, "User")?.into(),
             host: try_get_with_binary_fallback(row, "Host")?,
             has_password: row.try_get("has_password")?,
             is_locked: row.try_get("is_locked")?,
@@ -378,7 +377,7 @@ JOIN `global_priv` ON
 "#;
 
 pub async fn list_database_users(
-    db_users: Vec<String>,
+    db_users: Vec<MySQLUser>,
     unix_user: &UnixUser,
     connection: &mut MySqlConnection,
 ) -> ListUsersOutput {
@@ -398,7 +397,7 @@ pub async fn list_database_users(
         let mut result = sqlx::query_as::<_, DatabaseUser>(
             &(DB_USER_SELECT_STATEMENT.to_string() + "WHERE `mysql`.`user`.`User` = ?"),
         )
-        .bind(&db_user)
+        .bind(db_user.as_str())
         .fetch_optional(&mut *connection)
         .await;
 
@@ -463,7 +462,7 @@ pub async fn append_databases_where_user_has_privileges(
         )
         .as_str(),
     )
-    .bind(db_user.user.clone())
+    .bind(db_user.user.as_str())
     .fetch_all(&mut *connection)
     .await;