Skip to content

Commit eaddb53

Browse files
committed
Add content::ContentType header
1 parent de7afbb commit eaddb53

File tree

2 files changed

+124
-0
lines changed

2 files changed

+124
-0
lines changed

src/content/content_type.rs

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
use std::{convert::TryInto, str::FromStr};
2+
3+
use crate::headers::{HeaderName, HeaderValue, Headers, CONTENT_TYPE};
4+
use crate::Mime;
5+
6+
/// Indicate the media type of the resource.
7+
///
8+
/// [MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type)
9+
///
10+
/// # Specifications
11+
///
12+
/// - [RFC 7231, section 3.1.1.5: Content-Type](https://tools.ietf.org/html/rfc7231#section-3.1.1.5)
13+
/// - [RFC 7233, section 4.1: Content-Type in multipart](https://tools.ietf.org/html/rfc7233#section-4.1)
14+
///
15+
/// # Examples
16+
///
17+
/// ```
18+
/// # fn main() -> http_types::Result<()> {
19+
/// #
20+
/// use http_types::content::ContentType;
21+
/// use http_types::{Response, Mime};
22+
/// use std::str::FromStr;
23+
///
24+
/// let ct = ContentType::new(Mime::from_str("text/*")?);
25+
///
26+
/// let mut res = Response::new(200);
27+
/// ct.apply(&mut res);
28+
///
29+
/// let ct = ContentType::from_headers(res)?.unwrap();
30+
/// assert_eq!(ct.value(), format!("{}", Mime::from_str("text/*")?).as_str());
31+
/// #
32+
/// # Ok(()) }
33+
/// ```
34+
#[derive(Debug)]
35+
pub struct ContentType {
36+
media_type: Mime,
37+
}
38+
39+
impl ContentType {
40+
/// Create a new instance.
41+
pub fn new<U>(media_type: U) -> Self
42+
where
43+
U: TryInto<Mime>,
44+
U::Error: std::fmt::Debug,
45+
{
46+
Self {
47+
media_type: media_type
48+
.try_into()
49+
.expect("could not convert into a valid Mime type"),
50+
}
51+
}
52+
53+
/// Create a new instance from headers.
54+
///
55+
/// `Content-Type` headers can provide both full and partial URLs. In
56+
/// order to always return fully qualified URLs, a base URL must be passed to
57+
/// reference the current environment. In HTTP/1.1 and above this value can
58+
/// always be determined from the request.
59+
pub fn from_headers(headers: impl AsRef<Headers>) -> crate::Result<Option<Self>> {
60+
let headers = match headers.as_ref().get(CONTENT_TYPE) {
61+
Some(headers) => headers,
62+
None => return Ok(None),
63+
};
64+
65+
// If we successfully parsed the header then there's always at least one
66+
// entry. We want the last entry.
67+
let ctation = headers.iter().last().unwrap();
68+
69+
let media_type = Mime::from_str(ctation.as_str()).map_err(|mut e| {
70+
e.set_status(400);
71+
e
72+
})?;
73+
Ok(Some(Self { media_type }))
74+
}
75+
76+
/// Sets the header.
77+
pub fn apply(&self, mut headers: impl AsMut<Headers>) {
78+
headers.as_mut().insert(self.name(), self.value());
79+
}
80+
81+
/// Get the `HeaderName`.
82+
pub fn name(&self) -> HeaderName {
83+
CONTENT_TYPE
84+
}
85+
86+
/// Get the `HeaderValue`.
87+
pub fn value(&self) -> HeaderValue {
88+
let output = format!("{}", self.media_type);
89+
// SAFETY: the internal string is validated to be ASCII.
90+
unsafe { HeaderValue::from_bytes_unchecked(output.into()) }
91+
}
92+
}
93+
94+
#[cfg(test)]
95+
mod test {
96+
use super::*;
97+
use crate::headers::Headers;
98+
99+
#[test]
100+
fn smoke() -> crate::Result<()> {
101+
let ct = ContentType::new(Mime::from_str("text/*")?);
102+
103+
let mut headers = Headers::new();
104+
ct.apply(&mut headers);
105+
106+
let ct = ContentType::from_headers(headers)?.unwrap();
107+
assert_eq!(
108+
ct.value(),
109+
format!("{}", Mime::from_str("text/*")?).as_str()
110+
);
111+
Ok(())
112+
}
113+
114+
#[test]
115+
fn bad_request_on_parse_error() -> crate::Result<()> {
116+
let mut headers = Headers::new();
117+
headers.insert(CONTENT_TYPE, "<nori ate the tag. yum.>");
118+
let err = ContentType::from_headers(headers).unwrap_err();
119+
assert_eq!(err.status(), 400);
120+
Ok(())
121+
}
122+
}

src/content/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ pub mod content_encoding;
1010

1111
mod content_length;
1212
mod content_location;
13+
mod content_type;
1314
mod encoding;
1415
mod encoding_proposal;
1516

@@ -19,5 +20,6 @@ pub use accept_encoding::AcceptEncoding;
1920
pub use content_encoding::ContentEncoding;
2021
pub use content_length::ContentLength;
2122
pub use content_location::ContentLocation;
23+
pub use content_type::ContentType;
2224
pub use encoding::Encoding;
2325
pub use encoding_proposal::EncodingProposal;

0 commit comments

Comments
 (0)