use crate::uri::Uri;
use core::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ContentLocationError {
Empty,
InvalidUri,
FragmentNotAllowed,
}
impl fmt::Display for ContentLocationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ContentLocationError::Empty => write!(f, "empty Content-Location"),
ContentLocationError::InvalidUri => write!(f, "invalid Content-Location URI"),
ContentLocationError::FragmentNotAllowed => {
write!(f, "Content-Location must not contain a fragment")
}
}
}
}
impl std::error::Error for ContentLocationError {}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ContentLocation {
uri: Uri,
}
impl ContentLocation {
pub fn parse(input: &str) -> Result<Self, ContentLocationError> {
let input = input.trim();
if input.is_empty() {
return Err(ContentLocationError::Empty);
}
let uri = Uri::parse(input).map_err(|_| ContentLocationError::InvalidUri)?;
if let Some(scheme) = uri.scheme()
&& (scheme.eq_ignore_ascii_case("http") || scheme.eq_ignore_ascii_case("https"))
&& uri.host().is_none_or(|h| h.is_empty())
{
return Err(ContentLocationError::InvalidUri);
}
if uri.fragment().is_some() {
return Err(ContentLocationError::FragmentNotAllowed);
}
Ok(ContentLocation { uri })
}
pub fn uri(&self) -> &Uri {
&self.uri
}
}
impl fmt::Display for ContentLocation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.uri)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_absolute() {
let cl = ContentLocation::parse("https://example.com/path").unwrap();
assert_eq!(cl.uri().as_str(), "https://example.com/path");
}
#[test]
fn parse_relative() {
let cl = ContentLocation::parse("/assets/logo.png").unwrap();
assert_eq!(cl.uri().path(), "/assets/logo.png");
}
#[test]
fn parse_invalid() {
assert!(ContentLocation::parse("").is_err());
assert!(ContentLocation::parse("http://[::1").is_err());
}
#[test]
fn parse_fragment_rejected() {
assert_eq!(
ContentLocation::parse("https://example.com/path#frag"),
Err(ContentLocationError::FragmentNotAllowed)
);
assert_eq!(
ContentLocation::parse("/path#frag"),
Err(ContentLocationError::FragmentNotAllowed)
);
}
}