fuchsia_url/
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::{AbsolutePackageUrl, RelativePackageUrl, UrlParts};
7
8/// A URL locating a Fuchsia package. Can be either absolute or relative.
9/// See `AbsolutePackageUrl` 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 PackageUrl {
13    Absolute(AbsolutePackageUrl),
14    Relative(RelativePackageUrl),
15}
16
17impl PackageUrl {
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(AbsolutePackageUrl::from_parts(parts)?)
23        } else {
24            Self::Relative(RelativePackageUrl::from_parts(parts)?)
25        })
26    }
27}
28
29impl std::str::FromStr for PackageUrl {
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 PackageUrl {
38    type Error = ParseError;
39
40    fn try_from(value: &str) -> Result<Self, Self::Error> {
41        Self::parse(value)
42    }
43}
44
45impl From<AbsolutePackageUrl> for PackageUrl {
46    fn from(absolute: AbsolutePackageUrl) -> Self {
47        Self::Absolute(absolute)
48    }
49}
50
51impl From<RelativePackageUrl> for PackageUrl {
52    fn from(relative: RelativePackageUrl) -> Self {
53        Self::Relative(relative)
54    }
55}
56
57impl std::fmt::Display for PackageUrl {
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 PackageUrl {
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 PackageUrl {
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!(PackageUrl::parse(url), Err(_), "the url {:?}", url);
104            assert_matches!(url.parse::<PackageUrl>(), Err(_), "the url {:?}", url);
105            assert_matches!(PackageUrl::try_from(url), Err(_), "the url {:?}", url);
106            assert_matches!(serde_json::from_str::<PackageUrl>(url), Err(_), "the url {:?}", url);
107        }
108    }
109
110    #[test]
111    fn parse_ok_absolute() {
112        for url in [
113            "fuchsia-pkg://example.org/name",
114            "fuchsia-pkg://example.org/name/variant",
115            "fuchsia-pkg://example.org/name?hash=0000000000000000000000000000000000000000000000000000000000000000",
116            "fuchsia-pkg://example.org/name/variant?hash=0000000000000000000000000000000000000000000000000000000000000000",
117        ] {
118            let json_url = format!("\"{url}\"");
119            let validate = |parsed: &PackageUrl| {
120                assert_eq!(parsed.to_string(), url);
121                assert_eq!(serde_json::to_string(&parsed).unwrap(), json_url);
122            };
123            validate(&PackageUrl::parse(url).unwrap());
124            validate(&url.parse::<PackageUrl>().unwrap());
125            validate(&PackageUrl::try_from(url).unwrap());
126            validate(&serde_json::from_str::<PackageUrl>(&json_url).unwrap());
127        }
128    }
129
130    #[test]
131    fn parse_ok_relative() {
132        for url in ["name", "other3-name"] {
133            let json_url = format!("\"{url}\"");
134            let validate = |parsed: &PackageUrl| {
135                assert_eq!(parsed.to_string(), url);
136                assert_eq!(serde_json::to_string(&parsed).unwrap(), json_url);
137            };
138            validate(&PackageUrl::parse(url).unwrap());
139            validate(&url.parse::<PackageUrl>().unwrap());
140            validate(&PackageUrl::try_from(url).unwrap());
141            validate(&serde_json::from_str::<PackageUrl>(&json_url).unwrap());
142        }
143    }
144}