mod absolute_uri;
mod authority;
mod fragment;
mod query;
mod relative_reference;
mod scheme;
mod uri;
mod uri_reference;
use crate::array_cache::ArrayCache;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::path::{Path, PathBuf};
pub use absolute_uri::AbsoluteUri;
pub use authority::Authority;
pub use fragment::Fragment;
pub use query::Query;
pub use relative_reference::RelativeReference;
pub use scheme::Scheme;
pub use uri::Uri;
pub use uri_reference::UriReference;
pub type Error = String;
#[derive(Debug)]
pub struct ArrayUriReference {
base: Option<AbsoluteUri>,
relative: RelativeReference,
}
impl ArrayUriReference {
pub fn new(base: Option<AbsoluteUri>, relative: RelativeReference) -> Self {
let uri = Self { base, relative };
ArrayCache::register(&uri);
uri
}
pub fn is_relative(&self) -> bool {
self.base().is_none() && !self.relative().is_empty()
}
pub fn base(&self) -> Option<&AbsoluteUri> {
self.base.as_ref()
}
pub fn with_base(&self, base: &AbsoluteUri) -> Self {
Self::new(Some(base.to_owned()), self.relative().clone())
}
pub fn with_base_if_relative(&self, base: &AbsoluteUri) -> Self {
if self.is_relative() {
self.with_base(base)
} else {
self.clone()
}
}
pub fn relative(&self) -> &RelativeReference {
&self.relative
}
pub fn resolved(&self) -> Result<Uri, Error> {
let base = self.base().ok_or("No base URI to resolve against")?;
Ok(self.relative().resolve(base))
}
pub fn scheme(&self) -> Option<Scheme> {
self.base.as_ref().map(AbsoluteUri::scheme)
}
pub fn fragment(&self) -> Option<Fragment> {
self.relative().fragment()
}
pub fn with_fragment(&self, fragment: Option<&Fragment>) -> Self {
let mut relative = self.relative().clone();
relative.set_fragment(fragment);
Self::new(self.base.clone(), relative)
}
pub fn query(&self) -> Option<Query> {
self.relative().query()
}
pub fn join(&self, uri_reference: &UriReference) -> Result<Self, Error> {
match uri_reference.to_uri() {
Ok(uri) => Ok(Self::from(&uri)),
Err(relative) => {
if self.is_relative() {
Err("Cannot join a Relative Reference to another Relative Reference".to_owned())
} else {
let resolved = self.resolved()?;
let (absolute, _) = resolved.to_absolute_and_fragment();
Ok(Self::new(Some(absolute), relative))
}
}
}
}
}
impl Clone for ArrayUriReference {
fn clone(&self) -> Self {
Self::new(self.base().cloned(), self.relative().clone())
}
}
impl Drop for ArrayUriReference {
fn drop(&mut self) {
ArrayCache::deregister(self);
}
}
impl TryFrom<&str> for ArrayUriReference {
type Error = String;
fn try_from(value: &str) -> Result<Self, Self::Error> {
let uri_reference = UriReference::try_from(value)?;
Ok(Self::from(&uri_reference))
}
}
impl From<&UriReference> for ArrayUriReference {
fn from(value: &UriReference) -> Self {
match value.to_uri() {
Ok(uri) => Self::from(&uri),
Err(relative) => Self::from(&relative),
}
}
}
impl From<&ArrayUriReference> for UriReference {
fn from(value: &ArrayUriReference) -> Self {
if let Some(base) = value.base() {
UriReference::from(&value.relative().resolve(base))
} else {
UriReference::from(value.relative())
}
}
}
impl From<&Uri> for ArrayUriReference {
fn from(value: &Uri) -> Self {
let (absolute, fragment) = value.to_absolute_and_fragment();
let base = absolute.to_owned();
let mut relative = RelativeReference::empty();
relative.set_fragment(fragment.as_ref());
Self::new(Some(base), relative)
}
}
impl From<&RelativeReference> for ArrayUriReference {
fn from(value: &RelativeReference) -> Self {
Self::new(None, value.to_owned())
}
}
impl From<&AbsoluteUri> for ArrayUriReference {
fn from(value: &AbsoluteUri) -> Self {
Self::new(Some(value.to_owned()), RelativeReference::empty())
}
}
impl From<&Path> for ArrayUriReference {
fn from(value: &Path) -> Self {
if value.is_relative() {
let relative_reference: RelativeReference = value.try_into().unwrap();
(&relative_reference).into()
} else {
let absolute_uri: AbsoluteUri = value.try_into().unwrap();
(&absolute_uri).into()
}
}
}
impl TryFrom<&ArrayUriReference> for PathBuf {
type Error = String;
fn try_from(value: &ArrayUriReference) -> Result<Self, Self::Error> {
if value.is_relative() {
value.relative().try_into()
} else {
(&value.resolved()?).try_into()
}
}
}
impl Serialize for ArrayUriReference {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.relative().as_str())
}
}
impl<'de> Deserialize<'de> for ArrayUriReference {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let relative = String::deserialize(deserializer)?;
Self::try_from(relative.as_str()).map_err(serde::de::Error::custom)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::{Path, PathBuf};
use std::str::FromStr;
#[test]
fn from_str() {
assert!(ArrayUriReference::try_from("foo").is_ok());
assert!(ArrayUriReference::try_from(
"https://example.com/search?q=rust+language&page=2#section-comments"
)
.is_ok());
}
#[test]
fn from_file_uri_with_authority_into_path() {
let uri = ArrayUriReference::try_from("file://authority/foo").unwrap();
assert!(PathBuf::try_from(&uri).is_err());
}
#[cfg(unix)]
#[test]
fn from_path() {
let absolute = ArrayUriReference::try_from(Path::new("/root/.ssh/id_rsa")).unwrap();
assert_eq!(
absolute.base().unwrap().as_str(),
"file:///root/.ssh/id_rsa"
);
let relative = ArrayUriReference::try_from(Path::new(".ssh/id_rsa")).unwrap();
assert_eq!(relative.relative().as_str(), ".ssh/id_rsa");
}
#[cfg(windows)]
#[test]
fn from_path() {
for str in ["C:\\a\\b\\c", "C:/a/b/c"] {
let path = Path::new(str);
let uri = ArrayUriReference::from(path);
assert_eq!(uri.base().unwrap().as_str(), "file:///C:/a/b/c");
}
for str in [".ssh\\id_rsa", ".ssh/id_rsa"] {
let uri = ArrayUriReference::from(Path::new(str));
assert_eq!(uri.relative().as_str(), ".ssh/id_rsa");
}
}
#[cfg(windows)]
#[test]
#[should_panic]
fn from_unc_path() {
let _uri = ArrayUriReference::from(Path::new("\\\\authority\\foo"));
}
#[test]
fn scheme_relative() {
let result = ArrayUriReference::try_from("//www.alt.lu/foo");
assert!(result.is_ok());
let uri = result.unwrap();
assert!(uri.base().is_none());
assert!(uri.is_relative());
assert_eq!(uri.relative().as_str(), "//www.alt.lu/foo");
let uri = uri.with_base(&AbsoluteUri::try_from("http://example.com/bar/baz").unwrap());
let result = uri.resolved();
assert!(result.is_ok());
let uri = result.unwrap();
assert_eq!(uri.as_str(), "http://www.alt.lu/foo");
}
#[cfg(unix)]
#[test]
fn into_path() {
let ground_truth = [
("file:///root/foo#42", "/root/foo"),
("foo/bar#42", "foo/bar"),
];
for (ground_truth_uri, ground_truth_path) in ground_truth {
let uri = ArrayUriReference::try_from(ground_truth_uri).unwrap();
let path: Result<PathBuf, Error> = (&uri).try_into();
assert!(path.is_err());
let path: Result<PathBuf, Error> = (&uri.with_fragment(None)).try_into();
assert_eq!(path, Ok(PathBuf::from_str(ground_truth_path).unwrap()));
}
}
#[cfg(windows)]
#[test]
fn into_path() {
let ground_truth = [("file:///C:/foo#42", "C:/foo"), ("foo/bar#42", "foo/bar")];
for (ground_truth_uri, ground_truth_path) in ground_truth {
let uri = ArrayUriReference::try_from(ground_truth_uri).unwrap();
let path: Result<PathBuf, Error> = (&uri).try_into();
assert!(path.is_err());
let path: Result<PathBuf, Error> = (&uri.with_fragment(None)).try_into();
assert_eq!(path, Ok(PathBuf::from_str(ground_truth_path).unwrap()));
}
}
#[test]
fn unicode_characters() {
let result = ArrayUriReference::try_from("🦀");
assert!(result.is_ok());
let uri = result.unwrap();
assert_eq!(uri.relative().as_str(), "%F0%9F%A6%80");
let percent_encoded = ArrayUriReference::from(uri.relative());
assert_eq!(uri.relative(), percent_encoded.relative());
}
}