Cookbook / Requests and responses
Error handling
Return typed errors with ?, shape every error response yourself, and keep internals out of the response.
Return Err(ServerError::...), or use ?, and Axeon answers with the matching status and a JSON body: {"error": {"message": ..., "status": ...}}. Use on_error to change how every error is rendered, for example to match an API convention your clients already know.
use axeon::{Json, Response, Server, ServerError};
use axeon::json;
fn find_invoice(id: u64) -> Result<String, ServerError> {
match id {
1 => Ok("INV-0001".to_string()),
2 => Err(ServerError::Forbidden("that invoice belongs to someone else".into())),
_ => Err(ServerError::Status(404, format!("no invoice {}", id))),
}
}
fn main() {
let mut app = Server::new();
app.get("/invoices/:id", |req| async move {
let id: u64 = req.param("id")?;
Ok::<_, ServerError>(find_invoice(id)?)
});
// Handlers can also return None for a 404.
app.get("/teapot", |_req| async { None::<&str> });
// Panics become a 500; the message is logged, never sent to the client.
app.get("/boom", |_req| async {
if true {
panic!("this message stays in the logs");
}
"unreachable"
});
// Re-shape every error response.
app.on_error(|err| {
let status = err.status_code();
// Server errors may carry internal details: show clients a fixed message.
let detail = if status >= 500 { "something went wrong".to_string() } else { err.to_string() };
let body = json!({ "ok": false, "code": status, "detail": detail });
Response::json(&body).with_status(status)
});
app.get("/ok", |_req| async { Json(json!({ "ok": true })) });
app.listen("127.0.0.1:3000").expect("server failed to start");
}
curl -i http://127.0.0.1:3000/invoices/2
curl -i http://127.0.0.1:3000/invoices/abc # 400: not a number
curl -i http://127.0.0.1:3000/boom # 500: "something went wrong", nothing more
Notes#
- Errors reach middleware as responses, so headers added by middleware (CORS, request IDs, security headers) appear on error responses too.
- Axeon's default responses never include internal details. In your own
on_error, treat 5xx messages as internal too: log them and send a fixed message, as above. Result<T, E>works for anyE: Into<ServerError>. The ORM's errors convert automatically: "not found" becomes 404 and a unique-constraint violation becomes 409.