xmtp_mls_common/
libxmtp_version.rs1use thiserror::Error;
2
3#[derive(Debug, Clone, PartialEq, Eq, Error)]
5#[error("Invalid version format: {0}")]
6pub struct InvalidVersionFormat(pub String);
7
8#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
31pub struct LibXMTPVersion(semver::Version);
32
33impl LibXMTPVersion {
34 pub fn parse(version_str: &str) -> Result<Self, InvalidVersionFormat> {
35 semver::Version::parse(version_str)
36 .map(Self)
37 .map_err(|_| InvalidVersionFormat(version_str.to_string()))
38 }
39
40 pub fn semver(&self) -> &semver::Version {
42 &self.0
43 }
44}
45
46impl std::fmt::Display for LibXMTPVersion {
47 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48 std::fmt::Display::fmt(&self.0, f)
49 }
50}
51
52#[cfg(test)]
53mod tests {
54 use super::*;
55
56 #[xmtp_common::test(unwrap_try = true)]
57 fn test_parse_and_compare_basic_versions() {
58 let v1_0_0 = LibXMTPVersion::parse("1.0.0").unwrap();
59 let v1_0_1 = LibXMTPVersion::parse("1.0.1").unwrap();
60 let v1_1_0 = LibXMTPVersion::parse("1.1.0").unwrap();
61 let v2_0_0 = LibXMTPVersion::parse("2.0.0").unwrap();
62
63 assert!(v1_0_0 < v1_0_1);
64 assert!(v1_0_1 > v1_0_0);
65
66 assert!(v1_0_1 < v1_1_0);
67 assert!(v1_1_0 > v1_0_1);
68
69 assert!(v1_1_0 < v2_0_0);
70 assert!(v2_0_0 > v1_1_0);
71
72 let v1_0_0_dup = LibXMTPVersion::parse("1.0.0").unwrap();
73 assert_eq!(v1_0_0, v1_0_0_dup);
74 }
75
76 #[xmtp_common::test(unwrap_try = true)]
77 fn test_parse_and_compare_with_suffixes() {
78 let v1_0_0 = LibXMTPVersion::parse("1.0.0").unwrap();
79 let v1_0_0_alpha = LibXMTPVersion::parse("1.0.0-alpha").unwrap();
80 let v1_0_0_beta = LibXMTPVersion::parse("1.0.0-beta").unwrap();
81 let v1_0_0_rc1 = LibXMTPVersion::parse("1.0.0-rc1").unwrap();
82 let v1_0_1_alpha = LibXMTPVersion::parse("1.0.1-alpha").unwrap();
83
84 assert!(v1_0_0_alpha < v1_0_0_beta);
86 assert!(v1_0_0_beta < v1_0_0_rc1);
87
88 assert!(v1_0_0_alpha < v1_0_0);
93 assert!(v1_0_0_beta < v1_0_0);
94
95 assert!(v1_0_0 < v1_0_1_alpha);
97 assert!(v1_0_0_rc1 < v1_0_1_alpha);
98 }
99
100 #[xmtp_common::test(unwrap_try = true)]
101 fn test_parse_and_compare_zero_versions() {
102 let v0_0_0 = LibXMTPVersion::parse("0.0.0").unwrap();
103 let v0_0_1 = LibXMTPVersion::parse("0.0.1").unwrap();
104 let v0_1_0 = LibXMTPVersion::parse("0.1.0").unwrap();
105 let v1_0_0 = LibXMTPVersion::parse("1.0.0").unwrap();
106
107 assert!(v0_0_0 < v0_0_1);
108 assert!(v0_0_1 < v0_1_0);
109 assert!(v0_1_0 < v1_0_0);
110 }
111
112 #[xmtp_common::test(unwrap_try = true)]
114 fn test_numeric_pre_release_identifiers_compare_numerically() {
115 let v1_0_0_rc_2 = LibXMTPVersion::parse("1.0.0-rc.2").unwrap();
120 let v1_0_0_rc_10 = LibXMTPVersion::parse("1.0.0-rc.10").unwrap();
121 assert!(v1_0_0_rc_2 < v1_0_0_rc_10);
122
123 let v1_0_0_alpha_2 = LibXMTPVersion::parse("1.0.0-alpha.2").unwrap();
124 let v1_0_0_alpha_11 = LibXMTPVersion::parse("1.0.0-alpha.11").unwrap();
125 assert!(v1_0_0_alpha_2 < v1_0_0_alpha_11);
126 }
127
128 #[xmtp_common::test(unwrap_try = true)]
129 fn test_multi_segment_pre_release_parses() {
130 assert!(LibXMTPVersion::parse("1.0.0-alpha.1").is_ok());
134 assert!(LibXMTPVersion::parse("1.0.0-rc.1.build.42").is_ok());
135 }
136
137 #[xmtp_common::test(unwrap_try = true)]
138 fn test_build_metadata_parses() {
139 assert!(LibXMTPVersion::parse("1.0.0+build.5").is_ok());
151 assert!(LibXMTPVersion::parse("1.0.0-rc.1+build.5").is_ok());
152 }
153
154 #[xmtp_common::test(unwrap_try = true)]
155 fn test_parse_invalid_format() {
156 for bad in ["1.0", "1.0.0.0", "1.x.0", "a.b.c", "1..0", ""] {
157 assert_eq!(
158 LibXMTPVersion::parse(bad),
159 Err(InvalidVersionFormat(bad.to_string())),
160 "expected {bad:?} to fail parsing"
161 );
162 }
163 }
164
165 #[xmtp_common::test(unwrap_try = true)]
167 fn proposals_min_protocol_version_does_not_exceed_workspace_version() {
168 let default_floor =
169 LibXMTPVersion::parse(xmtp_configuration::PROPOSALS_MIN_PROTOCOL_VERSION)
170 .expect("PROPOSALS_MIN_PROTOCOL_VERSION must be valid semver");
171 let workspace = LibXMTPVersion::parse(env!("CARGO_PKG_VERSION"))
172 .expect("CARGO_PKG_VERSION must be valid semver");
173 assert!(
174 default_floor <= workspace,
175 "PROPOSALS_MIN_PROTOCOL_VERSION ({}) must be <= CARGO_PKG_VERSION ({}); \
176 a higher default would pause freshly created groups",
177 xmtp_configuration::PROPOSALS_MIN_PROTOCOL_VERSION,
178 env!("CARGO_PKG_VERSION"),
179 );
180 }
181}