Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add content::ContentLocation header #256

Merged
merged 14 commits into from
Nov 3, 2020
123 changes: 123 additions & 0 deletions src/content/content_location.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
use crate::headers::{HeaderName, HeaderValue, Headers, CONTENT_LOCATION};
use crate::{Status, Url};

use std::convert::TryInto;

/// Indicates an alternate location for the returned data.
///
Fishrock123 marked this conversation as resolved.
Show resolved Hide resolved
/// # Specifications
///
/// - [RFC 7231, section 3.1.4.2: Content-Location](https://tools.ietf.org/html/rfc7231#section-3.1.4.2)
///
/// # Examples
///
/// ```
/// # fn main() -> http_types::Result<()> {
/// #
/// use http_types::{Response,Url};
/// use http_types::content::{ContentLocation};
///
/// let content_location = ContentLocation::new(Url::parse("https://example.net/")?);
///
/// let mut res = Response::new(200);
/// content_location.apply(&mut res);
///
/// let content_location = ContentLocation::from_headers(Url::parse("https://example.net/").unwrap(),res)?.unwrap();
pepoviola marked this conversation as resolved.
Show resolved Hide resolved
/// assert_eq!(content_location.location(), "https://example.net/");
/// #
/// # Ok(()) }
/// ```
#[derive(Debug)]
pub struct ContentLocation {
url: Url,
}

impl ContentLocation {
/// Create a new instance of `Content-Location` header.
pub fn new(url: Url) -> Self {
Self { url }
}

/// Create a new instance from headers.
pub fn from_headers<U>(base_url: U, headers: impl AsRef<Headers>) -> crate::Result<Option<Self>>
yoshuawuyts marked this conversation as resolved.
Show resolved Hide resolved
where
U: TryInto<Url>,
U::Error: std::fmt::Debug,
{
let headers = match headers.as_ref().get(CONTENT_LOCATION) {
Some(headers) => headers,
None => return Ok(None),
};

// If we successfully parsed the header then there's always at least one
// entry. We want the last entry.
let value = headers.iter().last().unwrap();
let base = base_url.try_into().ok();
match base {
Some(base) => {
let url = base.join(value.as_str().trim()).status(400)?;
Ok(Some(Self { url }))
}
None => Ok(None),
}
}

/// Sets the header.
pub fn apply(&self, mut headers: impl AsMut<Headers>) {
headers.as_mut().insert(self.name(), self.value());
}

/// Get the `HeaderName`.
pub fn name(&self) -> HeaderName {
CONTENT_LOCATION
}

/// Get the `HeaderValue`.
pub fn value(&self) -> HeaderValue {
let output = self.url.to_string();

// SAFETY: the internal string is validated to be ASCII.
unsafe { HeaderValue::from_bytes_unchecked(output.into()) }
}

/// Get the url.
pub fn location(&self) -> String {
self.url.to_string()
}

/// Set the url.
pub fn set_location(&mut self, location: Url) {
self.url = location
}
}

#[cfg(test)]
mod test {
use super::*;
use crate::headers::Headers;

#[test]
fn smoke() -> crate::Result<()> {
let content_location = ContentLocation::new(Url::parse("https://example.net/test.json")?);

let mut headers = Headers::new();
content_location.apply(&mut headers);

let content_location =
ContentLocation::from_headers(Url::parse("https://example.net/").unwrap(), headers)?
.unwrap();
assert_eq!(content_location.location(), "https://example.net/test.json");
Ok(())
}

#[test]
fn bad_request_on_parse_error() -> crate::Result<()> {
let mut headers = Headers::new();
headers.insert(CONTENT_LOCATION, "htt://<nori ate the tag. yum.>");
let err =
ContentLocation::from_headers(Url::parse("https://example.net").unwrap(), headers)
.unwrap_err();
assert_eq!(err.status(), 400);
Ok(())
}
}
2 changes: 2 additions & 0 deletions src/content/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@
pub mod accept_encoding;
pub mod content_encoding;

mod content_location;
mod encoding;
mod encoding_proposal;

#[doc(inline)]
pub use accept_encoding::AcceptEncoding;
#[doc(inline)]
pub use content_encoding::ContentEncoding;
pub use content_location::ContentLocation;
pub use encoding::Encoding;
pub use encoding_proposal::EncodingProposal;