1use std::fmt;
2use std::str::FromStr;
3
4use anyhow::{anyhow, Context};
5use semver::Version;
6use serde::de::{Deserialize, Deserializer, Error, Visitor};
7use serde::ser::{Serialize, Serializer};
8
9use crate::package_name::PackageName;
10
11#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
20pub struct PackageId {
21 name: PackageName,
22 version: Version,
23}
24
25impl PackageId {
26 pub fn new(name: PackageName, version: Version) -> Self {
27 Self { name, version }
28 }
29
30 pub fn name(&self) -> &PackageName {
31 &self.name
32 }
33
34 pub fn version(&self) -> &Version {
35 &self.version
36 }
37
38 pub fn into_parts(self) -> (PackageName, Version) {
39 (self.name, self.version)
40 }
41}
42
43impl fmt::Display for PackageId {
44 fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
45 write!(formatter, "{}@{}", self.name, self.version)
46 }
47}
48
49impl FromStr for PackageId {
50 type Err = anyhow::Error;
51
52 fn from_str(value: &str) -> anyhow::Result<Self> {
53 const BAD_FORMAT_MSG: &str = "a package ID is of the form SCOPE/NAME@VERSION";
54
55 let mut first_half = value.splitn(2, '/');
56 let scope = first_half.next().ok_or_else(|| anyhow!(BAD_FORMAT_MSG))?;
57 let name_and_version = first_half.next().ok_or_else(|| anyhow!(BAD_FORMAT_MSG))?;
58
59 let mut second_half = name_and_version.splitn(2, '@');
60 let name = second_half.next().ok_or_else(|| anyhow!(BAD_FORMAT_MSG))?;
61 let version = second_half
62 .next()
63 .ok_or_else(|| anyhow!(BAD_FORMAT_MSG))?
64 .parse()
65 .context("could not parse version")?;
66
67 let package_name = PackageName::new(scope, name).context(BAD_FORMAT_MSG)?;
68 Ok(PackageId::new(package_name, version))
69 }
70}
71
72impl Serialize for PackageId {
73 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
74 let combined_name = format!(
75 "{}/{}@{}",
76 self.name().scope(),
77 self.name().name(),
78 self.version()
79 );
80 serializer.serialize_str(&combined_name)
81 }
82}
83
84impl<'de> Deserialize<'de> for PackageId {
85 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
86 deserializer.deserialize_str(PackageIdVisitor)
87 }
88}
89
90struct PackageIdVisitor;
91
92impl<'de> Visitor<'de> for PackageIdVisitor {
93 type Value = PackageId;
94
95 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
96 write!(formatter, "a package ID of the form SCOPE/NAME@VERSION")
97 }
98
99 fn visit_str<E: Error>(self, value: &str) -> Result<Self::Value, E> {
100 value.parse().map_err(|err| E::custom(err))
101 }
102}
103
104#[cfg(test)]
105mod test {
106 use super::*;
107
108 #[test]
109 fn new() {
110 let id = PackageId::new(
111 PackageName::new("foo", "bar").unwrap(),
112 Version::new(1, 2, 3),
113 );
114 assert_eq!(id.name().scope(), "foo");
115 assert_eq!(id.name().name(), "bar");
116 assert_eq!(id.version(), &Version::new(1, 2, 3));
117 }
118
119 #[test]
120 fn display() {
121 let id = PackageId::new(
122 PackageName::new("hello", "world").unwrap(),
123 Version::new(0, 2, 3),
124 );
125 assert_eq!(id.to_string(), "hello/world@0.2.3");
126 }
127
128 #[test]
129 fn parse() {
130 let hello: PackageId = "hello/world@1.2.3".parse().unwrap();
131 assert_eq!(hello.name().scope(), "hello");
132 assert_eq!(hello.name().name(), "world");
133 assert_eq!(hello.version(), &Version::new(1, 2, 3));
134 }
135
136 #[test]
137 fn parse_invalid() {
138 let no_version: Result<PackageId, _> = "hello/world".parse();
140 no_version.unwrap_err();
141 let no_version_at: Result<PackageId, _> = "hello/world@".parse();
142 no_version_at.unwrap_err();
143
144 let not_enough_version: Result<PackageId, _> = "foo/bar@2".parse();
146 not_enough_version.unwrap_err();
147 }
148
149 #[test]
150 fn serialization() {
151 let name = PackageName::new("lpghatguy", "asink").unwrap();
152 let package_id = PackageId::new(name, Version::new(2, 3, 1));
153
154 let serialized = serde_json::to_string(&package_id).unwrap();
155 assert_eq!(serialized, "\"lpghatguy/asink@2.3.1\"");
156
157 let deserialized: PackageId = serde_json::from_str(&serialized).unwrap();
158 assert_eq!(deserialized, package_id);
159 }
160}