axum_media/lib.rs
1//! [![github]](https://github.com/marekvospel/axum-media)
2//! [![crates.io]](https://crates.io/crates/axum_media)
3//! [![docs.rs]](https://docs.rs/axum_media/latest/axum_media)
4//!
5//!
6//! [github]: https://img.shields.io/badge/github-8da0cb?labelColor=555555&logo=github
7//! [crates.io]: https://img.shields.io/crates/v/axum_media.svg?color=fc8d62&logo=rust
8//! [docs.rs]: https://img.shields.io/badge/docs.rs-axum--media-66c2a5?labelColor=555555&logo=docs.rs
9//!
10//! <br>
11//!
12//! This crate provides a simple way to use multiple mime types for serializing and
13//! deserializing structs within the axum ecosystem. Inspired by axum's Json
14//! extractor.
15//!
16//!
17//! ## Example
18
19//! ```rust,no_run
20//! use axum_media::{AnyMedia, Accept};
21
22//! #[tokio::main]
23//! async fn main() {
24//! let app = axum::Router::new()
25//! .route("/", axum::routing::get(index))
26//! .route("/login", axum::routing::post(login));
27//!
28//! axum::Server::bind(&"127.0.0.1:3000".parse().unwrap())
29//! .serve(app.into_make_service())
30//! .await.unwrap();
31//! }
32//!
33//! async fn index(accept: Accept) -> impl axum::response::IntoResponse {
34//! // Chooses the right serializer based on the Accept header
35//! AnyMedia(
36//! serde_json::json!({
37//! "routes": ["/", "/login"],
38//! }),
39//! accept.into(),
40//! )
41//! }
42//!
43//! #[derive(serde::Deserialize)]
44//! struct LoginData {
45//! email: String,
46//! password: String,
47//! }
48//!
49//! // Automatically chooses the right deserializer based on the Content-Type header
50//! async fn login(AnyMedia(data, _): AnyMedia<LoginData>) -> String {
51//! data.email
52//! }
53//!
54//! ```
55//!
56//! ## Features
57//! - `urlencoded` - Enables support for `application/x-www-form-urlencoded` using [`serde_urlencoded`].
58
59use axum::{
60 extract::rejection::BytesRejection,
61 http::{header, StatusCode},
62 response::IntoResponse,
63};
64
65pub(crate) mod accept;
66pub(crate) mod anymedia;
67pub(crate) mod mimetypes;
68
69pub use accept::Accept;
70pub use anymedia::AnyMedia;
71
72/// Rejection for [`AnyMedia`] extractor.
73#[derive(Debug, thiserror::Error)]
74pub enum AnyMediaRejection {
75 #[error("Failed to deserialize the JSON body into the target type: {0}")]
76 JsonDataError(serde_path_to_error::Error<serde_json::Error>),
77 #[error("Failed to parse the request body as JSON: {0}")]
78 JsonSyntaxError(serde_path_to_error::Error<serde_json::Error>),
79 #[error("{0}")]
80 BytesRejection(#[from] BytesRejection),
81 #[cfg(feature = "urlencoded")]
82 #[error("Failed to deserialize the Form urlencoded body into the target type: {0}")]
83 UrlEncodedError(#[from] serde_urlencoded::de::Error),
84}
85
86impl IntoResponse for AnyMediaRejection {
87 fn into_response(self) -> axum::response::Response {
88 (
89 StatusCode::BAD_REQUEST,
90 [(header::CONTENT_TYPE, mime::UTF_8.to_string())],
91 format!("{self}"),
92 )
93 .into_response()
94 }
95}
96
97#[derive(Debug, thiserror::Error)]
98pub(crate) enum AnyMediaSerializeError {
99 #[error("{0}")]
100 JsonError(#[from] serde_json::Error),
101 #[cfg(feature = "urlencoded")]
102 #[error("{0}")]
103 UrlEncodedError(#[from] serde_urlencoded::ser::Error),
104}
105
106#[derive(Debug, thiserror::Error)]
107pub(crate) enum AnyMediaDeserializeError {
108 #[error("{0}")]
109 JsonError(#[from] serde_path_to_error::Error<serde_json::Error>),
110 #[cfg(feature = "urlencoded")]
111 #[error("{0}")]
112 UrlEncodedError(#[from] serde_urlencoded::de::Error),
113}