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