fuchsia_url/
component_url.rs1use crate::errors::ParseError;
6use crate::{AbsoluteComponentUrl, PackageUrl, RelativeComponentUrl, UrlParts};
7
8#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
12pub struct ComponentUrl {
13 package: PackageUrl,
14 resource: String,
15}
16
17impl ComponentUrl {
18 pub fn parse(url: &str) -> Result<Self, ParseError> {
20 let parts = UrlParts::parse(url)?;
21 Ok(if parts.scheme.is_some() {
22 let (absolute, resource) =
23 AbsoluteComponentUrl::from_parts(parts)?.into_package_and_resource();
24 Self { package: absolute.into(), resource }
25 } else {
26 let (relative, resource) =
27 RelativeComponentUrl::from_parts(parts)?.into_package_and_resource();
28 Self { package: relative.into(), resource }
29 })
30 }
31
32 pub fn package_url(&self) -> &PackageUrl {
34 &self.package
35 }
36
37 pub fn resource(&self) -> &str {
39 &self.resource
40 }
41}
42
43impl std::str::FromStr for ComponentUrl {
44 type Err = ParseError;
45
46 fn from_str(url: &str) -> Result<Self, Self::Err> {
47 Self::parse(url)
48 }
49}
50
51impl std::convert::TryFrom<&str> for ComponentUrl {
52 type Error = ParseError;
53
54 fn try_from(value: &str) -> Result<Self, Self::Error> {
55 Self::parse(value)
56 }
57}
58
59impl std::fmt::Display for ComponentUrl {
60 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61 write!(
62 f,
63 "{}#{}",
64 self.package,
65 percent_encoding::utf8_percent_encode(&self.resource, crate::FRAGMENT)
66 )
67 }
68}
69
70impl serde::Serialize for ComponentUrl {
71 fn serialize<S: serde::Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
72 self.to_string().serialize(ser)
73 }
74}
75
76impl<'de> serde::Deserialize<'de> for ComponentUrl {
77 fn deserialize<D>(de: D) -> Result<Self, D::Error>
78 where
79 D: serde::Deserializer<'de>,
80 {
81 let url = String::deserialize(de)?;
82 Ok(Self::parse(&url).map_err(|err| serde::de::Error::custom(err))?)
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89 use assert_matches::assert_matches;
90 use std::convert::TryFrom as _;
91
92 #[test]
93 fn parse_err() {
94 for url in [
95 "fuchsia-boot://example.org/name#resource",
96 "fuchsia-pkg://name#resource",
97 "fuchsia-pkg://example.org#resource",
98 "fuchsia-pkg://example.org/#resource",
99 "fuchsia-pkg://example.org//#resource",
100 "fuchsia-pkg://example.org/name",
101 "fuchsia-pkg://exaMple.org/name#resource",
102 "fuchsia-pkg:///name#resource",
103 "fuchsia-pkg://#resource",
104 "example.org/name#resource",
105 "name/variant#resource",
106 "name",
107 "name?hash=0000000000000000000000000000000000000000000000000000000000000000",
108 "#resource",
109 ] {
110 assert_matches!(ComponentUrl::parse(url), Err(_), "the url {:?}", url);
111 assert_matches!(url.parse::<ComponentUrl>(), Err(_), "the url {:?}", url);
112 assert_matches!(ComponentUrl::try_from(url), Err(_), "the url {:?}", url);
113 assert_matches!(serde_json::from_str::<ComponentUrl>(url), Err(_), "the url {:?}", url);
114 }
115 }
116
117 #[test]
118 fn parse_ok_absolute() {
119 for url in [
120 "fuchsia-pkg://example.org/name#resource",
121 "fuchsia-pkg://example.org/name#resource%09",
122 "fuchsia-pkg://example.org/name/variant#resource",
123 "fuchsia-pkg://example.org/name?hash=0000000000000000000000000000000000000000000000000000000000000000#resource",
124 "fuchsia-pkg://example.org/name/variant?hash=0000000000000000000000000000000000000000000000000000000000000000#resource",
125 ] {
126 let json_url = format!("\"{url}\"");
127 let validate = |parsed: &ComponentUrl| {
128 assert_eq!(parsed.to_string(), url);
129 assert_eq!(serde_json::to_string(&parsed).unwrap(), json_url);
130 };
131 validate(&ComponentUrl::parse(url).unwrap());
132 validate(&url.parse::<ComponentUrl>().unwrap());
133 validate(&ComponentUrl::try_from(url).unwrap());
134 validate(&serde_json::from_str::<ComponentUrl>(&json_url).unwrap());
135 }
136 }
137
138 #[test]
139 fn parse_ok_relative() {
140 for url in ["name#resource", "other-name#resource%09"] {
141 let json_url = format!("\"{url}\"");
142 let validate = |parsed: &ComponentUrl| {
143 assert_eq!(parsed.to_string(), url);
144 assert_eq!(serde_json::to_string(&parsed).unwrap(), json_url);
145 };
146 validate(&ComponentUrl::parse(url).unwrap());
147 validate(&url.parse::<ComponentUrl>().unwrap());
148 validate(&ComponentUrl::try_from(url).unwrap());
149 validate(&serde_json::from_str::<ComponentUrl>(&json_url).unwrap());
150 }
151 }
152}