1use crate::errors::ParseError;
6use crate::{Host, Scheme, UrlParts};
7
8pub const SCHEME: &str = "fuchsia-pkg";
9
10#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
14pub struct RepositoryUrl {
15 host: Host,
16}
17
18impl RepositoryUrl {
19 pub fn parse_host(host: String) -> Result<Self, ParseError> {
22 Ok(Self { host: Host::parse(host)? })
23 }
24
25 pub fn parse(url: &str) -> Result<Self, ParseError> {
27 let UrlParts { scheme, host, path, hash, resource } = UrlParts::parse(url)?;
28 let scheme = scheme.ok_or(ParseError::MissingScheme)?;
29 let host = host.ok_or(ParseError::MissingHost)?;
30 if path.is_some() {
31 return Err(ParseError::ExtraPathSegments);
32 }
33 if hash.is_some() {
34 return Err(ParseError::CannotContainHash);
35 }
36 if resource.is_some() {
37 return Err(ParseError::CannotContainResource);
38 }
39 Self::new(scheme, host)
40 }
41
42 pub(crate) fn new(scheme: Scheme, host: Host) -> Result<Self, ParseError> {
43 if scheme != Scheme::FuchsiaPkg {
44 return Err(ParseError::InvalidScheme);
45 }
46
47 Ok(Self { host })
48 }
49
50 pub fn host(&self) -> &str {
52 self.host.as_ref()
53 }
54
55 pub fn into_host(self) -> String {
57 self.host.into()
58 }
59}
60
61impl std::str::FromStr for RepositoryUrl {
62 type Err = ParseError;
63
64 fn from_str(url: &str) -> Result<Self, Self::Err> {
65 Self::parse(url)
66 }
67}
68
69impl std::convert::TryFrom<&str> for RepositoryUrl {
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 RepositoryUrl {
78 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79 write!(f, "{}://{}", SCHEME, self.host.as_ref())
80 }
81}
82
83impl serde::Serialize for RepositoryUrl {
84 fn serialize<S: serde::Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
85 self.to_string().serialize(ser)
86 }
87}
88
89impl<'de> serde::Deserialize<'de> for RepositoryUrl {
90 fn deserialize<D>(de: D) -> Result<Self, D::Error>
91 where
92 D: serde::Deserializer<'de>,
93 {
94 let url = String::deserialize(de)?;
95 Ok(Self::parse(&url).map_err(|err| serde::de::Error::custom(err))?)
96 }
97}
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102 use crate::errors::PackagePathSegmentError;
103 use assert_matches::assert_matches;
104 use std::convert::TryFrom as _;
105
106 #[test]
107 fn parse_err() {
108 for (url, err) in [
109 ("example.org", ParseError::MissingScheme),
110 ("fuchsia-boot://example.org", ParseError::InvalidScheme),
111 ("fuchsia-pkg://", ParseError::MissingHost),
112 ("fuchsia-pkg://exaMple.org", ParseError::InvalidHost),
113 ("fuchsia-pkg://-example.org", ParseError::InvalidHost),
114 ("fuchsia-pkg://example-.org", ParseError::InvalidHost),
115 ("fuchsia-pkg://xn--", ParseError::InvalidHost),
116 ("fuchsia-pkg://example.org/path", ParseError::ExtraPathSegments),
117 (
118 "fuchsia-pkg://example.org//",
119 ParseError::InvalidPathSegment(PackagePathSegmentError::Empty),
120 ),
121 (
122 "fuchsia-pkg://example.org?hash=0000000000000000000000000000000000000000000000000000000000000000",
123 ParseError::CannotContainHash,
124 ),
125 ("fuchsia-pkg://example.org#resource", ParseError::CannotContainResource),
126 ("fuchsia-pkg://example.org/#resource", ParseError::CannotContainResource),
127 ] {
128 assert_matches!(
129 RepositoryUrl::parse(url),
130 Err(e) if e == err,
131 "the url {:?}", url
132 );
133 assert_matches!(
134 url.parse::<RepositoryUrl>(),
135 Err(e) if e == err,
136 "the url {:?}", url
137 );
138 assert_matches!(
139 RepositoryUrl::try_from(url),
140 Err(e) if e == err,
141 "the url {:?}", url
142 );
143 assert_matches!(
144 serde_json::from_str::<RepositoryUrl>(url),
145 Err(_),
146 "the url {:?}",
147 url
148 );
149 }
150 }
151
152 #[test]
153 fn parse_ok() {
154 for (url, host, display) in [
155 ("fuchsia-pkg://example.org", "example.org", "fuchsia-pkg://example.org"),
156 ("fuchsia-pkg://example.org/", "example.org", "fuchsia-pkg://example.org"),
157 ("fuchsia-pkg://example", "example", "fuchsia-pkg://example"),
158 ("fuchsia-pkg://xn--99999", "xn--99999", "fuchsia-pkg://xn--99999"),
159 ] {
160 assert_eq!(RepositoryUrl::parse(url).unwrap().host(), host, "the url {:?}", url);
162 assert_eq!(url.parse::<RepositoryUrl>().unwrap().host(), host, "the url {:?}", url);
163 assert_eq!(RepositoryUrl::try_from(url).unwrap().host(), host, "the url {:?}", url);
164 assert_eq!(
165 serde_json::from_str::<RepositoryUrl>(&format!("\"{url}\"")).unwrap().host(),
166 host,
167 "the url {:?}",
168 url
169 );
170
171 assert_eq!(
173 RepositoryUrl::parse(url).unwrap().to_string(),
174 display,
175 "the url {:?}",
176 url
177 );
178 assert_eq!(
179 serde_json::to_string(&RepositoryUrl::parse(url).unwrap()).unwrap(),
180 format!("\"{display}\""),
181 "the url {:?}",
182 url
183 );
184 }
185 }
186}