assorted_debian_utils/
package.rs1use std::fmt::Display;
9
10use serde::Deserialize;
11use thiserror::Error;
12
13use crate::{utils::TryFromStrVisitor, version::PackageVersion};
14
15fn check_package_name(package: &str) -> Result<(), PackageError> {
16 if package.len() < 2 {
18 return Err(PackageError::InvalidNameLength);
19 }
20
21 if !package.chars().enumerate().all(|(i, c)| {
22 if c.is_ascii_lowercase() || c.is_ascii_digit() {
23 return true;
24 }
25 i > 0 && ".+-".contains(c)
26 }) {
27 return Err(PackageError::InvalidName);
28 }
29
30 Ok(())
31}
32
33#[derive(Clone, Copy, Debug, Error)]
35pub enum PackageError {
36 #[error("package name too short")]
37 InvalidNameLength,
39 #[error("package name contains invalid character")]
40 InvalidName,
42}
43
44#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
46pub struct PackageName(String);
47
48impl TryFrom<&str> for PackageName {
49 type Error = PackageError;
50
51 fn try_from(package: &str) -> Result<Self, Self::Error> {
52 check_package_name(package).map(|_| Self(package.to_owned()))
53 }
54}
55
56impl TryFrom<String> for PackageName {
57 type Error = PackageError;
58
59 fn try_from(package: String) -> Result<Self, Self::Error> {
60 check_package_name(&package).map(|_| Self(package))
61 }
62}
63
64impl AsRef<str> for PackageName {
65 fn as_ref(&self) -> &str {
66 self.0.as_str()
67 }
68}
69
70impl PartialEq<&str> for PackageName {
71 fn eq(&self, other: &&str) -> bool {
72 self.0.eq(other)
73 }
74}
75
76impl PartialEq<String> for PackageName {
77 fn eq(&self, other: &String) -> bool {
78 self.0.eq(other)
79 }
80}
81
82impl Display for PackageName {
83 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84 write!(f, "{}", self.0)
85 }
86}
87
88impl<'de> Deserialize<'de> for PackageName {
89 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
90 where
91 D: serde::Deserializer<'de>,
92 {
93 deserializer.deserialize_str(TryFromStrVisitor::new("a package name"))
94 }
95}
96
97#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
99pub struct VersionedPackage {
100 pub package: PackageName,
102 pub version: PackageVersion,
104}
105
106impl AsRef<PackageName> for VersionedPackage {
107 fn as_ref(&self) -> &PackageName {
108 &self.package
109 }
110}
111
112impl AsRef<PackageVersion> for VersionedPackage {
113 fn as_ref(&self) -> &PackageVersion {
114 &self.version
115 }
116}
117
118#[cfg(test)]
119mod test {
120 use super::*;
121
122 #[test]
123 fn valid_package_names() {
124 assert!(PackageName::try_from("zathura").is_ok());
125 assert!(PackageName::try_from("0ad").is_ok());
126 assert!(PackageName::try_from("zathura-pdf").is_ok());
127 }
128
129 #[test]
130 fn invalid_package_names() {
131 assert!(PackageName::try_from("z").is_err());
132 assert!(PackageName::try_from("-ad").is_err());
133 }
134}