#content-negotiation #middleware #axum-middleware

axum-content-negotiation

Axum middleware to use Accept and Content-Type headers to serialize with different formats

2 releases

0.1.1 Apr 27, 2024
0.1.0 Feb 4, 2024

#804 in Parser implementations

MIT/Apache

42KB
773 lines

axum-content-negotiation

HTTP Content Negotiation middleware and extractor for Axum.


A set of Axum Layers and Extractors that enable content negotiation using Accept and Content-Type headers. It implements schemaless serialization and deserialization content negotiation. Currently supported encodings are:

  • application/json
  • application/cbor

Installation

[dependencies]
axum-content-negotiation = "0.1"

Features

The following features can be enabled to include support for different encodings:

  • simd-json (default): Enables support for application/json encoding using simd-json.
  • cbor (default): Enables support for application/cbor encoding using cbor4ii.
  • json: Enables support for application/json encoding using serde_json.

The following features enable the default content type when Accept header is missing or Accept: */* is present:

  • default-json (default): Assumes application/json as the default content type.
  • default-cbor: Assumes application/cbor as the default content type.

In order to customize your dependencies, you can enable or disable the features as follows:

[dependencies]
axum-content-negotiation = { version = "0.1", default-features = false, features = ["json", "default-json"] }

Usage

Request payloads

The axum_content_negotiation::Negotiate is Extractor can be used in an Axum handlers to accept multiple Content-Type formats for the request body. This extractor will attempt to deserialize the request body into the desired type based on the Content-Type header and a list of supported schemaless encodings.

use axum::{http::StatusCode, response::IntoResponse, routing::post, Router};
use axum_content_negotiation::Negotiate;

#[derive(serde::Deserialize, Debug)]
struct YourType {
    name: String,
}

async fn handler(Negotiate(request_body): Negotiate<YourType>) -> impl IntoResponse {
    (StatusCode::OK, format!("Received ${:?}", request_body))
}

let router: Router<()> = Router::new().route("/", post(handler));

Response payloads

In order to respond with the correct Content-Type header, the axum_content_negotiation::Negotiate also implements an IntoResponse trait, but it requires axum_content_negotiation::NegotiateLayer in order to actually perform the serialization on the desired format.

use axum::{http::StatusCode, response::IntoResponse, routing::get, Router};
use axum_content_negotiation::{Negotiate, NegotiateLayer};

#[derive(serde::Serialize)]
struct YourType {
    name: String,
}

async fn handler() -> impl IntoResponse {
    let response = YourType {
        name: "John".to_string(),
    };
    (StatusCode::OK, Negotiate(response))
}

let router: Router<()> = Router::new().route("/", get(handler)).layer(NegotiateLayer);

All together

use axum::{http::StatusCode, response::IntoResponse, routing::*, Router};
use axum_content_negotiation::{Negotiate, NegotiateLayer};

#[derive(serde::Deserialize, Debug)]
struct Input {
    name: String,
}

#[derive(serde::Serialize)]
struct Output {
    name: String,
}

async fn handler(Negotiate(request_body): Negotiate<Input>) -> impl IntoResponse {
    let response = Output {
        name: format!("Hello there, {}!", request_body.name),
    };
    (StatusCode::OK, Negotiate(response))
}

let router: Router<()> = Router::new().route("/", put(handler)).layer(NegotiateLayer);

Dependencies

~10–17MB
~217K SLoC