chore: apply cargo fmt to core crate

config.rs had two single-line struct variants and a let-chain that
rustfmt (edition 2024) wants expanded; db.rs test module picked up the
same treatment. Whitespace-only - no behaviour change.

Why: 'cargo fmt --check' is part of every task's definition of done,
and main was handing over slightly dirty.
This commit is contained in:
hermes
2026-10-08 12:38:56 +02:00
parent a350afc053
commit e7acfcf899
2 changed files with 89 additions and 26 deletions
+50 -17
View File
@@ -18,9 +18,17 @@ pub enum ConfigError {
#[error("config file not found: {0}")] #[error("config file not found: {0}")]
NotFound(PathBuf), NotFound(PathBuf),
#[error("failed to read config file {path}: {source}")] #[error("failed to read config file {path}: {source}")]
Io { path: PathBuf, #[source] source: std::io::Error }, Io {
path: PathBuf,
#[source]
source: std::io::Error,
},
#[error("failed to parse config file {path}: {source}")] #[error("failed to parse config file {path}: {source}")]
Parse { path: PathBuf, #[source] source: toml::de::Error }, Parse {
path: PathBuf,
#[source]
source: toml::de::Error,
},
#[error("invalid config: {field} — {message}")] #[error("invalid config: {field} — {message}")]
Invalid { field: String, message: String }, Invalid { field: String, message: String },
} }
@@ -145,12 +153,13 @@ pub const DEFAULT_WEB_PORT: u16 = 8080;
impl Config { impl Config {
/// Load from `path`, deriving the default state dir from `$HOME`. /// Load from `path`, deriving the default state dir from `$HOME`.
pub fn load(path: &Path) -> Result<Config, ConfigError> { pub fn load(path: &Path) -> Result<Config, ConfigError> {
let home = std::env::var_os("HOME").map(PathBuf::from).ok_or_else(|| { let home =
ConfigError::Invalid { std::env::var_os("HOME")
field: "state.dir".to_string(), .map(PathBuf::from)
message: "$HOME is unset; set [state].dir explicitly".to_string(), .ok_or_else(|| ConfigError::Invalid {
} field: "state.dir".to_string(),
})?; message: "$HOME is unset; set [state].dir explicitly".to_string(),
})?;
Self::load_with(path, &home) Self::load_with(path, &home)
} }
@@ -160,17 +169,25 @@ impl Config {
if e.kind() == std::io::ErrorKind::NotFound { if e.kind() == std::io::ErrorKind::NotFound {
ConfigError::NotFound(path.to_path_buf()) ConfigError::NotFound(path.to_path_buf())
} else { } else {
ConfigError::Io { path: path.to_path_buf(), source: e } ConfigError::Io {
path: path.to_path_buf(),
source: e,
}
} }
})?; })?;
let raw: RawConfig = let raw: RawConfig = toml::from_str(&text).map_err(|e| ConfigError::Parse {
toml::from_str(&text).map_err(|e| ConfigError::Parse { path: path.to_path_buf(), source: e })?; path: path.to_path_buf(),
source: e,
})?;
Self::from_raw(raw, home) Self::from_raw(raw, home)
} }
fn from_raw(raw: RawConfig, home: &Path) -> Result<Config, ConfigError> { fn from_raw(raw: RawConfig, home: &Path) -> Result<Config, ConfigError> {
let state = if raw.state.dir.is_empty() { let state = if raw.state.dir.is_empty() {
home.join(".config").join("rpn").to_string_lossy().into_owned() home.join(".config")
.join("rpn")
.to_string_lossy()
.into_owned()
} else { } else {
raw.state.dir.clone() raw.state.dir.clone()
}; };
@@ -186,7 +203,10 @@ impl Config {
Ok(Config { Ok(Config {
database: DatabaseConfig { database: DatabaseConfig {
host: raw.database.host, host: raw.database.host,
port: raw.database.port.as_port("database.port", DEFAULT_DATABASE_PORT)?, port: raw
.database
.port
.as_port("database.port", DEFAULT_DATABASE_PORT)?,
user: raw.database.user, user: raw.database.user,
password: raw.database.password, password: raw.database.password,
name: raw.database.name, name: raw.database.name,
@@ -202,7 +222,13 @@ impl Config {
/// Human-readable rendering with the password masked. /// Human-readable rendering with the password masked.
pub fn show(&self) -> String { pub fn show(&self) -> String {
let mask = |s: &str| if s.is_empty() { String::new() } else { "***".to_string() }; let mask = |s: &str| {
if s.is_empty() {
String::new()
} else {
"***".to_string()
}
};
format!( format!(
"[database]\n\ "[database]\n\
host = \"{}\"\n\ host = \"{}\"\n\
@@ -320,7 +346,10 @@ dir = "/var/backup/rpn"
let d = tempfile::tempdir().unwrap(); let d = tempfile::tempdir().unwrap();
let p = d.path().join("nope.toml"); let p = d.path().join("nope.toml");
let err = Config::load_with(&p, Path::new("/home/u")).unwrap_err(); let err = Config::load_with(&p, Path::new("/home/u")).unwrap_err();
assert!(matches!(err, ConfigError::NotFound(ref q) if *q == p), "{err:?}"); assert!(
matches!(err, ConfigError::NotFound(ref q) if *q == p),
"{err:?}"
);
} }
#[test] #[test]
@@ -424,7 +453,10 @@ dir = \"/opt/rpn\"
let (_d, p) = dir_with("config.toml", FULL_TOML); let (_d, p) = dir_with("config.toml", FULL_TOML);
let cfg = Config::load_with(&p, Path::new("/home/u")).unwrap(); let cfg = Config::load_with(&p, Path::new("/home/u")).unwrap();
let s = cfg.show(); let s = cfg.show();
assert!(!s.contains("hunter2"), "password leaked into show output:\n{s}"); assert!(
!s.contains("hunter2"),
"password leaked into show output:\n{s}"
);
assert!(s.contains("***"), "{s}"); assert!(s.contains("***"), "{s}");
assert!(s.contains("alice"), "user should remain visible:\n{s}"); assert!(s.contains("alice"), "user should remain visible:\n{s}");
} }
@@ -441,7 +473,8 @@ dir = \"/opt/rpn\"
#[test] #[test]
fn validate_database_requires_host_user_name() { fn validate_database_requires_host_user_name() {
let body = "[database]\nhost = \"\"\nport = \"\"\nuser = \"u\"\npassword = \"\"\nname = \"\"\n"; let body =
"[database]\nhost = \"\"\nport = \"\"\nuser = \"u\"\npassword = \"\"\nname = \"\"\n";
let (_d, p) = dir_with("config.toml", body); let (_d, p) = dir_with("config.toml", body);
let cfg = Config::load_with(&p, Path::new("/home/u")).unwrap(); let cfg = Config::load_with(&p, Path::new("/home/u")).unwrap();
let err = cfg.validate_database().unwrap_err(); let err = cfg.validate_database().unwrap_err();
+39 -9
View File
@@ -22,9 +22,16 @@ mod tests {
password: "pw".into(), password: "pw".into(),
name: "rpn".into(), name: "rpn".into(),
}, },
web: WebConfig { port: 8080, fqdn: String::new() }, web: WebConfig {
state: StateConfig { dir: "/tmp/rpn".into() }, port: 8080,
backup: BackupConfig { dir: "/tmp/rpn/backups".into() }, fqdn: String::new(),
},
state: StateConfig {
dir: "/tmp/rpn".into(),
},
backup: BackupConfig {
dir: "/tmp/rpn/backups".into(),
},
} }
} }
@@ -37,7 +44,10 @@ mod tests {
#[test] #[test]
fn connection_url_with_database() { fn connection_url_with_database() {
let cfg = test_config(); let cfg = test_config();
assert_eq!(connection_url(&cfg, true), "mysql://u:***@db.example:3307/rpn"); assert_eq!(
connection_url(&cfg, true),
"mysql://u:***@db.example:3307/rpn"
);
} }
#[test] #[test]
@@ -82,8 +92,19 @@ mod tests {
#[test] #[test]
fn embedded_migration_is_schema_v1() { fn embedded_migration_is_schema_v1() {
for table in ["files", "customers", "batches", "cards", "transactions", "invoices", "invoice_items"] { for table in [
assert!(MIGRATION_V1.contains(&format!("CREATE TABLE {table}")), "{table}"); "files",
"customers",
"batches",
"cards",
"transactions",
"invoices",
"invoice_items",
] {
assert!(
MIGRATION_V1.contains(&format!("CREATE TABLE {table}")),
"{table}"
);
} }
// the migrations bookkeeping table is created by the app, not the file // the migrations bookkeeping table is created by the app, not the file
assert!(!MIGRATION_V1.contains("CREATE TABLE schema_migrations")); assert!(!MIGRATION_V1.contains("CREATE TABLE schema_migrations"));
@@ -92,11 +113,17 @@ mod tests {
#[test] #[test]
fn backup_command_uses_dump_client() { fn backup_command_uses_dump_client() {
let (program, args, use_pwd) = backup_command(&test_config()); let (program, args, use_pwd) = backup_command(&test_config());
assert!(program == "mariadb-dump" || program == "mysqldump", "{program}"); assert!(
program == "mariadb-dump" || program == "mysqldump",
"{program}"
);
assert!(args.iter().any(|a| a == "--host=db.example"), "{args:?}"); assert!(args.iter().any(|a| a == "--host=db.example"), "{args:?}");
assert!(args.iter().any(|a| a == "--port=3307"), "{args:?}"); assert!(args.iter().any(|a| a == "--port=3307"), "{args:?}");
assert!(args.iter().any(|a| a == "--user=u"), "{args:?}"); assert!(args.iter().any(|a| a == "--user=u"), "{args:?}");
assert!(!args.iter().any(|a| a.contains("pw")), "password on argv: {args:?}"); assert!(
!args.iter().any(|a| a.contains("pw")),
"password on argv: {args:?}"
);
assert!(use_pwd, "MYSQL_PWD must be set for the dump client"); assert!(use_pwd, "MYSQL_PWD must be set for the dump client");
assert!(args.iter().any(|a| a == "--single-transaction"), "{args:?}"); assert!(args.iter().any(|a| a == "--single-transaction"), "{args:?}");
assert!(args.iter().any(|a| a == "--databases"), "{args:?}"); assert!(args.iter().any(|a| a == "--databases"), "{args:?}");
@@ -113,7 +140,10 @@ mod tests {
assert!(program == "mariadb" || program == "mysql", "{program}"); assert!(program == "mariadb" || program == "mysql", "{program}");
assert!(args.iter().any(|a| a == "--host=db.example"), "{args:?}"); assert!(args.iter().any(|a| a == "--host=db.example"), "{args:?}");
assert!(args.iter().any(|a| a == "rpn"), "{args:?}"); assert!(args.iter().any(|a| a == "rpn"), "{args:?}");
assert!(!args.iter().any(|a| a.contains(".sql")), "file must not be on argv: {args:?}"); assert!(
!args.iter().any(|a| a.contains(".sql")),
"file must not be on argv: {args:?}"
);
assert!(use_pwd); assert!(use_pwd);
} }