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}