use alloc::vec::Vec;
use core::fmt;
use crate::{
bytesrepr::{Error, FromBytes, ToBytes},
SemVer,
};
#[derive(Copy, Clone, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct ProtocolVersion(SemVer);
#[derive(Debug, PartialEq, Eq)]
pub enum VersionCheckResult {
CodeIsRequired,
CodeIsOptional,
Invalid,
}
impl VersionCheckResult {
pub fn is_invalid(&self) -> bool {
match self {
VersionCheckResult::Invalid => true,
VersionCheckResult::CodeIsRequired | VersionCheckResult::CodeIsOptional => false,
}
}
pub fn is_code_required(&self) -> bool {
match self {
VersionCheckResult::CodeIsRequired => true,
_ => false,
}
}
}
impl ProtocolVersion {
pub const V1_0_0: ProtocolVersion = ProtocolVersion(SemVer {
major: 1,
minor: 0,
patch: 0,
});
pub fn new(version: SemVer) -> ProtocolVersion {
ProtocolVersion(version)
}
pub fn from_parts(major: u32, minor: u32, patch: u32) -> ProtocolVersion {
let sem_ver = SemVer::new(major, minor, patch);
Self::new(sem_ver)
}
pub fn value(&self) -> SemVer {
self.0
}
pub fn check_next_version(&self, next: &ProtocolVersion) -> VersionCheckResult {
if next.0.major < self.0.major || next.0.major > self.0.major + 1 {
return VersionCheckResult::Invalid;
}
if next.0.major == self.0.major.saturating_add(1) {
if next.0.minor != 0 || next.0.patch != 0 {
return VersionCheckResult::Invalid;
}
return VersionCheckResult::CodeIsRequired;
}
debug_assert_eq!(next.0.major, self.0.major);
if next.0.minor < self.0.minor || next.0.minor > self.0.minor + 1 {
return VersionCheckResult::Invalid;
}
if next.0.minor == self.0.minor + 1 {
if next.0.patch != 0 {
return VersionCheckResult::Invalid;
}
return VersionCheckResult::CodeIsOptional;
}
debug_assert_eq!(next.0.minor, self.0.minor);
if next.0.patch <= self.0.patch {
return VersionCheckResult::Invalid;
}
VersionCheckResult::CodeIsOptional
}
pub fn is_compatible_with(&self, version: &ProtocolVersion) -> bool {
self.0.major == version.0.major
}
}
impl ToBytes for ProtocolVersion {
fn to_bytes(&self) -> Result<Vec<u8>, Error> {
self.value().to_bytes()
}
fn serialized_length(&self) -> usize {
self.value().serialized_length()
}
}
impl FromBytes for ProtocolVersion {
fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> {
let (version, rem) = SemVer::from_bytes(bytes)?;
let protocol_version = ProtocolVersion::new(version);
Ok((protocol_version, rem))
}
}
impl fmt::Display for ProtocolVersion {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::SemVer;
#[test]
fn should_follow_version_with_optional_code() {
let value = VersionCheckResult::CodeIsOptional;
assert!(!value.is_invalid());
assert!(!value.is_code_required());
}
#[test]
fn should_follow_version_with_required_code() {
let value = VersionCheckResult::CodeIsRequired;
assert!(!value.is_invalid());
assert!(value.is_code_required());
}
#[test]
fn should_not_follow_version_with_invalid_code() {
let value = VersionCheckResult::Invalid;
assert!(value.is_invalid());
assert!(!value.is_code_required());
}
#[test]
fn should_be_able_to_get_instance() {
let initial_value = SemVer::new(1, 0, 0);
let item = ProtocolVersion::new(initial_value);
assert_eq!(initial_value, item.value(), "should have equal value")
}
#[test]
fn should_be_able_to_compare_two_instances() {
let lhs = ProtocolVersion::new(SemVer::new(1, 0, 0));
let rhs = ProtocolVersion::new(SemVer::new(1, 0, 0));
assert_eq!(lhs, rhs, "should be equal");
let rhs = ProtocolVersion::new(SemVer::new(2, 0, 0));
assert_ne!(lhs, rhs, "should not be equal")
}
#[test]
fn should_be_able_to_default() {
let defaulted = ProtocolVersion::default();
let expected = ProtocolVersion::new(SemVer::new(0, 0, 0));
assert_eq!(defaulted, expected, "should be equal")
}
#[test]
fn should_be_able_to_compare_relative_value() {
let lhs = ProtocolVersion::new(SemVer::new(2, 0, 0));
let rhs = ProtocolVersion::new(SemVer::new(1, 0, 0));
assert!(lhs > rhs, "should be gt");
let rhs = ProtocolVersion::new(SemVer::new(2, 0, 0));
assert!(lhs >= rhs, "should be gte");
assert!(lhs <= rhs, "should be lte");
let lhs = ProtocolVersion::new(SemVer::new(1, 0, 0));
assert!(lhs < rhs, "should be lt");
}
#[test]
fn should_follow_major_version_upgrade() {
let prev = ProtocolVersion::new(SemVer::new(1, 0, 0));
let next = ProtocolVersion::new(SemVer::new(2, 0, 0));
assert_eq!(
prev.check_next_version(&next),
VersionCheckResult::CodeIsRequired
);
}
#[test]
fn should_reject_if_major_version_decreases() {
let prev = ProtocolVersion::new(SemVer::new(10, 0, 0));
let next = ProtocolVersion::new(SemVer::new(9, 0, 0));
assert_eq!(prev.check_next_version(&next), VersionCheckResult::Invalid);
}
#[test]
fn should_check_follows_minor_version_upgrade() {
let prev = ProtocolVersion::new(SemVer::new(1, 1, 0));
let next = ProtocolVersion::new(SemVer::new(1, 2, 0));
assert_eq!(
prev.check_next_version(&next),
VersionCheckResult::CodeIsOptional
);
}
#[test]
fn should_check_if_minor_bump_resets_patch() {
let prev = ProtocolVersion::new(SemVer::new(1, 2, 0));
let next = ProtocolVersion::new(SemVer::new(1, 3, 1));
assert_eq!(prev.check_next_version(&next), VersionCheckResult::Invalid);
let prev = ProtocolVersion::new(SemVer::new(1, 20, 42));
let next = ProtocolVersion::new(SemVer::new(1, 30, 43));
assert_eq!(prev.check_next_version(&next), VersionCheckResult::Invalid);
}
#[test]
fn should_check_if_major_resets_minor_and_patch() {
let prev = ProtocolVersion::new(SemVer::new(1, 0, 0));
let next = ProtocolVersion::new(SemVer::new(2, 1, 0));
assert_eq!(prev.check_next_version(&next), VersionCheckResult::Invalid);
let next = ProtocolVersion::new(SemVer::new(2, 0, 1));
assert_eq!(prev.check_next_version(&next), VersionCheckResult::Invalid);
let next = ProtocolVersion::new(SemVer::new(2, 1, 1));
assert_eq!(prev.check_next_version(&next), VersionCheckResult::Invalid);
}
#[test]
fn should_reject_patch_version_rollback() {
let prev = ProtocolVersion::new(SemVer::new(1, 0, 42));
let next = ProtocolVersion::new(SemVer::new(1, 0, 41));
assert_eq!(prev.check_next_version(&next), VersionCheckResult::Invalid);
let next = ProtocolVersion::new(SemVer::new(1, 0, 13));
assert_eq!(prev.check_next_version(&next), VersionCheckResult::Invalid);
}
#[test]
fn should_accept_patch_version_update_with_optional_code() {
let prev = ProtocolVersion::new(SemVer::new(1, 0, 0));
let next = ProtocolVersion::new(SemVer::new(1, 0, 1));
assert_eq!(
prev.check_next_version(&next),
VersionCheckResult::CodeIsOptional
);
let prev = ProtocolVersion::new(SemVer::new(1, 0, 8));
let next = ProtocolVersion::new(SemVer::new(1, 0, 42));
assert_eq!(
prev.check_next_version(&next),
VersionCheckResult::CodeIsOptional
);
}
#[test]
fn should_accept_minor_version_update_with_optional_code() {
let prev = ProtocolVersion::new(SemVer::new(1, 0, 0));
let next = ProtocolVersion::new(SemVer::new(1, 1, 0));
assert_eq!(
prev.check_next_version(&next),
VersionCheckResult::CodeIsOptional
);
let prev = ProtocolVersion::new(SemVer::new(3, 98, 0));
let next = ProtocolVersion::new(SemVer::new(3, 99, 0));
assert_eq!(
prev.check_next_version(&next),
VersionCheckResult::CodeIsOptional
);
}
#[test]
fn should_not_skip_minor_version_within_major_version() {
let prev = ProtocolVersion::new(SemVer::new(1, 1, 0));
let next = ProtocolVersion::new(SemVer::new(1, 3, 0));
assert_eq!(prev.check_next_version(&next), VersionCheckResult::Invalid);
let next = ProtocolVersion::new(SemVer::new(1, 7, 0));
assert_eq!(prev.check_next_version(&next), VersionCheckResult::Invalid);
}
#[test]
fn should_reset_minor_and_patch_on_major_bump() {
let prev = ProtocolVersion::new(SemVer::new(1, 0, 0));
let next = ProtocolVersion::new(SemVer::new(2, 1, 1));
assert_eq!(prev.check_next_version(&next), VersionCheckResult::Invalid);
let prev = ProtocolVersion::new(SemVer::new(1, 1, 1));
let next = ProtocolVersion::new(SemVer::new(2, 2, 3));
assert_eq!(prev.check_next_version(&next), VersionCheckResult::Invalid);
}
#[test]
fn should_allow_code_on_major_update() {
let prev = ProtocolVersion::new(SemVer::new(1, 0, 0));
let next = ProtocolVersion::new(SemVer::new(2, 0, 0));
assert_eq!(
prev.check_next_version(&next),
VersionCheckResult::CodeIsRequired
);
let prev = ProtocolVersion::new(SemVer::new(2, 99, 99));
let next = ProtocolVersion::new(SemVer::new(3, 0, 0));
assert_eq!(
prev.check_next_version(&next),
VersionCheckResult::CodeIsRequired
);
}
#[test]
fn should_not_skip_major_version() {
let prev = ProtocolVersion::new(SemVer::new(1, 0, 0));
let next = ProtocolVersion::new(SemVer::new(3, 0, 0));
assert_eq!(prev.check_next_version(&next), VersionCheckResult::Invalid);
}
#[test]
fn should_reject_major_version_rollback() {
let prev = ProtocolVersion::new(SemVer::new(2, 0, 0));
let next = ProtocolVersion::new(SemVer::new(0, 0, 0));
assert_eq!(prev.check_next_version(&next), VersionCheckResult::Invalid);
}
#[test]
fn should_check_same_version_is_invalid() {
for ver in &[
ProtocolVersion::from_parts(1, 0, 0),
ProtocolVersion::from_parts(1, 2, 0),
ProtocolVersion::from_parts(1, 2, 3),
] {
assert_eq!(ver.check_next_version(&ver), VersionCheckResult::Invalid);
}
}
#[test]
fn should_not_be_compatible_with_different_major_version() {
let current = ProtocolVersion::from_parts(1, 2, 3);
let other = ProtocolVersion::from_parts(2, 5, 6);
assert!(!current.is_compatible_with(&other));
let current = ProtocolVersion::from_parts(1, 0, 0);
let other = ProtocolVersion::from_parts(2, 0, 0);
assert!(!current.is_compatible_with(&other));
}
#[test]
fn should_be_compatible_with_equal_major_version_backwards() {
let current = ProtocolVersion::from_parts(1, 99, 99);
let other = ProtocolVersion::from_parts(1, 0, 0);
assert!(current.is_compatible_with(&other));
}
#[test]
fn should_be_compatible_with_equal_major_version_forwards() {
let current = ProtocolVersion::from_parts(1, 0, 0);
let other = ProtocolVersion::from_parts(1, 99, 99);
assert!(current.is_compatible_with(&other));
}
}