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,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);
}