Skip to content

Commit de7afbb

Browse files
authored
Merge pull request #256 from pepoviola/header-content-location
Add `content::ContentLocation` header
2 parents a6cbf95 + 85a5089 commit de7afbb

File tree

2 files changed

+125
-0
lines changed

2 files changed

+125
-0
lines changed

src/content/content_location.rs

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
use crate::headers::{HeaderName, HeaderValue, Headers, CONTENT_LOCATION};
2+
use crate::{bail_status as bail, Status, Url};
3+
4+
use std::convert::TryInto;
5+
6+
/// Indicates an alternate location for the returned data.
7+
///
8+
/// # Specifications
9+
///
10+
/// - [RFC 7231, section 3.1.4.2: Content-Location](https://tools.ietf.org/html/rfc7231#section-3.1.4.2)
11+
///
12+
/// # Examples
13+
///
14+
/// ```
15+
/// # fn main() -> http_types::Result<()> {
16+
/// #
17+
/// use http_types::{Response,Url};
18+
/// use http_types::content::{ContentLocation};
19+
///
20+
/// let content_location = ContentLocation::new(Url::parse("https://example.net/")?);
21+
///
22+
/// let mut res = Response::new(200);
23+
/// content_location.apply(&mut res);
24+
///
25+
/// let url = Url::parse("https://example.net/")?;
26+
/// let content_location = ContentLocation::from_headers(url, res)?.unwrap();
27+
/// assert_eq!(content_location.location(), "https://example.net/");
28+
/// #
29+
/// # Ok(()) }
30+
/// ```
31+
#[derive(Debug)]
32+
pub struct ContentLocation {
33+
url: Url,
34+
}
35+
36+
impl ContentLocation {
37+
/// Create a new instance of `Content-Location` header.
38+
pub fn new(url: Url) -> Self {
39+
Self { url }
40+
}
41+
42+
/// Create a new instance from headers.
43+
pub fn from_headers<U>(base_url: U, headers: impl AsRef<Headers>) -> crate::Result<Option<Self>>
44+
where
45+
U: TryInto<Url>,
46+
U::Error: std::fmt::Debug,
47+
{
48+
let headers = match headers.as_ref().get(CONTENT_LOCATION) {
49+
Some(headers) => headers,
50+
None => return Ok(None),
51+
};
52+
53+
// If we successfully parsed the header then there's always at least one
54+
// entry. We want the last entry.
55+
let value = headers.iter().last().unwrap();
56+
let base = match base_url.try_into() {
57+
Ok(b) => b,
58+
Err(_) => bail!(400, "Invalid base url provided"),
59+
};
60+
61+
let url = base.join(value.as_str().trim()).status(400)?;
62+
Ok(Some(Self { url }))
63+
}
64+
65+
/// Sets the header.
66+
pub fn apply(&self, mut headers: impl AsMut<Headers>) {
67+
headers.as_mut().insert(self.name(), self.value());
68+
}
69+
70+
/// Get the `HeaderName`.
71+
pub fn name(&self) -> HeaderName {
72+
CONTENT_LOCATION
73+
}
74+
75+
/// Get the `HeaderValue`.
76+
pub fn value(&self) -> HeaderValue {
77+
let output = self.url.to_string();
78+
79+
// SAFETY: the internal string is validated to be ASCII.
80+
unsafe { HeaderValue::from_bytes_unchecked(output.into()) }
81+
}
82+
83+
/// Get the url.
84+
pub fn location(&self) -> String {
85+
self.url.to_string()
86+
}
87+
88+
/// Set the url.
89+
pub fn set_location(&mut self, location: Url) {
90+
self.url = location
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 content_location = ContentLocation::new(Url::parse("https://example.net/test.json")?);
102+
103+
let mut headers = Headers::new();
104+
content_location.apply(&mut headers);
105+
106+
let content_location =
107+
ContentLocation::from_headers(Url::parse("https://example.net/").unwrap(), headers)?
108+
.unwrap();
109+
assert_eq!(content_location.location(), "https://example.net/test.json");
110+
Ok(())
111+
}
112+
113+
#[test]
114+
fn bad_request_on_parse_error() -> crate::Result<()> {
115+
let mut headers = Headers::new();
116+
headers.insert(CONTENT_LOCATION, "htt://<nori ate the tag. yum.>");
117+
let err =
118+
ContentLocation::from_headers(Url::parse("https://example.net").unwrap(), headers)
119+
.unwrap_err();
120+
assert_eq!(err.status(), 400);
121+
Ok(())
122+
}
123+
}

src/content/mod.rs

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

1111
mod content_length;
12+
mod content_location;
1213
mod encoding;
1314
mod encoding_proposal;
1415

@@ -17,5 +18,6 @@ pub use accept_encoding::AcceptEncoding;
1718
#[doc(inline)]
1819
pub use content_encoding::ContentEncoding;
1920
pub use content_length::ContentLength;
21+
pub use content_location::ContentLocation;
2022
pub use encoding::Encoding;
2123
pub use encoding_proposal::EncodingProposal;

0 commit comments

Comments
 (0)