1use crate::errors::PackagePathSegmentError;
6use serde::{Deserialize, Serialize};
7use std::convert::TryInto as _;
8
9pub const MAX_PACKAGE_PATH_SEGMENT_BYTES: usize = 255;
10
11pub fn validate_package_path_segment(string: &str) -> Result<(), PackagePathSegmentError> {
13 if string.is_empty() {
14 return Err(PackagePathSegmentError::Empty);
15 }
16 if string.len() > MAX_PACKAGE_PATH_SEGMENT_BYTES {
17 return Err(PackagePathSegmentError::TooLong(string.len()));
18 }
19 if let Some(invalid_byte) = string.bytes().find(|&b| {
20 !(b.is_ascii_lowercase() || b.is_ascii_digit() || b == b'-' || b == b'.' || b == b'_')
21 }) {
22 return Err(PackagePathSegmentError::InvalidCharacter { character: invalid_byte.into() });
23 }
24 if string == "." {
25 return Err(PackagePathSegmentError::DotSegment);
26 }
27 if string == ".." {
28 return Err(PackagePathSegmentError::DotDotSegment);
29 }
30
31 Ok(())
32}
33
34#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Hash, Serialize)]
37pub struct PackageName(String);
38
39impl std::str::FromStr for PackageName {
40 type Err = PackagePathSegmentError;
41 fn from_str(s: &str) -> Result<Self, Self::Err> {
42 let () = validate_package_path_segment(s)?;
43 Ok(Self(s.into()))
44 }
45}
46
47impl TryFrom<String> for PackageName {
48 type Error = PackagePathSegmentError;
49 fn try_from(value: String) -> Result<Self, Self::Error> {
50 let () = validate_package_path_segment(&value)?;
51 Ok(Self(value))
52 }
53}
54
55impl TryFrom<&crate::Path> for PackageName {
56 type Error = crate::ParseError;
57 fn try_from(path: &crate::Path) -> Result<Self, Self::Error> {
58 path.parse().map_err(Self::Error::InvalidName)
60 }
61}
62
63impl From<PackageName> for String {
64 fn from(name: PackageName) -> Self {
65 name.0
66 }
67}
68
69impl From<&PackageName> for String {
70 fn from(name: &PackageName) -> Self {
71 name.0.clone()
72 }
73}
74
75impl std::fmt::Display for PackageName {
76 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77 write!(f, "{}", self.0)
78 }
79}
80
81impl AsRef<str> for PackageName {
82 fn as_ref(&self) -> &str {
83 &self.0
84 }
85}
86
87impl<'de> Deserialize<'de> for PackageName {
88 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
89 where
90 D: serde::Deserializer<'de>,
91 {
92 let value = String::deserialize(deserializer)?;
93 value
94 .try_into()
95 .map_err(|e| serde::de::Error::custom(format!("invalid package name: {}", e)))
96 }
97}
98
99#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Hash, Serialize)]
101pub struct PackageVariant(String);
102
103impl PackageVariant {
104 pub const ZERO_STR: &str = "0";
106
107 pub fn zero() -> Self {
109 Self::ZERO_STR.parse().expect("\"0\" is a valid variant")
110 }
111
112 pub fn is_zero(&self) -> bool {
114 self.0 == Self::ZERO_STR
115 }
116}
117
118impl std::str::FromStr for PackageVariant {
119 type Err = PackagePathSegmentError;
120 fn from_str(s: &str) -> Result<Self, Self::Err> {
121 let () = validate_package_path_segment(s)?;
122 Ok(Self(s.into()))
123 }
124}
125
126impl TryFrom<String> for PackageVariant {
127 type Error = PackagePathSegmentError;
128 fn try_from(value: String) -> Result<Self, Self::Error> {
129 let () = validate_package_path_segment(&value)?;
130 Ok(Self(value))
131 }
132}
133
134impl std::fmt::Display for PackageVariant {
135 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
136 write!(f, "{}", self.0)
137 }
138}
139
140impl AsRef<str> for PackageVariant {
141 fn as_ref(&self) -> &str {
142 &self.0
143 }
144}
145
146impl<'de> Deserialize<'de> for PackageVariant {
147 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
148 where
149 D: serde::Deserializer<'de>,
150 {
151 let value = String::deserialize(deserializer)?;
152 value
153 .try_into()
154 .map_err(|e| serde::de::Error::custom(format!("invalid package variant {}", e)))
155 }
156}
157
158#[cfg(test)]
159mod test_validate_package_path_segment {
160 use super::*;
161 use crate::test::random_package_segment;
162 use proptest::prelude::*;
163
164 #[test]
165 fn reject_empty_segment() {
166 assert_eq!(validate_package_path_segment(""), Err(PackagePathSegmentError::Empty));
167 }
168
169 #[test]
170 fn reject_dot_segment() {
171 assert_eq!(validate_package_path_segment("."), Err(PackagePathSegmentError::DotSegment));
172 }
173
174 #[test]
175 fn reject_dot_dot_segment() {
176 assert_eq!(
177 validate_package_path_segment(".."),
178 Err(PackagePathSegmentError::DotDotSegment)
179 );
180 }
181
182 proptest! {
183 #![proptest_config(ProptestConfig{
184 failure_persistence: None,
185 ..Default::default()
186 })]
187
188 #[test]
189 fn reject_segment_too_long(ref s in r"[-_0-9a-z\.]{256, 300}")
190 {
191 prop_assert_eq!(
192 validate_package_path_segment(s),
193 Err(PackagePathSegmentError::TooLong(s.len()))
194 );
195 }
196
197 #[test]
198 fn reject_invalid_character(ref s in r"[-_0-9a-z\.]{0, 48}[^-_0-9a-z\.][-_0-9a-z\.]{0, 48}")
199 {
200 let pass = matches!(
201 validate_package_path_segment(s),
202 Err(PackagePathSegmentError::InvalidCharacter{..})
203 );
204 prop_assert!(pass);
205 }
206
207 #[test]
208 fn valid_segment(ref s in random_package_segment())
209 {
210 prop_assert_eq!(
211 validate_package_path_segment(s),
212 Ok(())
213 );
214 }
215 }
216}
217
218#[cfg(test)]
219mod test_package_name {
220 use super::*;
221 use assert_matches::assert_matches;
222
223 #[test]
224 fn from_str_rejects_invalid() {
225 assert_eq!(
226 "?".parse::<PackageName>(),
227 Err(PackagePathSegmentError::InvalidCharacter { character: '?'.into() })
228 );
229 }
230
231 #[test]
232 fn from_str_succeeds() {
233 "package-name".parse::<PackageName>().unwrap();
234 }
235
236 #[test]
237 fn try_from_rejects_invalid() {
238 assert_eq!(
239 PackageName::try_from("?".to_string()),
240 Err(PackagePathSegmentError::InvalidCharacter { character: '?'.into() })
241 );
242 }
243
244 #[test]
245 fn try_from_succeeds() {
246 PackageName::try_from("valid-name".to_string()).unwrap();
247 }
248
249 #[test]
250 fn from_succeeds() {
251 assert_eq!(
252 String::from("package-name".parse::<PackageName>().unwrap()),
253 "package-name".to_string()
254 );
255 }
256
257 #[test]
258 fn display() {
259 let path: PackageName = "package-name".parse().unwrap();
260 assert_eq!(format!("{}", path), "package-name");
261 }
262
263 #[test]
264 fn as_ref() {
265 let path: PackageName = "package-name".parse().unwrap();
266 assert_eq!(path.as_ref(), "package-name");
267 }
268
269 #[test]
270 fn deserialize_success() {
271 let actual_value =
272 serde_json::from_str::<PackageName>("\"package-name\"").expect("json to deserialize");
273 assert_eq!(actual_value, "package-name".parse::<PackageName>().unwrap());
274 }
275
276 #[test]
277 fn deserialize_rejects_invalid() {
278 let msg = serde_json::from_str::<PackageName>("\"pack!age-name\"").unwrap_err().to_string();
279 assert!(msg.contains("invalid package name"), r#"Bad error message: "{}""#, msg);
280 }
281
282 #[test]
283 fn try_from_path_ref_success() {
284 let path: crate::Path = "valid-name".parse().unwrap();
285 assert_eq!(PackageName::try_from(&path).unwrap().as_ref(), "valid-name");
286 }
287
288 #[test]
289 fn try_from_path_ref_error() {
290 let path: crate::Path = "in/valid/name".parse().unwrap();
291 assert_matches!(
292 PackageName::try_from(&path),
293 Err(crate::ParseError::InvalidName(PackagePathSegmentError::InvalidCharacter {
294 character: '/'
295 }))
296 );
297 }
298}
299
300#[cfg(test)]
301mod test_package_variant {
302 use super::*;
303
304 #[test]
305 fn zero() {
306 assert_eq!(PackageVariant::zero().as_ref(), "0");
307 assert!(PackageVariant::zero().is_zero());
308 assert_eq!("1".parse::<PackageVariant>().unwrap().is_zero(), false);
309 }
310
311 #[test]
312 fn from_str_rejects_invalid() {
313 assert_eq!(
314 "?".parse::<PackageVariant>(),
315 Err(PackagePathSegmentError::InvalidCharacter { character: '?'.into() })
316 );
317 }
318
319 #[test]
320 fn from_str_succeeds() {
321 "package-variant".parse::<PackageVariant>().unwrap();
322 }
323
324 #[test]
325 fn try_from_rejects_invalid() {
326 assert_eq!(
327 PackageVariant::try_from("?".to_string()),
328 Err(PackagePathSegmentError::InvalidCharacter { character: '?'.into() })
329 );
330 }
331
332 #[test]
333 fn try_from_succeeds() {
334 PackageVariant::try_from("valid-variant".to_string()).unwrap();
335 }
336
337 #[test]
338 fn display() {
339 let path: PackageVariant = "package-variant".parse().unwrap();
340 assert_eq!(format!("{}", path), "package-variant");
341 }
342
343 #[test]
344 fn as_ref() {
345 let path: PackageVariant = "package-variant".parse().unwrap();
346 assert_eq!(path.as_ref(), "package-variant");
347 }
348
349 #[test]
350 fn deserialize_success() {
351 let actual_value = serde_json::from_str::<PackageVariant>("\"package-variant\"")
352 .expect("json to deserialize");
353 assert_eq!(actual_value, "package-variant".parse::<PackageVariant>().unwrap());
354 }
355
356 #[test]
357 fn deserialize_rejects_invalid() {
358 let msg =
359 serde_json::from_str::<PackageVariant>("\"pack!age-variant\"").unwrap_err().to_string();
360 assert!(msg.contains("invalid package variant"), r#"Bad error message: "{}""#, msg);
361 }
362}