improve user-facing error handling

This commit is contained in:
ari melody 2026-09-13 15:14:43 +01:00
parent 4e96f3989c
commit faed4ceb30
Signed by: ari
GPG key ID: CF99829C92678188
3 changed files with 39 additions and 17 deletions

View file

@ -11,8 +11,9 @@ pub enum StatusCode {
OK, OK,
BadRequest, BadRequest,
NotFound, NotFound,
ImATeapot,
InternalServerError, InternalServerError,
// ImATeapot, BadGateway,
} }
impl StatusCode { impl StatusCode {
@ -21,8 +22,9 @@ impl StatusCode {
StatusCode::OK => 200, StatusCode::OK => 200,
StatusCode::BadRequest => 400, StatusCode::BadRequest => 400,
StatusCode::NotFound => 404, StatusCode::NotFound => 404,
// StatusCode::ImATeapot => 418, StatusCode::ImATeapot => 418,
StatusCode::InternalServerError => 500, StatusCode::InternalServerError => 500,
StatusCode::BadGateway => 502,
} }
} }

View file

@ -53,6 +53,23 @@ fn parse_address(address: &str) -> Result<SocketAddr> {
} }
} }
fn query_error_to_user_response(err: &Error, query_address: &String, address: &SocketAddr) -> (StatusCode, String) {
match err.kind() {
ErrorKind::HostUnreachable =>
(StatusCode::BadGateway, format!(
"Host unreachable: {} ({})\n",
query_address, address)),
ErrorKind::InvalidData =>
(StatusCode::BadGateway, format!(
"Server returned invalid data: {} ({})\n",
query_address, address)),
_ =>
(StatusCode::InternalServerError, format!(
"Failed to connect: {} ({}).\n",
query_address, address)),
}
}
fn handle_html_request(request: &Request, response: &mut Response, verbose: bool) -> Result<StatusCode> { fn handle_html_request(request: &Request, response: &mut Response, verbose: bool) -> Result<StatusCode> {
let content = include_str!("views/index.html"); let content = include_str!("views/index.html");
response.set_header("Content-Type", "text/html".to_string()); response.set_header("Content-Type", "text/html".to_string());
@ -75,19 +92,16 @@ fn handle_html_request(request: &Request, response: &mut Response, verbose: bool
match MinecraftStatus::fetch(&address, verbose) { match MinecraftStatus::fetch(&address, verbose) {
Err(err) => { Err(err) => {
println!( println!(
"Failed to connect to {} ({}): {}", "Failed to query {} ({}): {}",
query_address, address, err); query_address, address, err);
response.body(format!( let (code, msg) = query_error_to_user_response(&err, query_address, &address);
"Failed to connect to {} ({}).\n",
query_address, address)); response.status(code);
response.status(StatusCode::InternalServerError);
query_response = format!( query_response = format!(
"<hr/> "<hr/>
<h2>Server Details</h2> <h2>Server Details</h2>
<pre><code>Failed to connect to {} ({}).</pre></code>", <pre><code>{msg}</pre></code>",
sanitize_html(query_address),
sanitize_html(&address.to_string()),
); );
} }
Ok(status) => { Ok(status) => {
@ -155,10 +169,10 @@ fn handle_json_request(request: &Request, response: &mut Response, verbose: bool
"Failed to connect to {} ({}): {}", "Failed to connect to {} ({}): {}",
query_address, address, err); query_address, address, err);
response.status(StatusCode::InternalServerError); let (code, msg) = query_error_to_user_response(&err, query_address, &address);
response.body(format!(
"Failed to connect to {} ({}).\n", response.status(code);
query_address, address)); response.body(msg);
} }
Ok(status) => { Ok(status) => {
let minecraft_status = MinecraftStatusResponse{ let minecraft_status = MinecraftStatusResponse{
@ -241,7 +255,7 @@ fn main() -> Result<()> {
Err (e) => { Err (e) => {
return Err(Error::new( return Err(Error::new(
ErrorKind::Other, ErrorKind::Other,
format!("Failed to fetch status: {}", e))); format!("Failed to query server: {}", e)));
} }
}; };
} }

View file

@ -128,10 +128,16 @@ impl MinecraftStatus {
} }
} }
let data = std::str::from_utf8(&data[offset..]).expect("Failed to parse UTF-8 data"); let data = match std::str::from_utf8(&data[offset..]) {
Ok(data) => data,
Err(err) => return Err(Error::new(ErrorKind::InvalidData, format!("Failed to parse UTF-8 data: {err}")))
};
let sanitised: String = data.chars().filter( let sanitised: String = data.chars().filter(
|&c| c >= '\u{20}' || c == '\n' || c == '\r' || c == '\t').collect(); |&c| c >= '\u{20}' || c == '\n' || c == '\r' || c == '\t').collect();
let status: MinecraftStatus = serde_json::from_str(&sanitised).expect("Failed to parse JSON"); let status: MinecraftStatus = match serde_json::from_str(&sanitised) {
Ok(status) => status,
Err(err) => return Err(Error::new(ErrorKind::InvalidData, format!("Failed to parse JSON: {err}"))),
};
Ok(status) Ok(status)
} }