Skip to main content

fuchsia_url/
fuchsia_pkg_component_url.rs

1// Copyright 2022 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::errors::ParseError;
6use crate::{
7    FuchsiaPkgAbsoluteComponentUrl, FuchsiaPkgPackageUrl, RelativeComponentUrl, Resource, UrlParts,
8};
9
10/// A URL locating a Fuchsia component. Can be either absolute or relative.
11/// See `FuchsiaPkgAbsoluteComponentUrl` and `RelativeComponentUrl` for more details.
12/// https://fuchsia.dev/fuchsia-src/concepts/packages/package_url
13#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
14pub struct FuchsiaPkgComponentUrl {
15    package: FuchsiaPkgPackageUrl,
16    resource: Resource,
17}
18
19impl FuchsiaPkgComponentUrl {
20    /// Parse a FuchsiaPkgComponentUrl URL.
21    pub fn parse(url: &str) -> Result<Self, ParseError> {
22        let parts = UrlParts::parse(url)?;
23        Ok(if parts.scheme.is_some() {
24            let (absolute, resource) =
25                FuchsiaPkgAbsoluteComponentUrl::from_parts(parts)?.into_package_and_resource();
26            Self { package: absolute.into(), resource }
27        } else {
28            let (relative, resource) =
29                RelativeComponentUrl::from_parts(parts)?.into_package_and_resource();
30            Self { package: relative.into(), resource }
31        })
32    }
33
34    /// The package URL of this URL (this URL without the resource path).
35    pub fn package_url(&self) -> &FuchsiaPkgPackageUrl {
36        &self.package
37    }
38
39    /// The resource path of this URL.
40    pub fn resource(&self) -> &Resource {
41        &self.resource
42    }
43}
44
45impl std::str::FromStr for FuchsiaPkgComponentUrl {
46    type Err = ParseError;
47
48    fn from_str(url: &str) -> Result<Self, Self::Err> {
49        Self::parse(url)
50    }
51}
52
53impl std::convert::TryFrom<&str> for FuchsiaPkgComponentUrl {
54    type Error = ParseError;
55
56    fn try_from(value: &str) -> Result<Self, Self::Error> {
57        Self::parse(value)
58    }
59}
60
61impl std::fmt::Display for FuchsiaPkgComponentUrl {
62    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63        write!(f, "{}#{}", self.package, self.resource.percent_encode())
64    }
65}
66
67impl serde::Serialize for FuchsiaPkgComponentUrl {
68    fn serialize<S: serde::Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
69        self.to_string().serialize(ser)
70    }
71}
72
73impl<'de> serde::Deserialize<'de> for FuchsiaPkgComponentUrl {
74    fn deserialize<D>(de: D) -> Result<Self, D::Error>
75    where
76        D: serde::Deserializer<'de>,
77    {
78        let url = String::deserialize(de)?;
79        Ok(Self::parse(&url).map_err(|err| serde::de::Error::custom(err))?)
80    }
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86    use assert_matches::assert_matches;
87    use std::convert::TryFrom as _;
88
89    #[test]
90    fn parse_err() {
91        for url in [
92            "fuchsia-boot://example.org/name#resource",
93            "fuchsia-pkg://name#resource",
94            "fuchsia-pkg://example.org#resource",
95            "fuchsia-pkg://example.org/#resource",
96            "fuchsia-pkg://example.org//#resource",
97            "fuchsia-pkg://example.org/name",
98            "fuchsia-pkg://exaMple.org/name#resource",
99            "fuchsia-pkg:///name#resource",
100            "fuchsia-pkg://#resource",
101            "example.org/name#resource",
102            "name/variant#resource",
103            "name",
104            "name?hash=0000000000000000000000000000000000000000000000000000000000000000",
105            "#resource",
106        ] {
107            assert_matches!(FuchsiaPkgComponentUrl::parse(url), Err(_), "the url {:?}", url);
108            assert_matches!(url.parse::<FuchsiaPkgComponentUrl>(), Err(_), "the url {:?}", url);
109            assert_matches!(FuchsiaPkgComponentUrl::try_from(url), Err(_), "the url {:?}", url);
110            assert_matches!(
111                serde_json::from_str::<FuchsiaPkgComponentUrl>(url),
112                Err(_),
113                "the url {:?}",
114                url
115            );
116        }
117    }
118
119    #[test]
120    fn parse_ok_absolute() {
121        for url in [
122            "fuchsia-pkg://example.org/name#resource",
123            "fuchsia-pkg://example.org/name#resource%09",
124            "fuchsia-pkg://example.org/name/variant#resource",
125            "fuchsia-pkg://example.org/name?hash=0000000000000000000000000000000000000000000000000000000000000000#resource",
126            "fuchsia-pkg://example.org/name/variant?hash=0000000000000000000000000000000000000000000000000000000000000000#resource",
127        ] {
128            let json_url = format!("\"{url}\"");
129            let validate = |parsed: &FuchsiaPkgComponentUrl| {
130                assert_eq!(parsed.to_string(), url);
131                assert_eq!(serde_json::to_string(&parsed).unwrap(), json_url);
132            };
133            validate(&FuchsiaPkgComponentUrl::parse(url).unwrap());
134            validate(&url.parse::<FuchsiaPkgComponentUrl>().unwrap());
135            validate(&FuchsiaPkgComponentUrl::try_from(url).unwrap());
136            validate(&serde_json::from_str::<FuchsiaPkgComponentUrl>(&json_url).unwrap());
137        }
138    }
139
140    #[test]
141    fn parse_ok_relative() {
142        for url in ["name#resource", "other-name#resource%09"] {
143            let json_url = format!("\"{url}\"");
144            let validate = |parsed: &FuchsiaPkgComponentUrl| {
145                assert_eq!(parsed.to_string(), url);
146                assert_eq!(serde_json::to_string(&parsed).unwrap(), json_url);
147            };
148            validate(&FuchsiaPkgComponentUrl::parse(url).unwrap());
149            validate(&url.parse::<FuchsiaPkgComponentUrl>().unwrap());
150            validate(&FuchsiaPkgComponentUrl::try_from(url).unwrap());
151            validate(&serde_json::from_str::<FuchsiaPkgComponentUrl>(&json_url).unwrap());
152        }
153    }
154}