rust 57 lines · 9 steps

Typed error handling in an Axum handler

An Axum route extracts typed params, validates input, queries the database, and maps every failure to an HTTP response through one error enum.

Explained by highlit
1use axum::{
2 extract::{Path, State},
3 http::StatusCode,
4 response::{IntoResponse, Response},
5 Json,
6};
7use serde::Deserialize;
8use uuid::Uuid;
9 
10#[derive(Deserialize)]
11pub struct PostParams {
12 workspace_id: Uuid,
13 post_slug: String,
14}
15 
16pub async fn get_post(
17 State(state): State<AppState>,
18 Path(PostParams { workspace_id, post_slug }): Path<PostParams>,
19) -> Result<Json<Post>, ApiError> {
20 if post_slug.len() > 128 || !post_slug.chars().all(|c| c.is_alphanumeric() || c == '-') {
21 return Err(ApiError::InvalidSlug);
22 }
23 
24 let post = sqlx::query_as::<_, Post>(
25 "SELECT * FROM posts WHERE workspace_id = $1 AND slug = $2",
26 )
27 .bind(workspace_id)
28 .bind(&post_slug)
29 .fetch_optional(&state.db)
30 .await?
31 .ok_or(ApiError::NotFound)?;
32 
33 Ok(Json(post))
34}
35 
36pub enum ApiError {
37 InvalidSlug,
38 NotFound,
39 Database(sqlx::Error),
40}
41 
42impl From<sqlx::Error> for ApiError {
43 fn from(err: sqlx::Error) -> Self {
44 ApiError::Database(err)
45 }
46}
47 
48impl IntoResponse for ApiError {
49 fn into_response(self) -> Response {
50 let (status, message) = match self {
51 ApiError::InvalidSlug => (StatusCode::BAD_REQUEST, "invalid post slug"),
52 ApiError::NotFound => (StatusCode::NOT_FOUND, "post not found"),
53 ApiError::Database(_) => (StatusCode::INTERNAL_SERVER_ERROR, "internal error"),
54 };
55 (status, Json(serde_json::json!({ "error": message }))).into_response()
56 }
57}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1A single error enum implementing IntoResponse centralizes how every failure becomes an HTTP status and body.
  2. 2Implementing From lets the `?` operator convert library errors into your domain error automatically.
  3. 3Destructuring extractors in the function signature pulls typed request data straight into named locals.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Typed error handling in an Axum handler — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code