|
1 |
| -use std::convert::Infallible; |
| 1 | +use core::fmt; |
| 2 | +use std::{convert::Infallible, str::FromStr}; |
2 | 3 |
|
3 | 4 | use axum::{
|
4 | 5 | extract::FromRequestParts,
|
5 | 6 | http::{header, request::Parts, HeaderValue},
|
6 | 7 | };
|
7 | 8 |
|
8 | 9 | #[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
| 10 | +#[non_exhaustive] |
9 | 11 | pub enum ContentType {
|
10 | 12 | #[cfg(feature = "json")]
|
11 | 13 | Json,
|
@@ -71,6 +73,47 @@ impl Default for ContentType {
|
71 | 73 | }
|
72 | 74 | }
|
73 | 75 |
|
| 76 | +impl fmt::Display for ContentType { |
| 77 | + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 78 | + f.write_str(self.as_str()) |
| 79 | + } |
| 80 | +} |
| 81 | + |
| 82 | +#[derive(Debug, thiserror::Error)] |
| 83 | +pub enum FromStrError { |
| 84 | + #[error("invalid content type")] |
| 85 | + InvalidContentType, |
| 86 | + #[error(transparent)] |
| 87 | + Mime(#[from] mime::FromStrError), |
| 88 | +} |
| 89 | + |
| 90 | +impl FromStr for ContentType { |
| 91 | + type Err = FromStrError; |
| 92 | + |
| 93 | + fn from_str(s: &str) -> Result<Self, Self::Err> { |
| 94 | + let mime = s.parse::<mime::Mime>()?; |
| 95 | + let subtype = mime.suffix().unwrap_or_else(|| mime.subtype()); |
| 96 | + |
| 97 | + Ok(match (mime.type_().as_str(), subtype.as_str()) { |
| 98 | + #[cfg(feature = "json")] |
| 99 | + ("application", "json") => Self::Json, |
| 100 | + #[cfg(feature = "msgpack")] |
| 101 | + ("application", "msgpack" | "vnd.msgpack" | "x-msgpack" | "x.msgpack") => Self::MsgPack, |
| 102 | + #[cfg(feature = "bincode")] |
| 103 | + ("application", "bincode" | "vnd.bincode" | "x-bincode" | "x.bincode") => Self::Bincode, |
| 104 | + #[cfg(feature = "bitcode")] |
| 105 | + ("application", "bitcode" | "vnd.bitcode" | "x-bitcode" | "x.bitcode") => Self::Bitcode, |
| 106 | + #[cfg(feature = "cbor")] |
| 107 | + ("application", "cbor") => Self::Cbor, |
| 108 | + #[cfg(feature = "yaml")] |
| 109 | + ("application" | "text", "yaml" | "yml" | "x-yaml") => Self::Yaml, |
| 110 | + #[cfg(feature = "toml")] |
| 111 | + ("application" | "text", "toml" | "x-toml" | "vnd.toml") => Self::Toml, |
| 112 | + _ => return Err(FromStrError::InvalidContentType), |
| 113 | + }) |
| 114 | + } |
| 115 | +} |
| 116 | + |
74 | 117 | impl ContentType {
|
75 | 118 | /// Attempts to parse the given [`HeaderValue`] into a [`ContentType`]
|
76 | 119 | /// by treating it as a MIME type.
|
@@ -99,26 +142,36 @@ impl ContentType {
|
99 | 142 | /// assert_eq!(content_type, ContentType::MsgPack);
|
100 | 143 | /// # }
|
101 | 144 | pub fn from_header(header: &HeaderValue) -> Option<Self> {
|
102 |
| - let mime = header.to_str().ok()?.parse::<mime::Mime>().ok()?; |
103 |
| - let subtype = mime.suffix().map_or_else(|| mime.subtype(), |name| name); |
| 145 | + header.to_str().ok()?.parse().ok() |
| 146 | + } |
104 | 147 |
|
105 |
| - Some(match (mime.type_().as_str(), subtype.as_str()) { |
| 148 | + /// Returns the MIME type as a string slice. |
| 149 | + /// |
| 150 | + /// ```edition2021 |
| 151 | + /// # use axum_codec::ContentType; |
| 152 | + /// # |
| 153 | + /// let content_type = ContentType::Json; |
| 154 | + /// |
| 155 | + /// assert_eq!(content_type.as_str(), "application/json"); |
| 156 | + /// ``` |
| 157 | + #[must_use] |
| 158 | + pub fn as_str(&self) -> &'static str { |
| 159 | + match self { |
106 | 160 | #[cfg(feature = "json")]
|
107 |
| - ("application", "json") => Self::Json, |
| 161 | + Self::Json => "application/json", |
108 | 162 | #[cfg(feature = "msgpack")]
|
109 |
| - ("application", "msgpack" | "vnd.msgpack" | "x-msgpack" | "x.msgpack") => Self::MsgPack, |
| 163 | + Self::MsgPack => "application/vnd.msgpack", |
110 | 164 | #[cfg(feature = "bincode")]
|
111 |
| - ("application", "bincode" | "vnd.bincode" | "x-bincode" | "x.bincode") => Self::Bincode, |
| 165 | + Self::Bincode => "application/vnd.bincode", |
112 | 166 | #[cfg(feature = "bitcode")]
|
113 |
| - ("application", "bitcode" | "vnd.bitcode" | "x-bitcode" | "x.bitcode") => Self::Bitcode, |
| 167 | + Self::Bitcode => "application/vnd.bitcode", |
114 | 168 | #[cfg(feature = "cbor")]
|
115 |
| - ("application", "cbor") => Self::Cbor, |
| 169 | + Self::Cbor => "application/cbor", |
116 | 170 | #[cfg(feature = "yaml")]
|
117 |
| - ("application" | "text", "yaml" | "yml" | "x-yaml") => Self::Yaml, |
| 171 | + Self::Yaml => "application/x-yaml", |
118 | 172 | #[cfg(feature = "toml")]
|
119 |
| - ("application" | "text", "toml" | "x-toml" | "vnd.toml") => Self::Toml, |
120 |
| - _ => return None, |
121 |
| - }) |
| 173 | + Self::Toml => "text/toml", |
| 174 | + } |
122 | 175 | }
|
123 | 176 |
|
124 | 177 | /// Converts the [`ContentType`] into a [`HeaderValue`].
|
@@ -150,24 +203,7 @@ impl ContentType {
|
150 | 203 | /// # }
|
151 | 204 | #[must_use]
|
152 | 205 | pub fn into_header(self) -> HeaderValue {
|
153 |
| - let text = match self { |
154 |
| - #[cfg(feature = "json")] |
155 |
| - Self::Json => "application/json", |
156 |
| - #[cfg(feature = "msgpack")] |
157 |
| - Self::MsgPack => "application/vnd.msgpack", |
158 |
| - #[cfg(feature = "bincode")] |
159 |
| - Self::Bincode => "application/vnd.bincode", |
160 |
| - #[cfg(feature = "bitcode")] |
161 |
| - Self::Bitcode => "application/vnd.bitcode", |
162 |
| - #[cfg(feature = "cbor")] |
163 |
| - Self::Cbor => "application/cbor", |
164 |
| - #[cfg(feature = "yaml")] |
165 |
| - Self::Yaml => "application/x-yaml", |
166 |
| - #[cfg(feature = "toml")] |
167 |
| - Self::Toml => "text/toml", |
168 |
| - }; |
169 |
| - |
170 |
| - HeaderValue::from_static(text) |
| 206 | + HeaderValue::from_static(self.as_str()) |
171 | 207 | }
|
172 | 208 | }
|
173 | 209 |
|
|
0 commit comments