headers/common/
content_type.rs

1use std::fmt;
2
3use http::{HeaderName, HeaderValue};
4use mime::Mime;
5
6use crate::{Error, Header};
7
8/// `Content-Type` header, defined in
9/// [RFC7231](https://datatracker.ietf.org/doc/html/rfc7231#section-3.1.1.5)
10///
11/// The `Content-Type` header field indicates the media type of the
12/// associated representation: either the representation enclosed in the
13/// message payload or the selected representation, as determined by the
14/// message semantics.  The indicated media type defines both the data
15/// format and how that data is intended to be processed by a recipient,
16/// within the scope of the received message semantics, after any content
17/// codings indicated by Content-Encoding are decoded.
18///
19/// Although the `mime` crate allows the mime options to be any slice, this crate
20/// forces the use of Vec. This is to make sure the same header can't have more than 1 type. If
21/// this is an issue, it's possible to implement `Header` on a custom struct.
22///
23/// # ABNF
24///
25/// ```text
26/// Content-Type = media-type
27/// ```
28///
29/// # Example values
30///
31/// * `text/html; charset=utf-8`
32/// * `application/json`
33///
34/// # Examples
35///
36/// ```
37/// use headers::ContentType;
38///
39/// let ct = ContentType::json();
40/// ```
41#[derive(Clone, Debug, PartialEq)]
42pub struct ContentType(Mime);
43
44impl ContentType {
45    /// A constructor  to easily create a `Content-Type: application/json` header.
46    #[inline]
47    pub fn json() -> ContentType {
48        ContentType(mime::APPLICATION_JSON)
49    }
50
51    /// A constructor  to easily create a `Content-Type: text/plain` header.
52    #[inline]
53    pub fn text() -> ContentType {
54        ContentType(mime::TEXT_PLAIN)
55    }
56
57    /// A constructor  to easily create a `Content-Type: text/plain; charset=utf-8` header.
58    #[inline]
59    pub fn text_utf8() -> ContentType {
60        ContentType(mime::TEXT_PLAIN_UTF_8)
61    }
62
63    /// A constructor  to easily create a `Content-Type: text/html` header.
64    #[inline]
65    pub fn html() -> ContentType {
66        ContentType(mime::TEXT_HTML)
67    }
68
69    /// A constructor  to easily create a `Content-Type: text/xml` header.
70    #[inline]
71    pub fn xml() -> ContentType {
72        ContentType(mime::TEXT_XML)
73    }
74
75    /// A constructor  to easily create a `Content-Type: application/www-form-url-encoded` header.
76    #[inline]
77    pub fn form_url_encoded() -> ContentType {
78        ContentType(mime::APPLICATION_WWW_FORM_URLENCODED)
79    }
80    /// A constructor  to easily create a `Content-Type: image/jpeg` header.
81    #[inline]
82    pub fn jpeg() -> ContentType {
83        ContentType(mime::IMAGE_JPEG)
84    }
85
86    /// A constructor  to easily create a `Content-Type: image/png` header.
87    #[inline]
88    pub fn png() -> ContentType {
89        ContentType(mime::IMAGE_PNG)
90    }
91
92    /// A constructor  to easily create a `Content-Type: application/octet-stream` header.
93    #[inline]
94    pub fn octet_stream() -> ContentType {
95        ContentType(mime::APPLICATION_OCTET_STREAM)
96    }
97}
98
99impl Header for ContentType {
100    fn name() -> &'static HeaderName {
101        &::http::header::CONTENT_TYPE
102    }
103
104    fn decode<'i, I: Iterator<Item = &'i HeaderValue>>(values: &mut I) -> Result<Self, Error> {
105        values
106            .next()
107            .and_then(|v| v.to_str().ok()?.parse().ok())
108            .map(ContentType)
109            .ok_or_else(Error::invalid)
110    }
111
112    fn encode<E: Extend<HeaderValue>>(&self, values: &mut E) {
113        let value = self
114            .0
115            .as_ref()
116            .parse()
117            .expect("Mime is always a valid HeaderValue");
118        values.extend(::std::iter::once(value));
119    }
120}
121
122impl From<mime::Mime> for ContentType {
123    fn from(m: mime::Mime) -> ContentType {
124        ContentType(m)
125    }
126}
127
128impl From<ContentType> for mime::Mime {
129    fn from(ct: ContentType) -> mime::Mime {
130        ct.0
131    }
132}
133
134impl fmt::Display for ContentType {
135    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
136        fmt::Display::fmt(&self.0, f)
137    }
138}
139
140impl std::str::FromStr for ContentType {
141    type Err = Error;
142
143    fn from_str(s: &str) -> Result<ContentType, Self::Err> {
144        s.parse::<Mime>()
145            .map(|m| m.into())
146            .map_err(|_| Error::invalid())
147    }
148}
149
150#[cfg(test)]
151mod tests {
152    use super::super::test_decode;
153    use super::ContentType;
154
155    #[test]
156    fn json() {
157        assert_eq!(
158            test_decode::<ContentType>(&["application/json"]),
159            Some(ContentType::json()),
160        );
161    }
162
163    #[test]
164    fn from_str() {
165        assert_eq!(
166            "application/json".parse::<ContentType>().unwrap(),
167            ContentType::json(),
168        );
169        assert!("invalid-mimetype".parse::<ContentType>().is_err());
170    }
171
172    bench_header!(bench_plain, ContentType, "text/plain");
173    bench_header!(bench_json, ContentType, "application/json");
174    bench_header!(
175        bench_formdata,
176        ContentType,
177        "multipart/form-data; boundary=---------------abcd"
178    );
179}