use std::io::{Read, Write};
use std::net::{SocketAddr, TcpStream};
use std::time::Duration;
use console::style;
const HOST: &str = "127.0.0.1";
const PORT: u16 = 8000;
const HEALTH_PATH: &str = "/admin/health";
const DOCS_PATH: &str = "/admin/docs";
const ONLINE_DOCS: &str = "https://docs.rs/rustio-admin";
const REPO_URL: &str = "https://github.com/abdulwahed-sweden/rustio-admin";
const PROBE_TIMEOUT: Duration = Duration::from_millis(500);
pub(crate) fn print_docs(open: bool) -> Result<(), String> {
let local_url = format!("http://{HOST}:{PORT}{DOCS_PATH}");
let running = health_probe();
if open {
if running {
println!(
"Opening {} in your default browser …",
style(&local_url).cyan().underlined()
);
return open_url_in_browser(&local_url);
}
eprintln!(
"Cannot open the local docs: no server reachable on {}.",
style(&format!("http://{HOST}:{PORT}")).cyan()
);
eprintln!(
"Start the server with {}, then re-run {}.",
style("`cargo run`").green(),
style("`rustio-admin docs --open`").green(),
);
eprintln!();
eprintln!(
" {} {}",
style("online").dim(),
style(ONLINE_DOCS).cyan().underlined()
);
eprintln!(
" {} {}",
style("repo").dim(),
style(REPO_URL).cyan().underlined()
);
return Err("server unreachable; --open could not be honoured".into());
}
if running {
println!("RustIO docs are running on this project.");
println!();
println!(
" {} {}",
style("local").dim(),
style(&local_url).cyan().underlined()
);
println!(
" {} {}",
style("online").dim(),
style(ONLINE_DOCS).cyan().underlined()
);
println!();
println!(
"({} to open the local docs in your browser)",
style("pass --open").dim()
);
} else {
println!("RustIO documentation");
println!();
println!(
" {} {} ({} to enable)",
style("local").dim(),
style(&local_url).cyan().underlined(),
style("start the server with `cargo run`").dim()
);
println!(
" {} {}",
style("online").dim(),
style(ONLINE_DOCS).cyan().underlined()
);
println!(
" {} {}",
style("repo").dim(),
style(REPO_URL).cyan().underlined()
);
println!();
println!(
"({} to open the local docs in your browser, once the server is running)",
style("pass --open").dim()
);
}
Ok(())
}
fn health_probe() -> bool {
let addr = SocketAddr::from(([127, 0, 0, 1], PORT));
let mut stream = match TcpStream::connect_timeout(&addr, PROBE_TIMEOUT) {
Ok(s) => s,
Err(_) => return false,
};
if stream.set_read_timeout(Some(PROBE_TIMEOUT)).is_err() {
return false;
}
if stream.set_write_timeout(Some(PROBE_TIMEOUT)).is_err() {
return false;
}
let req = format!(
"GET {HEALTH_PATH} HTTP/1.1\r\n\
Host: {HOST}:{PORT}\r\n\
User-Agent: rustio-cli-health-probe\r\n\
Connection: close\r\n\r\n"
);
if stream.write_all(req.as_bytes()).is_err() {
return false;
}
let mut buf = vec![0u8; 1024];
let mut total = 0;
while total < buf.len() {
match stream.read(&mut buf[total..]) {
Ok(0) => break,
Ok(n) => total += n,
Err(_) => break,
}
if buf[..total].windows(4).any(|w| w == b"\r\n\r\n") {
break;
}
}
if total < 12 || !buf.starts_with(b"HTTP/1.") {
return false;
}
if !matches!(buf[9], b'2' | b'3') {
return false;
}
let lower: Vec<u8> = buf[..total]
.iter()
.map(|b| b.to_ascii_lowercase())
.collect();
lower
.windows(b"x-correlation-id:".len())
.any(|w| w == b"x-correlation-id:")
}
fn open_url_in_browser(url: &str) -> Result<(), String> {
let result = if cfg!(target_os = "macos") {
std::process::Command::new("open").arg(url).status()
} else if cfg!(target_os = "windows") {
std::process::Command::new("cmd")
.args(["/c", "start", "", url])
.status()
} else {
std::process::Command::new("xdg-open").arg(url).status()
};
let status = result.map_err(|e| format!("failed to launch browser opener: {e}"))?;
if !status.success() {
return Err(format!(
"browser opener exited with status {status} (URL: {url})"
));
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::{Read, Write};
use std::net::TcpListener;
use std::thread;
#[test]
fn health_probe_returns_false_when_nothing_listening() {
if TcpStream::connect_timeout(
&SocketAddr::from(([127, 0, 0, 1], PORT)),
Duration::from_millis(50),
)
.is_ok()
{
eprintln!("[skip] port {PORT} appears to be in use; cannot verify failure-closed");
return;
}
assert!(
!health_probe(),
"probe must return false on refused connect"
);
}
fn spawn_stub(canned: &'static [u8]) -> Option<thread::JoinHandle<()>> {
let listener = TcpListener::bind(("127.0.0.1", PORT)).ok()?;
Some(thread::spawn(move || {
if let Ok((mut stream, _)) = listener.accept() {
let mut buf = [0u8; 256];
let _ = stream.read(&mut buf);
let _ = stream.write_all(canned);
}
}))
}
#[test]
fn health_probe_recognises_rustio_200_with_correlation_id() {
let Some(handle) = spawn_stub(
b"HTTP/1.1 200 OK\r\n\
Content-Type: text/plain\r\n\
x-correlation-id: 019e0000-0000-7000-0000-000000000001\r\n\
Content-Length: 2\r\n\
Connection: close\r\n\r\nok",
) else {
eprintln!("[skip] port {PORT} in use; cannot bind stub");
return;
};
let result = health_probe();
let _ = handle.join();
assert!(
result,
"probe must recognise a rustio 200 (with correlation_id) as 'running'"
);
}
#[test]
fn health_probe_recognises_303_with_correlation_id() {
let Some(handle) = spawn_stub(
b"HTTP/1.1 303 See Other\r\n\
Location: /admin/login\r\n\
x-correlation-id: 019e0000-0000-7000-0000-000000000002\r\n\
Content-Length: 0\r\n\
Connection: close\r\n\r\n",
) else {
eprintln!("[skip] port {PORT} in use");
return;
};
let result = health_probe();
let _ = handle.join();
assert!(
result,
"probe must classify rustio 303 (with correlation_id) as 'running'"
);
}
#[test]
fn health_probe_rejects_foreign_server_without_correlation_id() {
let Some(handle) = spawn_stub(
b"HTTP/1.1 303 See Other\r\n\
Location: /login\r\n\
Content-Length: 0\r\n\
Connection: close\r\n\r\n",
) else {
eprintln!("[skip] port {PORT} in use");
return;
};
let result = health_probe();
let _ = handle.join();
assert!(!result, "probe must reject a 303 without x-correlation-id");
}
#[test]
fn health_probe_classifies_500_as_not_running() {
let Some(handle) = spawn_stub(
b"HTTP/1.1 500 Internal Server Error\r\n\
x-correlation-id: 019e0000-0000-7000-0000-000000000003\r\n\
Content-Length: 0\r\n\
Connection: close\r\n\r\n",
) else {
eprintln!("[skip] port {PORT} in use");
return;
};
let result = health_probe();
let _ = handle.join();
assert!(!result, "probe must NOT classify a 500 as 'running'");
}
#[test]
fn health_probe_rejects_non_http_response() {
let Some(handle) = spawn_stub(b"GARBLE_GARBLE_NOT_HTTP\r\n") else {
eprintln!("[skip] port {PORT} in use");
return;
};
let result = health_probe();
let _ = handle.join();
assert!(
!result,
"non-HTTP response must be classified as not running"
);
}
}