use std::{borrow::Borrow, fmt::Display};
use serde::Deserialize;
use thiserror::Error;
use crate::{utils::TryFromStrVisitor, version::PackageVersion};
fn check_package_name(package: &str) -> Result<(), PackageError> {
if package.len() < 2 {
return Err(PackageError::InvalidNameLength);
}
if !package
.chars()
.enumerate()
.all(|(i, c)| c.is_ascii_lowercase() || c.is_ascii_digit() || (i > 0 && ".+-".contains(c)))
{
Err(PackageError::InvalidName)
} else {
Ok(())
}
}
#[derive(Clone, Copy, Debug, Error)]
pub enum PackageError {
#[error("package name too short")]
InvalidNameLength,
#[error("package name contains invalid character")]
InvalidName,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct PackageName(String);
impl TryFrom<&str> for PackageName {
type Error = PackageError;
fn try_from(package: &str) -> Result<Self, Self::Error> {
check_package_name(package).map(|_| Self(package.to_owned()))
}
}
impl TryFrom<String> for PackageName {
type Error = PackageError;
fn try_from(package: String) -> Result<Self, Self::Error> {
check_package_name(&package).map(|_| Self(package))
}
}
impl AsRef<str> for PackageName {
fn as_ref(&self) -> &str {
self.0.as_str()
}
}
impl Borrow<str> for PackageName {
fn borrow(&self) -> &str {
self.0.as_str()
}
}
impl PartialEq<&str> for PackageName {
fn eq(&self, other: &&str) -> bool {
self.0.eq(other)
}
}
impl PartialEq<String> for PackageName {
fn eq(&self, other: &String) -> bool {
self.0.eq(other)
}
}
impl Display for PackageName {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl<'de> Deserialize<'de> for PackageName {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_str(TryFromStrVisitor::new("a package name"))
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct VersionedPackage {
pub package: PackageName,
pub version: PackageVersion,
}
impl AsRef<PackageName> for VersionedPackage {
fn as_ref(&self) -> &PackageName {
&self.package
}
}
impl AsRef<PackageVersion> for VersionedPackage {
fn as_ref(&self) -> &PackageVersion {
&self.version
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn valid_package_names() {
assert!(PackageName::try_from("zathura").is_ok());
assert!(PackageName::try_from("0ad").is_ok());
assert!(PackageName::try_from("zathura-pdf").is_ok());
}
#[test]
fn invalid_package_names() {
assert!(PackageName::try_from("z").is_err());
assert!(PackageName::try_from("-ad").is_err());
}
}