1use std::{cmp::Ordering, fmt};
2
3use lazy_static::lazy_static;
4use regex::Regex;
5
6#[cfg(feature = "serde")]
7use serde::{
8 ser::{SerializeStruct, Serializer},
9 Serialize,
10};
11
12lazy_static! {
13 static ref RELEASE_REGEX: Regex = Regex::new(r#"^(@?[^@]+)@(.+?)$"#).unwrap();
14 static ref VERSION_REGEX: Regex = Regex::new(
15 r"(?x)
16 ^
17 (?P<major>[0-9][0-9]*)
18 (?:\.(?P<minor>[0-9][0-9]*))?
19 (?:\.(?P<patch>[0-9][0-9]*))?
20 (?:\.(?P<revision>[0-9][0-9]*))?
21 (?:
22 (?P<prerelease>
23 (?:-|[a-z])
24 (?:0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*)?
25 (?:\.(?:0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*))*)
26 )?
27 (?:\+(?P<build_code>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?
28 $
29 "
30 )
31 .unwrap();
32 static ref HEX_REGEX: Regex = Regex::new(r#"^[a-fA-F0-9]+$"#).unwrap();
33 static ref VALID_API_ATTRIBUTE_REGEX: Regex = Regex::new(r"^[^\\/\r\n\t\x7f\x00-\x1f]*\z").unwrap();
36}
37
38#[derive(Debug, Clone, PartialEq)]
40#[cfg_attr(feature = "serde", derive(Serialize))]
41pub struct InvalidVersion;
42
43impl std::error::Error for InvalidVersion {}
44
45impl fmt::Display for InvalidVersion {
46 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
47 write!(f, "invalid version")
48 }
49}
50
51#[derive(Debug, Clone, PartialEq)]
53pub enum InvalidRelease {
54 TooLong,
56 RestrictedName,
58 BadCharacters,
60}
61
62#[derive(Debug, Clone, PartialEq)]
64pub enum InvalidEnvironment {
65 TooLong,
67 RestrictedName,
69 BadCharacters,
71}
72
73impl std::error::Error for InvalidRelease {}
74
75impl fmt::Display for InvalidRelease {
76 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
77 write!(
78 f,
79 "invalid release: {}",
80 match *self {
81 InvalidRelease::BadCharacters => "bad characters in release name",
82 InvalidRelease::RestrictedName => "restricted release name",
83 InvalidRelease::TooLong => "release name too long",
84 }
85 )
86 }
87}
88
89impl std::error::Error for InvalidEnvironment {}
90
91impl fmt::Display for InvalidEnvironment {
92 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
93 write!(
94 f,
95 "invalid environment: {}",
96 match *self {
97 InvalidEnvironment::BadCharacters => "bad characters in environment name",
98 InvalidEnvironment::RestrictedName => "restricted environment name",
99 InvalidEnvironment::TooLong => "environment name too long",
100 }
101 )
102 }
103}
104
105#[derive(Debug, Clone)]
107pub struct Version<'a> {
108 raw: &'a str,
109 major: &'a str,
110 minor: &'a str,
111 patch: &'a str,
112 revision: &'a str,
113 pre: &'a str,
114 before_code: &'a str,
115 build_code: &'a str,
116 components: u8,
117}
118
119#[cfg(feature = "serde")]
120impl<'a> Serialize for Version<'a> {
121 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
122 where
123 S: Serializer,
124 {
125 let mut state = serializer.serialize_struct("Version", 5)?;
126 state.serialize_field("major", &self.major())?;
127 state.serialize_field("minor", &self.minor())?;
128 state.serialize_field("patch", &self.patch())?;
129 state.serialize_field("revision", &self.revision())?;
130 state.serialize_field("pre", &self.pre())?;
131 state.serialize_field("build_code", &self.build_code())?;
132 state.serialize_field("raw_short", &self.raw_short())?;
133 state.serialize_field("components", &self.components())?;
134 state.serialize_field("raw_quad", &self.raw_quad())?;
135 state.end()
136 }
137}
138
139fn is_build_hash(s: &str) -> bool {
140 match s.len() {
141 12 | 16 | 20 | 32 | 40 | 64 => HEX_REGEX.is_match(s),
142 _ => false,
143 }
144}
145
146impl<'a> Version<'a> {
147 pub fn parse(version: &'a str) -> Result<Version<'a>, InvalidVersion> {
149 let caps = if let Some(caps) = VERSION_REGEX.captures(version) {
150 caps
151 } else {
152 return Err(InvalidVersion);
153 };
154
155 let components = 1
156 + caps.get(2).map_or(0, |_| 1)
157 + caps.get(3).map_or(0, |_| 1)
158 + caps.get(4).map_or(0, |_| 1);
159
160 if components == 1 && caps.get(5).map_or(false, |x| !x.as_str().starts_with('-')) {
164 return Err(InvalidVersion);
165 }
166
167 let before_code = match caps.get(6) {
168 Some(cap) => &version[..cap.start() - 1],
169 None => version,
170 };
171
172 Ok(Version {
173 raw: version,
174 major: caps.get(1).map(|x| x.as_str()).unwrap_or_default(),
175 minor: caps.get(2).map(|x| x.as_str()).unwrap_or_default(),
176 patch: caps.get(3).map(|x| x.as_str()).unwrap_or_default(),
177 revision: caps.get(4).map(|x| x.as_str()).unwrap_or_default(),
178 pre: caps
179 .get(5)
180 .map(|x| {
181 let mut pre = x.as_str();
182 if pre.starts_with('-') {
183 pre = &pre[1..];
184 }
185 pre
186 })
187 .unwrap_or(""),
188 before_code,
189 build_code: caps.get(6).map(|x| x.as_str()).unwrap_or(""),
190 components,
191 })
192 }
193
194 #[cfg(feature = "semver")]
198 pub fn as_semver(&self) -> semver::Version {
199 fn split(s: &str) -> Vec<semver::Identifier> {
200 s.split('.')
201 .map(|item| {
202 if let Ok(val) = item.parse::<u64>() {
203 semver::Identifier::Numeric(val)
204 } else {
205 semver::Identifier::AlphaNumeric(item.into())
206 }
207 })
208 .collect()
209 }
210
211 semver::Version {
212 major: self.major(),
213 minor: self.minor(),
214 patch: self.patch(),
215 pre: split(self.pre),
216 build: split(self.build_code),
217 }
218 }
219
220 pub fn major(&self) -> u64 {
222 self.major.parse().unwrap_or_default()
223 }
224
225 pub fn minor(&self) -> u64 {
227 self.minor.parse().unwrap_or_default()
228 }
229
230 pub fn patch(&self) -> u64 {
232 self.patch.parse().unwrap_or_default()
233 }
234
235 pub fn revision(&self) -> u64 {
237 self.revision.parse().unwrap_or_default()
238 }
239
240 pub fn pre(&self) -> Option<&'a str> {
242 if self.pre.is_empty() {
243 None
244 } else {
245 Some(self.pre)
246 }
247 }
248
249 pub fn build_code(&self) -> Option<&'a str> {
251 if self.build_code.is_empty() {
252 None
253 } else {
254 Some(self.build_code)
255 }
256 }
257
258 pub fn build_number(&self) -> Option<u64> {
260 self.build_code().and_then(|val| val.parse().ok())
261 }
262
263 pub fn components(&self) -> u8 {
265 self.components
266 }
267
268 pub fn raw(&self) -> &'a str {
272 self.raw
273 }
274
275 pub fn raw_short(&self) -> &'a str {
282 self.before_code
283 }
284
285 pub fn triple(&self) -> (u64, u64, u64) {
287 (self.major(), self.minor(), self.patch())
288 }
289
290 pub fn quad(&self) -> (u64, u64, u64, u64) {
292 (self.major(), self.minor(), self.patch(), self.revision())
293 }
294
295 pub fn raw_quad(&self) -> (&'a str, Option<&'a str>, Option<&'a str>, Option<&'a str>) {
297 (
298 self.major,
299 (self.components > 1).then_some(self.minor),
300 (self.components > 2).then_some(self.patch),
301 (self.components > 3).then_some(self.revision),
302 )
303 }
304}
305
306impl<'a> Ord for Version<'a> {
307 fn cmp(&self, other: &Self) -> Ordering {
308 match self.quad().cmp(&other.quad()) {
309 Ordering::Equal => {
310 match (self.pre(), other.pre()) {
312 (None, Some(_)) => return Ordering::Greater,
313 (Some(_), None) => return Ordering::Less,
314 (Some(self_pre), Some(other_pre)) => {
315 match self_pre.cmp(other_pre) {
316 Ordering::Equal => {}
317 other => return other,
318 };
319 }
320 (None, None) => {}
321 }
322
323 if let (Some(self_num), Some(other_num)) =
325 (self.build_number(), other.build_number())
326 {
327 return self_num.cmp(&other_num);
328 }
329
330 self.build_code().cmp(&other.build_code())
332 }
333 other => other,
334 }
335 }
336}
337
338impl<'a> PartialOrd for Version<'a> {
339 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
340 Some(self.cmp(other))
341 }
342}
343
344impl<'a> PartialEq for Version<'a> {
345 fn eq(&self, other: &Self) -> bool {
346 self.quad() == other.quad()
347 && self.pre() == other.pre()
348 && self.build_code() == other.build_code()
349 }
350}
351
352impl<'a> Eq for Version<'a> {}
353
354impl<'a> fmt::Display for Version<'a> {
355 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
356 write!(f, "{}", self.raw)?;
357 Ok(())
358 }
359}
360
361#[derive(Debug, Clone, PartialEq)]
363pub struct Release<'a> {
364 raw: &'a str,
365 package: &'a str,
366 version_raw: &'a str,
367 version: Option<Version<'a>>,
368}
369
370#[cfg(feature = "serde")]
371impl<'a> Serialize for Release<'a> {
372 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
373 where
374 S: Serializer,
375 {
376 let mut state = serializer.serialize_struct("Release", 6)?;
377 state.serialize_field("package", &self.package())?;
378 state.serialize_field("version_raw", &self.version_raw())?;
379 state.serialize_field("version_parsed", &self.version())?;
380 state.serialize_field("build_hash", &self.build_hash())?;
381 state.serialize_field("description", &self.describe().to_string())?;
382 state.end()
383 }
384}
385
386pub fn validate_release(release: &str) -> Result<(), InvalidRelease> {
388 if release.len() > 200 {
389 Err(InvalidRelease::TooLong)
390 } else if release == "." || release == ".." || release.eq_ignore_ascii_case("latest") {
391 Err(InvalidRelease::RestrictedName)
392 } else if !VALID_API_ATTRIBUTE_REGEX.is_match(release) {
393 Err(InvalidRelease::BadCharacters)
394 } else {
395 Ok(())
396 }
397}
398
399pub fn validate_environment(environment: &str) -> Result<(), InvalidEnvironment> {
401 if environment.len() > 64 {
402 Err(InvalidEnvironment::TooLong)
403 } else if environment == "." || environment == ".." || environment.eq_ignore_ascii_case("none")
404 {
405 Err(InvalidEnvironment::RestrictedName)
406 } else if !VALID_API_ATTRIBUTE_REGEX.is_match(environment) {
407 Err(InvalidEnvironment::BadCharacters)
408 } else {
409 Ok(())
410 }
411}
412
413impl<'a> Release<'a> {
414 pub fn parse(release: &'a str) -> Result<Release<'a>, InvalidRelease> {
416 let release = release.trim();
417 validate_release(release)?;
418 if let Some(caps) = RELEASE_REGEX.captures(release) {
419 let package = caps.get(1).unwrap().as_str();
420 let version_raw = caps.get(2).unwrap().as_str();
421 if !is_build_hash(version_raw) {
422 let version = Version::parse(version_raw).ok();
423 return Ok(Release {
424 raw: release,
425 package,
426 version_raw,
427 version,
428 });
429 } else {
430 return Ok(Release {
431 raw: release,
432 package,
433 version_raw,
434 version: None,
435 });
436 }
437 }
438 Ok(Release {
439 raw: release,
440 package: "",
441 version_raw: release,
442 version: None,
443 })
444 }
445
446 pub fn raw(&self) -> &'a str {
450 self.raw
451 }
452
453 pub fn package(&self) -> Option<&'a str> {
455 if self.package.is_empty() {
456 None
457 } else {
458 Some(self.package)
459 }
460 }
461
462 pub fn version_raw(&self) -> &'a str {
467 self.version_raw
468 }
469
470 pub fn version(&self) -> Option<&Version<'a>> {
472 self.version.as_ref()
473 }
474
475 pub fn build_hash(&self) -> Option<&'a str> {
477 self.version
478 .as_ref()
479 .and_then(|x| x.build_code())
480 .filter(|x| is_build_hash(x))
481 .or_else(|| {
482 if is_build_hash(self.version_raw()) {
483 Some(self.version_raw())
484 } else {
485 None
486 }
487 })
488 }
489
490 pub fn describe(&self) -> ReleaseDescription<'_> {
496 ReleaseDescription(self)
497 }
498}
499
500#[derive(Debug)]
502pub struct ReleaseDescription<'a>(&'a Release<'a>);
503
504impl<'a> fmt::Display for ReleaseDescription<'a> {
505 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
506 let short_hash = self
507 .0
508 .build_hash()
509 .map(|hash| hash.get(..12).unwrap_or(hash));
510
511 if let Some(ver) = self.0.version() {
512 write!(f, "{}", ver.raw_short())?;
513 if let Some(short_hash) = short_hash {
514 write!(f, " ({})", short_hash)?;
515 } else if let Some(build_code) = ver.build_code() {
516 write!(f, " ({})", build_code)?;
517 }
518 } else if let Some(short_hash) = short_hash {
519 write!(f, "{}", short_hash)?;
520 } else {
521 write!(f, "{}", self.0)?;
522 }
523 Ok(())
524 }
525}
526
527impl<'a> fmt::Display for Release<'a> {
528 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
529 let mut have_package = false;
530 if let Some(package) = self.package() {
531 write!(f, "{}", package)?;
532 have_package = true;
533 }
534 if let Some(version) = self.version() {
535 if have_package {
536 write!(f, "@")?;
537 }
538 write!(f, "{}", version)?;
539 } else {
540 if have_package {
541 write!(f, "@")?;
542 }
543 write!(f, "{}", self.version_raw)?;
544 }
545 Ok(())
546 }
547}
548
549#[test]
550fn test_release_validation() {
551 assert_eq!(
552 validate_release("latest"),
553 Err(InvalidRelease::RestrictedName)
554 );
555 assert_eq!(validate_release("."), Err(InvalidRelease::RestrictedName));
556 assert_eq!(validate_release(".."), Err(InvalidRelease::RestrictedName));
557 assert_eq!(
558 validate_release("foo\nbar"),
559 Err(InvalidRelease::BadCharacters)
560 );
561 assert_eq!(validate_release("good"), Ok(()));
562}
563
564#[test]
565fn test_environment_validation() {
566 assert_eq!(
567 validate_environment("none"),
568 Err(InvalidEnvironment::RestrictedName)
569 );
570 assert_eq!(
571 validate_environment("."),
572 Err(InvalidEnvironment::RestrictedName)
573 );
574 assert_eq!(
575 validate_environment(".."),
576 Err(InvalidEnvironment::RestrictedName)
577 );
578 assert_eq!(
579 validate_environment("f4f3db928593f258e1d850997be07b577f0779cc5549f9968bae625ea001175bX"),
580 Err(InvalidEnvironment::TooLong)
581 );
582 assert_eq!(
583 validate_environment("foo\nbar"),
584 Err(InvalidEnvironment::BadCharacters)
585 );
586 assert_eq!(validate_environment("good"), Ok(()));
587}
588
589#[test]
590fn test_version_ordering() {
591 macro_rules! ver {
592 ($v:expr) => {
593 Version::parse($v).unwrap()
594 };
595 }
596
597 assert_eq!(ver!("1.0.0"), ver!("1.0.0"));
598 assert_ne!(ver!("1.1.0"), ver!("1.0.0"));
599 assert_eq!(ver!("1.0"), ver!("1.0.0"));
600 assert_eq!(ver!("1.0dev"), ver!("1.0.0-dev"));
601 assert_eq!(ver!("1.0dev"), ver!("1.0.0.0-dev"));
602 assert_ne!(ver!("1.0dev"), ver!("1.0.0.0-dev1"));
603
604 assert!(ver!("1.0dev") >= ver!("1.0.0.0-dev"));
605 assert!(ver!("1.0dev") < ver!("1.0.0.0"));
606
607 assert!(ver!("1.0.0") > ver!("1.0.0-rc1"));
608 assert!(ver!("1.0.0") >= ver!("1.0.0-rc1"));
609 assert!(ver!("1.0.0-rc1") > ver!("0.9"));
610 assert!(ver!("1.0.0+10") < ver!("1.0.0+20"));
611 assert!(ver!("1.0.0+a") < ver!("1.0.0+b"));
612
613 assert!(ver!("1.0") < ver!("2.0"));
614 assert!(ver!("1.1.0") < ver!("10.0"));
615 assert!(ver!("1.1.0") < ver!("1.1.1"));
616 assert!(ver!("1.1.0.1") < ver!("1.1.0.2"));
617 assert!(ver!("1.1.0.1") > ver!("1.1.0.0"));
618 assert!(ver!("1.1.0.1") > ver!("1.0.0.0"));
619 assert!(ver!("1.1.0.1") > ver!("1.0.42.0"));
620
621 assert!(ver!("1.0+abcd") < ver!("1.0+abcde"));
622}