fuchsia_url/
relative_package_url.rs
1use crate::errors::ParseError;
6use crate::parse::PackageName;
7use crate::UrlParts;
8
9#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
14pub struct RelativePackageUrl {
15 path: PackageName,
16}
17
18impl RelativePackageUrl {
19 pub(crate) fn from_parts(parts: UrlParts) -> Result<Self, ParseError> {
20 let UrlParts { scheme, host, path, hash, resource } = parts;
21 if scheme.is_some() {
22 return Err(ParseError::CannotContainScheme);
23 }
24 if host.is_some() {
25 return Err(ParseError::HostMustBeEmpty);
26 }
27 if hash.is_some() {
28 return Err(ParseError::CannotContainHash);
29 }
30 if resource.is_some() {
31 return Err(ParseError::CannotContainResource);
32 }
33 let (name, variant) = crate::parse_path_to_name_and_variant(&path)?;
34 if variant.is_some() {
35 return Err(ParseError::RelativePathCannotSpecifyVariant);
36 }
37 Ok(Self { path: name })
38 }
39
40 pub fn parse(url: &str) -> Result<Self, ParseError> {
42 let parts = UrlParts::parse(url)?;
43 let relative_package_url = Self::from_parts(parts)?;
44 let () = crate::validate_inverse_relative_url(url)?;
45 Ok(relative_package_url)
46 }
47}
48
49impl std::str::FromStr for RelativePackageUrl {
50 type Err = ParseError;
51
52 fn from_str(url: &str) -> Result<Self, Self::Err> {
53 Self::parse(url)
54 }
55}
56
57impl From<PackageName> for RelativePackageUrl {
58 fn from(path: PackageName) -> Self {
59 Self { path }
60 }
61}
62
63impl From<RelativePackageUrl> for PackageName {
64 fn from(url: RelativePackageUrl) -> Self {
65 url.path
66 }
67}
68
69impl std::convert::TryFrom<&str> for RelativePackageUrl {
70 type Error = ParseError;
71
72 fn try_from(value: &str) -> Result<Self, Self::Error> {
73 Self::parse(value)
74 }
75}
76
77impl std::fmt::Display for RelativePackageUrl {
78 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79 self.path.fmt(f)
80 }
81}
82
83impl AsRef<str> for RelativePackageUrl {
84 fn as_ref(&self) -> &str {
85 self.path.as_ref()
86 }
87}
88
89impl From<&RelativePackageUrl> for String {
90 fn from(url: &RelativePackageUrl) -> Self {
91 url.to_string()
92 }
93}
94
95impl serde::Serialize for RelativePackageUrl {
96 fn serialize<S: serde::Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
97 self.to_string().serialize(ser)
98 }
99}
100
101impl<'de> serde::Deserialize<'de> for RelativePackageUrl {
102 fn deserialize<D>(de: D) -> Result<Self, D::Error>
103 where
104 D: serde::Deserializer<'de>,
105 {
106 let url = String::deserialize(de)?;
107 Ok(Self::parse(&url).map_err(|err| serde::de::Error::custom(err))?)
108 }
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114 use assert_matches::assert_matches;
115 use std::convert::TryFrom as _;
116
117 #[test]
118 fn parse_err() {
119 for (url, err) in [
120 ("fuchsia-boot://example.org/name", ParseError::CannotContainScheme),
121 ("fuchsia-pkg://", ParseError::CannotContainScheme),
122 ("fuchsia-pkg://name", ParseError::CannotContainScheme),
123 ("fuchsia-pkg:///name", ParseError::CannotContainScheme),
124 ("//example.org/name", ParseError::HostMustBeEmpty),
125 (
126 "///name",
127 ParseError::InvalidRelativePath("///name".to_string(), Some("name".to_string())),
128 ),
129 ("example.org/name", ParseError::RelativePathCannotSpecifyVariant),
130 ("fuchsia-pkg://example.org/name", ParseError::CannotContainScheme),
131 ("name/variant", ParseError::RelativePathCannotSpecifyVariant),
132 ("name#resource", ParseError::CannotContainResource),
133 (
134 "/name",
135 ParseError::InvalidRelativePath("/name".to_string(), Some("name".to_string())),
136 ),
137 (".", ParseError::MissingName),
138 ("..", ParseError::MissingName),
139 (
140 "name?hash=0000000000000000000000000000000000000000000000000000000000000000",
141 ParseError::CannotContainHash,
142 ),
143 ] {
144 assert_matches!(
145 RelativePackageUrl::parse(url),
146 Err(e) if e == err,
147 "the url {:?}, expected: {:?}", url, err
148 );
149 assert_matches!(
150 url.parse::<RelativePackageUrl>(),
151 Err(e) if e == err,
152 "the url {:?}, expected: {:?}", url, err
153 );
154 assert_matches!(
155 RelativePackageUrl::try_from(url),
156 Err(e) if e == err,
157 "the url {:?}, expected: {:?}", url, err
158 );
159 assert_matches!(
160 serde_json::from_str::<RelativePackageUrl>(url),
161 Err(_),
162 "the url {:?}",
163 url
164 );
165 }
166 }
167
168 #[test]
169 fn parse_ok() {
170 for url in ["name", "other3-name"] {
171 let normalized_url = url.trim_start_matches('/');
172 let json_url = format!("\"{url}\"");
173 let normalized_json_url = format!("\"{normalized_url}\"");
174
175 let name = normalized_url.parse::<crate::PackageName>().unwrap();
177 let validate = |parsed: &RelativePackageUrl| {
178 assert_eq!(parsed.path, name);
179 };
180 validate(&RelativePackageUrl::parse(url).unwrap());
181 validate(&url.parse::<RelativePackageUrl>().unwrap());
182 validate(&RelativePackageUrl::try_from(url).unwrap());
183 validate(&serde_json::from_str::<RelativePackageUrl>(&json_url).unwrap());
184
185 assert_eq!(
187 RelativePackageUrl::parse(url).unwrap().to_string(),
188 normalized_url,
189 "the url {:?}",
190 url
191 );
192 assert_eq!(
193 RelativePackageUrl::parse(url).unwrap().as_ref(),
194 normalized_url,
195 "the url {:?}",
196 url
197 );
198 assert_eq!(
199 serde_json::to_string(&RelativePackageUrl::parse(url).unwrap()).unwrap(),
200 normalized_json_url,
201 "the url {:?}",
202 url
203 );
204 }
205 }
206}