Skip to content

Commit

Permalink
Merge pull request #256 from pepoviola/header-content-location
Browse files Browse the repository at this point in the history
Add `content::ContentLocation` header
  • Loading branch information
yoshuawuyts authored Nov 3, 2020
2 parents a6cbf95 + 85a5089 commit de7afbb
Show file tree
Hide file tree
Showing 2 changed files with 125 additions and 0 deletions.
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::{bail_status as bail, Status, Url};

use std::convert::TryInto;

/// Indicates an alternate location for the returned data.
///
/// # 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 url = Url::parse("https://example.net/")?;
/// let content_location = ContentLocation::from_headers(url, res)?.unwrap();
/// 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>>
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 = match base_url.try_into() {
Ok(b) => b,
Err(_) => bail!(400, "Invalid base url provided"),
};

let url = base.join(value.as_str().trim()).status(400)?;
Ok(Some(Self { url }))
}

/// 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 @@ -9,6 +9,7 @@ pub mod accept_encoding;
pub mod content_encoding;

mod content_length;
mod content_location;
mod encoding;
mod encoding_proposal;

Expand All @@ -17,5 +18,6 @@ pub use accept_encoding::AcceptEncoding;
#[doc(inline)]
pub use content_encoding::ContentEncoding;
pub use content_length::ContentLength;
pub use content_location::ContentLocation;
pub use encoding::Encoding;
pub use encoding_proposal::EncodingProposal;

0 comments on commit de7afbb

Please sign in to comment.