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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
use std::fmt;
use std::str::FromStr;

use anyhow::{anyhow, Context};
use semver::Version;
use serde::de::{Deserialize, Deserializer, Error, Visitor};
use serde::ser::{Serialize, Serializer};

use crate::package_name::PackageName;

/// Refers to a specific version of a package. Package IDs consist of a scope,
/// name, and SemVer version.
///
/// All rules for package names apply to the first portion of a package ID.
///
/// Examples of package IDs:
/// * `hello/world@1.2.3`
/// * `miss-frizz/magic-school-bus@0.2.3-pre1+build102312`
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct PackageId {
    name: PackageName,
    version: Version,
}

impl PackageId {
    pub fn new(name: PackageName, version: Version) -> Self {
        Self { name, version }
    }

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

    pub fn version(&self) -> &Version {
        &self.version
    }

    pub fn into_parts(self) -> (PackageName, Version) {
        (self.name, self.version)
    }
}

impl fmt::Display for PackageId {
    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        write!(formatter, "{}@{}", self.name, self.version)
    }
}

impl FromStr for PackageId {
    type Err = anyhow::Error;

    fn from_str(value: &str) -> anyhow::Result<Self> {
        const BAD_FORMAT_MSG: &str = "a package ID is of the form SCOPE/NAME@VERSION";

        let mut first_half = value.splitn(2, '/');
        let scope = first_half.next().ok_or_else(|| anyhow!(BAD_FORMAT_MSG))?;
        let name_and_version = first_half.next().ok_or_else(|| anyhow!(BAD_FORMAT_MSG))?;

        let mut second_half = name_and_version.splitn(2, '@');
        let name = second_half.next().ok_or_else(|| anyhow!(BAD_FORMAT_MSG))?;
        let version = second_half
            .next()
            .ok_or_else(|| anyhow!(BAD_FORMAT_MSG))?
            .parse()
            .context("could not parse version")?;

        let package_name = PackageName::new(scope, name).context(BAD_FORMAT_MSG)?;
        Ok(PackageId::new(package_name, version))
    }
}

impl Serialize for PackageId {
    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        let combined_name = format!(
            "{}/{}@{}",
            self.name().scope(),
            self.name().name(),
            self.version()
        );
        serializer.serialize_str(&combined_name)
    }
}

impl<'de> Deserialize<'de> for PackageId {
    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
        deserializer.deserialize_str(PackageIdVisitor)
    }
}

struct PackageIdVisitor;

impl<'de> Visitor<'de> for PackageIdVisitor {
    type Value = PackageId;

    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        write!(formatter, "a package ID of the form SCOPE/NAME@VERSION")
    }

    fn visit_str<E: Error>(self, value: &str) -> Result<Self::Value, E> {
        value.parse().map_err(|err| E::custom(err))
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn new() {
        let id = PackageId::new(
            PackageName::new("foo", "bar").unwrap(),
            Version::new(1, 2, 3),
        );
        assert_eq!(id.name().scope(), "foo");
        assert_eq!(id.name().name(), "bar");
        assert_eq!(id.version(), &Version::new(1, 2, 3));
    }

    #[test]
    fn display() {
        let id = PackageId::new(
            PackageName::new("hello", "world").unwrap(),
            Version::new(0, 2, 3),
        );
        assert_eq!(id.to_string(), "hello/world@0.2.3");
    }

    #[test]
    fn parse() {
        let hello: PackageId = "hello/world@1.2.3".parse().unwrap();
        assert_eq!(hello.name().scope(), "hello");
        assert_eq!(hello.name().name(), "world");
        assert_eq!(hello.version(), &Version::new(1, 2, 3));
    }

    #[test]
    fn parse_invalid() {
        // Package IDs require a version.
        let no_version: Result<PackageId, _> = "hello/world".parse();
        no_version.unwrap_err();
        let no_version_at: Result<PackageId, _> = "hello/world@".parse();
        no_version_at.unwrap_err();

        // Incomplete versions are not allowed.
        let not_enough_version: Result<PackageId, _> = "foo/bar@2".parse();
        not_enough_version.unwrap_err();
    }

    #[test]
    fn serialization() {
        let name = PackageName::new("lpghatguy", "asink").unwrap();
        let package_id = PackageId::new(name, Version::new(2, 3, 1));

        let serialized = serde_json::to_string(&package_id).unwrap();
        assert_eq!(serialized, "\"lpghatguy/asink@2.3.1\"");

        let deserialized: PackageId = serde_json::from_str(&serialized).unwrap();
        assert_eq!(deserialized, package_id);
    }
}