refactor: move TLS error classification into tls module
main.rs no longer downcasts a boxed error to figure out whether it's a permission-denied case. tls::try_data_dir_advisory(&err, &dir) encapsulates the downcast + kind match and returns Some(advisory) or None, mirroring system_dns::try_port53_advisory. main.rs becomes a plain if-let, symmetric with the port-53 path. Trim the docstrings on both advisory functions: they were narrating the implementation (errno mapping) instead of stating the contract. Add unit tests for try_data_dir_advisory covering PermissionDenied, other io::ErrorKind, and non-io errors. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
12
src/main.rs
12
src/main.rs
@@ -223,14 +223,10 @@ async fn main() -> numa::Result<()> {
|
||||
) {
|
||||
Ok(tls_config) => Some(ArcSwap::from(tls_config)),
|
||||
Err(e) => {
|
||||
match e.downcast_ref::<std::io::Error>() {
|
||||
Some(io_err) if io_err.kind() == std::io::ErrorKind::PermissionDenied => {
|
||||
eprint!(
|
||||
"{}",
|
||||
numa::tls::data_dir_permission_advisory(&resolved_data_dir)
|
||||
);
|
||||
}
|
||||
_ => log::warn!("TLS setup failed, HTTPS proxy disabled: {}", e),
|
||||
if let Some(advisory) = numa::tls::try_data_dir_advisory(&e, &resolved_data_dir) {
|
||||
eprint!("{}", advisory);
|
||||
} else {
|
||||
log::warn!("TLS setup failed, HTTPS proxy disabled: {}", e);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
@@ -46,11 +46,8 @@ pub fn discover_system_dns() -> SystemDnsInfo {
|
||||
}
|
||||
}
|
||||
|
||||
/// Diagnostic advisory for port-53 bind failures. Returns `Some(msg)`
|
||||
/// when `bind_addr` targets port 53 and `err` is a kind we can advise
|
||||
/// on (EADDRINUSE — another process holds it; EACCES — non-root on a
|
||||
/// privileged port). Returns `None` for non-53 targets or unrelated
|
||||
/// error kinds, so the caller can fall back to the raw error.
|
||||
/// Advisory for port-53 bind failures (EADDRINUSE or EACCES); `None`
|
||||
/// if not applicable so the caller can fall back to the raw error.
|
||||
pub fn try_port53_advisory(bind_addr: &str, err: &std::io::Error) -> Option<String> {
|
||||
if !is_port_53(bind_addr) {
|
||||
return None;
|
||||
|
||||
49
src/tls.rs
49
src/tls.rs
@@ -40,15 +40,16 @@ pub fn regenerate_tls(ctx: &ServerCtx) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Human-readable diagnostic for TLS data-dir permission failures.
|
||||
/// Triggered when numa can't write its local CA to the configured
|
||||
/// data dir (typically `/usr/local/var/numa` without root). HTTPS
|
||||
/// proxy is disabled; DNS resolution and plain-HTTP proxy keep
|
||||
/// working.
|
||||
pub fn data_dir_permission_advisory(data_dir: &Path) -> String {
|
||||
let o = "\x1b[1;38;2;192;98;58m"; // bold orange
|
||||
/// Advisory for TLS-setup failures caused by a non-writable data dir;
|
||||
/// `None` if not applicable so the caller can fall back to the raw error.
|
||||
pub fn try_data_dir_advisory(err: &crate::Error, data_dir: &Path) -> Option<String> {
|
||||
let io_err = err.downcast_ref::<std::io::Error>()?;
|
||||
if io_err.kind() != std::io::ErrorKind::PermissionDenied {
|
||||
return None;
|
||||
}
|
||||
let o = "\x1b[1;38;2;192;98;58m";
|
||||
let r = "\x1b[0m";
|
||||
format!(
|
||||
Some(format!(
|
||||
"
|
||||
{o}Numa{r} — HTTPS proxy disabled: cannot write TLS CA to {}.
|
||||
|
||||
@@ -70,7 +71,7 @@ pub fn data_dir_permission_advisory(data_dir: &Path) -> String {
|
||||
|
||||
",
|
||||
data_dir.display()
|
||||
)
|
||||
))
|
||||
}
|
||||
|
||||
/// Build a TLS config with a cert covering all provided service names.
|
||||
@@ -203,3 +204,33 @@ fn generate_service_cert(
|
||||
|
||||
Ok((vec![cert_der, ca_der], key_der))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[test]
|
||||
fn try_data_dir_advisory_permission_denied() {
|
||||
let err: crate::Error =
|
||||
Box::new(std::io::Error::from(std::io::ErrorKind::PermissionDenied));
|
||||
let path = PathBuf::from("/usr/local/var/numa");
|
||||
let msg = try_data_dir_advisory(&err, &path).expect("should advise");
|
||||
assert!(msg.contains("HTTPS proxy disabled"));
|
||||
assert!(msg.contains("/usr/local/var/numa"));
|
||||
assert!(msg.contains("numa install"));
|
||||
assert!(msg.contains("data_dir"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn try_data_dir_advisory_skips_other_io_kinds() {
|
||||
let err: crate::Error = Box::new(std::io::Error::from(std::io::ErrorKind::NotFound));
|
||||
assert!(try_data_dir_advisory(&err, &PathBuf::from("/x")).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn try_data_dir_advisory_skips_non_io_errors() {
|
||||
let err: crate::Error = "rcgen failure".into();
|
||||
assert!(try_data_dir_advisory(&err, &PathBuf::from("/x")).is_none());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user