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}