1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

use crate::errors::ParsePackagePathError;
pub use fuchsia_url::{PackageName, PackageVariant, MAX_PACKAGE_PATH_SEGMENT_BYTES};

/// A Fuchsia Package Path. Paths must currently be "{name}/{variant}".
#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Hash)]
pub struct PackagePath {
    name: PackageName,
    variant: PackageVariant,
}

impl PackagePath {
    pub const MAX_NAME_BYTES: usize = MAX_PACKAGE_PATH_SEGMENT_BYTES;
    pub const MAX_VARIANT_BYTES: usize = MAX_PACKAGE_PATH_SEGMENT_BYTES;

    pub fn from_name_and_variant(name: PackageName, variant: PackageVariant) -> Self {
        Self { name, variant }
    }

    pub fn name(&self) -> &PackageName {
        &self.name
    }

    pub fn variant(&self) -> &PackageVariant {
        &self.variant
    }

    pub fn into_name_and_variant(self) -> (PackageName, PackageVariant) {
        (self.name, self.variant)
    }
}

impl std::str::FromStr for PackagePath {
    type Err = ParsePackagePathError;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let (name, variant_with_leading_slash) = match (s.find('/'), s.rfind('/')) {
            (Option::Some(l), Option::Some(r)) if l == r => s.split_at(l),
            (Option::Some(_), Option::Some(_)) => {
                return Err(Self::Err::TooManySegments);
            }
            _ => {
                return Err(Self::Err::TooFewSegments);
            }
        };
        Ok(Self::from_name_and_variant(
            name.parse().map_err(ParsePackagePathError::PackageName)?,
            variant_with_leading_slash[1..]
                .parse()
                .map_err(ParsePackagePathError::PackageVariant)?,
        ))
    }
}

impl std::fmt::Display for PackagePath {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}/{}", self.name, self.variant)
    }
}

#[cfg(test)]
mod test {
    use {
        super::*, crate::test::random_package_path, fuchsia_url::errors::PackagePathSegmentError,
        proptest::prelude::*,
    };

    #[test]
    fn reject_invalid_name() {
        let res: Result<PackagePath, _> = "/0".parse();
        assert_eq!(res, Err(ParsePackagePathError::PackageName(PackagePathSegmentError::Empty)));
    }

    #[test]
    fn reject_invalid_variant() {
        let res: Result<PackagePath, _> = "valid_name/".parse();
        assert_eq!(res, Err(ParsePackagePathError::PackageVariant(PackagePathSegmentError::Empty)));
    }

    #[test]
    fn display() {
        assert_eq!(
            format!(
                "{}",
                PackagePath::from_name_and_variant(
                    "package-name".parse().unwrap(),
                    "package-variant".parse().unwrap()
                )
            ),
            "package-name/package-variant"
        );
    }

    #[test]
    fn accessors() {
        let name = "package-name".parse::<PackageName>().unwrap();
        let variant = "package-variant".parse::<PackageVariant>().unwrap();
        let path = PackagePath::from_name_and_variant(name.clone(), variant.clone());
        assert_eq!(path.name(), &name);
        assert_eq!(path.variant(), &variant);
    }

    proptest! {
        #![proptest_config(ProptestConfig{
            failure_persistence: None,
            ..Default::default()
        })]

        #[test]
        fn display_from_str_round_trip(path in random_package_path()) {
            prop_assert_eq!(
                &path,
                &path.to_string().parse().unwrap()
            );
        }
    }
}