fuchsia_pkg/
path.rs

1// Copyright 2019 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::ParsePackagePathError;
6pub use fuchsia_url::{PackageName, PackageVariant, MAX_PACKAGE_PATH_SEGMENT_BYTES};
7
8/// A Fuchsia Package Path. Paths must currently be "{name}/{variant}".
9#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Hash)]
10pub struct PackagePath {
11    name: PackageName,
12    variant: PackageVariant,
13}
14
15impl PackagePath {
16    pub const MAX_NAME_BYTES: usize = MAX_PACKAGE_PATH_SEGMENT_BYTES;
17    pub const MAX_VARIANT_BYTES: usize = MAX_PACKAGE_PATH_SEGMENT_BYTES;
18
19    pub fn from_name_and_variant(name: PackageName, variant: PackageVariant) -> Self {
20        Self { name, variant }
21    }
22
23    pub fn name(&self) -> &PackageName {
24        &self.name
25    }
26
27    pub fn variant(&self) -> &PackageVariant {
28        &self.variant
29    }
30
31    pub fn into_name_and_variant(self) -> (PackageName, PackageVariant) {
32        (self.name, self.variant)
33    }
34}
35
36impl std::str::FromStr for PackagePath {
37    type Err = ParsePackagePathError;
38    fn from_str(s: &str) -> Result<Self, Self::Err> {
39        let (name, variant_with_leading_slash) = match (s.find('/'), s.rfind('/')) {
40            (Option::Some(l), Option::Some(r)) if l == r => s.split_at(l),
41            (Option::Some(_), Option::Some(_)) => {
42                return Err(Self::Err::TooManySegments);
43            }
44            _ => {
45                return Err(Self::Err::TooFewSegments);
46            }
47        };
48        Ok(Self::from_name_and_variant(
49            name.parse().map_err(ParsePackagePathError::PackageName)?,
50            variant_with_leading_slash[1..]
51                .parse()
52                .map_err(ParsePackagePathError::PackageVariant)?,
53        ))
54    }
55}
56
57impl std::fmt::Display for PackagePath {
58    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
59        write!(f, "{}/{}", self.name, self.variant)
60    }
61}
62
63#[cfg(test)]
64mod test {
65    use super::*;
66    use crate::test::random_package_path;
67    use fuchsia_url::errors::PackagePathSegmentError;
68    use proptest::prelude::*;
69
70    #[test]
71    fn reject_invalid_name() {
72        let res: Result<PackagePath, _> = "/0".parse();
73        assert_eq!(res, Err(ParsePackagePathError::PackageName(PackagePathSegmentError::Empty)));
74    }
75
76    #[test]
77    fn reject_invalid_variant() {
78        let res: Result<PackagePath, _> = "valid_name/".parse();
79        assert_eq!(res, Err(ParsePackagePathError::PackageVariant(PackagePathSegmentError::Empty)));
80    }
81
82    #[test]
83    fn display() {
84        assert_eq!(
85            format!(
86                "{}",
87                PackagePath::from_name_and_variant(
88                    "package-name".parse().unwrap(),
89                    "package-variant".parse().unwrap()
90                )
91            ),
92            "package-name/package-variant"
93        );
94    }
95
96    #[test]
97    fn accessors() {
98        let name = "package-name".parse::<PackageName>().unwrap();
99        let variant = "package-variant".parse::<PackageVariant>().unwrap();
100        let path = PackagePath::from_name_and_variant(name.clone(), variant.clone());
101        assert_eq!(path.name(), &name);
102        assert_eq!(path.variant(), &variant);
103    }
104
105    proptest! {
106        #![proptest_config(ProptestConfig{
107            failure_persistence: None,
108            ..Default::default()
109        })]
110
111        #[test]
112        fn display_from_str_round_trip(path in random_package_path()) {
113            prop_assert_eq!(
114                &path,
115                &path.to_string().parse().unwrap()
116            );
117        }
118    }
119}