Skip to main content

pacenotes_shared/
package_ref.rs

1/// A package's `directory/name`: its download path, its cache and ledger key,
2/// and the wire form of a `requires` entry.
3#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
4pub struct PackageRef {
5    pub directory: String,
6    pub name: String,
7}
8
9impl PackageRef {
10    /// Splits on the first `/`, since the name is itself a path, and rejects
11    /// anything `valid_name` rejects.
12    pub fn parse(text: &str) -> Option<PackageRef> {
13        let (directory, name) = text.split_once('/')?;
14        if directory.is_empty() || directory.contains('\\') || !crate::valid_name(name) {
15            return None;
16        }
17        Some(PackageRef {
18            directory: directory.to_string(),
19            name: name.to_string(),
20        })
21    }
22}
23
24impl std::fmt::Display for PackageRef {
25    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26        write!(formatter, "{}/{}", self.directory, self.name)
27    }
28}
29
30#[cfg(test)]
31mod tests {
32    #[test]
33    fn a_ref_splits_directory_from_a_path_shaped_name() {
34        let reference = super::PackageRef::parse("public/codriver/polish/male/Kamil").unwrap();
35        assert_eq!(reference.directory, "public");
36        assert_eq!(reference.name, "codriver/polish/male/Kamil");
37        assert_eq!(reference.to_string(), "public/codriver/polish/male/Kamil");
38    }
39
40    #[test]
41    fn a_ref_round_trips_through_its_text_form() {
42        let reference = super::PackageRef::parse("premium-voices/pro").unwrap();
43        assert_eq!(reference.directory, "premium-voices");
44        assert_eq!(reference.name, "pro");
45        assert_eq!(reference.to_string(), "premium-voices/pro");
46    }
47
48    #[test]
49    fn a_ref_can_never_widen_into_a_filesystem_path() {
50        for bad in [
51            "pro",
52            "public",
53            "/pro",
54            "/codriver/Kamil",
55            "public/",
56            "public/../secrets",
57            "public\\pro",
58            "public\\codriver/Kamil",
59            "..\\..\\data/auth.db",
60        ] {
61            assert!(
62                super::PackageRef::parse(bad).is_none(),
63                "{bad:?} should be rejected"
64            );
65        }
66    }
67}