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.

src/main.rs
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");
}
Terminal
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 any E: Into<ServerError>. The ORM's errors convert automatically: "not found" becomes 404 and a unique-constraint violation becomes 409.