pub fn build_health_check_probe_url(cname: &str, path: &str) -> String {
let path = if path.starts_with('/') {
path.to_string()
} else if path.is_empty() {
"/".to_string()
} else {
format!("/{path}")
};
format!("http://{cname}{path}")
}
pub async fn run_health_check_probe(url: &str) -> Result<(), String> {
use tokio::process::Command;
let out = Command::new("curl")
.args([
"-s",
"-o",
"/dev/null",
"-L",
"--max-time",
"2",
"-w",
"%{http_code}",
"-I",
])
.arg(url)
.output()
.await
.map_err(|e| format!("could not invoke curl: {e}"))?;
if !out.status.success() {
let stderr = String::from_utf8_lossy(&out.stderr);
let stderr = stderr.trim();
if !stderr.is_empty() {
return Err(stderr
.lines()
.next()
.unwrap_or("transport error")
.to_string());
}
return Err(format!("curl exit {}", out.status.code().unwrap_or(-1)));
}
let code_str = String::from_utf8_lossy(&out.stdout);
let code: u16 = code_str
.trim()
.parse()
.map_err(|_| format!("unparseable status `{}`", code_str.trim()))?;
classify_health_check_status(code)
}
pub fn classify_health_check_status(code: u16) -> Result<(), String> {
match code {
200..=299 => Ok(()),
0 => Err("no response (transport error)".into()),
300..=399 => Err(format!("HTTP {code} (redirect — curl was told to follow)")),
400..=599 => Err(format!("HTTP {code}")),
_ => Err(format!("HTTP {code}")),
}
}
#[cfg(test)]
mod tests {
#[test]
fn build_health_check_probe_url_normalises_path() {
assert_eq!(
super::build_health_check_probe_url("api.example.com", "/healthz"),
"http://api.example.com/healthz"
);
assert_eq!(
super::build_health_check_probe_url("api.example.com", "healthz"),
"http://api.example.com/healthz"
);
assert_eq!(
super::build_health_check_probe_url("api.example.com", ""),
"http://api.example.com/"
);
assert_eq!(
super::build_health_check_probe_url("api.example.com", "/"),
"http://api.example.com/"
);
}
#[test]
fn classify_health_check_status_treats_2xx_as_ok_and_others_as_warning() {
assert!(super::classify_health_check_status(200).is_ok());
assert!(super::classify_health_check_status(201).is_ok());
assert!(super::classify_health_check_status(299).is_ok());
let err = super::classify_health_check_status(0).unwrap_err();
assert!(err.contains("no response"));
let err = super::classify_health_check_status(301).unwrap_err();
assert!(err.contains("301"));
let err = super::classify_health_check_status(404).unwrap_err();
assert!(err.contains("404"));
let err = super::classify_health_check_status(503).unwrap_err();
assert!(err.contains("503"));
}
}