use crate::errors::ParseError;
use crate::{Host, Scheme, UrlParts};
pub const SCHEME: &str = "fuchsia-pkg";
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct RepositoryUrl {
host: Host,
}
impl RepositoryUrl {
pub fn parse_host(host: String) -> Result<Self, ParseError> {
Ok(Self { host: Host::parse(host)? })
}
pub fn parse(url: &str) -> Result<Self, ParseError> {
let UrlParts { scheme, host, path, hash, resource } = UrlParts::parse(url)?;
let scheme = scheme.ok_or(ParseError::MissingScheme)?;
let host = host.ok_or(ParseError::MissingHost)?;
if path != "/" {
return Err(ParseError::ExtraPathSegments);
}
if hash.is_some() {
return Err(ParseError::CannotContainHash);
}
if resource.is_some() {
return Err(ParseError::CannotContainResource);
}
Self::new(scheme, host)
}
pub(crate) fn new(scheme: Scheme, host: Host) -> Result<Self, ParseError> {
if scheme != Scheme::FuchsiaPkg {
return Err(ParseError::InvalidScheme);
}
Ok(Self { host })
}
pub fn host(&self) -> &str {
self.host.as_ref()
}
pub fn into_host(self) -> String {
self.host.into()
}
}
impl std::str::FromStr for RepositoryUrl {
type Err = ParseError;
fn from_str(url: &str) -> Result<Self, Self::Err> {
Self::parse(url)
}
}
impl std::convert::TryFrom<&str> for RepositoryUrl {
type Error = ParseError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
Self::parse(value)
}
}
impl std::fmt::Display for RepositoryUrl {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}://{}", SCHEME, self.host.as_ref())
}
}
impl serde::Serialize for RepositoryUrl {
fn serialize<S: serde::Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
self.to_string().serialize(ser)
}
}
impl<'de> serde::Deserialize<'de> for RepositoryUrl {
fn deserialize<D>(de: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let url = String::deserialize(de)?;
Ok(Self::parse(&url).map_err(|err| serde::de::Error::custom(err))?)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::errors::PackagePathSegmentError;
use assert_matches::assert_matches;
use std::convert::TryFrom as _;
#[test]
fn parse_err() {
for (url, err) in [
("example.org", ParseError::MissingScheme),
("fuchsia-boot://example.org", ParseError::InvalidScheme),
("fuchsia-pkg://", ParseError::MissingHost),
("fuchsia-pkg://exaMple.org", ParseError::InvalidHost),
("fuchsia-pkg://example.org/path", ParseError::ExtraPathSegments),
("fuchsia-pkg://example.org//", ParseError::InvalidPathSegment(PackagePathSegmentError::Empty)),
("fuchsia-pkg://example.org?hash=0000000000000000000000000000000000000000000000000000000000000000", ParseError::CannotContainHash),
("fuchsia-pkg://example.org#resource", ParseError::CannotContainResource),
("fuchsia-pkg://example.org/#resource", ParseError::CannotContainResource),
] {
assert_matches!(
RepositoryUrl::parse(url),
Err(e) if e == err,
"the url {:?}", url
);
assert_matches!(
url.parse::<RepositoryUrl>(),
Err(e) if e == err,
"the url {:?}", url
);
assert_matches!(
RepositoryUrl::try_from(url),
Err(e) if e == err,
"the url {:?}", url
);
assert_matches!(
serde_json::from_str::<RepositoryUrl>(url),
Err(_),
"the url {:?}", url
);
}
}
#[test]
fn parse_ok() {
for (url, host, display) in [
("fuchsia-pkg://example.org", "example.org", "fuchsia-pkg://example.org"),
("fuchsia-pkg://example.org/", "example.org", "fuchsia-pkg://example.org"),
("fuchsia-pkg://example", "example", "fuchsia-pkg://example"),
] {
assert_eq!(RepositoryUrl::parse(url).unwrap().host(), host, "the url {:?}", url);
assert_eq!(url.parse::<RepositoryUrl>().unwrap().host(), host, "the url {:?}", url);
assert_eq!(RepositoryUrl::try_from(url).unwrap().host(), host, "the url {:?}", url);
assert_eq!(
serde_json::from_str::<RepositoryUrl>(&format!("\"{url}\"")).unwrap().host(),
host,
"the url {:?}",
url
);
assert_eq!(
RepositoryUrl::parse(url).unwrap().to_string(),
display,
"the url {:?}",
url
);
assert_eq!(
serde_json::to_string(&RepositoryUrl::parse(url).unwrap()).unwrap(),
format!("\"{display}\""),
"the url {:?}",
url
);
}
}
}