fuchsia_url/
package_url.rs1use crate::errors::ParseError;
6use crate::{AbsolutePackageUrl, RelativePackageUrl, UrlParts};
7
8#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
12pub enum PackageUrl {
13 Absolute(AbsolutePackageUrl),
14 Relative(RelativePackageUrl),
15}
16
17impl PackageUrl {
18 pub fn parse(url: &str) -> Result<Self, ParseError> {
20 let parts = UrlParts::parse(url)?;
21 Ok(if parts.scheme.is_some() {
22 Self::Absolute(AbsolutePackageUrl::from_parts(parts)?)
23 } else {
24 Self::Relative(RelativePackageUrl::from_parts(parts)?)
25 })
26 }
27}
28
29impl std::str::FromStr for PackageUrl {
30 type Err = ParseError;
31
32 fn from_str(url: &str) -> Result<Self, Self::Err> {
33 Self::parse(url)
34 }
35}
36
37impl std::convert::TryFrom<&str> for PackageUrl {
38 type Error = ParseError;
39
40 fn try_from(value: &str) -> Result<Self, Self::Error> {
41 Self::parse(value)
42 }
43}
44
45impl From<AbsolutePackageUrl> for PackageUrl {
46 fn from(absolute: AbsolutePackageUrl) -> Self {
47 Self::Absolute(absolute)
48 }
49}
50
51impl From<RelativePackageUrl> for PackageUrl {
52 fn from(relative: RelativePackageUrl) -> Self {
53 Self::Relative(relative)
54 }
55}
56
57impl std::fmt::Display for PackageUrl {
58 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
59 match self {
60 Self::Absolute(absolute) => absolute.fmt(f),
61 Self::Relative(relative) => relative.fmt(f),
62 }
63 }
64}
65
66impl serde::Serialize for PackageUrl {
67 fn serialize<S: serde::Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
68 self.to_string().serialize(ser)
69 }
70}
71
72impl<'de> serde::Deserialize<'de> for PackageUrl {
73 fn deserialize<D>(de: D) -> Result<Self, D::Error>
74 where
75 D: serde::Deserializer<'de>,
76 {
77 let url = String::deserialize(de)?;
78 Ok(Self::parse(&url).map_err(|err| serde::de::Error::custom(err))?)
79 }
80}
81
82#[cfg(test)]
83mod tests {
84 use super::*;
85 use assert_matches::assert_matches;
86 use std::convert::TryFrom as _;
87
88 #[test]
89 fn parse_err() {
90 for url in [
91 "fuchsia-boot://example.org/name",
92 "fuchsia-pkg://",
93 "fuchsia-pkg://example.org/",
94 "fuchsia-pkg://example.org//",
95 "fuchsia-pkg://exaMple.org/name",
96 "fuchsia-pkg:///name",
97 "fuchsia-pkg://name",
98 "example.org/name",
99 "name/variant",
100 "name#resource",
101 "name?hash=0000000000000000000000000000000000000000000000000000000000000000",
102 ] {
103 assert_matches!(PackageUrl::parse(url), Err(_), "the url {:?}", url);
104 assert_matches!(url.parse::<PackageUrl>(), Err(_), "the url {:?}", url);
105 assert_matches!(PackageUrl::try_from(url), Err(_), "the url {:?}", url);
106 assert_matches!(serde_json::from_str::<PackageUrl>(url), Err(_), "the url {:?}", url);
107 }
108 }
109
110 #[test]
111 fn parse_ok_absolute() {
112 for url in [
113 "fuchsia-pkg://example.org/name",
114 "fuchsia-pkg://example.org/name/variant",
115 "fuchsia-pkg://example.org/name?hash=0000000000000000000000000000000000000000000000000000000000000000",
116 "fuchsia-pkg://example.org/name/variant?hash=0000000000000000000000000000000000000000000000000000000000000000",
117 ] {
118 let json_url = format!("\"{url}\"");
119 let validate = |parsed: &PackageUrl| {
120 assert_eq!(parsed.to_string(), url);
121 assert_eq!(serde_json::to_string(&parsed).unwrap(), json_url);
122 };
123 validate(&PackageUrl::parse(url).unwrap());
124 validate(&url.parse::<PackageUrl>().unwrap());
125 validate(&PackageUrl::try_from(url).unwrap());
126 validate(&serde_json::from_str::<PackageUrl>(&json_url).unwrap());
127 }
128 }
129
130 #[test]
131 fn parse_ok_relative() {
132 for url in ["name", "other3-name"] {
133 let json_url = format!("\"{url}\"");
134 let validate = |parsed: &PackageUrl| {
135 assert_eq!(parsed.to_string(), url);
136 assert_eq!(serde_json::to_string(&parsed).unwrap(), json_url);
137 };
138 validate(&PackageUrl::parse(url).unwrap());
139 validate(&url.parse::<PackageUrl>().unwrap());
140 validate(&PackageUrl::try_from(url).unwrap());
141 validate(&serde_json::from_str::<PackageUrl>(&json_url).unwrap());
142 }
143 }
144}