From 02d097a40e48eb9a5f215dea7899c350bd6833fe Mon Sep 17 00:00:00 2001 From: clerie Date: Mon, 2 Jun 2025 20:21:37 +0200 Subject: [PATCH] Properly handle Result types --- Cargo.lock | 7 +++++ Cargo.toml | 1 + src/main.rs | 78 ++++++++++++++++++++++++++++++++++++++++++----------- 3 files changed, 71 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 09aec7d..c602eca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,6 +26,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "anyhow" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" + [[package]] name = "axum" version = "0.8.4" @@ -656,6 +662,7 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" name = "xmpp-blackbox-exporter" version = "0.1.0" dependencies = [ + "anyhow", "axum", "regex", "serde", diff --git a/Cargo.toml b/Cargo.toml index bce2175..b0cd161 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] +anyhow = "1.0.98" axum = "0.8.4" regex = "1.11.1" serde = { version = "1.0.219", features = ["derive"] } diff --git a/src/main.rs b/src/main.rs index 41447f5..6adbc16 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,16 @@ +use anyhow::{ + anyhow, + Context, +}; use axum::{ extract::Query, + http::{ + StatusCode, + }, + response::{ + IntoResponse, + Response, + }, routing::get, Router, }; @@ -40,11 +51,42 @@ impl ProbeFacts { } } + +struct AppError(anyhow::Error); + +impl std::fmt::Display for AppError { + fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + std::fmt::Display::fmt(&self.0, formatter) + } +} + +impl std::fmt::Debug for AppError { + fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + std::fmt::Debug::fmt(&self.0, formatter) + } +} + +impl IntoResponse for AppError { + fn into_response(self) -> Response { + ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Internal server error:\n{:?}", self), + ) + .into_response() + } +} + +impl From for AppError where E: Into { + fn from(err: E) -> Self { + Self(err.into()) + } +} + #[tokio::main] -async fn main() { +async fn main() -> anyhow::Result<()> { let mut args = std::env::args(); - let _name = args.next().unwrap(); + let _name = args.next().ok_or(anyhow!(""))?; let mut listen = String::from("[::]:3000"); @@ -57,7 +99,7 @@ async fn main() { match arg.as_str() { "--listen" => { - listen = args.next().unwrap(); + listen = args.next().ok_or(anyhow!(""))?; } unknown => { println!("unknown option: {}", unknown); @@ -70,40 +112,45 @@ async fn main() { .route("/", get(route_index)) .route("/probe-client-to-server", get(route_probe_client_to_server)); - let listener = tokio::net::TcpListener::bind(listen).await.unwrap(); - println!("Server listening on: http://{}", listener.local_addr().unwrap()); + let listener = tokio::net::TcpListener::bind(listen).await?; + println!("Server listening on: http://{}", listener.local_addr()?); println!("Probe with:"); - println!(" http://{}/probe-client-to-server?domain=fem-net.de&hostname=xmpp-2.fem-net.de&port=5222", listener.local_addr().unwrap()); + println!(" http://{}/probe-client-to-server?domain=fem-net.de&hostname=xmpp-2.fem-net.de&port=5222", listener.local_addr()?); - axum::serve(listener, app).await.unwrap(); + axum::serve(listener, app).await?; + + Ok(()) } async fn route_index() -> String { return String::from("Prometheus exporter for checking XMPP server availability"); } -async fn probe_client_to_server(domain: &str, hostname: &str, port: u16) -> Result { +async fn probe_client_to_server(domain: &str, hostname: &str, port: u16) -> anyhow::Result { let mut probe_facts = ProbeFacts::new(); - let mut stream = TcpStream::connect((hostname, port)).await.unwrap(); + let mut stream = TcpStream::connect((hostname, port)).await + .context("Couldn't connect to XMPP server")?; let connect_string = format!("", domain); - stream.write_all(connect_string.as_bytes()).await.unwrap(); + stream.write_all(connect_string.as_bytes()).await + .context("Failed to write XMPP connection test payload")?; let mut stream = BufReader::new(stream); let mut response = String::new(); - stream.read_line(&mut response).await.unwrap(); + stream.read_line(&mut response).await + .context("Failed to read XMPP connection response")?; println!("{}", response); - let re_match_xmpp_stream = Regex::new(r"http://etherx\.jabber\.org/streams").unwrap(); + let re_match_xmpp_stream = Regex::new(r"http://etherx\.jabber\.org/streams")?; if re_match_xmpp_stream.is_match(&response) { probe_facts.is_xmpp_stream = true; } - let re_match_xmpp_client = Regex::new(r"jabber:client").unwrap(); + let re_match_xmpp_client = Regex::new(r"jabber:client")?; if re_match_xmpp_client.is_match(&response) { probe_facts.is_xmpp_client = true; } @@ -113,9 +160,10 @@ async fn probe_client_to_server(domain: &str, hostname: &str, port: u16) -> Resu async fn route_probe_client_to_server( Query(query): Query, -) -> Result { +) -> Result { - let probe_facts = probe_client_to_server(&query.domain, &query.hostname, query.port).await.unwrap(); + let probe_facts = probe_client_to_server(&query.domain, &query.hostname, query.port).await + .context("Failed probing XMPP connection")?; let mut out = String::new();