use super::*;
#[derive(Clone, Copy, PartialEq)]
pub(super) enum Fix {
Password,
Tunnel,
Name,
}
pub(super) struct Failure {
pub(super) said: String,
pub(super) fix: Fix,
}
pub(super) fn why_failed(c: &Conn, s: &Settings) -> Option<Failure> {
if !c.engine.networked() {
return None;
}
let (argv, env) = match c.engine {
Engine::Pg => engines::pg::probe_argv(c, s),
Engine::MySql => engines::mysql::probe_argv(c, s),
Engine::MsSql => engines::mssql::probe_argv(c, s),
Engine::Sqlite => return None,
};
let mut cmd = Command::new(&argv[0]);
cmd.args(&argv[1..]);
for (k, v) in env {
cmd.env(k, v);
}
let out = cmd.output().ok()?;
if out.status.success() {
return None;
}
let text = String::from_utf8_lossy(&out.stderr);
let said = first_line(&text)?;
Some(Failure {
fix: classify(&text)?,
said,
})
}
fn first_line(text: &str) -> Option<String> {
let line = text.lines().map(str::trim).find(|l| {
!l.is_empty() && !l.starts_with("Try '") && !l.ends_with("--help' for more information.")
})?;
let trimmed = line
.rsplit_once("FATAL:")
.map(|(_, rest)| rest.trim())
.unwrap_or(line);
Some(trimmed.to_string())
}
fn classify(text: &str) -> Option<Fix> {
const PASSWORD: [&str; 5] = [
"no password supplied",
"password authentication failed",
"Access denied for user",
"authentication method 10 not supported",
"Login failed for user",
];
const TUNNEL: [&str; 11] = [
"Connection refused",
"Connection timed out",
"No route to host",
"could not translate host name",
"Name or service not known",
"no pg_hba.conf entry",
"Can't connect to MySQL server",
"Can't connect to server",
"Unknown MySQL server host",
"TCP Provider",
"Login timeout expired",
];
const NAME: [&str; 3] = [
"does not exist",
"Unknown database",
"Cannot open database",
];
if PASSWORD.iter().any(|p| text.contains(p)) {
return Some(Fix::Password);
}
if TUNNEL.iter().any(|p| text.contains(p)) {
return Some(Fix::Tunnel);
}
if NAME.iter().any(|p| text.contains(p)) {
return Some(Fix::Name);
}
None
}