| Crates.io | axum-autoformat |
| lib.rs | axum-autoformat |
| version | 0.3.0 |
| created_at | 2025-12-17 17:11:30.092336+00 |
| updated_at | 2025-12-17 17:11:30.092336+00 |
| description | Content negotiation middleware for Axum |
| homepage | https://github.com/JonathanTroyer/axum-autoformat |
| repository | https://github.com/JonathanTroyer/axum-autoformat |
| max_upload_size | |
| id | 1990752 |
| size | 75,528 |
Content negotiation middleware for Axum. Automatically serializes responses and deserializes requests based on Accept and Content-Type headers.
Accept headerContent-Type headerjson feature, enabled by default)MessagePack support (via msgpack feature, enabled by default)q=) support[dependencies]
axum-autoformat = "0.3"
To disable default features:
[dependencies]
axum-autoformat = { version = "0.3", default-features = false, features = ["json"] }
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 JSONAccept: application/msgpack returns MessagePackAccept header defaults to the first configured media typeUse 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
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()
);
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()
);
MIT