Skip to main content

ahri_tre_protocol/
version.rs

1use semver::Version;
2use serde::{Deserialize, Serialize};
3use std::fmt;
4
5pub const CURRENT_PROTOCOL_VERSION: &str = "2.0.0";
6pub const PROTOCOL_COMPATIBILITY_RULE: &str = "same_major_not_newer";
7
8#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
9pub struct ProtocolCompatibility {
10    pub current: String,
11    pub compatibility: ProtocolCompatibilityRange,
12}
13
14#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
15pub struct ProtocolCompatibilityRange {
16    pub minimum: String,
17    pub maximum: String,
18    pub rule: String,
19}
20
21pub fn current_protocol_compatibility() -> ProtocolCompatibility {
22    let current = ProtocolVersion::current();
23
24    ProtocolCompatibility {
25        current: current.to_string(),
26        compatibility: ProtocolCompatibilityRange {
27            minimum: format!("{}.0.0", current.major()),
28            maximum: current.to_string(),
29            rule: PROTOCOL_COMPATIBILITY_RULE.to_string(),
30        },
31    }
32}
33
34#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
35#[serde(try_from = "String", into = "String")]
36pub struct ProtocolVersion(Version);
37
38impl ProtocolVersion {
39    pub fn current() -> Self {
40        Self(Version::parse(CURRENT_PROTOCOL_VERSION).expect("current protocol version is valid"))
41    }
42
43    pub fn parse(value: impl AsRef<str>) -> Result<Self, ProtocolVersionError> {
44        let value = value.as_ref();
45        if value.starts_with('v') {
46            return Err(ProtocolVersionError::InvalidFormat);
47        }
48        let version = Version::parse(value).map_err(|_| ProtocolVersionError::InvalidFormat)?;
49        if !version.pre.is_empty() || !version.build.is_empty() {
50            return Err(ProtocolVersionError::InvalidFormat);
51        }
52        Ok(Self(version))
53    }
54
55    pub fn ensure_supported(&self, server: &Self) -> Result<(), ProtocolVersionError> {
56        if self.0.major != server.0.major {
57            return Err(ProtocolVersionError::UnsupportedMajor);
58        }
59        if self.0 > server.0 {
60            return Err(ProtocolVersionError::UnsupportedVersion);
61        }
62        Ok(())
63    }
64
65    pub const fn major(&self) -> u64 {
66        self.0.major
67    }
68}
69
70impl Default for ProtocolVersion {
71    fn default() -> Self {
72        Self::current()
73    }
74}
75
76impl fmt::Display for ProtocolVersion {
77    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
78        self.0.fmt(formatter)
79    }
80}
81
82impl TryFrom<String> for ProtocolVersion {
83    type Error = ProtocolVersionError;
84
85    fn try_from(value: String) -> Result<Self, Self::Error> {
86        Self::parse(value)
87    }
88}
89
90impl From<ProtocolVersion> for String {
91    fn from(value: ProtocolVersion) -> Self {
92        value.to_string()
93    }
94}
95
96#[derive(Debug, Clone, Copy, PartialEq, Eq)]
97pub enum ProtocolVersionError {
98    InvalidFormat,
99    UnsupportedMajor,
100    UnsupportedVersion,
101}
102
103impl fmt::Display for ProtocolVersionError {
104    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
105        match self {
106            Self::InvalidFormat => formatter.write_str("invalid protocol version format"),
107            Self::UnsupportedMajor => formatter.write_str("unsupported protocol major version"),
108            Self::UnsupportedVersion => formatter.write_str("unsupported protocol version"),
109        }
110    }
111}
112
113#[cfg(test)]
114mod tests {
115    use super::*;
116
117    #[test]
118    fn compatibility_range_is_owned_by_protocol_version_contract() {
119        let compatibility = current_protocol_compatibility();
120        let current = ProtocolVersion::current();
121
122        assert_eq!(compatibility.current, CURRENT_PROTOCOL_VERSION);
123        assert_eq!(compatibility.compatibility.minimum, "2.0.0");
124        assert_eq!(compatibility.compatibility.maximum, current.to_string());
125        assert_eq!(
126            compatibility.compatibility.rule,
127            PROTOCOL_COMPATIBILITY_RULE
128        );
129    }
130}