clippy pedantic fix + get rid of a few unwraps
All checks were successful
Build and test / docs (push) Successful in 7m1s
Build and test / check-license (push) Successful in 57s
Build and test / check (push) Successful in 2m46s
Build and test / build (push) Successful in 3m12s
Build and test / test (push) Successful in 3m25s

This commit is contained in:
2025-12-23 13:40:46 +09:00
parent c866400b4a
commit 4c3677d6d3
51 changed files with 596 additions and 545 deletions

View File

@@ -1,4 +1,9 @@
use std::{fs, path::PathBuf, sync::Arc, time::Duration};
use std::{
fs,
path::{Path, PathBuf},
sync::Arc,
time::Duration,
};
use anyhow::{Context, anyhow};
use clap_verbosity_flag::{InfoLevel, Verbosity};
@@ -136,7 +141,7 @@ fn connect_to_external_server(
Err(e) => match e.kind() {
std::io::ErrorKind::NotFound => Err(anyhow::anyhow!("Socket not found")),
std::io::ErrorKind::PermissionDenied => Err(anyhow::anyhow!("Permission denied")),
_ => Err(anyhow::anyhow!("Failed to connect to socket: {}", e)),
_ => Err(anyhow::anyhow!("Failed to connect to socket: {e}")),
},
};
}
@@ -148,7 +153,7 @@ fn connect_to_external_server(
Err(e) => match e.kind() {
std::io::ErrorKind::NotFound => Err(anyhow::anyhow!("Socket not found")),
std::io::ErrorKind::PermissionDenied => Err(anyhow::anyhow!("Permission denied")),
_ => Err(anyhow::anyhow!("Failed to connect to socket: {}", e)),
_ => Err(anyhow::anyhow!("Failed to connect to socket: {e}")),
},
};
}
@@ -192,10 +197,10 @@ fn bootstrap_internal_server_and_drop_privs(
}
tracing::debug!("Starting server with config at {:?}", config_path);
let socket = invoke_server_with_config(config_path)?;
let socket = invoke_server_with_config(&config_path)?;
drop_privs()?;
return Ok(socket);
};
}
let config_path = PathBuf::from(DEFAULT_CONFIG_PATH);
if fs::metadata(&config_path).is_ok() {
@@ -203,10 +208,10 @@ fn bootstrap_internal_server_and_drop_privs(
anyhow::bail!("Executable is not SUID/SGID - refusing to start internal sever");
}
tracing::debug!("Starting server with default config at {:?}", config_path);
let socket = invoke_server_with_config(config_path)?;
let socket = invoke_server_with_config(&config_path)?;
drop_privs()?;
return Ok(socket);
};
}
anyhow::bail!("No config path provided, and no default config found");
}
@@ -216,7 +221,7 @@ fn bootstrap_internal_server_and_drop_privs(
/// Fork a child process to run the server with the provided config.
/// The server will exit silently by itself when it is done, and this function
/// will only return for the client with the socket for the server.
fn invoke_server_with_config(config_path: PathBuf) -> anyhow::Result<StdUnixStream> {
fn invoke_server_with_config(config_path: &Path) -> anyhow::Result<StdUnixStream> {
let (server_socket, client_socket) = StdUnixStream::pair()?;
let unix_user = UnixUser::from_uid(nix::unistd::getuid().as_raw())?;
@@ -228,18 +233,18 @@ fn invoke_server_with_config(config_path: PathBuf) -> anyhow::Result<StdUnixStre
nix::unistd::ForkResult::Child => {
tracing::debug!("Running server in child process");
landlock_restrict_server(Some(config_path.as_path()))
landlock_restrict_server(Some(config_path))
.context("Failed to apply Landlock restrictions to the server process")?;
match run_forked_server(config_path, server_socket, unix_user) {
match run_forked_server(config_path, server_socket, &unix_user) {
Err(e) => Err(e),
Ok(_) => unreachable!(),
Ok(()) => unreachable!(),
}
}
}
}
/// Construct a MySQL connection pool that consists of exactly one connection.
/// Construct a `MySQL` connection pool that consists of exactly one connection.
///
/// This is used for the internal server in SUID/SGID mode, where the server session
/// only ever will get a single client.
@@ -273,11 +278,11 @@ async fn construct_single_connection_mysql_pool(
/// This function will not return, but will exit the process with a success code.
/// The function assumes that it's caller has already forked the process.
fn run_forked_server(
config_path: PathBuf,
config_path: &Path,
server_socket: StdUnixStream,
unix_user: UnixUser,
unix_user: &UnixUser,
) -> anyhow::Result<()> {
let config = ServerConfig::read_config_from_path(&config_path)
let config = ServerConfig::read_config_from_path(config_path)
.context("Failed to read server config in forked process")?;
let group_denylist = if let Some(denylist_path) = &config.authorization.group_denylist_file {
@@ -306,7 +311,7 @@ fn run_forked_server(
let db_pool = Arc::new(RwLock::new(db_pool));
session_handler::session_handler_with_unix_user(
socket,
&unix_user,
unix_user,
db_pool,
db_is_mariadb,
&group_denylist,

View File

@@ -10,13 +10,13 @@ pub const DEFAULT_CONFIG_PATH: &str = "/etc/muscl/config.toml";
pub const DEFAULT_SOCKET_PATH: &str = "/run/muscl/muscl.sock";
pub const ASCII_BANNER: &str = indoc! {
r#"
r"
__
____ ___ __ ____________/ /
/ __ `__ \/ / / / ___/ ___/ /
/ / / / / / /_/ (__ ) /__/ /
/_/ /_/ /_/\__,_/____/\___/_/
"#
"
};
pub const KIND_REGARDS: &str = concat!(
@@ -95,7 +95,7 @@ impl UnixUser {
Ok(UnixUser {
username: libc_user.name,
groups: groups.iter().map(|g| g.name.to_owned()).collect(),
groups: groups.iter().map(|g| g.name.clone()).collect(),
})
}

View File

@@ -12,6 +12,7 @@ use crate::{
},
};
#[must_use]
pub fn mysql_database_completer(current: &std::ffi::OsStr) -> Vec<CompletionCandidate> {
match tokio::runtime::Builder::new_current_thread()
.enable_all()
@@ -20,18 +21,18 @@ pub fn mysql_database_completer(current: &std::ffi::OsStr) -> Vec<CompletionCand
Ok(runtime) => match runtime.block_on(mysql_database_completer_(current)) {
Ok(completions) => completions,
Err(err) => {
eprintln!("Error getting MySQL database completions: {}", err);
eprintln!("Error getting MySQL database completions: {err}");
Vec::new()
}
},
Err(err) => {
eprintln!("Error starting Tokio runtime: {}", err);
eprintln!("Error starting Tokio runtime: {err}");
Vec::new()
}
}
}
/// Connect to the server to get MySQL database completions.
/// Connect to the server to get `MySQL` database completions.
async fn mysql_database_completer_(
current: &std::ffi::OsStr,
) -> anyhow::Result<Vec<CompletionCandidate>> {
@@ -44,11 +45,11 @@ async fn mysql_database_completer_(
while let Some(Ok(message)) = server_connection.next().await {
match message {
Response::Error(err) => {
anyhow::bail!("{}", err);
anyhow::bail!("{err}");
}
Response::Ready => break,
message => {
eprintln!("Unexpected message from server: {:?}", message);
eprintln!("Unexpected message from server: {message:?}");
}
}
}
@@ -62,7 +63,7 @@ async fn mysql_database_completer_(
let result = match server_connection.next().await {
Some(Ok(Response::CompleteDatabaseName(suggestions))) => suggestions,
response => return erroneous_server_response(response).map(|_| vec![]),
response => return erroneous_server_response(response).map(|()| vec![]),
};
server_connection.send(Request::Exit).await?;

View File

@@ -12,6 +12,7 @@ use crate::{
},
};
#[must_use]
pub fn mysql_user_completer(current: &std::ffi::OsStr) -> Vec<CompletionCandidate> {
match tokio::runtime::Builder::new_current_thread()
.enable_all()
@@ -20,18 +21,18 @@ pub fn mysql_user_completer(current: &std::ffi::OsStr) -> Vec<CompletionCandidat
Ok(runtime) => match runtime.block_on(mysql_user_completer_(current)) {
Ok(completions) => completions,
Err(err) => {
eprintln!("Error getting MySQL user completions: {}", err);
eprintln!("Error getting MySQL user completions: {err}");
Vec::new()
}
},
Err(err) => {
eprintln!("Error starting Tokio runtime: {}", err);
eprintln!("Error starting Tokio runtime: {err}");
Vec::new()
}
}
}
/// Connect to the server to get MySQL user completions.
/// Connect to the server to get `MySQL` user completions.
async fn mysql_user_completer_(
current: &std::ffi::OsStr,
) -> anyhow::Result<Vec<CompletionCandidate>> {
@@ -44,11 +45,11 @@ async fn mysql_user_completer_(
while let Some(Ok(message)) = server_connection.next().await {
match message {
Response::Error(err) => {
anyhow::bail!("{}", err);
anyhow::bail!("{err}");
}
Response::Ready => break,
message => {
eprintln!("Unexpected message from server: {:?}", message);
eprintln!("Unexpected message from server: {message:?}");
}
}
}
@@ -62,7 +63,7 @@ async fn mysql_user_completer_(
let result = match server_connection.next().await {
Some(Ok(Response::CompleteUserName(suggestions))) => suggestions,
response => return erroneous_server_response(response).map(|_| vec![]),
response => return erroneous_server_response(response).map(|()| vec![]),
};
server_connection.send(Request::Exit).await?;

View File

@@ -12,6 +12,7 @@ use crate::{
},
};
#[must_use]
pub fn prefix_completer(current: &std::ffi::OsStr) -> Vec<CompletionCandidate> {
match tokio::runtime::Builder::new_current_thread()
.enable_all()
@@ -20,18 +21,18 @@ pub fn prefix_completer(current: &std::ffi::OsStr) -> Vec<CompletionCandidate> {
Ok(runtime) => match runtime.block_on(prefix_completer_(current)) {
Ok(completions) => completions,
Err(err) => {
eprintln!("Error getting prefix completions: {}", err);
eprintln!("Error getting prefix completions: {err}");
Vec::new()
}
},
Err(err) => {
eprintln!("Error starting Tokio runtime: {}", err);
eprintln!("Error starting Tokio runtime: {err}");
Vec::new()
}
}
}
/// Connect to the server to get MySQL user completions.
/// Connect to the server to get `MySQL` user completions.
async fn prefix_completer_(_current: &std::ffi::OsStr) -> anyhow::Result<Vec<CompletionCandidate>> {
let server_connection =
bootstrap_server_connection_and_drop_privileges(None, None, Verbosity::new(0, 1))?;
@@ -42,11 +43,11 @@ async fn prefix_completer_(_current: &std::ffi::OsStr) -> anyhow::Result<Vec<Com
while let Some(Ok(message)) = server_connection.next().await {
match message {
Response::Error(err) => {
anyhow::bail!("{}", err);
anyhow::bail!("{err}");
}
Response::Ready => break,
message => {
eprintln!("Unexpected message from server: {:?}", message);
eprintln!("Unexpected message from server: {message:?}");
}
}
}
@@ -60,7 +61,7 @@ async fn prefix_completer_(_current: &std::ffi::OsStr) -> anyhow::Result<Vec<Com
let result = match server_connection.next().await {
Some(Ok(Response::ListValidNamePrefixes(prefixes))) => prefixes,
response => return erroneous_server_response(response).map(|_| vec![]),
response => return erroneous_server_response(response).map(|()| vec![]),
};
server_connection.send(Request::Exit).await?;

View File

@@ -1,5 +1,5 @@
//! This module contains some base datastructures and functionality for dealing with
//! database privileges in MySQL.
//! database privileges in `MySQL`.
use std::fmt;
@@ -49,6 +49,7 @@ pub struct DatabasePrivilegeRow {
impl DatabasePrivilegeRow {
/// Gets the value of a privilege by its name as a &str.
#[must_use]
pub fn get_privilege_by_name(&self, name: &str) -> Option<bool> {
match name {
"select_priv" => Some(self.select_priv),
@@ -83,6 +84,7 @@ impl fmt::Display for DatabasePrivilegeRow {
}
/// Converts a database privilege field name to a human-readable name.
#[must_use]
pub fn db_priv_field_human_readable_name(name: &str) -> String {
match name {
"Db" => "Database".to_owned(),
@@ -98,12 +100,13 @@ pub fn db_priv_field_human_readable_name(name: &str) -> String {
"create_tmp_table_priv" => "Temp".to_owned(),
"lock_tables_priv" => "Lock".to_owned(),
"references_priv" => "References".to_owned(),
_ => format!("Unknown({})", name),
_ => format!("Unknown({name})"),
}
}
/// Converts a database privilege field name to a single-character name.
/// (the characters from the cli privilege editor)
#[must_use]
pub fn db_priv_field_single_character_name(name: &str) -> &str {
match name {
"select_priv" => "s",

View File

@@ -51,9 +51,7 @@ impl DatabasePrivilegeEdit {
.map(|c| format!("'{c}'"))
.join(", ");
anyhow::bail!(
"Invalid character(s) in privilege edit entry: {}\n\nValid characters are: {}",
invalid_chars,
valid_characters,
"Invalid character(s) in privilege edit entry: {invalid_chars}\n\nValid characters are: {valid_characters}",
);
}
@@ -72,7 +70,7 @@ impl std::fmt::Display for DatabasePrivilegeEdit {
DatabasePrivilegeEditEntryType::Remove => write!(f, "-")?,
}
for priv_char in &self.privileges {
write!(f, "{}", priv_char)?;
write!(f, "{priv_char}")?;
}
Ok(())
@@ -99,7 +97,7 @@ impl DatabasePrivilegeEditEntry {
/// `database_name:username:[+|-]privileges`
///
/// where:
/// - database_name is the name of the database to edit privileges for
/// - `database_name` is the name of the database to edit privileges for
/// - username is the name of the user to edit privileges for
/// - privileges is a string of characters representing the privileges to add, set or remove
/// - the `+` or `-` prefix indicates whether to add or remove the privileges, if omitted the privileges are set directly
@@ -107,13 +105,13 @@ impl DatabasePrivilegeEditEntry {
pub fn parse_from_str(arg: &str) -> anyhow::Result<Self> {
let parts: Vec<&str> = arg.split(':').collect();
if parts.len() != 3 {
anyhow::bail!("Invalid privilege edit entry format: {}", arg);
anyhow::bail!("Invalid privilege edit entry format: {arg}");
}
let (database, user, user_privs) = (parts[0].to_string(), parts[1].to_string(), parts[2]);
if user.is_empty() {
anyhow::bail!("Username cannot be empty in privilege edit entry: {}", arg);
anyhow::bail!("Username cannot be empty in privilege edit entry: {arg}");
}
let privilege_edit = DatabasePrivilegeEdit::parse_from_str(user_privs)?;

View File

@@ -18,6 +18,7 @@ pub enum DatabasePrivilegeChange {
}
impl DatabasePrivilegeChange {
#[must_use]
pub fn new(p1: bool, p2: bool) -> Option<DatabasePrivilegeChange> {
match (p1, p2) {
(true, false) => Some(DatabasePrivilegeChange::YesToNo),
@@ -49,6 +50,7 @@ pub struct DatabasePrivilegeRowDiff {
impl DatabasePrivilegeRowDiff {
/// Calculates the difference between two [`DatabasePrivilegeRow`] instances.
#[must_use]
pub fn from_rows(
row1: &DatabasePrivilegeRow,
row2: &DatabasePrivilegeRow,
@@ -56,8 +58,8 @@ impl DatabasePrivilegeRowDiff {
debug_assert!(row1.db == row2.db && row1.user == row2.user);
DatabasePrivilegeRowDiff {
db: row1.db.to_owned(),
user: row1.user.to_owned(),
db: row1.db.clone(),
user: row1.user.clone(),
select_priv: DatabasePrivilegeChange::new(row1.select_priv, row2.select_priv),
insert_priv: DatabasePrivilegeChange::new(row1.insert_priv, row2.insert_priv),
update_priv: DatabasePrivilegeChange::new(row1.update_priv, row2.update_priv),
@@ -82,6 +84,7 @@ impl DatabasePrivilegeRowDiff {
}
/// Returns true if there are no changes in this diff.
#[must_use]
pub fn is_empty(&self) -> bool {
self.select_priv.is_none()
&& self.insert_priv.is_none()
@@ -113,7 +116,7 @@ impl DatabasePrivilegeRowDiff {
"create_tmp_table_priv" => Ok(self.create_tmp_table_priv),
"lock_tables_priv" => Ok(self.lock_tables_priv),
"references_priv" => Ok(self.references_priv),
_ => anyhow::bail!("Unknown privilege name: {}", privilege_name),
_ => anyhow::bail!("Unknown privilege name: {privilege_name}"),
}
}
@@ -159,7 +162,7 @@ impl DatabasePrivilegeRowDiff {
/// Removes any no-op changes from the diff, based on the original privilege row.
fn remove_noops(&mut self, from: &DatabasePrivilegeRow) {
fn new_value(
change: &Option<DatabasePrivilegeChange>,
change: Option<&DatabasePrivilegeChange>,
from_value: bool,
) -> Option<DatabasePrivilegeChange> {
change.as_ref().and_then(|c| match c {
@@ -173,22 +176,24 @@ impl DatabasePrivilegeRowDiff {
})
}
self.select_priv = new_value(&self.select_priv, from.select_priv);
self.insert_priv = new_value(&self.insert_priv, from.insert_priv);
self.update_priv = new_value(&self.update_priv, from.update_priv);
self.delete_priv = new_value(&self.delete_priv, from.delete_priv);
self.create_priv = new_value(&self.create_priv, from.create_priv);
self.drop_priv = new_value(&self.drop_priv, from.drop_priv);
self.alter_priv = new_value(&self.alter_priv, from.alter_priv);
self.index_priv = new_value(&self.index_priv, from.index_priv);
self.create_tmp_table_priv =
new_value(&self.create_tmp_table_priv, from.create_tmp_table_priv);
self.lock_tables_priv = new_value(&self.lock_tables_priv, from.lock_tables_priv);
self.references_priv = new_value(&self.references_priv, from.references_priv);
self.select_priv = new_value(self.select_priv.as_ref(), from.select_priv);
self.insert_priv = new_value(self.insert_priv.as_ref(), from.insert_priv);
self.update_priv = new_value(self.update_priv.as_ref(), from.update_priv);
self.delete_priv = new_value(self.delete_priv.as_ref(), from.delete_priv);
self.create_priv = new_value(self.create_priv.as_ref(), from.create_priv);
self.drop_priv = new_value(self.drop_priv.as_ref(), from.drop_priv);
self.alter_priv = new_value(self.alter_priv.as_ref(), from.alter_priv);
self.index_priv = new_value(self.index_priv.as_ref(), from.index_priv);
self.create_tmp_table_priv = new_value(
self.create_tmp_table_priv.as_ref(),
from.create_tmp_table_priv,
);
self.lock_tables_priv = new_value(self.lock_tables_priv.as_ref(), from.lock_tables_priv);
self.references_priv = new_value(self.references_priv.as_ref(), from.references_priv);
}
fn apply(&self, base: &mut DatabasePrivilegeRow) {
fn apply_change(change: &Option<DatabasePrivilegeChange>, target: &mut bool) {
fn apply_change(change: Option<&DatabasePrivilegeChange>, target: &mut bool) {
match change {
Some(DatabasePrivilegeChange::YesToNo) => *target = false,
Some(DatabasePrivilegeChange::NoToYes) => *target = true,
@@ -196,17 +201,20 @@ impl DatabasePrivilegeRowDiff {
}
}
apply_change(&self.select_priv, &mut base.select_priv);
apply_change(&self.insert_priv, &mut base.insert_priv);
apply_change(&self.update_priv, &mut base.update_priv);
apply_change(&self.delete_priv, &mut base.delete_priv);
apply_change(&self.create_priv, &mut base.create_priv);
apply_change(&self.drop_priv, &mut base.drop_priv);
apply_change(&self.alter_priv, &mut base.alter_priv);
apply_change(&self.index_priv, &mut base.index_priv);
apply_change(&self.create_tmp_table_priv, &mut base.create_tmp_table_priv);
apply_change(&self.lock_tables_priv, &mut base.lock_tables_priv);
apply_change(&self.references_priv, &mut base.references_priv);
apply_change(self.select_priv.as_ref(), &mut base.select_priv);
apply_change(self.insert_priv.as_ref(), &mut base.insert_priv);
apply_change(self.update_priv.as_ref(), &mut base.update_priv);
apply_change(self.delete_priv.as_ref(), &mut base.delete_priv);
apply_change(self.create_priv.as_ref(), &mut base.create_priv);
apply_change(self.drop_priv.as_ref(), &mut base.drop_priv);
apply_change(self.alter_priv.as_ref(), &mut base.alter_priv);
apply_change(self.index_priv.as_ref(), &mut base.index_priv);
apply_change(
self.create_tmp_table_priv.as_ref(),
&mut base.create_tmp_table_priv,
);
apply_change(self.lock_tables_priv.as_ref(), &mut base.lock_tables_priv);
apply_change(self.references_priv.as_ref(), &mut base.references_priv);
}
}
@@ -214,7 +222,7 @@ impl fmt::Display for DatabasePrivilegeRowDiff {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fn format_change(
f: &mut fmt::Formatter<'_>,
change: &Option<DatabasePrivilegeChange>,
change: Option<DatabasePrivilegeChange>,
field_name: &str,
) -> fmt::Result {
if let Some(change) = change {
@@ -233,17 +241,17 @@ impl fmt::Display for DatabasePrivilegeRowDiff {
}
}
format_change(f, &self.select_priv, "select_priv")?;
format_change(f, &self.insert_priv, "insert_priv")?;
format_change(f, &self.update_priv, "update_priv")?;
format_change(f, &self.delete_priv, "delete_priv")?;
format_change(f, &self.create_priv, "create_priv")?;
format_change(f, &self.drop_priv, "drop_priv")?;
format_change(f, &self.alter_priv, "alter_priv")?;
format_change(f, &self.index_priv, "index_priv")?;
format_change(f, &self.create_tmp_table_priv, "create_tmp_table_priv")?;
format_change(f, &self.lock_tables_priv, "lock_tables_priv")?;
format_change(f, &self.references_priv, "references_priv")?;
format_change(f, self.select_priv, "select_priv")?;
format_change(f, self.insert_priv, "insert_priv")?;
format_change(f, self.update_priv, "update_priv")?;
format_change(f, self.delete_priv, "delete_priv")?;
format_change(f, self.create_priv, "create_priv")?;
format_change(f, self.drop_priv, "drop_priv")?;
format_change(f, self.alter_priv, "alter_priv")?;
format_change(f, self.index_priv, "index_priv")?;
format_change(f, self.create_tmp_table_priv, "create_tmp_table_priv")?;
format_change(f, self.lock_tables_priv, "lock_tables_priv")?;
format_change(f, self.references_priv, "references_priv")?;
Ok(())
}
@@ -259,6 +267,7 @@ pub enum DatabasePrivilegesDiff {
}
impl DatabasePrivilegesDiff {
#[must_use]
pub fn get_database_name(&self) -> &MySQLDatabase {
match self {
DatabasePrivilegesDiff::New(p) => &p.db,
@@ -268,6 +277,7 @@ impl DatabasePrivilegesDiff {
}
}
#[must_use]
pub fn get_user_name(&self) -> &MySQLUser {
match self {
DatabasePrivilegesDiff::New(p) => &p.user,
@@ -305,7 +315,7 @@ impl DatabasePrivilegesDiff {
}
if matches!(self, DatabasePrivilegesDiff::Noop { .. }) {
*self = other.to_owned();
other.clone_into(self);
return Ok(());
} else if matches!(other, DatabasePrivilegesDiff::Noop { .. }) {
return Ok(());
@@ -327,8 +337,8 @@ impl DatabasePrivilegesDiff {
inner_diff.mappend(modified);
if inner_diff.is_empty() {
let db = inner_diff.db.to_owned();
let user = inner_diff.user.to_owned();
let db = inner_diff.db.clone();
let user = inner_diff.user.clone();
*self = DatabasePrivilegesDiff::Noop { db, user };
}
}
@@ -352,28 +362,27 @@ pub type DatabasePrivilegeState<'a> = &'a [DatabasePrivilegeRow];
/// This function calculates the differences between two sets of database privileges.
/// It returns a set of [`DatabasePrivilegesDiff`] that can be used to display or
/// apply a set of privilege modifications to the database.
#[must_use]
pub fn diff_privileges(
from: DatabasePrivilegeState<'_>,
to: &[DatabasePrivilegeRow],
) -> BTreeSet<DatabasePrivilegesDiff> {
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 from_lookup_table: HashMap<(MySQLDatabase, MySQLUser), DatabasePrivilegeRow> = from
.iter()
.cloned()
.map(|p| ((p.db.clone(), p.user.clone()), p))
.collect();
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 to_lookup_table: HashMap<(MySQLDatabase, MySQLUser), DatabasePrivilegeRow> = to
.iter()
.cloned()
.map(|p| ((p.db.clone(), p.user.clone()), p))
.collect();
let mut result = BTreeSet::new();
for p in to {
if let Some(old_p) = from_lookup_table.get(&(p.db.to_owned(), p.user.to_owned())) {
if let Some(old_p) = from_lookup_table.get(&(p.db.clone(), p.user.clone())) {
let diff = DatabasePrivilegeRowDiff::from_rows(old_p, p);
if !diff.is_empty() {
result.insert(DatabasePrivilegesDiff::Modified(diff));
@@ -384,7 +393,7 @@ pub fn diff_privileges(
}
for p in from {
if !to_lookup_table.contains_key(&(p.db.to_owned(), p.user.to_owned())) {
if !to_lookup_table.contains_key(&(p.db.clone(), p.user.clone())) {
result.insert(DatabasePrivilegesDiff::Deleted(p.to_owned()));
}
}
@@ -400,17 +409,16 @@ pub fn create_or_modify_privilege_rows(
from: DatabasePrivilegeState<'_>,
to: &BTreeSet<DatabasePrivilegeRowDiff>,
) -> anyhow::Result<BTreeSet<DatabasePrivilegesDiff>> {
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 from_lookup_table: HashMap<(MySQLDatabase, MySQLUser), DatabasePrivilegeRow> = from
.iter()
.cloned()
.map(|p| ((p.db.clone(), p.user.clone()), p))
.collect();
let mut result = BTreeSet::new();
for diff in to {
if let Some(old_p) = from_lookup_table.get(&(diff.db.to_owned(), diff.user.to_owned())) {
if let Some(old_p) = from_lookup_table.get(&(diff.db.clone(), diff.user.clone())) {
let mut modified_diff = diff.to_owned();
modified_diff.remove_noops(old_p);
if !modified_diff.is_empty() {
@@ -418,8 +426,8 @@ pub fn create_or_modify_privilege_rows(
}
} else {
let mut new_row = DatabasePrivilegeRow {
db: diff.db.to_owned(),
user: diff.user.to_owned(),
db: diff.db.clone(),
user: diff.user.clone(),
select_priv: false,
insert_priv: false,
update_priv: false,
@@ -450,12 +458,11 @@ pub fn reduce_privilege_diffs(
from: DatabasePrivilegeState<'_>,
to: BTreeSet<DatabasePrivilegesDiff>,
) -> anyhow::Result<BTreeSet<DatabasePrivilegesDiff>> {
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 from_lookup_table: HashMap<(MySQLDatabase, MySQLUser), DatabasePrivilegeRow> = from
.iter()
.cloned()
.map(|p| ((p.db.clone(), p.user.clone()), p))
.collect();
let mut result: HashMap<(MySQLDatabase, MySQLUser), DatabasePrivilegesDiff> = from_lookup_table
.iter()
@@ -481,19 +488,19 @@ pub fn reduce_privilege_diffs(
existing_diff.mappend(&diff)?;
}
Entry::Vacant(vacant_entry) => {
vacant_entry.insert(diff.to_owned());
vacant_entry.insert(diff.clone());
}
}
}
for (key, diff) in result.iter_mut() {
for (key, diff) in &mut result {
if let Some(from_row) = from_lookup_table.get(key)
&& let DatabasePrivilegesDiff::Modified(modified_diff) = diff
{
modified_diff.remove_noops(from_row);
if modified_diff.is_empty() {
let db = modified_diff.db.to_owned();
let user = modified_diff.user.to_owned();
let db = modified_diff.db.clone();
let user = modified_diff.user.clone();
*diff = DatabasePrivilegesDiff::Noop { db, user };
}
}
@@ -506,6 +513,7 @@ pub fn reduce_privilege_diffs(
}
/// Renders a set of [`DatabasePrivilegesDiff`] into a human-readable formatted table.
#[must_use]
pub fn display_privilege_diffs(diffs: &BTreeSet<DatabasePrivilegesDiff>) -> String {
let mut table = Table::new();
table.set_titles(row!["Database", "User", "Privilege diff",]);

View File

@@ -13,6 +13,7 @@ use itertools::Itertools;
use std::cmp::max;
/// Generates a single row of the privileges table for the editor.
#[must_use]
pub fn format_privileges_line_for_editor(
privs: &DatabasePrivilegeRow,
database_name_len: usize,
@@ -25,6 +26,7 @@ pub fn format_privileges_line_for_editor(
"User" => format!("{:width$}", privs.user, width = username_len),
privilege => format!(
"{:width$}",
// SAFETY: unwrap is safe here because the field names are static
yn(privs.get_privilege_by_name(privilege).unwrap()),
width = db_priv_field_human_readable_name(privilege).len()
),
@@ -34,14 +36,14 @@ pub fn format_privileges_line_for_editor(
.to_string()
}
const EDITOR_COMMENT: &str = r#"
const EDITOR_COMMENT: &str = r"
# Welcome to the privilege editor.
# Each line defines what privileges a single user has on a single database.
# The first two columns respectively represent the database name and the user, and the remaining columns are the privileges.
# If the user should have a certain privilege, write 'Y', otherwise write 'N'.
#
# Lines starting with '#' are comments and will be ignored.
"#;
";
/// Generates the content for the privilege editor.
///
@@ -52,9 +54,9 @@ pub fn generate_editor_content_from_privilege_data(
unix_user: &str,
database_name: Option<&MySQLDatabase>,
) -> String {
let example_user = format!("{}_user", unix_user);
let example_user = format!("{unix_user}_user");
let example_db = database_name
.unwrap_or(&format!("{}_db", unix_user).into())
.unwrap_or(&format!("{unix_user}_db").into())
.to_string();
// NOTE: `.max()`` fails when the iterator is empty.
@@ -114,7 +116,7 @@ pub fn generate_editor_content_from_privilege_data(
EDITOR_COMMENT,
header.join(" "),
if privilege_data.is_empty() {
format!("# {}", example_line)
format!("# {example_line}")
} else {
privilege_data
.iter()
@@ -145,11 +147,8 @@ enum PrivilegeRowParseResult {
fn parse_privilege_cell_from_editor(yn: &str, name: &str) -> anyhow::Result<bool> {
let human_readable_name = db_priv_field_human_readable_name(name);
rev_yn(yn)
.ok_or_else(|| anyhow!("Expected Y or N, found {}", yn))
.context(format!(
"Could not parse '{}' privilege",
human_readable_name
))
.ok_or_else(|| anyhow!("Expected Y or N, found {yn}"))
.context(format!("Could not parse '{human_readable_name}' privilege"))
}
#[inline]
@@ -272,12 +271,12 @@ fn parse_privilege_row_from_editor(row: &str) -> PrivilegeRowParseResult {
}
pub fn parse_privilege_data_from_editor_content(
content: String,
content: &str,
) -> anyhow::Result<Vec<DatabasePrivilegeRow>> {
content
.trim()
.split('\n')
.map(|line| line.trim())
.lines()
.map(str::trim)
.enumerate()
.map(|(i, line)| {
let mut header: Vec<_> = DATABASE_PRIVILEGE_FIELDS
@@ -314,7 +313,7 @@ pub fn parse_privilege_data_from_editor_content(
PrivilegeRowParseResult::Empty => Ok(None),
}
})
.filter_map(|result| result.transpose())
.filter_map(std::result::Result::transpose)
.collect::<anyhow::Result<Vec<DatabasePrivilegeRow>>>()
}
@@ -417,7 +416,7 @@ mod tests {
let content = generate_editor_content_from_privilege_data(&permissions, "user", None);
let parsed_permissions = parse_privilege_data_from_editor_content(content).unwrap();
let parsed_permissions = parse_privilege_data_from_editor_content(&content).unwrap();
assert_eq!(permissions, parsed_permissions);
}

View File

@@ -57,10 +57,12 @@ pub fn print_check_authorization_output_status_json(output: &CheckAuthorizationR
}
impl CheckAuthorizationError {
#[must_use]
pub fn to_error_message(&self, db_or_user: &DbOrUser) -> String {
self.0.to_error_message(db_or_user.clone())
self.0.to_error_message(db_or_user)
}
#[must_use]
pub fn error_type(&self) -> String {
self.0.error_type()
}

View File

@@ -29,7 +29,7 @@ pub fn print_create_databases_output_status(output: &CreateDatabasesResponse) {
for (database_name, result) in output {
match result {
Ok(()) => {
println!("Database '{}' created successfully.", database_name);
println!("Database '{database_name}' created successfully.");
}
Err(err) => {
eprintln!("{}", err.to_error_message(database_name));
@@ -63,20 +63,22 @@ pub fn print_create_databases_output_status_json(output: &CreateDatabasesRespons
}
impl CreateDatabaseError {
#[must_use]
pub fn to_error_message(&self, database_name: &MySQLDatabase) -> String {
match self {
CreateDatabaseError::ValidationError(err) => {
err.to_error_message(DbOrUser::Database(database_name.clone()))
err.to_error_message(&DbOrUser::Database(database_name.clone()))
}
CreateDatabaseError::DatabaseAlreadyExists => {
format!("Database {} already exists.", database_name)
format!("Database {database_name} already exists.")
}
CreateDatabaseError::MySqlError(err) => {
format!("MySQL error: {}", err)
format!("MySQL error: {err}")
}
}
}
#[must_use]
pub fn error_type(&self) -> String {
match self {
CreateDatabaseError::ValidationError(err) => err.error_type(),

View File

@@ -29,7 +29,7 @@ pub fn print_create_users_output_status(output: &CreateUsersResponse) {
for (username, result) in output {
match result {
Ok(()) => {
println!("User '{}' created successfully.", username);
println!("User '{username}' created successfully.");
}
Err(err) => {
eprintln!("{}", err.to_error_message(username));
@@ -63,20 +63,22 @@ pub fn print_create_users_output_status_json(output: &CreateUsersResponse) {
}
impl CreateUserError {
#[must_use]
pub fn to_error_message(&self, username: &MySQLUser) -> String {
match self {
CreateUserError::ValidationError(err) => {
err.to_error_message(DbOrUser::User(username.clone()))
err.to_error_message(&DbOrUser::User(username.clone()))
}
CreateUserError::UserAlreadyExists => {
format!("User '{}' already exists.", username)
format!("User '{username}' already exists.")
}
CreateUserError::MySqlError(err) => {
format!("MySQL error: {}", err)
format!("MySQL error: {err}")
}
}
}
#[must_use]
pub fn error_type(&self) -> String {
match self {
CreateUserError::ValidationError(err) => err.error_type(),

View File

@@ -66,20 +66,22 @@ pub fn print_drop_databases_output_status_json(output: &DropDatabasesResponse) {
}
impl DropDatabaseError {
#[must_use]
pub fn to_error_message(&self, database_name: &MySQLDatabase) -> String {
match self {
DropDatabaseError::ValidationError(err) => {
err.to_error_message(DbOrUser::Database(database_name.clone()))
err.to_error_message(&DbOrUser::Database(database_name.clone()))
}
DropDatabaseError::DatabaseDoesNotExist => {
format!("Database {} does not exist.", database_name)
format!("Database {database_name} does not exist.")
}
DropDatabaseError::MySqlError(err) => {
format!("MySQL error: {}", err)
format!("MySQL error: {err}")
}
}
}
#[must_use]
pub fn error_type(&self) -> String {
match self {
DropDatabaseError::ValidationError(err) => err.error_type(),

View File

@@ -29,7 +29,7 @@ pub fn print_drop_users_output_status(output: &DropUsersResponse) {
for (username, result) in output {
match result {
Ok(()) => {
println!("User '{}' dropped successfully.", username);
println!("User '{username}' dropped successfully.");
}
Err(err) => {
eprintln!("{}", err.to_error_message(username));
@@ -63,20 +63,22 @@ pub fn print_drop_users_output_status_json(output: &DropUsersResponse) {
}
impl DropUserError {
#[must_use]
pub fn to_error_message(&self, username: &MySQLUser) -> String {
match self {
DropUserError::ValidationError(err) => {
err.to_error_message(DbOrUser::User(username.clone()))
err.to_error_message(&DbOrUser::User(username.clone()))
}
DropUserError::UserDoesNotExist => {
format!("User '{}' does not exist.", username)
format!("User '{username}' does not exist.")
}
DropUserError::MySqlError(err) => {
format!("MySQL error: {}", err)
format!("MySQL error: {err}")
}
}
}
#[must_use]
pub fn error_type(&self) -> String {
match self {
DropUserError::ValidationError(err) => err.error_type(),

View File

@@ -12,13 +12,15 @@ pub enum ListAllDatabasesError {
}
impl ListAllDatabasesError {
#[must_use]
pub fn to_error_message(&self) -> String {
match self {
ListAllDatabasesError::MySqlError(err) => format!("MySQL error: {}", err),
ListAllDatabasesError::MySqlError(err) => format!("MySQL error: {err}"),
}
}
#[allow(dead_code)]
#[must_use]
pub fn error_type(&self) -> String {
match self {
ListAllDatabasesError::MySqlError(_) => "mysql-error".to_string(),

View File

@@ -12,13 +12,15 @@ pub enum ListAllPrivilegesError {
}
impl ListAllPrivilegesError {
#[must_use]
pub fn to_error_message(&self) -> String {
match self {
ListAllPrivilegesError::MySqlError(err) => format!("MySQL error: {}", err),
ListAllPrivilegesError::MySqlError(err) => format!("MySQL error: {err}"),
}
}
#[allow(dead_code)]
#[must_use]
pub fn error_type(&self) -> String {
match self {
ListAllPrivilegesError::MySqlError(_) => "mysql-error".to_string(),

View File

@@ -12,13 +12,15 @@ pub enum ListAllUsersError {
}
impl ListAllUsersError {
#[must_use]
pub fn to_error_message(&self) -> String {
match self {
ListAllUsersError::MySqlError(err) => format!("MySQL error: {}", err),
ListAllUsersError::MySqlError(err) => format!("MySQL error: {err}"),
}
}
#[allow(dead_code)]
#[must_use]
pub fn error_type(&self) -> String {
match self {
ListAllUsersError::MySqlError(_) => "mysql-error".to_string(),

View File

@@ -113,20 +113,22 @@ pub fn print_list_databases_output_status_json(output: &ListDatabasesResponse) {
}
impl ListDatabasesError {
#[must_use]
pub fn to_error_message(&self, database_name: &MySQLDatabase) -> String {
match self {
ListDatabasesError::ValidationError(err) => {
err.to_error_message(DbOrUser::Database(database_name.clone()))
err.to_error_message(&DbOrUser::Database(database_name.clone()))
}
ListDatabasesError::DatabaseDoesNotExist => {
format!("Database '{}' does not exist.", database_name)
format!("Database '{database_name}' does not exist.")
}
ListDatabasesError::MySqlError(err) => {
format!("MySQL error: {}", err)
format!("MySQL error: {err}")
}
}
}
#[must_use]
pub fn error_type(&self) -> String {
match self {
ListDatabasesError::ValidationError(err) => err.error_type(),

View File

@@ -65,7 +65,7 @@ pub fn print_list_privileges_output_status(output: &ListPrivilegesResponse, long
));
for (_database, rows) in final_privs_map {
for row in rows.iter() {
for row in &rows {
table.add_row(row![
row.db,
row.user,
@@ -129,20 +129,22 @@ pub enum ListPrivilegesError {
}
impl ListPrivilegesError {
#[must_use]
pub fn to_error_message(&self, database_name: &MySQLDatabase) -> String {
match self {
ListPrivilegesError::ValidationError(err) => {
err.to_error_message(DbOrUser::Database(database_name.clone()))
err.to_error_message(&DbOrUser::Database(database_name.clone()))
}
ListPrivilegesError::DatabaseDoesNotExist => {
format!("Database '{}' does not exist.", database_name)
format!("Database '{database_name}' does not exist.")
}
ListPrivilegesError::MySqlError(err) => {
format!("MySQL error: {}", err)
format!("MySQL error: {err}")
}
}
}
#[must_use]
pub fn error_type(&self) -> String {
match self {
ListPrivilegesError::ValidationError(err) => err.error_type(),

View File

@@ -97,20 +97,22 @@ pub fn print_list_users_output_status_json(output: &ListUsersResponse) {
}
impl ListUsersError {
#[must_use]
pub fn to_error_message(&self, username: &MySQLUser) -> String {
match self {
ListUsersError::ValidationError(err) => {
err.to_error_message(DbOrUser::User(username.clone()))
err.to_error_message(&DbOrUser::User(username.clone()))
}
ListUsersError::UserDoesNotExist => {
format!("User '{}' does not exist.", username)
format!("User '{username}' does not exist.")
}
ListUsersError::MySqlError(err) => {
format!("MySQL error: {}", err)
format!("MySQL error: {err}")
}
}
}
#[must_use]
pub fn error_type(&self) -> String {
match self {
ListUsersError::ValidationError(err) => err.error_type(),

View File

@@ -32,7 +32,7 @@ pub fn print_lock_users_output_status(output: &LockUsersResponse) {
for (username, result) in output {
match result {
Ok(()) => {
println!("User '{}' locked successfully.", username);
println!("User '{username}' locked successfully.");
}
Err(err) => {
eprintln!("{}", err.to_error_message(username));
@@ -66,23 +66,25 @@ pub fn print_lock_users_output_status_json(output: &LockUsersResponse) {
}
impl LockUserError {
#[must_use]
pub fn to_error_message(&self, username: &MySQLUser) -> String {
match self {
LockUserError::ValidationError(err) => {
err.to_error_message(DbOrUser::User(username.clone()))
err.to_error_message(&DbOrUser::User(username.clone()))
}
LockUserError::UserDoesNotExist => {
format!("User '{}' does not exist.", username)
format!("User '{username}' does not exist.")
}
LockUserError::UserIsAlreadyLocked => {
format!("User '{}' is already locked.", username)
format!("User '{username}' is already locked.")
}
LockUserError::MySqlError(err) => {
format!("MySQL error: {}", err)
format!("MySQL error: {err}")
}
}
}
#[must_use]
pub fn error_type(&self) -> String {
match self {
LockUserError::ValidationError(err) => err.error_type(),

View File

@@ -53,8 +53,7 @@ pub fn print_modify_database_privileges_output_status(output: &ModifyPrivilegesR
match result {
Ok(()) => {
println!(
"Privileges for user '{}' on database '{}' modified successfully.",
username, database_name
"Privileges for user '{username}' on database '{database_name}' modified successfully."
);
}
Err(err) => {
@@ -67,19 +66,20 @@ pub fn print_modify_database_privileges_output_status(output: &ModifyPrivilegesR
}
impl ModifyDatabasePrivilegesError {
#[must_use]
pub fn to_error_message(&self, database_name: &MySQLDatabase, username: &MySQLUser) -> String {
match self {
ModifyDatabasePrivilegesError::DatabaseValidationError(err) => {
err.to_error_message(DbOrUser::Database(database_name.clone()))
err.to_error_message(&DbOrUser::Database(database_name.clone()))
}
ModifyDatabasePrivilegesError::UserValidationError(err) => {
err.to_error_message(DbOrUser::User(username.clone()))
err.to_error_message(&DbOrUser::User(username.clone()))
}
ModifyDatabasePrivilegesError::DatabaseDoesNotExist => {
format!("Database '{}' does not exist.", database_name)
format!("Database '{database_name}' does not exist.")
}
ModifyDatabasePrivilegesError::UserDoesNotExist => {
format!("User '{}' does not exist.", username)
format!("User '{username}' does not exist.")
}
ModifyDatabasePrivilegesError::DiffDoesNotApply(diff) => {
format!(
@@ -88,12 +88,13 @@ impl ModifyDatabasePrivilegesError {
)
}
ModifyDatabasePrivilegesError::MySqlError(err) => {
format!("MySQL error: {}", err)
format!("MySQL error: {err}")
}
}
}
#[allow(dead_code)]
#[must_use]
pub fn error_type(&self) -> String {
match self {
ModifyDatabasePrivilegesError::DatabaseValidationError(err) => {
@@ -113,29 +114,26 @@ impl ModifyDatabasePrivilegesError {
}
impl DiffDoesNotApplyError {
#[must_use]
pub fn to_error_message(&self) -> String {
match self {
DiffDoesNotApplyError::RowAlreadyExists(database_name, username) => {
format!(
"Privileges for user '{}' on database '{}' already exist.",
username, database_name
"Privileges for user '{username}' on database '{database_name}' already exist."
)
}
DiffDoesNotApplyError::RowDoesNotExist(database_name, username) => {
format!(
"Privileges for user '{}' on database '{}' do not exist.",
username, database_name
"Privileges for user '{username}' on database '{database_name}' do not exist."
)
}
DiffDoesNotApplyError::RowPrivilegeChangeDoesNotApply(diff, row) => {
format!(
"Could not apply privilege change {:?} to row {:?}",
diff, row
)
format!("Could not apply privilege change {diff:?} to row {row:?}")
}
}
}
#[must_use]
pub fn error_type(&self) -> String {
match self {
DiffDoesNotApplyError::RowAlreadyExists(_, _) => "row-already-exists".to_string(),

View File

@@ -25,7 +25,7 @@ pub enum SetPasswordError {
pub fn print_set_password_output_status(output: &SetUserPasswordResponse, username: &MySQLUser) {
match output {
Ok(()) => {
println!("Password for user '{}' set successfully.", username);
println!("Password for user '{username}' set successfully.");
}
Err(err) => {
eprintln!("{}", err.to_error_message(username));
@@ -35,21 +35,23 @@ pub fn print_set_password_output_status(output: &SetUserPasswordResponse, userna
}
impl SetPasswordError {
#[must_use]
pub fn to_error_message(&self, username: &MySQLUser) -> String {
match self {
SetPasswordError::ValidationError(err) => {
err.to_error_message(DbOrUser::User(username.clone()))
err.to_error_message(&DbOrUser::User(username.clone()))
}
SetPasswordError::UserDoesNotExist => {
format!("User '{}' does not exist.", username)
format!("User '{username}' does not exist.")
}
SetPasswordError::MySqlError(err) => {
format!("MySQL error: {}", err)
format!("MySQL error: {err}")
}
}
}
#[allow(dead_code)]
#[must_use]
pub fn error_type(&self) -> String {
match self {
SetPasswordError::ValidationError(err) => err.error_type(),

View File

@@ -32,7 +32,7 @@ pub fn print_unlock_users_output_status(output: &UnlockUsersResponse) {
for (username, result) in output {
match result {
Ok(()) => {
println!("User '{}' unlocked successfully.", username);
println!("User '{username}' unlocked successfully.");
}
Err(err) => {
eprintln!("{}", err.to_error_message(username));
@@ -66,23 +66,25 @@ pub fn print_unlock_users_output_status_json(output: &UnlockUsersResponse) {
}
impl UnlockUserError {
#[must_use]
pub fn to_error_message(&self, username: &MySQLUser) -> String {
match self {
UnlockUserError::ValidationError(err) => {
err.to_error_message(DbOrUser::User(username.clone()))
err.to_error_message(&DbOrUser::User(username.clone()))
}
UnlockUserError::UserDoesNotExist => {
format!("User '{}' does not exist.", username)
format!("User '{username}' does not exist.")
}
UnlockUserError::UserIsAlreadyUnlocked => {
format!("User '{}' is already unlocked.", username)
format!("User '{username}' is already unlocked.")
}
UnlockUserError::MySqlError(err) => {
format!("MySQL error: {}", err)
format!("MySQL error: {err}")
}
}
}
#[must_use]
pub fn error_type(&self) -> String {
match self {
UnlockUserError::ValidationError(err) => err.error_type(),

View File

@@ -22,7 +22,8 @@ pub enum NameValidationError {
}
impl NameValidationError {
pub fn to_error_message(self, db_or_user: DbOrUser) -> String {
#[must_use]
pub fn to_error_message(self, db_or_user: &DbOrUser) -> String {
match self {
NameValidationError::EmptyString => {
format!("{} name can not be empty.", db_or_user.capitalized_noun())
@@ -32,15 +33,16 @@ impl NameValidationError {
db_or_user.capitalized_noun()
),
NameValidationError::InvalidCharacters => format!(
indoc! {r#"
indoc! {r"
Invalid characters in {} name: '{}', only A-Z, a-z, 0-9, _ (underscore) and - (dash) are permitted.
"#},
"},
db_or_user.lowercased_noun(),
db_or_user.name(),
),
}
}
#[must_use]
pub fn error_type(&self) -> &'static str {
match self {
NameValidationError::EmptyString => "empty-string",
@@ -64,7 +66,8 @@ pub enum AuthorizationError {
}
impl AuthorizationError {
pub fn to_error_message(self, db_or_user: DbOrUser) -> String {
#[must_use]
pub fn to_error_message(self, db_or_user: &DbOrUser) -> String {
match self {
AuthorizationError::IllegalPrefix => format!(
"Illegal {} name prefix: you are not allowed to manage databases or users prefixed with '{}'",
@@ -82,6 +85,7 @@ impl AuthorizationError {
}
}
#[must_use]
pub fn error_type(&self) -> &'static str {
match self {
AuthorizationError::IllegalPrefix => "illegal-prefix",
@@ -102,7 +106,8 @@ pub enum ValidationError {
}
impl ValidationError {
pub fn to_error_message(&self, db_or_user: DbOrUser) -> String {
#[must_use]
pub fn to_error_message(&self, db_or_user: &DbOrUser) -> String {
match self {
ValidationError::NameValidationError(err) => err.to_error_message(db_or_user),
ValidationError::AuthorizationError(err) => err.to_error_message(db_or_user),
@@ -116,6 +121,7 @@ impl ValidationError {
}
}
#[must_use]
pub fn error_type(&self) -> String {
match self {
ValidationError::NameValidationError(err) => {
@@ -153,7 +159,7 @@ pub fn validate_authorization_by_unix_user(
name: &str,
user: &UnixUser,
) -> Result<(), AuthorizationError> {
let prefixes = std::iter::once(user.username.to_owned())
let prefixes = std::iter::once(user.username.clone())
.chain(user.groups.iter().cloned())
.collect::<Vec<String>>();
@@ -174,12 +180,12 @@ pub fn validate_authorization_by_prefixes(
if prefixes
.iter()
.filter(|p| name.starts_with(&(p.to_string() + "_")))
.filter(|p| name.starts_with(&((*p).clone() + "_")))
.collect::<Vec<_>>()
.is_empty()
{
return Err(AuthorizationError::IllegalPrefix);
};
}
Ok(())
}

View File

@@ -112,6 +112,7 @@ pub enum DbOrUser {
}
impl DbOrUser {
#[must_use]
pub fn lowercased_noun(&self) -> &'static str {
match self {
DbOrUser::Database(_) => "database",
@@ -119,6 +120,7 @@ impl DbOrUser {
}
}
#[must_use]
pub fn capitalized_noun(&self) -> &'static str {
match self {
DbOrUser::Database(_) => "Database",
@@ -126,6 +128,7 @@ impl DbOrUser {
}
}
#[must_use]
pub fn name(&self) -> &str {
match self {
DbOrUser::Database(db) => db.as_str(),
@@ -133,6 +136,7 @@ impl DbOrUser {
}
}
#[must_use]
pub fn prefix(&self) -> &str {
match self {
DbOrUser::Database(db) => db.split('_').next().unwrap_or("?"),