diff --git a/Application/src/core/src/config.rs b/Application/src/core/src/config.rs index cc611cf..4cdc94d 100644 --- a/Application/src/core/src/config.rs +++ b/Application/src/core/src/config.rs @@ -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,12 +153,13 @@ 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 { - 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(), - } - })?; + 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 { 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(); diff --git a/Application/src/core/src/db.rs b/Application/src/core/src/db.rs index 9e5582a..50092f6 100644 --- a/Application/src/core/src/db.rs +++ b/Application/src/core/src/db.rs @@ -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); }