1use crate::errors::ParseError;
6use crate::parse::{PackageName, PackageVariant};
7use crate::{FuchsiaPkgAbsolutePackageUrl, RepositoryUrl};
8
9#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
16pub struct FuchsiaPkgUnpinnedAbsolutePackageUrl {
17 repo: RepositoryUrl,
18 name: PackageName,
19 variant: Option<PackageVariant>,
21}
22
23impl FuchsiaPkgUnpinnedAbsolutePackageUrl {
24 pub fn new(repo: RepositoryUrl, name: PackageName, variant: Option<PackageVariant>) -> Self {
26 Self { repo, name, variant }
27 }
28
29 pub fn new_with_path(repo: RepositoryUrl, path: &str) -> Result<Self, ParseError> {
32 let (name, variant) = crate::parse_path_to_name_and_variant(path)?;
33 Ok(Self::new(repo, name, variant))
34 }
35
36 pub fn parse(url: &str) -> Result<Self, ParseError> {
38 match FuchsiaPkgAbsolutePackageUrl::parse(url)? {
39 FuchsiaPkgAbsolutePackageUrl::Unpinned(unpinned) => Ok(unpinned),
40 FuchsiaPkgAbsolutePackageUrl::Pinned(_) => Err(ParseError::CannotContainHash),
41 }
42 }
43
44 pub fn repository(&self) -> &RepositoryUrl {
46 &self.repo
47 }
48
49 pub fn name(&self) -> &PackageName {
51 &self.name
52 }
53
54 pub fn variant(&self) -> Option<&PackageVariant> {
56 self.variant.as_ref()
57 }
58
59 pub fn path(&self) -> String {
61 match &self.variant {
62 Some(variant) => format!("{}/{}", self.name, variant),
63 None => self.name.to_string(),
64 }
65 }
66
67 pub fn set_repository(&mut self, repository: RepositoryUrl) -> &mut Self {
69 self.repo = repository;
70 self
71 }
72
73 pub fn clear_variant(&mut self) -> &mut Self {
75 self.variant = None;
76 self
77 }
78}
79
80impl std::ops::Deref for FuchsiaPkgUnpinnedAbsolutePackageUrl {
83 type Target = RepositoryUrl;
84
85 fn deref(&self) -> &Self::Target {
86 &self.repo
87 }
88}
89
90impl std::str::FromStr for FuchsiaPkgUnpinnedAbsolutePackageUrl {
91 type Err = ParseError;
92
93 fn from_str(url: &str) -> Result<Self, Self::Err> {
94 Self::parse(url)
95 }
96}
97
98impl std::convert::TryFrom<&str> for FuchsiaPkgUnpinnedAbsolutePackageUrl {
99 type Error = ParseError;
100
101 fn try_from(value: &str) -> Result<Self, Self::Error> {
102 Self::parse(value)
103 }
104}
105
106impl std::fmt::Display for FuchsiaPkgUnpinnedAbsolutePackageUrl {
107 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
108 let () = write!(f, "{}/{}", self.repo, self.name)?;
109 if let Some(variant) = &self.variant {
110 let () = write!(f, "/{}", variant)?;
111 }
112 Ok(())
113 }
114}
115
116impl serde::Serialize for FuchsiaPkgUnpinnedAbsolutePackageUrl {
117 fn serialize<S: serde::Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
118 self.to_string().serialize(ser)
119 }
120}
121
122impl<'de> serde::Deserialize<'de> for FuchsiaPkgUnpinnedAbsolutePackageUrl {
123 fn deserialize<D>(de: D) -> Result<Self, D::Error>
124 where
125 D: serde::Deserializer<'de>,
126 {
127 let url = String::deserialize(de)?;
128 Ok(Self::parse(&url).map_err(|err| serde::de::Error::custom(err))?)
129 }
130}
131
132#[cfg(test)]
133mod tests {
134 use super::*;
135 use crate::errors::PackagePathSegmentError;
136 use assert_matches::assert_matches;
137 use std::convert::TryFrom as _;
138
139 #[test]
140 fn new_with_path_err() {
141 for (path, err) in [
142 ("", ParseError::MissingName),
143 ("/", ParseError::InvalidName(PackagePathSegmentError::Empty)),
144 ("name/variant/other", ParseError::ExtraPathSegments),
145 ] {
146 assert_matches!(
147 FuchsiaPkgUnpinnedAbsolutePackageUrl::new_with_path(
148 "fuchsia-pkg://example.org".parse().unwrap(),
149 path.into(),
150 ),
151 Err(e) if e == err,
152 "the path {:?}", path
153 );
154 }
155 }
156
157 #[test]
158 fn new_with_path_ok() {
159 let repo = "fuchsia-pkg://example.org".parse::<RepositoryUrl>().unwrap();
160 let url = FuchsiaPkgUnpinnedAbsolutePackageUrl::new_with_path(repo.clone(), "name".into())
161 .unwrap();
162 assert_eq!(url.name().as_ref(), "name");
163 assert_eq!(url.variant(), None);
164
165 let url = FuchsiaPkgUnpinnedAbsolutePackageUrl::new_with_path(
166 repo.clone(),
167 "name/variant".into(),
168 )
169 .unwrap();
170 assert_eq!(url.name().as_ref(), "name");
171 assert_eq!(url.variant().map(|v| v.as_ref()), Some("variant"));
172 }
173
174 #[test]
175 fn parse_err() {
176 for (url, err) in [
177 ("fuchsia-boot://example.org/name", ParseError::InvalidScheme),
178 ("fuchsia-pkg://", ParseError::MissingHost),
179 ("fuchsia-pkg://exaMple.org", ParseError::InvalidHost),
180 ("fuchsia-pkg://example.org/", ParseError::MissingName),
181 (
182 "fuchsia-pkg://example.org//",
183 ParseError::InvalidPathSegment(PackagePathSegmentError::Empty),
184 ),
185 ("fuchsia-pkg://example.org/name/variant/extra", ParseError::ExtraPathSegments),
186 ("fuchsia-pkg://example.org/name#resource", ParseError::CannotContainResource),
187 (
188 "fuchsia-pkg://example.org/name?hash=0000000000000000000000000000000000000000000000000000000000000000",
189 ParseError::CannotContainHash,
190 ),
191 ] {
192 assert_matches!(
193 FuchsiaPkgUnpinnedAbsolutePackageUrl::parse(url),
194 Err(e) if e == err,
195 "the url {:?}", url
196 );
197 assert_matches!(
198 url.parse::<FuchsiaPkgUnpinnedAbsolutePackageUrl>(),
199 Err(e) if e == err,
200 "the url {:?}", url
201 );
202 assert_matches!(
203 FuchsiaPkgUnpinnedAbsolutePackageUrl::try_from(url),
204 Err(e) if e == err,
205 "the url {:?}", url
206 );
207 assert_matches!(
208 serde_json::from_str::<FuchsiaPkgUnpinnedAbsolutePackageUrl>(url),
209 Err(_),
210 "the url {:?}",
211 url
212 );
213 }
214 }
215
216 #[test]
217 fn parse_ok() {
218 for (url, host, name, variant, path) in [
219 ("fuchsia-pkg://example.org/name", "example.org", "name", None, "name"),
220 (
221 "fuchsia-pkg://example.org/name/variant",
222 "example.org",
223 "name",
224 Some("variant"),
225 "name/variant",
226 ),
227 ] {
228 let json_url = format!("\"{url}\"");
229
230 let name = name.parse::<crate::PackageName>().unwrap();
232 let variant = variant.map(|v| v.parse::<crate::PackageVariant>().unwrap());
233 let validate = |parsed: &FuchsiaPkgUnpinnedAbsolutePackageUrl| {
234 assert_eq!(parsed.host(), host);
235 assert_eq!(parsed.name(), &name);
236 assert_eq!(parsed.variant(), variant.as_ref());
237 assert_eq!(parsed.path(), path);
238 };
239 validate(&FuchsiaPkgUnpinnedAbsolutePackageUrl::parse(url).unwrap());
240 validate(&url.parse::<FuchsiaPkgUnpinnedAbsolutePackageUrl>().unwrap());
241 validate(&FuchsiaPkgUnpinnedAbsolutePackageUrl::try_from(url).unwrap());
242 validate(
243 &serde_json::from_str::<FuchsiaPkgUnpinnedAbsolutePackageUrl>(&json_url).unwrap(),
244 );
245
246 assert_eq!(
248 FuchsiaPkgUnpinnedAbsolutePackageUrl::parse(url).unwrap().to_string(),
249 url,
250 "the url {:?}",
251 url
252 );
253 assert_eq!(
254 serde_json::to_string(&FuchsiaPkgUnpinnedAbsolutePackageUrl::parse(url).unwrap())
255 .unwrap(),
256 json_url,
257 "the url {:?}",
258 url
259 );
260 }
261 }
262
263 #[test]
264 fn set_repository() {
265 let mut url =
266 FuchsiaPkgUnpinnedAbsolutePackageUrl::parse("fuchsia-pkg://example.org/name").unwrap();
267
268 url.set_repository("fuchsia-pkg://example.com".parse().unwrap());
269
270 assert_eq!(url.host(), "example.com");
271 }
272
273 #[test]
274 fn clear_variant() {
275 let mut url =
276 FuchsiaPkgUnpinnedAbsolutePackageUrl::parse("fuchsia-pkg://example.org/name/variant")
277 .unwrap();
278 url.clear_variant();
279 assert_eq!(url.variant(), None);
280
281 let mut url =
282 FuchsiaPkgUnpinnedAbsolutePackageUrl::parse("fuchsia-pkg://example.org/name").unwrap();
283 url.clear_variant();
284 assert_eq!(url.variant(), None);
285 }
286}