Skip to main content

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