1use core::fmt;
2use std::{convert::Infallible, str::FromStr};
3
4use axum::{
5 extract::FromRequestParts,
6 http::{header, request::Parts, HeaderValue},
7};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10#[non_exhaustive]
11pub enum ContentType {
12 #[cfg(feature = "json")]
13 Json,
14 #[cfg(feature = "form")]
15 Form,
16 #[cfg(feature = "msgpack")]
17 MsgPack,
18 #[cfg(feature = "bincode")]
19 Bincode,
20 #[cfg(feature = "bitcode")]
21 Bitcode,
22 #[cfg(feature = "cbor")]
23 Cbor,
24 #[cfg(feature = "yaml")]
25 Yaml,
26 #[cfg(feature = "toml")]
27 Toml,
28}
29
30#[cfg(not(any(
31 feature = "json",
32 feature = "form",
33 feature = "msgpack",
34 feature = "bincode",
35 feature = "bitcode",
36 feature = "cbor",
37 feature = "yaml",
38 feature = "toml"
39)))]
40const _: () = {
41 compile_error!(
42 "At least one of the following features must be enabled: `json`, `form`, `msgpack`, \
43 `bincode`, `bitcode`, `cbor`, `yaml`, `toml`."
44 );
45
46 impl Default for ContentType {
47 fn default() -> Self {
48 unimplemented!()
49 }
50 }
51};
52
53#[cfg(any(
54 feature = "json",
55 feature = "form",
56 feature = "msgpack",
57 feature = "bincode",
58 feature = "bitcode",
59 feature = "cbor",
60 feature = "yaml",
61 feature = "toml"
62))]
63impl Default for ContentType {
64 #[allow(unreachable_code)]
65 fn default() -> Self {
66 #[cfg(feature = "json")]
67 return Self::Json;
68 #[cfg(feature = "form")]
69 return Self::Form;
70 #[cfg(feature = "msgpack")]
71 return Self::MsgPack;
72 #[cfg(feature = "bincode")]
73 return Self::Bincode;
74 #[cfg(feature = "bitcode")]
75 return Self::Bitcode;
76 #[cfg(feature = "cbor")]
77 return Self::Cbor;
78 #[cfg(feature = "yaml")]
79 return Self::Yaml;
80 #[cfg(feature = "toml")]
81 return Self::Toml;
82 }
83}
84
85impl fmt::Display for ContentType {
86 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87 f.write_str(self.as_str())
88 }
89}
90
91#[derive(Debug, thiserror::Error)]
92pub enum FromStrError {
93 #[error("invalid content type")]
94 InvalidContentType,
95 #[error(transparent)]
96 Mime(#[from] mime::FromStrError),
97}
98
99impl FromStr for ContentType {
100 type Err = FromStrError;
101
102 fn from_str(s: &str) -> Result<Self, Self::Err> {
103 let mime = s.parse::<mime::Mime>()?;
104 let subtype = mime.suffix().unwrap_or_else(|| mime.subtype());
105
106 Ok(match (mime.type_().as_str(), subtype.as_str()) {
107 #[cfg(feature = "json")]
108 ("application", "json") => Self::Json,
109 #[cfg(feature = "form")]
110 ("application", "x-www-form-urlencoded") => Self::Form,
111 #[cfg(feature = "msgpack")]
112 ("application", "msgpack" | "vnd.msgpack" | "x-msgpack" | "x.msgpack") => Self::MsgPack,
113 #[cfg(feature = "bincode")]
114 ("application", "bincode" | "vnd.bincode" | "x-bincode" | "x.bincode") => Self::Bincode,
115 #[cfg(feature = "bitcode")]
116 ("application", "bitcode" | "vnd.bitcode" | "x-bitcode" | "x.bitcode") => Self::Bitcode,
117 #[cfg(feature = "cbor")]
118 ("application", "cbor") => Self::Cbor,
119 #[cfg(feature = "yaml")]
120 ("application" | "text", "yaml" | "yml" | "x-yaml") => Self::Yaml,
121 #[cfg(feature = "toml")]
122 ("application" | "text", "toml" | "x-toml" | "vnd.toml") => Self::Toml,
123 _ => return Err(FromStrError::InvalidContentType),
124 })
125 }
126}
127
128impl ContentType {
129 pub fn from_header(header: &HeaderValue) -> Option<Self> {
156 header.to_str().ok()?.parse().ok()
157 }
158
159 #[must_use]
169 pub fn as_str(&self) -> &'static str {
170 match *self {
171 #[cfg(feature = "json")]
172 Self::Json => "application/json",
173 #[cfg(feature = "form")]
174 Self::Form => "application/x-www-form-urlencoded",
175 #[cfg(feature = "msgpack")]
176 Self::MsgPack => "application/vnd.msgpack",
177 #[cfg(feature = "bincode")]
178 Self::Bincode => "application/vnd.bincode",
179 #[cfg(feature = "bitcode")]
180 Self::Bitcode => "application/vnd.bitcode",
181 #[cfg(feature = "cbor")]
182 Self::Cbor => "application/cbor",
183 #[cfg(feature = "yaml")]
184 Self::Yaml => "application/x-yaml",
185 #[cfg(feature = "toml")]
186 Self::Toml => "text/toml",
187 }
188 }
189
190 #[must_use]
218 pub fn into_header(self) -> HeaderValue {
219 HeaderValue::from_static(self.as_str())
220 }
221}
222
223impl<S> FromRequestParts<S> for ContentType
224where
225 S: Sync,
226{
227 type Rejection = Infallible;
228
229 async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
230 let header = parts
231 .headers
232 .get(header::CONTENT_TYPE)
233 .and_then(Self::from_header);
234
235 Ok(header.unwrap_or_default())
236 }
237}
238
239#[derive(Debug, Clone, Copy)]
265pub struct Accept(ContentType);
266
267impl Accept {
268 #[inline]
270 #[must_use]
271 pub fn content_type(self) -> ContentType {
272 self.0
273 }
274}
275
276impl From<Accept> for ContentType {
277 #[inline]
278 fn from(accept: Accept) -> Self {
279 accept.0
280 }
281}
282
283impl<S> FromRequestParts<S> for Accept
284where
285 S: Send + Sync + 'static,
286{
287 type Rejection = Infallible;
288
289 async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
290 let header = None
291 .or_else(|| {
292 parts
293 .headers
294 .get(header::ACCEPT)
295 .and_then(ContentType::from_header)
296 })
297 .or_else(|| {
298 parts
299 .headers
300 .get(header::CONTENT_TYPE)
301 .and_then(ContentType::from_header)
302 })
303 .unwrap_or_default();
304
305 Ok(Self(header))
306 }
307}