rust 53 lines · 7 steps

A custom tenant extractor in Axum

Implementing FromRequestParts lets a handler pull a validated X-Tenant-Id header straight from its arguments.

Explained by highlit
1use axum::{
2 async_trait,
3 extract::FromRequestParts,
4 http::{header::HeaderName, request::Parts, StatusCode},
5 response::{IntoResponse, Response},
6 Json,
7};
8use serde_json::json;
9 
10static X_TENANT_ID: HeaderName = HeaderName::from_static("x-tenant-id");
11 
12pub struct TenantId(pub String);
13 
14pub enum TenantRejection {
15 Missing,
16 Invalid,
17}
18 
19impl IntoResponse for TenantRejection {
20 fn into_response(self) -> Response {
21 let message = match self {
22 TenantRejection::Missing => "missing required X-Tenant-Id header",
23 TenantRejection::Invalid => "X-Tenant-Id header is not valid UTF-8",
24 };
25 (StatusCode::BAD_REQUEST, Json(json!({ "error": message }))).into_response()
26 }
27}
28 
29#[async_trait]
30impl<S> FromRequestParts<S> for TenantId
31where
32 S: Send + Sync,
33{
34 type Rejection = TenantRejection;
35 
36 async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
37 let value = parts
38 .headers
39 .get(&X_TENANT_ID)
40 .ok_or(TenantRejection::Missing)?;
41 
42 let tenant = value
43 .to_str()
44 .map_err(|_| TenantRejection::Invalid)?
45 .trim();
46 
47 if tenant.is_empty() {
48 return Err(TenantRejection::Missing);
49 }
50 
51 Ok(TenantId(tenant.to_owned()))
52 }
53}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Implementing FromRequestParts turns a plain struct into a reusable, self-validating handler argument.
  2. 2Pairing an extractor with an IntoResponse rejection keeps failure paths as first-class, well-typed responses.
  3. 3The ? operator chains header lookup, decoding, and validation into a single clean fallible flow.

Related explainers

Share this explainer

Here's the card — post it anywhere.

A custom tenant extractor in Axum — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code