Skip to content

Commit aa54f4b

Browse files
committed
initial work to review
1 parent 82e3d5e commit aa54f4b

File tree

2 files changed

+109
-0
lines changed

2 files changed

+109
-0
lines changed

src/content/content_location.rs

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
use crate::headers::{HeaderName, HeaderValue, Headers, CONTENT_LOCATION};
2+
use crate::{Status, Url};
3+
4+
///
5+
/// # Specifications
6+
///
7+
/// - [RFC 7230, section 3.3.2: Content-Length](https://tools.ietf.org/html/rfc7230#section-3.3.2)
8+
///
9+
/// # Examples
10+
///
11+
/// ```
12+
/// # fn main() -> http_types::Result<()> {
13+
/// #
14+
/// use http_types::Response;
15+
/// use http_types::content::{ContentLocation};
16+
///
17+
/// let content_location = ContentLocation::new("https://example.net/".to_string());
18+
///
19+
/// let mut res = Response::new(200);
20+
/// content_location.apply(&mut res);
21+
///
22+
/// let content_location = ContentLocation::from_headers(res)?.unwrap();
23+
/// assert_eq!(content_location.location(), "https://example.net/");
24+
/// #
25+
/// # Ok(()) }
26+
/// ```
27+
#[derive(Debug)]
28+
pub struct ContentLocation {
29+
url: String,
30+
}
31+
32+
#[allow(clippy::len_without_is_empty)]
33+
impl ContentLocation {
34+
/// Create a new instance of `Content-Location` header.
35+
pub fn new(url: String) -> Self {
36+
Self { url }
37+
}
38+
39+
/// Create a new instance from headers.
40+
pub fn from_headers(headers: impl AsRef<Headers>) -> crate::Result<Option<Self>> {
41+
let headers = match headers.as_ref().get(CONTENT_LOCATION) {
42+
Some(headers) => headers,
43+
None => return Ok(None),
44+
};
45+
46+
// If we successfully parsed the header then there's always at least one
47+
// entry. We want the last entry.
48+
let value = headers.iter().last().unwrap();
49+
let url = Url::parse(value.as_str().trim()).status(400)?;
50+
Ok(Some(Self { url : url.into_string() }))
51+
}
52+
53+
/// Sets the header.
54+
pub fn apply(&self, mut headers: impl AsMut<Headers>) {
55+
headers.as_mut().insert(self.name(), self.value());
56+
}
57+
58+
/// Get the `HeaderName`.
59+
pub fn name(&self) -> HeaderName {
60+
CONTENT_LOCATION
61+
}
62+
63+
/// Get the `HeaderValue`.
64+
pub fn value(&self) -> HeaderValue {
65+
let output = format!("{}", self.url);
66+
67+
// SAFETY: the internal string is validated to be ASCII.
68+
unsafe { HeaderValue::from_bytes_unchecked(output.into()) }
69+
}
70+
71+
/// Get the url.
72+
pub fn location(&self) -> String {
73+
String::from(&self.url)
74+
}
75+
76+
/// Set the url.
77+
pub fn set_location(&mut self, location: &str) {
78+
self.url = location.to_string();
79+
}
80+
}
81+
82+
#[cfg(test)]
83+
mod test {
84+
use super::*;
85+
use crate::headers::Headers;
86+
87+
#[test]
88+
fn smoke() -> crate::Result<()> {
89+
let content_location = ContentLocation::new("https://example.net/test".to_string());
90+
91+
let mut headers = Headers::new();
92+
content_location.apply(&mut headers);
93+
94+
let content_location = ContentLocation::from_headers(headers)?.unwrap();
95+
assert_eq!(content_location.location(), "https://example.net/test");
96+
Ok(())
97+
}
98+
99+
#[test]
100+
fn bad_request_on_parse_error() -> crate::Result<()> {
101+
let mut headers = Headers::new();
102+
headers.insert(CONTENT_LOCATION, "<nori ate the tag. yum.>");
103+
let err = ContentLocation::from_headers(headers).unwrap_err();
104+
assert_eq!(err.status(), 400);
105+
Ok(())
106+
}
107+
}

src/content/mod.rs

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

1111
mod encoding;
1212
mod encoding_proposal;
13+
mod content_location;
1314

1415
#[doc(inline)]
1516
pub use accept_encoding::AcceptEncoding;
1617
#[doc(inline)]
1718
pub use content_encoding::ContentEncoding;
1819
pub use encoding::Encoding;
1920
pub use encoding_proposal::EncodingProposal;
21+
pub use content_location::ContentLocation;

0 commit comments

Comments
 (0)