use crate::semver;
use std::convert::TryFrom;
use std::fmt::{Display, Formatter};
use std::str::FromStr;
type BareVersionUsize = u64;
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum BareVersion {
TwoComponents(BareVersionUsize, BareVersionUsize),
ThreeComponents(BareVersionUsize, BareVersionUsize, BareVersionUsize),
}
impl FromStr for BareVersion {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::try_from(s)
}
}
impl<'s> TryFrom<&'s str> for BareVersion {
type Error = Error;
fn try_from(value: &'s str) -> Result<Self, Self::Error> {
parse_bare_version(value)
}
}
impl BareVersion {
pub fn two_component_from_semver(version: &semver::Version) -> Self {
Self::TwoComponents(version.major, version.minor)
}
pub fn major(&self) -> BareVersionUsize {
match self {
Self::TwoComponents(major, _) => *major,
Self::ThreeComponents(major, _, _) => *major,
}
}
pub fn minor(&self) -> BareVersionUsize {
match self {
Self::TwoComponents(_, minor) => *minor,
Self::ThreeComponents(_, minor, _) => *minor,
}
}
pub fn patch(&self) -> Option<BareVersionUsize> {
match self {
Self::TwoComponents(_, _) => None,
Self::ThreeComponents(_, _, patch) => Some(*patch),
}
}
pub fn to_comparator(&self) -> semver::Comparator {
match self {
Self::TwoComponents(major, minor) => semver::Comparator {
op: semver::Op::Tilde,
major: *major,
minor: Some(*minor),
patch: None,
pre: semver::Prerelease::EMPTY,
},
Self::ThreeComponents(major, minor, patch) => semver::Comparator {
op: semver::Op::Exact,
major: *major,
minor: Some(*minor),
patch: Some(*patch),
pre: semver::Prerelease::EMPTY,
},
}
}
pub fn try_to_semver<'s, I>(
&self,
available: I,
) -> Result<&'s semver::Version, NoVersionMatchesManifestMsrvError>
where
I: Iterator<Item = &'s semver::Version> + Clone,
{
let requirements = self.to_comparator();
available
.clone()
.find(|version| requirements.matches(version))
.ok_or_else(|| {
let requirement = self.clone();
NoVersionMatchesManifestMsrvError {
requested: requirement,
available: available.cloned().collect(),
}
})
}
pub fn to_semver_version(&self) -> semver::Version {
match self {
Self::TwoComponents(major, minor) => semver::Version::new(*major, *minor, 0),
Self::ThreeComponents(major, minor, patch) => {
semver::Version::new(*major, *minor, *patch)
}
}
}
}
impl Display for BareVersion {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::TwoComponents(major, minor) => f.write_fmt(format_args!("{}.{}", major, minor)),
Self::ThreeComponents(major, minor, patch) => {
f.write_fmt(format_args!("{}.{}.{}", major, minor, patch))
}
}
}
}
impl BareVersion {
pub fn is_at_least(&self, given: &semver::Version) -> bool {
match (self, given) {
(BareVersion::ThreeComponents(min_major, min_minor, min_patch), v) => {
Self::at_least_3_component((*min_major, *min_minor, *min_patch), v)
}
(BareVersion::TwoComponents(min_major, min_minor), v) => {
Self::at_least_2_component((*min_major, *min_minor), v)
}
}
}
pub fn is_at_most(&self, given: &semver::Version) -> bool {
match (self, given) {
(BareVersion::ThreeComponents(max_major, max_minor, max_patch), v) => {
Self::at_most_3_component((*max_major, *max_minor, *max_patch), v)
}
(BareVersion::TwoComponents(max_major, max_minor), v) => {
Self::at_most_2_component((*max_major, *max_minor), v)
}
}
}
fn at_least_2_component(
min_version: (BareVersionUsize, BareVersionUsize),
version: &semver::Version,
) -> bool {
let (min_major, min_minor) = min_version;
if version.major != min_major {
return version.major >= min_major;
}
if version.minor != min_minor {
return version.minor >= min_minor;
}
true
}
fn at_least_3_component(
min_version: (BareVersionUsize, BareVersionUsize, BareVersionUsize),
version: &semver::Version,
) -> bool {
let (min_major, min_minor, min_patch) = min_version;
if version.major != min_major {
return version.major >= min_major;
}
if version.minor != min_minor {
return version.minor >= min_minor;
}
if version.patch != min_patch {
return version.patch >= min_patch;
}
true
}
fn at_most_2_component(
max_version: (BareVersionUsize, BareVersionUsize),
version: &semver::Version,
) -> bool {
let (max_major, max_minor) = max_version;
if version.major != max_major {
return version.major <= max_major;
}
if version.minor != max_minor {
return version.minor <= max_minor;
}
true
}
fn at_most_3_component(
max_version: (BareVersionUsize, BareVersionUsize, BareVersionUsize),
version: &semver::Version,
) -> bool {
let (max_major, max_minor, max_patch) = max_version;
if version.major != max_major {
return version.major <= max_major;
}
if version.minor != max_minor {
return version.minor <= max_minor;
}
if version.patch != max_patch {
return version.patch <= max_patch;
}
true
}
}
impl<'r> From<&'r semver::Version> for BareVersion {
fn from(version: &'r semver::Version) -> Self {
BareVersion::ThreeComponents(version.major, version.minor, version.patch)
}
}
#[derive(Debug, Eq, PartialEq)]
pub enum ExpectedToken {
Number,
Dot,
}
#[derive(Debug, Eq, PartialEq, thiserror::Error)]
pub enum Error {
#[error("Expected end of input")]
ExpectedEndOfInput,
#[error("Component would overflow")]
Overflow,
#[error("Pre-release modifiers are not allowed")]
PreReleaseModifierNotAllowed,
#[error("Unexpected token '{0}', expected token of kind {1:?}")]
UnexpectedToken(u8, ExpectedToken),
#[error("Unexpected end of input")]
UnexpectedEndOfInput,
}
fn parse_separator(input: &[u8]) -> Result<ParsedTokens, Error> {
match input.iter().next() {
Some(b'.') => Ok(1),
Some(t) => Err(Error::UnexpectedToken(*t, ExpectedToken::Dot)),
None => Err(Error::UnexpectedEndOfInput),
}
}
type ParsedTokens = usize;
fn parse_number(input: &[u8]) -> Result<(BareVersionUsize, ParsedTokens), Error> {
const ZERO_MIN: u8 = b'0' - 1;
const NINE_PLUS: u8 = b'9' + 1;
let mut out: BareVersionUsize = 0;
let mut len = 0;
while let Some(token) = input.get(len) {
match token {
b'0'..=b'9' => {
out = out.checked_mul(10).ok_or(Error::Overflow)?;
out = out
.checked_add(BareVersionUsize::from(*token - b'0'))
.ok_or(Error::Overflow)?;
len += 1;
}
0u8..=ZERO_MIN | NINE_PLUS..=u8::MAX => {
break;
}
}
}
match len {
0 => Err(Error::UnexpectedEndOfInput),
_ => Ok((out, len)),
}
}
fn expect_end_of_input(input: &[u8]) -> Result<(), Error> {
if input.is_empty() {
Ok(())
} else {
Err(Error::ExpectedEndOfInput)
}
}
fn parse_bare_version(input: &str) -> Result<BareVersion, Error> {
let input = input.as_bytes();
let mut parsed_tokens = 0;
let (major, tokens) = parse_number(input)?;
parsed_tokens += tokens;
let tokens = parse_separator(&input[parsed_tokens..])?;
parsed_tokens += tokens;
let (minor, tokens) = parse_number(&input[parsed_tokens..])?;
parsed_tokens += tokens;
if expect_end_of_input(&input[parsed_tokens..]).is_ok() {
return Ok(BareVersion::TwoComponents(major, minor));
}
let tokens = parse_separator(&input[parsed_tokens..])?;
parsed_tokens += tokens;
let (patch, tokens) = parse_number(&input[parsed_tokens..])?;
parsed_tokens += tokens;
if expect_end_of_input(&input[parsed_tokens..]).is_ok() {
return Ok(BareVersion::ThreeComponents(major, minor, patch));
}
if input[parsed_tokens..].starts_with(b"-") {
return Err(Error::PreReleaseModifierNotAllowed);
}
Err(Error::ExpectedEndOfInput)
}
#[derive(Debug, thiserror::Error)]
#[error("The MSRV requirement ({requested}) did not match any available version, available: [{}]", .available.iter().map(|s| s.to_string()).collect::<Vec<_>>().join(", "))]
pub struct NoVersionMatchesManifestMsrvError {
pub requested: BareVersion,
pub available: Vec<semver::Version>,
}
impl serde::Serialize for BareVersion {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.collect_str(self)
}
}
#[cfg(test)]
mod bare_version_tests {
use crate::manifest::BareVersion;
use rust_releases::{semver, Release, ReleaseIndex};
use std::iter::FromIterator;
use yare::parameterized;
fn release_indices() -> ReleaseIndex {
FromIterator::from_iter(vec![
Release::new_stable(semver::Version::new(2, 56, 0)),
Release::new_stable(semver::Version::new(1, 56, 0)),
Release::new_stable(semver::Version::new(1, 55, 0)),
Release::new_stable(semver::Version::new(1, 54, 2)),
Release::new_stable(semver::Version::new(1, 54, 1)),
Release::new_stable(semver::Version::new(1, 0, 0)),
])
}
#[parameterized(
two_component_two_fifty_six = { "2.56", BareVersion::TwoComponents(2, 56) },
three_component_two_fifty_six = { "2.56.0", BareVersion::ThreeComponents(2, 56, 0) },
two_component_one_fifty_five = { "1.55", BareVersion::TwoComponents(1, 55) },
three_component_one_fifty_five = { "1.55.0", BareVersion::ThreeComponents(1, 55, 0) },
three_component_one_fifty_four = { "1.54.0", BareVersion::ThreeComponents(1, 54, 0) },
three_component_one_fifty_four_p1 = { "1.54.1", BareVersion::ThreeComponents(1, 54, 1) },
three_component_one_fifty_four_p10 = { "1.54.10", BareVersion::ThreeComponents(1, 54, 10) },
two_component_zeros = { "0.0", BareVersion::TwoComponents(0, 0) },
three_component_zeros = { "0.0.0", BareVersion::ThreeComponents(0, 0, 0) },
two_component_large_major = { "18446744073709551615.0", BareVersion::TwoComponents(18_446_744_073_709_551_615, 0) },
two_component_large_minor = { "0.18446744073709551615", BareVersion::TwoComponents(0, 18_446_744_073_709_551_615) },
three_component_large_major = { "18446744073709551615.0.0", BareVersion::ThreeComponents(18_446_744_073_709_551_615, 0, 0) },
three_component_large_minor = { "0.18446744073709551615.0", BareVersion::ThreeComponents(0, 18_446_744_073_709_551_615, 0) },
three_component_large_patch = { "0.0.18446744073709551615", BareVersion::ThreeComponents(0, 0, 18_446_744_073_709_551_615) },
)]
fn try_from_ok(version: &str, expected: BareVersion) {
use std::convert::TryFrom;
let version = BareVersion::try_from(version).unwrap();
assert_eq!(version, expected);
}
#[parameterized(
empty = { "" }, // no first component
no_components_space = { "1 36 0" },
no_components_comma = { "1,36,0" },
first_component_nan = { "x.0.0" },
no_second_component = { "1." },
second_component_nan = { "1.x" },
no_third_component = { "1.0." },
third_component_nan = { "1.36.x" },
too_large_int_major_2c = { "18446744073709551616.0" },
too_large_int_minor_2c = { "0.18446744073709551616" },
too_large_int_major_3c = { "18446744073709551616.0.0" },
too_large_int_minor_3c = { "0.18446744073709551616.0" },
too_large_int_patch_3c = { "0.0.18446744073709551616" },
neg_int_major = { "-1.0.0" },
neg_int_minor = { "0.-1.0" },
neg_int_patch = { "0.0.-1" },
build_postfix_without_pre_release_id = { "0.0.0+some" },
two_component_pre_release_id_variant_1 = { "0.0-nightly" },
two_component_pre_release_id_variant_2 = { "0.0-beta.0" },
two_component_pre_release_id_variant_3 = { "0.0-beta.1" },
two_component_pre_release_id_variant_4 = { "0.0-anything", },
two_component_pre_release_id_variant_5 = { "0.0-anything+build" },
three_component_pre_release_id_variant_2 = { "0.0.0-beta.0" },
three_component_pre_release_id_variant_3 = { "0.0.0-beta.1" },
three_component_pre_release_id_variant_1 = { "0.0.0-nightly" },
three_component_pre_release_id_variant_4 = { "0.0.0-anything" },
three_component_pre_release_id_variant_5 = { "0.0.0-anything+build" },
)]
fn try_from_err(version: &str) {
use std::convert::TryFrom;
let res = BareVersion::try_from(version);
assert!(res.is_err());
}
#[parameterized(
two_fifty_six = { BareVersion::TwoComponents(2, 56), semver::Version::new(2, 56, 0) },
one_fifty_six = { BareVersion::TwoComponents(1, 56), semver::Version::new(1, 56, 0) },
one_fifty_five = { BareVersion::TwoComponents(1, 55), semver::Version::new(1, 55, 0) },
one_fifty_four_p2 = { BareVersion::TwoComponents(1, 54), semver::Version::new(1, 54, 2) },
one_fifty_four_p1 = { BareVersion::TwoComponents(1, 54), semver::Version::new(1, 54, 2) },
one_fifty_four_p0 = { BareVersion::TwoComponents(1, 54), semver::Version::new(1, 54, 2) },
one = { BareVersion::TwoComponents(1, 0), semver::Version::new(1, 0, 0) },
)]
fn two_components_to_semver(version: BareVersion, expected: semver::Version) {
let index = release_indices();
let available = index.releases().iter().map(Release::version);
let v = version.try_to_semver(available).unwrap();
assert_eq!(v, &expected);
}
#[parameterized(
two_fifty_six = { BareVersion::ThreeComponents(2, 56, 0), semver::Version::new(2, 56, 0) },
one_fifty_six = { BareVersion::ThreeComponents(1, 56, 0), semver::Version::new(1, 56, 0) },
one_fifty_five = { BareVersion::ThreeComponents(1, 55, 0), semver::Version::new(1, 55, 0) },
one_fifty_four_p2 = { BareVersion::ThreeComponents(1, 54, 2), semver::Version::new(1, 54, 2) },
one_fifty_four_p1 = { BareVersion::ThreeComponents(1, 54, 1), semver::Version::new(1, 54, 1) },
one = { BareVersion::ThreeComponents(1, 0, 0), semver::Version::new(1, 0, 0) },
)]
fn three_components_to_semver(version: BareVersion, expected: semver::Version) {
let index = release_indices();
let available = index.releases().iter().map(Release::version);
let v = version.try_to_semver(available).unwrap();
assert_eq!(v, &expected);
}
#[test]
fn not_in_index() {
let index = release_indices();
let available = index.releases().iter().map(Release::version);
let given = BareVersion::ThreeComponents(1, 54, 0);
assert!(given.try_to_semver(available).is_err())
}
#[parameterized(
accept_min_three_component_eq = { BareVersion::ThreeComponents(1, 56, 0), semver::Version::new(1, 56, 0), true },
accept_min_three_component_gt_patch = { BareVersion::ThreeComponents(1, 56, 0), semver::Version::new(1, 56, 1), true },
accept_min_three_component_gt_minor = { BareVersion::ThreeComponents(1, 56, 0), semver::Version::new(1, 57, 0), true },
accept_min_three_component_gt_major = { BareVersion::ThreeComponents(1, 56, 0), semver::Version::new(2, 0, 0), true },
reject_min_three_component_gt_patch = { BareVersion::ThreeComponents(1, 56, 1), semver::Version::new(1, 56, 0), false },
reject_min_three_component_gt_minor = { BareVersion::ThreeComponents(1, 56, 1), semver::Version::new(1, 56, 0), false },
reject_min_three_component_gt_minor_2 = { BareVersion::ThreeComponents(1, 56, 0), semver::Version::new(1, 55, 0), false },
reject_min_three_component_gt_major = { BareVersion::ThreeComponents(2, 0, 1), semver::Version::new(2, 0, 0), false },
reject_min_three_component_gt_major_2 = { BareVersion::ThreeComponents(3, 0, 0), semver::Version::new(2, 0, 0), false },
accept_min_two_component_eq = { BareVersion::TwoComponents(1, 56), semver::Version::new(1, 56, 0), true },
accept_min_two_component_gt_patch = { BareVersion::TwoComponents(1, 56), semver::Version::new(1, 56, 1), true },
accept_min_two_component_gt_minor = { BareVersion::TwoComponents(1, 56), semver::Version::new(1, 57, 0), true },
accept_min_two_component_gt_major = { BareVersion::TwoComponents(1, 56), semver::Version::new(2, 0, 0), true },
reject_min_two_component_gt_minor = { BareVersion::TwoComponents(1, 56), semver::Version::new(1, 55, 0), false },
reject_min_two_component_gt_major = { BareVersion::TwoComponents(3, 0), semver::Version::new(2, 0, 0), false },
reject_min_two_component_gt_major_2 = { BareVersion::TwoComponents(3, 5), semver::Version::new(2, 4, 0), false },
)]
fn is_at_least(accepting_min: BareVersion, given: semver::Version, accept: bool) {
assert_eq!(accepting_min.is_at_least(&given), accept);
}
#[parameterized(
accept_max_three_component_eq_patch = { BareVersion::ThreeComponents(1, 56, 1), semver::Version::new(1, 56, 1), true },
accept_max_three_component_eq_minor = { BareVersion::ThreeComponents(1, 56, 0), semver::Version::new(1, 56, 0), true },
accept_max_three_component_eq_major = { BareVersion::ThreeComponents(2, 0, 0), semver::Version::new(2, 0, 0), true },
accept_max_three_component_lt_patch = { BareVersion::ThreeComponents(1, 56, 1), semver::Version::new(1, 56, 0), true },
accept_max_three_component_lt_minor = { BareVersion::ThreeComponents(1, 56, 0), semver::Version::new(1, 55, 0), true },
accept_max_three_component_lt_major = { BareVersion::ThreeComponents(3, 0, 0), semver::Version::new(2, 0, 0), true },
reject_max_three_component_lt_patch = { BareVersion::ThreeComponents(1, 56, 0), semver::Version::new(1, 56, 1), false },
reject_max_three_component_lt_patch_2 = { BareVersion::ThreeComponents(2, 0, 0), semver::Version::new(2, 0, 1), false },
reject_max_three_component_lt_minor = { BareVersion::ThreeComponents(1, 56, 0), semver::Version::new(1, 57, 0), false },
reject_max_three_component_lt_major_2 = { BareVersion::ThreeComponents(3, 0, 0), semver::Version::new(4, 0, 0), false },
accept_max_two_component_eq_minor = { BareVersion::TwoComponents(1, 56), semver::Version::new(1, 56, 0), true },
accept_max_two_component_eq_major = { BareVersion::TwoComponents(2, 0), semver::Version::new(2, 0, 0), true },
accept_max_two_component_lt_patch = { BareVersion::TwoComponents(1, 56), semver::Version::new(1, 55, 99), true },
accept_max_two_component_lt_patch_2 = { BareVersion::TwoComponents(1, 56), semver::Version::new(1, 55, 1), true },
accept_max_two_component_lt_minor = { BareVersion::TwoComponents(1, 56), semver::Version::new(1, 55, 0), true },
accept_max_two_component_lt_minor_2 = { BareVersion::TwoComponents(2, 1), semver::Version::new(2, 0, 0), true },
reject_max_two_component_lt_minor = { BareVersion::TwoComponents(1, 56), semver::Version::new(1, 57, 0), false },
reject_max_two_component_lt_major = { BareVersion::TwoComponents(3, 0), semver::Version::new(4, 0, 0), false },
reject_max_two_component_lt_major_2 = { BareVersion::TwoComponents(3, 5), semver::Version::new(4, 6, 0), false },
)]
fn is_at_most(accepting_min: BareVersion, given: semver::Version, accept: bool) {
assert_eq!(accepting_min.is_at_most(&given), accept);
}
}