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