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:
@@ -18,9 +18,17 @@ pub enum ConfigError {
|
||||
#[error("config file not found: {0}")]
|
||||
NotFound(PathBuf),
|
||||
#[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}")]
|
||||
Parse { path: PathBuf, #[source] source: toml::de::Error },
|
||||
Parse {
|
||||
path: PathBuf,
|
||||
#[source]
|
||||
source: toml::de::Error,
|
||||
},
|
||||
#[error("invalid config: {field} — {message}")]
|
||||
Invalid { field: String, message: String },
|
||||
}
|
||||
@@ -145,11 +153,12 @@ pub const DEFAULT_WEB_PORT: u16 = 8080;
|
||||
impl Config {
|
||||
/// Load from `path`, deriving the default state dir from `$HOME`.
|
||||
pub fn load(path: &Path) -> Result<Config, ConfigError> {
|
||||
let home = std::env::var_os("HOME").map(PathBuf::from).ok_or_else(|| {
|
||||
ConfigError::Invalid {
|
||||
let home =
|
||||
std::env::var_os("HOME")
|
||||
.map(PathBuf::from)
|
||||
.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)
|
||||
}
|
||||
@@ -160,17 +169,25 @@ impl Config {
|
||||
if e.kind() == std::io::ErrorKind::NotFound {
|
||||
ConfigError::NotFound(path.to_path_buf())
|
||||
} else {
|
||||
ConfigError::Io { path: path.to_path_buf(), source: e }
|
||||
ConfigError::Io {
|
||||
path: path.to_path_buf(),
|
||||
source: e,
|
||||
}
|
||||
}
|
||||
})?;
|
||||
let raw: RawConfig =
|
||||
toml::from_str(&text).map_err(|e| ConfigError::Parse { path: path.to_path_buf(), source: e })?;
|
||||
let raw: RawConfig = toml::from_str(&text).map_err(|e| ConfigError::Parse {
|
||||
path: path.to_path_buf(),
|
||||
source: e,
|
||||
})?;
|
||||
Self::from_raw(raw, home)
|
||||
}
|
||||
|
||||
fn from_raw(raw: RawConfig, home: &Path) -> Result<Config, ConfigError> {
|
||||
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 {
|
||||
raw.state.dir.clone()
|
||||
};
|
||||
@@ -186,7 +203,10 @@ impl Config {
|
||||
Ok(Config {
|
||||
database: DatabaseConfig {
|
||||
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,
|
||||
password: raw.database.password,
|
||||
name: raw.database.name,
|
||||
@@ -202,7 +222,13 @@ impl Config {
|
||||
|
||||
/// Human-readable rendering with the password masked.
|
||||
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!(
|
||||
"[database]\n\
|
||||
host = \"{}\"\n\
|
||||
@@ -320,7 +346,10 @@ dir = "/var/backup/rpn"
|
||||
let d = tempfile::tempdir().unwrap();
|
||||
let p = d.path().join("nope.toml");
|
||||
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]
|
||||
@@ -424,7 +453,10 @@ dir = \"/opt/rpn\"
|
||||
let (_d, p) = dir_with("config.toml", FULL_TOML);
|
||||
let cfg = Config::load_with(&p, Path::new("/home/u")).unwrap();
|
||||
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("alice"), "user should remain visible:\n{s}");
|
||||
}
|
||||
@@ -441,7 +473,8 @@ dir = \"/opt/rpn\"
|
||||
|
||||
#[test]
|
||||
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 cfg = Config::load_with(&p, Path::new("/home/u")).unwrap();
|
||||
let err = cfg.validate_database().unwrap_err();
|
||||
|
||||
@@ -22,9 +22,16 @@ mod tests {
|
||||
password: "pw".into(),
|
||||
name: "rpn".into(),
|
||||
},
|
||||
web: WebConfig { port: 8080, fqdn: String::new() },
|
||||
state: StateConfig { dir: "/tmp/rpn".into() },
|
||||
backup: BackupConfig { dir: "/tmp/rpn/backups".into() },
|
||||
web: WebConfig {
|
||||
port: 8080,
|
||||
fqdn: String::new(),
|
||||
},
|
||||
state: StateConfig {
|
||||
dir: "/tmp/rpn".into(),
|
||||
},
|
||||
backup: BackupConfig {
|
||||
dir: "/tmp/rpn/backups".into(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +44,10 @@ mod tests {
|
||||
#[test]
|
||||
fn connection_url_with_database() {
|
||||
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]
|
||||
@@ -82,8 +92,19 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn embedded_migration_is_schema_v1() {
|
||||
for table in ["files", "customers", "batches", "cards", "transactions", "invoices", "invoice_items"] {
|
||||
assert!(MIGRATION_V1.contains(&format!("CREATE TABLE {table}")), "{table}");
|
||||
for table in [
|
||||
"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
|
||||
assert!(!MIGRATION_V1.contains("CREATE TABLE schema_migrations"));
|
||||
@@ -92,11 +113,17 @@ mod tests {
|
||||
#[test]
|
||||
fn backup_command_uses_dump_client() {
|
||||
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 == "--port=3307"), "{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!(args.iter().any(|a| a == "--single-transaction"), "{args:?}");
|
||||
assert!(args.iter().any(|a| a == "--databases"), "{args:?}");
|
||||
@@ -113,7 +140,10 @@ mod tests {
|
||||
assert!(program == "mariadb" || program == "mysql", "{program}");
|
||||
assert!(args.iter().any(|a| a == "--host=db.example"), "{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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user