rust
46 lines · 8 steps
Content negotiation in an Axum handler
An Axum handler inspects the Accept header and returns JSON, HTML, or a 406 from the same route.
Explained by
highlit
1use axum::{
2 extract::Path,
3 http::{header::ACCEPT, HeaderMap, StatusCode},
4 response::{Html, IntoResponse, Json, Response},
5};
6use serde::Serialize;
7
8#[derive(Serialize)]
9struct Product {
10 id: u64,
11 name: String,
12 price_cents: u32,
13}
14
15pub async fn show_product(
16 Path(id): Path<u64>,
17 headers: HeaderMap,
18) -> Response {
19 let product = Product {
20 id,
21 name: "Aeron Chair".into(),
22 price_cents: 149900,
23 };
24
25 let accept = headers
26 .get(ACCEPT)
27 .and_then(|v| v.to_str().ok())
28 .unwrap_or("*/*");
29
30 if accept.contains("application/json") {
31 Json(product).into_response()
32 } else if accept.contains("text/html") || accept.contains("*/*") {
33 let body = format!(
34 "<article>\n <h1>{}</h1>\n <p>${:.2}</p>\n</article>",
35 product.name,
36 product.price_cents as f64 / 100.0,
37 );
38 Html(body).into_response()
39 } else {
40 (
41 StatusCode::NOT_ACCEPTABLE,
42 "Supported types: application/json, text/html",
43 )
44 .into_response()
45 }
46}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1A single handler can serve multiple representations by branching on the Accept header.
- 2Returning the erased Response type lets one function yield JSON, HTML, or an error uniformly.
- 3Always provide a fallback so an unmatched Accept value produces a clear 406 rather than a surprise.
Related explainers
rust
use serde::Deserialize; #[derive(Debug, Deserialize)] #[serde(untagged)]
Parsing flexible JSON shapes with serde
deserialization
enums
json
Intermediate
6 steps
rust
use std::cmp::{Ordering, Reverse}; use std::collections::BinaryHeap; use std::fs::File; use std::io::{self, BufRead, BufReader, BufWriter, Lines, Write};
K-way merge of sorted logs in Rust
binary-heap
k-way-merge
streaming-io
Intermediate
8 steps
rust
use axum::{ extract::{Path, State}, response::sse::{Event, KeepAlive, Sse}, };
Streaming import progress with SSE in Axum
server-sent-events
streams
watch-channel
Advanced
7 steps
rust
use std::f64::consts::PI; #[derive(Debug, Clone, Copy)] pub struct LatLng {
Building geographic bounding boxes in Rust
geospatial
value-types
option
Intermediate
7 steps
rust
use chrono::{Duration, NaiveDate}; #[derive(Debug)] pub struct DateRange {
Parsing and iterating date ranges in Rust
error-handling
iterators
parsing
Intermediate
7 steps
rust
use std::collections::VecDeque; use std::sync::{Arc, Condvar, Mutex}; use std::time::{Duration, Instant};
Building a counting semaphore in Rust
concurrency
synchronization
condition-variable
Advanced
9 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/content-negotiation-in-an-axum-handler-explained-rust-8d85/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.