use crate::errors::ParseError;
use crate::parse::{PackageName, PackageVariant};
use crate::{AbsolutePackageUrl, RepositoryUrl, UnpinnedAbsolutePackageUrl};
use fuchsia_hash::Hash;
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct PinnedAbsolutePackageUrl {
unpinned: UnpinnedAbsolutePackageUrl,
hash: Hash,
}
impl PinnedAbsolutePackageUrl {
pub fn new(
repo: RepositoryUrl,
name: PackageName,
variant: Option<PackageVariant>,
hash: Hash,
) -> Self {
Self { unpinned: UnpinnedAbsolutePackageUrl::new(repo, name, variant), hash }
}
pub fn new_with_path(repo: RepositoryUrl, path: &str, hash: Hash) -> Result<Self, ParseError> {
Ok(Self { unpinned: UnpinnedAbsolutePackageUrl::new_with_path(repo, path)?, hash })
}
pub fn parse(url: &str) -> Result<Self, ParseError> {
match AbsolutePackageUrl::parse(url)? {
AbsolutePackageUrl::Unpinned(_) => Err(ParseError::MissingHash),
AbsolutePackageUrl::Pinned(pinned) => Ok(pinned),
}
}
pub fn from_unpinned(unpinned: UnpinnedAbsolutePackageUrl, hash: Hash) -> Self {
Self { unpinned, hash }
}
pub fn into_unpinned_and_hash(self) -> (UnpinnedAbsolutePackageUrl, Hash) {
let Self { unpinned, hash } = self;
(unpinned, hash)
}
pub fn as_unpinned(&self) -> &UnpinnedAbsolutePackageUrl {
&self.unpinned
}
pub fn hash(&self) -> Hash {
self.hash
}
pub fn set_repository(&mut self, repository: RepositoryUrl) -> &mut Self {
self.unpinned.set_repository(repository);
self
}
}
impl std::ops::Deref for PinnedAbsolutePackageUrl {
type Target = UnpinnedAbsolutePackageUrl;
fn deref(&self) -> &Self::Target {
&self.unpinned
}
}
impl std::ops::DerefMut for PinnedAbsolutePackageUrl {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.unpinned
}
}
impl std::str::FromStr for PinnedAbsolutePackageUrl {
type Err = ParseError;
fn from_str(url: &str) -> Result<Self, Self::Err> {
Self::parse(url)
}
}
impl std::convert::TryFrom<&str> for PinnedAbsolutePackageUrl {
type Error = ParseError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
Self::parse(value)
}
}
impl std::fmt::Display for PinnedAbsolutePackageUrl {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}?hash={}", self.unpinned, self.hash)
}
}
impl serde::Serialize for PinnedAbsolutePackageUrl {
fn serialize<S: serde::Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
self.to_string().serialize(ser)
}
}
impl<'de> serde::Deserialize<'de> for PinnedAbsolutePackageUrl {
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 [
("fuchsia-boot://example.org/name?hash=0000000000000000000000000000000000000000000000000000000000000000", ParseError::InvalidScheme),
("fuchsia-pkg://?hash=0000000000000000000000000000000000000000000000000000000000000000", ParseError::MissingHost),
("fuchsia-pkg://exaMple.org?hash=0000000000000000000000000000000000000000000000000000000000000000", ParseError::InvalidHost),
("fuchsia-pkg://example.org/?hash=0000000000000000000000000000000000000000000000000000000000000000", ParseError::MissingName),
(
"fuchsia-pkg://example.org//?hash=0000000000000000000000000000000000000000000000000000000000000000",
ParseError::InvalidPathSegment(PackagePathSegmentError::Empty),
),
("fuchsia-pkg://example.org/name/variant/extra?hash=0000000000000000000000000000000000000000000000000000000000000000", ParseError::ExtraPathSegments),
("fuchsia-pkg://example.org/name?hash=0000000000000000000000000000000000000000000000000000000000000000#resource", ParseError::CannotContainResource),
] {
assert_matches!(
PinnedAbsolutePackageUrl::parse(url),
Err(e) if e == err,
"the url {:?}", url
);
assert_matches!(
url.parse::<PinnedAbsolutePackageUrl>(),
Err(e) if e == err,
"the url {:?}", url
);
assert_matches!(
PinnedAbsolutePackageUrl::try_from(url),
Err(e) if e == err,
"the url {:?}", url
);
assert_matches!(
serde_json::from_str::<PinnedAbsolutePackageUrl>(url),
Err(_),
"the url {:?}",
url
);
}
}
#[test]
fn parse_ok() {
for (url, variant, path) in [
("fuchsia-pkg://example.org/name?hash=0000000000000000000000000000000000000000000000000000000000000000", None, "/name"),
(
"fuchsia-pkg://example.org/name/variant?hash=0000000000000000000000000000000000000000000000000000000000000000",
Some("variant"),
"/name/variant",
),
] {
let json_url = format!("\"{url}\"");
let host = "example.org";
let name = "name";
let hash = "0000000000000000000000000000000000000000000000000000000000000000".parse::<Hash>().unwrap();
let name = name.parse::<crate::PackageName>().unwrap();
let variant = variant.map(|v| v.parse::<crate::PackageVariant>().unwrap());
let validate = |parsed: &PinnedAbsolutePackageUrl| {
assert_eq!(parsed.host(), host);
assert_eq!(parsed.name(), &name);
assert_eq!(parsed.variant(), variant.as_ref());
assert_eq!(parsed.path(), path);
assert_eq!(parsed.hash(), hash);
};
validate(&PinnedAbsolutePackageUrl::parse(url).unwrap());
validate(&url.parse::<PinnedAbsolutePackageUrl>().unwrap());
validate(&PinnedAbsolutePackageUrl::try_from(url).unwrap());
validate(&serde_json::from_str::<PinnedAbsolutePackageUrl>(&json_url).unwrap());
assert_eq!(
PinnedAbsolutePackageUrl::parse(url).unwrap().to_string(),
url,
"the url {:?}",
url
);
assert_eq!(
serde_json::to_string(&PinnedAbsolutePackageUrl::parse(url).unwrap()).unwrap(),
json_url,
"the url {:?}",
url
);
}
}
}