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