Properly handle Result types

This commit is contained in:
2025-06-02 20:21:37 +02:00
parent b22a62016b
commit 02d097a40e
3 changed files with 71 additions and 15 deletions

7
Cargo.lock generated
View File

@@ -26,6 +26,12 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "anyhow"
version = "1.0.98"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
[[package]] [[package]]
name = "axum" name = "axum"
version = "0.8.4" version = "0.8.4"
@@ -656,6 +662,7 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
name = "xmpp-blackbox-exporter" name = "xmpp-blackbox-exporter"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow",
"axum", "axum",
"regex", "regex",
"serde", "serde",

View File

@@ -4,6 +4,7 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
anyhow = "1.0.98"
axum = "0.8.4" axum = "0.8.4"
regex = "1.11.1" regex = "1.11.1"
serde = { version = "1.0.219", features = ["derive"] } serde = { version = "1.0.219", features = ["derive"] }

View File

@@ -1,5 +1,16 @@
use anyhow::{
anyhow,
Context,
};
use axum::{ use axum::{
extract::Query, extract::Query,
http::{
StatusCode,
},
response::{
IntoResponse,
Response,
},
routing::get, routing::get,
Router, 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<E> From<E> for AppError where E: Into<anyhow::Error> {
fn from(err: E) -> Self {
Self(err.into())
}
}
#[tokio::main] #[tokio::main]
async fn main() { async fn main() -> anyhow::Result<()> {
let mut args = std::env::args(); let mut args = std::env::args();
let _name = args.next().unwrap(); let _name = args.next().ok_or(anyhow!(""))?;
let mut listen = String::from("[::]:3000"); let mut listen = String::from("[::]:3000");
@@ -57,7 +99,7 @@ async fn main() {
match arg.as_str() { match arg.as_str() {
"--listen" => { "--listen" => {
listen = args.next().unwrap(); listen = args.next().ok_or(anyhow!(""))?;
} }
unknown => { unknown => {
println!("unknown option: {}", unknown); println!("unknown option: {}", unknown);
@@ -70,40 +112,45 @@ async fn main() {
.route("/", get(route_index)) .route("/", get(route_index))
.route("/probe-client-to-server", get(route_probe_client_to_server)); .route("/probe-client-to-server", get(route_probe_client_to_server));
let listener = tokio::net::TcpListener::bind(listen).await.unwrap(); let listener = tokio::net::TcpListener::bind(listen).await?;
println!("Server listening on: http://{}", listener.local_addr().unwrap()); println!("Server listening on: http://{}", listener.local_addr()?);
println!("Probe with:"); 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 { async fn route_index() -> String {
return String::from("Prometheus exporter for checking XMPP server availability"); return String::from("Prometheus exporter for checking XMPP server availability");
} }
async fn probe_client_to_server(domain: &str, hostname: &str, port: u16) -> Result<ProbeFacts, String> { async fn probe_client_to_server(domain: &str, hostname: &str, port: u16) -> anyhow::Result<ProbeFacts> {
let mut probe_facts = ProbeFacts::new(); 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!("<stream:stream to='{}' xmlns:stream='http://etherx.jabber.org/streams' xmlns='jabber:client' xml:lang='en' version='1.0'></stream:stream>", domain); let connect_string = format!("<stream:stream to='{}' xmlns:stream='http://etherx.jabber.org/streams' xmlns='jabber:client' xml:lang='en' version='1.0'></stream:stream>", 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 stream = BufReader::new(stream);
let mut response = String::new(); 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); 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) { if re_match_xmpp_stream.is_match(&response) {
probe_facts.is_xmpp_stream = true; 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) { if re_match_xmpp_client.is_match(&response) {
probe_facts.is_xmpp_client = true; 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( async fn route_probe_client_to_server(
Query(query): Query<ProbeClientToServerQuery>, Query(query): Query<ProbeClientToServerQuery>,
) -> Result<String, String> { ) -> Result<impl IntoResponse, AppError> {
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(); let mut out = String::new();