rocket_http/header/content_type.rs
1use std::borrow::Cow;
2use std::ops::Deref;
3use std::str::FromStr;
4use std::fmt;
5
6use crate::header::{Header, MediaType};
7use crate::uncased::UncasedStr;
8use crate::ext::IntoCollection;
9
10/// Representation of HTTP Content-Types.
11///
12/// # Usage
13///
14/// `ContentType`s should rarely be created directly. Instead, an associated
15/// constant should be used; one is declared for most commonly used content
16/// types.
17///
18/// ## Example
19///
20/// A Content-Type of `text/html; charset=utf-8` can be instantiated via the
21/// `HTML` constant:
22///
23/// ```rust
24/// # extern crate rocket;
25/// use rocket::http::ContentType;
26///
27/// # #[allow(unused_variables)]
28/// let html = ContentType::HTML;
29/// ```
30///
31/// # Header
32///
33/// `ContentType` implements `Into<Header>`. As such, it can be used in any
34/// context where an `Into<Header>` is expected:
35///
36/// ```rust
37/// # extern crate rocket;
38/// use rocket::http::ContentType;
39/// use rocket::response::Response;
40///
41/// let response = Response::build().header(ContentType::HTML).finalize();
42/// ```
43#[derive(Debug, Clone, PartialEq, Eq, Hash)]
44pub struct ContentType(pub MediaType);
45
46macro_rules! content_types {
47 (
48 $(
49 $name:ident ($check:ident): $str:expr,
50 $t:expr, $s:expr $(; $k:expr => $v:expr)*,
51 )+
52 ) => {
53 $(
54
55 /// Content Type for
56 #[doc = concat!("**", $str, "**: ")]
57 #[doc = concat!("`", $t, "/", $s, $("; ", $k, "=", $v,)* "`")]
58
59 #[allow(non_upper_case_globals)]
60 pub const $name: ContentType = ContentType(MediaType::$name);
61 )+
62}}
63
64macro_rules! from_extension {
65 ($($ext:expr => $name:ident,)*) => (
66 /// Returns the Content-Type associated with the extension `ext`.
67 ///
68 /// Extensions are matched case-insensitively. Not all extensions are
69 /// recognized. If an extensions is not recognized, `None` is returned.
70 /// The currently recognized extensions are:
71 ///
72 $(
73 #[doc = concat!("* ", $ext, " - [`ContentType::", stringify!($name), "`]")]
74 )*
75 ///
76 /// This list is likely to grow.
77 ///
78 /// # Example
79 ///
80 /// Recognized content types:
81 ///
82 /// ```rust
83 /// # extern crate rocket;
84 /// use rocket::http::ContentType;
85 ///
86 /// let xml = ContentType::from_extension("xml");
87 /// assert_eq!(xml, Some(ContentType::XML));
88 ///
89 /// let xml = ContentType::from_extension("XML");
90 /// assert_eq!(xml, Some(ContentType::XML));
91 /// ```
92 ///
93 /// An unrecognized content type:
94 ///
95 /// ```rust
96 /// # extern crate rocket;
97 /// use rocket::http::ContentType;
98 ///
99 /// let foo = ContentType::from_extension("foo");
100 /// assert!(foo.is_none());
101 /// ```
102 #[inline]
103 pub fn from_extension(ext: &str) -> Option<ContentType> {
104 MediaType::from_extension(ext).map(ContentType)
105 }
106 )
107}
108
109macro_rules! extension {
110 ($($ext:expr => $name:ident,)*) => (
111 /// Returns the most common file extension associated with the
112 /// Content-Type `self` if it is known. Otherwise, returns `None`.
113 ///
114 /// The currently recognized extensions are identical to those in
115 /// [`ContentType::from_extension()`] with the most common extension
116 /// being the first extension appearing in the list for a given
117 /// Content-Type.
118 ///
119 /// # Example
120 ///
121 /// Known extension:
122 ///
123 /// ```rust
124 /// # extern crate rocket;
125 /// use rocket::http::ContentType;
126 ///
127 /// assert_eq!(ContentType::JSON.extension().unwrap(), "json");
128 /// assert_eq!(ContentType::JPEG.extension().unwrap(), "jpeg");
129 /// assert_eq!(ContentType::JPEG.extension().unwrap(), "JPEG");
130 /// assert_eq!(ContentType::PDF.extension().unwrap(), "pdf");
131 /// ```
132 ///
133 /// An unknown extension:
134 ///
135 /// ```rust
136 /// # extern crate rocket;
137 /// use rocket::http::ContentType;
138 ///
139 /// let foo = ContentType::new("foo", "bar");
140 /// assert!(foo.extension().is_none());
141 /// ```
142 #[inline]
143 pub fn extension(&self) -> Option<&UncasedStr> {
144 $(if self == &ContentType::$name { return Some($ext.into()) })*
145 None
146 }
147 )
148}
149
150macro_rules! parse_flexible {
151 ($($short:expr => $name:ident,)*) => (
152 /// Flexibly parses `name` into a [`ContentType`]. The parse is
153 /// _flexible_ because, in addition to strictly correct content types,
154 /// it recognizes the following shorthands:
155 ///
156 $(
157 #[doc = concat!("* ", $short, " - [`ContentType::", stringify!($name), "`]")]
158 )*
159 ///
160 /// For regular parsing, use [`ContentType::from_str()`].
161 ///
162 /// # Example
163 ///
164 /// Using a shorthand:
165 ///
166 /// ```rust
167 /// # extern crate rocket;
168 /// use rocket::http::ContentType;
169 ///
170 /// let html = ContentType::parse_flexible("html");
171 /// assert_eq!(html, Some(ContentType::HTML));
172 ///
173 /// let json = ContentType::parse_flexible("json");
174 /// assert_eq!(json, Some(ContentType::JSON));
175 /// ```
176 ///
177 /// Using the full content-type:
178 ///
179 /// ```rust
180 /// # extern crate rocket;
181 /// use rocket::http::ContentType;
182 ///
183 /// let html = ContentType::parse_flexible("text/html; charset=utf-8");
184 /// assert_eq!(html, Some(ContentType::HTML));
185 ///
186 /// let json = ContentType::parse_flexible("application/json");
187 /// assert_eq!(json, Some(ContentType::JSON));
188 ///
189 /// let custom = ContentType::parse_flexible("application/x+custom");
190 /// assert_eq!(custom, Some(ContentType::new("application", "x+custom")));
191 /// ```
192 ///
193 /// An unrecognized content-type:
194 ///
195 /// ```rust
196 /// # extern crate rocket;
197 /// use rocket::http::ContentType;
198 ///
199 /// let foo = ContentType::parse_flexible("foo");
200 /// assert_eq!(foo, None);
201 ///
202 /// let bar = ContentType::parse_flexible("foo/bar/baz");
203 /// assert_eq!(bar, None);
204 /// ```
205 #[inline]
206 pub fn parse_flexible(name: &str) -> Option<ContentType> {
207 MediaType::parse_flexible(name).map(ContentType)
208 }
209 )
210}
211
212impl ContentType {
213 /// Creates a new `ContentType` with top-level type `top` and subtype `sub`.
214 /// This should _only_ be used to construct uncommon or custom content
215 /// types. Use an associated constant for everything else.
216 ///
217 /// # Example
218 ///
219 /// Create a custom `application/x-person` content type:
220 ///
221 /// ```rust
222 /// # extern crate rocket;
223 /// use rocket::http::ContentType;
224 ///
225 /// let custom = ContentType::new("application", "x-person");
226 /// assert_eq!(custom.top(), "application");
227 /// assert_eq!(custom.sub(), "x-person");
228 /// ```
229 #[inline(always)]
230 pub fn new<T, S>(top: T, sub: S) -> ContentType
231 where T: Into<Cow<'static, str>>, S: Into<Cow<'static, str>>
232 {
233 ContentType(MediaType::new(top, sub))
234 }
235
236 known_shorthands!(parse_flexible);
237
238 known_extensions!(from_extension);
239
240 /// Sets the parameters `parameters` on `self`.
241 ///
242 /// # Example
243 ///
244 /// Create a custom `application/x-id; id=1` media type:
245 ///
246 /// ```rust
247 /// # extern crate rocket;
248 /// use rocket::http::ContentType;
249 ///
250 /// let id = ContentType::new("application", "x-id").with_params(("id", "1"));
251 /// assert_eq!(id.to_string(), "application/x-id; id=1".to_string());
252 /// ```
253 ///
254 /// Create a custom `text/person; name=bob; weight=175` media type:
255 ///
256 /// ```rust
257 /// # extern crate rocket;
258 /// use rocket::http::ContentType;
259 ///
260 /// let mt = ContentType::new("text", "person")
261 /// .with_params([("name", "bob"), ("ref", "2382")]);
262 ///
263 /// assert_eq!(mt.to_string(), "text/person; name=bob; ref=2382".to_string());
264 /// ```
265 pub fn with_params<K, V, P>(self, parameters: P) -> ContentType
266 where K: Into<Cow<'static, str>>,
267 V: Into<Cow<'static, str>>,
268 P: IntoCollection<(K, V)>
269 {
270 ContentType(self.0.with_params(parameters))
271 }
272
273 /// Borrows the inner `MediaType` of `self`.
274 ///
275 /// # Example
276 ///
277 /// ```rust
278 /// # extern crate rocket;
279 /// use rocket::http::{ContentType, MediaType};
280 ///
281 /// let http = ContentType::HTML;
282 /// let media_type = http.media_type();
283 /// ```
284 #[inline(always)]
285 pub fn media_type(&self) -> &MediaType {
286 &self.0
287 }
288
289 known_extensions!(extension);
290
291 known_media_types!(content_types);
292}
293
294impl Default for ContentType {
295 /// Returns a ContentType of `Any`, or `*/*`.
296 #[inline(always)]
297 fn default() -> ContentType {
298 ContentType::Any
299 }
300}
301
302impl Deref for ContentType {
303 type Target = MediaType;
304
305 #[inline(always)]
306 fn deref(&self) -> &MediaType {
307 &self.0
308 }
309}
310
311impl FromStr for ContentType {
312 type Err = String;
313
314 /// Parses a `ContentType` from a given Content-Type header value.
315 ///
316 /// # Examples
317 ///
318 /// Parsing an `application/json`:
319 ///
320 /// ```rust
321 /// # extern crate rocket;
322 /// use std::str::FromStr;
323 /// use rocket::http::ContentType;
324 ///
325 /// let json = ContentType::from_str("application/json").unwrap();
326 /// assert!(json.is_known());
327 /// assert_eq!(json, ContentType::JSON);
328 /// ```
329 ///
330 /// Parsing a content type extension:
331 ///
332 /// ```rust
333 /// # extern crate rocket;
334 /// use std::str::FromStr;
335 /// use rocket::http::ContentType;
336 ///
337 /// let custom = ContentType::from_str("application/x-custom").unwrap();
338 /// assert!(!custom.is_known());
339 /// assert_eq!(custom.top(), "application");
340 /// assert_eq!(custom.sub(), "x-custom");
341 /// ```
342 ///
343 /// Parsing an invalid Content-Type value:
344 ///
345 /// ```rust
346 /// # extern crate rocket;
347 /// use std::str::FromStr;
348 /// use rocket::http::ContentType;
349 ///
350 /// let custom = ContentType::from_str("application//x-custom");
351 /// assert!(custom.is_err());
352 /// ```
353 #[inline(always)]
354 fn from_str(raw: &str) -> Result<ContentType, String> {
355 MediaType::from_str(raw).map(ContentType)
356 }
357}
358
359impl From<MediaType> for ContentType {
360 fn from(media_type: MediaType) -> Self {
361 ContentType(media_type)
362 }
363}
364
365impl fmt::Display for ContentType {
366 /// Formats the ContentType as an HTTP Content-Type value.
367 ///
368 /// # Example
369 ///
370 /// ```rust
371 /// # extern crate rocket;
372 /// use rocket::http::ContentType;
373 ///
374 /// let ct = format!("{}", ContentType::JSON);
375 /// assert_eq!(ct, "application/json");
376 /// ```
377 #[inline(always)]
378 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
379 write!(f, "{}", self.0)
380 }
381}
382
383/// Creates a new `Header` with name `Content-Type` and the value set to the
384/// HTTP rendering of this Content-Type.
385impl From<ContentType> for Header<'static> {
386 #[inline(always)]
387 fn from(content_type: ContentType) -> Self {
388 if let Some(src) = content_type.known_source() {
389 Header::new("Content-Type", src)
390 } else {
391 Header::new("Content-Type", content_type.to_string())
392 }
393 }
394}