Skip to main content

runmat_package/identity/
package.rs

1use super::RegistryId;
2use crate::policy::{validate_canonical_segment, PACKAGE_ALIAS_MAX_LEN, PACKAGE_SEGMENT_MAX_LEN};
3use crate::IdentityError;
4use serde::{Deserialize, Serialize};
5use std::fmt::{Display, Formatter};
6use std::str::FromStr;
7
8#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
9#[serde(try_from = "String", into = "String")]
10pub struct CanonicalPackageId {
11    registry: RegistryId,
12    organization: String,
13    name: String,
14}
15
16impl CanonicalPackageId {
17    pub fn new(
18        registry: RegistryId,
19        organization: impl Into<String>,
20        name: impl Into<String>,
21    ) -> Result<Self, IdentityError> {
22        let organization = organization.into();
23        let name = name.into();
24        validate_canonical_segment(
25            &organization,
26            "package organization",
27            PACKAGE_SEGMENT_MAX_LEN,
28        )?;
29        validate_canonical_segment(&name, "package name", PACKAGE_SEGMENT_MAX_LEN)?;
30        Ok(Self {
31            registry,
32            organization,
33            name,
34        })
35    }
36
37    pub fn registry(&self) -> &RegistryId {
38        &self.registry
39    }
40
41    pub fn organization(&self) -> &str {
42        &self.organization
43    }
44
45    pub fn name(&self) -> &str {
46        &self.name
47    }
48}
49
50impl Display for CanonicalPackageId {
51    fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
52        write!(
53            formatter,
54            "{}:{}/{}",
55            self.registry, self.organization, self.name
56        )
57    }
58}
59
60impl FromStr for CanonicalPackageId {
61    type Err = IdentityError;
62
63    fn from_str(value: &str) -> Result<Self, Self::Err> {
64        let Some((registry, package)) = value.split_once(':') else {
65            return Err(invalid_package(value, "missing registry prefix"));
66        };
67        let Some((organization, name)) = package.split_once('/') else {
68            return Err(invalid_package(value, "missing organization namespace"));
69        };
70        if name.contains('/') {
71            return Err(invalid_package(
72                value,
73                "contains more than two package segments",
74            ));
75        }
76        Self::new(registry.parse()?, organization, name)
77    }
78}
79
80impl TryFrom<String> for CanonicalPackageId {
81    type Error = IdentityError;
82
83    fn try_from(value: String) -> Result<Self, Self::Error> {
84        value.parse()
85    }
86}
87
88impl From<CanonicalPackageId> for String {
89    fn from(value: CanonicalPackageId) -> Self {
90        value.to_string()
91    }
92}
93
94#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
95#[serde(try_from = "String", into = "String")]
96pub struct PackageAlias(String);
97
98impl PackageAlias {
99    pub fn new(value: impl Into<String>) -> Result<Self, IdentityError> {
100        let value = value.into();
101        validate_canonical_segment(&value, "dependency alias", PACKAGE_ALIAS_MAX_LEN)?;
102        Ok(Self(value))
103    }
104
105    pub fn as_str(&self) -> &str {
106        &self.0
107    }
108}
109
110impl Display for PackageAlias {
111    fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
112        formatter.write_str(&self.0)
113    }
114}
115
116impl FromStr for PackageAlias {
117    type Err = IdentityError;
118
119    fn from_str(value: &str) -> Result<Self, Self::Err> {
120        Self::new(value)
121    }
122}
123
124impl TryFrom<String> for PackageAlias {
125    type Error = IdentityError;
126
127    fn try_from(value: String) -> Result<Self, Self::Error> {
128        Self::new(value)
129    }
130}
131
132impl From<PackageAlias> for String {
133    fn from(value: PackageAlias) -> Self {
134        value.0
135    }
136}
137
138fn invalid_package(value: &str, reason: &'static str) -> IdentityError {
139    IdentityError::InvalidName {
140        kind: "canonical package ID",
141        value: value.to_string(),
142        reason,
143    }
144}