rust 53 lines · 8 steps

Distinguishing absent from null in an Axum PATCH

A double-Option JSON field lets a partial-update handler tell "field omitted" apart from "field set to null."

Explained by highlit
1use axum::{
2 extract::{Path, State},
3 http::StatusCode,
4 Json,
5};
6use serde::Deserialize;
7use sqlx::PgPool;
8use uuid::Uuid;
9 
10#[derive(Debug, Deserialize)]
11#[serde(rename_all = "camelCase")]
12pub struct DraftPatch {
13 #[serde(default, deserialize_with = "deserialize_optional_field")]
14 pub title: Option<Option<String>>,
15 #[serde(default, deserialize_with = "deserialize_optional_field")]
16 pub body: Option<Option<String>>,
17}
18 
19fn deserialize_optional_field<'de, T, D>(deserializer: D) -> Result<Option<Option<T>>, D::Error>
20where
21 T: Deserialize<'de>,
22 D: serde::Deserializer<'de>,
23{
24 Ok(Some(Option::deserialize(deserializer)?))
25}
26 
27pub async fn patch_draft(
28 State(pool): State<PgPool>,
29 Path(id): Path<Uuid>,
30 Json(patch): Json<DraftPatch>,
31) -> Result<StatusCode, StatusCode> {
32 let row = sqlx::query!(
33 r#"
34 UPDATE drafts
35 SET title = COALESCE($2, title),
36 body = COALESCE($3, body),
37 updated_at = now()
38 WHERE id = $1
39 RETURNING id
40 "#,
41 id,
42 patch.title.flatten(),
43 patch.body.flatten(),
44 )
45 .fetch_optional(&pool)
46 .await
47 .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
48 
49 match row {
50 Some(_) => Ok(StatusCode::NO_CONTENT),
51 None => Err(StatusCode::NOT_FOUND),
52 }
53}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Option<Option<T>> models three states: absent, null, and present — exactly what PATCH semantics need.
  2. 2A custom serde deserializer with #[serde(default)] converts a missing key into the outer None cleanly.
  3. 3COALESCE combined with flatten() lets the same SQL leave omitted columns untouched while still updating provided ones.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Distinguishing absent from null in an Axum PATCH — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code