Skip to main content

runmat_package/identity/
registry.rs

1use crate::policy::{validate_canonical_segment, REGISTRY_SEGMENT_MAX_LEN};
2use crate::IdentityError;
3use serde::{Deserialize, Serialize};
4use std::fmt::{Display, Formatter};
5use std::str::FromStr;
6use url::Url;
7
8#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
9#[serde(try_from = "String", into = "String")]
10pub struct RegistryId(String);
11
12impl RegistryId {
13    pub fn new(value: impl Into<String>) -> Result<Self, IdentityError> {
14        let value = value.into();
15        validate_canonical_segment(&value, "registry name", REGISTRY_SEGMENT_MAX_LEN)?;
16        Ok(Self(value))
17    }
18
19    pub fn as_str(&self) -> &str {
20        &self.0
21    }
22}
23
24impl Default for RegistryId {
25    fn default() -> Self {
26        Self("default".to_string())
27    }
28}
29
30impl Display for RegistryId {
31    fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
32        formatter.write_str(&self.0)
33    }
34}
35
36impl FromStr for RegistryId {
37    type Err = IdentityError;
38
39    fn from_str(value: &str) -> Result<Self, Self::Err> {
40        Self::new(value)
41    }
42}
43
44impl TryFrom<String> for RegistryId {
45    type Error = IdentityError;
46
47    fn try_from(value: String) -> Result<Self, Self::Error> {
48        Self::new(value)
49    }
50}
51
52impl From<RegistryId> for String {
53    fn from(value: RegistryId) -> Self {
54        value.0
55    }
56}
57
58#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
59#[serde(try_from = "String", into = "String")]
60pub struct RegistryOrigin(String);
61
62impl RegistryOrigin {
63    pub fn new(value: &str) -> Result<Self, IdentityError> {
64        let mut url =
65            Url::parse(value).map_err(|_| invalid_source(value, "must be an HTTPS URL"))?;
66        if url.scheme() != "https"
67            || !url.username().is_empty()
68            || url.password().is_some()
69            || url.query().is_some()
70            || url.fragment().is_some()
71            || !matches!(url.path(), "" | "/")
72        {
73            return Err(invalid_source(
74                value,
75                "must be a credential-free HTTPS origin without a path, query, or fragment",
76            ));
77        }
78        let host = url
79            .host_str()
80            .ok_or_else(|| invalid_source(value, "must include a host"))?
81            .to_ascii_lowercase();
82        url.set_host(Some(&host))
83            .map_err(|_| invalid_source(value, "contains an invalid host"))?;
84        if url.port_or_known_default() == Some(443) {
85            url.set_port(None)
86                .map_err(|_| invalid_source(value, "contains an invalid port"))?;
87        }
88        url.set_path("");
89        Ok(Self(url.to_string().trim_end_matches('/').to_string()))
90    }
91
92    pub fn as_str(&self) -> &str {
93        &self.0
94    }
95}
96
97impl Display for RegistryOrigin {
98    fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
99        formatter.write_str(&self.0)
100    }
101}
102
103impl FromStr for RegistryOrigin {
104    type Err = IdentityError;
105
106    fn from_str(value: &str) -> Result<Self, Self::Err> {
107        Self::new(value)
108    }
109}
110
111impl TryFrom<String> for RegistryOrigin {
112    type Error = IdentityError;
113
114    fn try_from(value: String) -> Result<Self, Self::Error> {
115        Self::new(&value)
116    }
117}
118
119impl From<RegistryOrigin> for String {
120    fn from(value: RegistryOrigin) -> Self {
121        value.0
122    }
123}
124
125#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
126#[serde(try_from = "String", into = "String")]
127pub struct RegistryReleaseId(String);
128
129impl RegistryReleaseId {
130    pub fn new(value: impl Into<String>) -> Result<Self, IdentityError> {
131        let value = value.into();
132        let suffix = value
133            .strip_prefix("rel_")
134            .ok_or_else(|| invalid_source(&value, "release ID must start with `rel_`"))?;
135        if suffix.len() != 32
136            || suffix
137                .bytes()
138                .any(|byte| !byte.is_ascii_hexdigit() || byte.is_ascii_uppercase())
139        {
140            return Err(invalid_source(
141                &value,
142                "release ID must contain 32 lowercase hexadecimal digits",
143            ));
144        }
145        Ok(Self(value))
146    }
147
148    pub fn as_str(&self) -> &str {
149        &self.0
150    }
151}
152
153impl Display for RegistryReleaseId {
154    fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
155        formatter.write_str(&self.0)
156    }
157}
158
159impl FromStr for RegistryReleaseId {
160    type Err = IdentityError;
161
162    fn from_str(value: &str) -> Result<Self, Self::Err> {
163        Self::new(value)
164    }
165}
166
167impl TryFrom<String> for RegistryReleaseId {
168    type Error = IdentityError;
169
170    fn try_from(value: String) -> Result<Self, Self::Error> {
171        Self::new(value)
172    }
173}
174
175impl From<RegistryReleaseId> for String {
176    fn from(value: RegistryReleaseId) -> Self {
177        value.0
178    }
179}
180
181fn invalid_source(value: &str, reason: &'static str) -> IdentityError {
182    IdentityError::InvalidRegistrySource {
183        value: value.to_string(),
184        reason,
185    }
186}