axum-autoformat

Crates.ioaxum-autoformat
lib.rsaxum-autoformat
version0.3.0
created_at2025-12-17 17:11:30.092336+00
updated_at2025-12-17 17:11:30.092336+00
descriptionContent negotiation middleware for Axum
homepagehttps://github.com/JonathanTroyer/axum-autoformat
repositoryhttps://github.com/JonathanTroyer/axum-autoformat
max_upload_size
id1990752
size75,528
Jonathan Troyer (JonathanTroyer)

documentation

README

axum-autoformat

Content negotiation middleware for Axum. Automatically serializes responses and deserializes requests based on Accept and Content-Type headers.

Features

  • Automatic format selection based on Accept header
  • Request body deserialization based on Content-Type header
  • JSON support (via json feature, enabled by default)
  • MessagePack support (via msgpack feature, enabled by default)
  • RFC 7231 quality value (q=) support
  • Configurable default format and strict matching mode

Installation

[dependencies]
axum-autoformat = "0.3"

To disable default features:

[dependencies]
axum-autoformat = { version = "0.3", default-features = false, features = ["json"] }

Usage

Response Serialization

Wrap your response data with Negotiate<T> to enable automatic format selection:

use axum::{Router, routing::get};
use axum_autoformat::{Negotiate, NegotiateLayer};
use serde::Serialize;

#[derive(Serialize)]
struct User {
    name: String,
    email: String,
}

async fn get_user() -> Negotiate<User> {
    Negotiate(User {
        name: "Alice".into(),
        email: "alice@example.com".into(),
    })
}

let app: Router = Router::new()
    .route("/user", get(get_user))
    .layer(NegotiateLayer::default());

The middleware reads the client's Accept header and responds in the preferred format:

  • Accept: application/json returns JSON
  • Accept: application/msgpack returns MessagePack
  • etc.
  • No Accept header defaults to the first configured media type

Request Deserialization

Use Negotiate<T> as an extractor to deserialize request bodies:

use axum::routing::post;
use axum_autoformat::Negotiate;
use serde::{Deserialize, Serialize};

#[derive(Deserialize, Serialize)]
struct CreateUser {
    name: String,
    email: String,
}

async fn create_user(Negotiate(user): Negotiate<CreateUser>) -> Negotiate<CreateUser> {
    // user is deserialized based on Content-Type header
    Negotiate(user)
}

The extractor automatically deserializes based on Content-Type

Configuration

Use NegotiateConfig to customize behavior:

use axum_autoformat::{NegotiateLayer, NegotiateConfig};

// Return 406 Not Acceptable when no format matches or client sends q=0
let layer = NegotiateLayer::new(
    NegotiateConfig::builder()
        .strict_matching(true)
        .build()
);

Change the default format to MessagePack (requires msgpack feature):

use axum_autoformat::{NegotiateLayer, NegotiateConfig, MsgPack};

let layer = NegotiateLayer::new(
    NegotiateConfig::builder()
        .default_format(MsgPack)
        .build()
);

Custom Formats

Create custom formats by implementing the Format trait. Add mediatype and a serialization crate (like serde_json) to your dependencies:

use axum_autoformat::{Format, FormatRef, AsMediaType, OwnedSerializer, OwnedDeserializer};
use axum_autoformat::{NegotiateLayer, NegotiateConfig, Json};
use mediatype::{MediaType, Name, names::APPLICATION};

const VND_MYAPI_JSON: Name<'static> = Name::new_unchecked("vnd.myapi+json");

#[derive(Debug, Copy, Clone)]
struct CustomJson;

impl Format for CustomJson {
    fn media_types(&self) -> &[impl AsMediaType] {
        const { &[MediaType::new(APPLICATION, VND_MYAPI_JSON)] }
    }

    fn serializer<'a>(&'a self, bytes: &'a mut Vec<u8>) -> erased_serde::Result<impl OwnedSerializer + 'a> {
        Ok(serde_json::Serializer::new(bytes))
    }

    fn deserializer<'a>(&'a self, bytes: &'a [u8]) -> erased_serde::Result<impl OwnedDeserializer<'a> + 'a> {
        Ok(serde_json::Deserializer::from_slice(bytes))
    }
}

let layer = NegotiateLayer::new(
    NegotiateConfig::builder()
        .formats([FormatRef::Static(&CustomJson), FormatRef::Static(&Json)])
        .build()
);

License

MIT

Commit count: 0

cargo fmt