lib/
version.rs

1// This file defines the current version of Bonnie
2// This MUST be updated before all releases!
3
4pub const BONNIE_VERSION: &str = "0.3.2";
5
6// The different between two major/minor/patch versions
7#[derive(Debug, PartialEq, Eq)]
8pub enum VersionDifference {
9	TooOld,
10	TooNew,
11}
12
13// The compatibility of two versions with one another
14#[derive(Debug, PartialEq, Eq)]
15pub enum VersionCompatibility {
16	Identical,
17	DifferentMajor(VersionDifference), // Only this means the versions are incompatible
18	DifferentMinor(VersionDifference),
19	DifferentPatch(VersionDifference),
20	DifferentBetaVersion(VersionDifference), // In beta, this also means the versions are incompatible
21}
22
23#[derive(Debug, PartialEq, Eq)]
24pub struct Version {
25	patch: u16,
26	minor: u16,
27	major: u16,
28}
29impl Version {
30	// Compares this with another version returns their compatibility
31	// It will return an embedded version difference as to whether the version being compared to is too old/new or nothing if they're identical
32	pub fn is_compatible_with(&self, comparison: &Version) -> VersionCompatibility {
33		let compatibility = match self.major {
34			_ if self.major > comparison.major => {
35				VersionCompatibility::DifferentMajor(VersionDifference::TooOld)
36			}
37			_ if self.major < comparison.major => {
38				VersionCompatibility::DifferentMajor(VersionDifference::TooNew)
39			}
40			_ if self.minor > comparison.minor => {
41				VersionCompatibility::DifferentMinor(VersionDifference::TooOld)
42			}
43			_ if self.minor < comparison.minor => {
44				VersionCompatibility::DifferentMinor(VersionDifference::TooNew)
45			}
46			_ if self.patch > comparison.patch => {
47				VersionCompatibility::DifferentPatch(VersionDifference::TooOld)
48			}
49			_ if self.patch < comparison.patch => {
50				VersionCompatibility::DifferentPatch(VersionDifference::TooNew)
51			}
52			_ => VersionCompatibility::Identical,
53		};
54		// If we're in beta (0.x.x), any difference is tantamount to treason
55		if self.major == 0 && !matches!(compatibility, VersionCompatibility::Identical) {
56			// Here we figure out if the comparison version is too old or too new
57			VersionCompatibility::DifferentBetaVersion(match compatibility {
58                VersionCompatibility::DifferentMajor(version_difference) => version_difference,
59                VersionCompatibility::DifferentMinor(version_difference) => version_difference,
60                VersionCompatibility::DifferentPatch(version_difference) => version_difference,
61                _ => panic!("Critical logic failure in version compatibility checks. You should report this as a bug."), // This shouldn't be possible, we know more than the compiler
62            })
63		} else {
64			compatibility
65		}
66	}
67}
68
69// This breaks a given version down into major/minor/patch numbers
70pub fn get_version_parts(version_str: &str) -> Result<Version, String> {
71	let split: Vec<&str> = version_str.split('.').collect();
72	// Get each component of that
73	let patch = split.get(2)
74        .ok_or_else(|| String::from(
75            "Couldn't extract the patch version number from the given version string. If the version string in your Bonnie configuration file is definitely of the form 'x.y.z', you should report this as a bug."
76        ))?
77        .parse::<u16>()
78        .map_err(|_| String::from(
79            "Couldn't serialize the patch version number from the given version string into an integer. If the version string in your Bonnie configuration file is definitely of the form 'x.y.z', where each of those are integers, you should report this as a bug."
80        ))?;
81	let minor = split.get(1)
82        .ok_or_else(|| String::from(
83            "Couldn't extract the minor version number from the given version string. If the version string in your Bonnie configuration file is definitely of the form 'x.y.z', you should report this as a bug."
84        ))?
85        .parse::<u16>()
86        .map_err(|_| String::from(
87            "Couldn't serialize the minor version number from the given version string into an integer. If the version string in your Bonnie configuration file is definitely of the form 'x.y.z', where each of those are integers, you should report this as a bug."
88        ))?;
89	let major = split.first()
90        .ok_or_else(|| String::from(
91            "Couldn't extract the major version number from the given version string. If the version string in your Bonnie configuration file is definitely of the form 'x.y.z', you should report this as a bug."
92        ))?
93        .parse::<u16>()
94        .map_err(|_| String::from(
95            "Couldn't serialize the major version number from the given version string into an integer. If the version string in your Bonnie configuration file is definitely of the form 'x.y.z', where each of those are integers, you should report this as a bug."
96        ))?;
97	// Construct a version
98	Ok(Version {
99		patch,
100		minor,
101		major,
102	})
103}
104
105// TESTING
106
107// Creates a version from a vector for convenience (testing utility only)
108// This will panic if something goes wrong
109#[cfg(test)]
110fn build_version(parts: Vec<u16>) -> Version {
111	// We specify in reverse (e.g. [1, 2, 3] -> 1.2.3)
112	Version {
113		patch: parts[2],
114		minor: parts[1],
115		major: parts[0],
116	}
117}
118
119// Tests for comparing versions
120// None of these actually test how we interpret what's valid/invalid for compatibility (that logic is in `read_cfg.rs`)
121#[test]
122fn identifies_identical_versions() {
123	let version = build_version(vec![2, 3, 4]);
124	let comparison = build_version(vec![2, 3, 4]);
125	let compat = version.is_compatible_with(&comparison);
126
127	assert_eq!(compat, VersionCompatibility::Identical);
128}
129#[test]
130fn identifies_major_too_new() {
131	let version = build_version(vec![2, 3, 4]);
132	let comparison = build_version(vec![3, 3, 4]);
133	let compat = version.is_compatible_with(&comparison);
134
135	assert_eq!(
136		compat,
137		VersionCompatibility::DifferentMajor(VersionDifference::TooNew)
138	);
139}
140#[test]
141fn identifies_major_too_old() {
142	let version = build_version(vec![2, 3, 4]);
143	let comparison = build_version(vec![1, 3, 4]);
144	let compat = version.is_compatible_with(&comparison);
145
146	assert_eq!(
147		compat,
148		VersionCompatibility::DifferentMajor(VersionDifference::TooOld)
149	);
150}
151#[test]
152fn identifies_minor_too_new() {
153	let version = build_version(vec![2, 3, 4]);
154	let comparison = build_version(vec![2, 4, 4]);
155	let compat = version.is_compatible_with(&comparison);
156
157	assert_eq!(
158		compat,
159		VersionCompatibility::DifferentMinor(VersionDifference::TooNew)
160	);
161}
162#[test]
163fn identifies_minor_too_old() {
164	let version = build_version(vec![2, 3, 4]);
165	let comparison = build_version(vec![2, 2, 4]);
166	let compat = version.is_compatible_with(&comparison);
167
168	assert_eq!(
169		compat,
170		VersionCompatibility::DifferentMinor(VersionDifference::TooOld)
171	);
172}
173#[test]
174fn identifies_patch_too_new() {
175	let version = build_version(vec![2, 3, 4]);
176	let comparison = build_version(vec![2, 3, 5]);
177	let compat = version.is_compatible_with(&comparison);
178
179	assert_eq!(
180		compat,
181		VersionCompatibility::DifferentPatch(VersionDifference::TooNew)
182	);
183}
184#[test]
185fn identifies_patch_too_old() {
186	let version = build_version(vec![2, 3, 4]);
187	let comparison = build_version(vec![2, 3, 3]);
188	let compat = version.is_compatible_with(&comparison);
189
190	assert_eq!(
191		compat,
192		VersionCompatibility::DifferentPatch(VersionDifference::TooOld)
193	);
194}
195// All those tests for beta
196#[test]
197fn identifies_identical_versions_in_beta() {
198	let version = build_version(vec![0, 3, 4]);
199	let comparison = build_version(vec![0, 3, 4]);
200	let compat = version.is_compatible_with(&comparison);
201
202	assert_eq!(compat, VersionCompatibility::Identical);
203}
204#[test]
205fn identifies_major_too_new_in_beta() {
206	let version = build_version(vec![0, 3, 4]);
207	let comparison = build_version(vec![1, 3, 4]);
208	let compat = version.is_compatible_with(&comparison);
209
210	assert_eq!(
211		compat,
212		VersionCompatibility::DifferentBetaVersion(VersionDifference::TooNew)
213	);
214}
215#[test]
216fn identifies_minor_too_new_in_beta() {
217	let version = build_version(vec![0, 3, 4]);
218	let comparison = build_version(vec![0, 4, 4]);
219	let compat = version.is_compatible_with(&comparison);
220
221	assert_eq!(
222		compat,
223		VersionCompatibility::DifferentBetaVersion(VersionDifference::TooNew)
224	);
225}
226#[test]
227fn identifies_minor_too_old_in_beta() {
228	let version = build_version(vec![0, 3, 4]);
229	let comparison = build_version(vec![0, 2, 4]);
230	let compat = version.is_compatible_with(&comparison);
231
232	assert_eq!(
233		compat,
234		VersionCompatibility::DifferentBetaVersion(VersionDifference::TooOld)
235	);
236}
237#[test]
238fn identifies_patch_too_new_in_beta() {
239	let version = build_version(vec![0, 3, 4]);
240	let comparison = build_version(vec![0, 3, 5]);
241	let compat = version.is_compatible_with(&comparison);
242
243	assert_eq!(
244		compat,
245		VersionCompatibility::DifferentBetaVersion(VersionDifference::TooNew)
246	);
247}
248#[test]
249fn identifies_patch_too_old_in_beta() {
250	let version = build_version(vec![0, 3, 4]);
251	let comparison = build_version(vec![0, 3, 3]);
252	let compat = version.is_compatible_with(&comparison);
253
254	assert_eq!(
255		compat,
256		VersionCompatibility::DifferentBetaVersion(VersionDifference::TooOld)
257	);
258}
259
260// Tests for splitting the version into its parts
261#[test]
262fn returns_correct_part_division() {
263	let version = "1.2.3";
264	let parts = get_version_parts(version);
265
266	assert_eq!(parts, Ok(build_version(vec![1, 2, 3])))
267}
268#[test]
269fn returns_error_on_missing_patch_number() {
270	let version = "1.2";
271	let parts = get_version_parts(version);
272
273	if parts.is_ok() {
274		panic!("Didn't return an error on missing patch number.")
275	}
276}
277#[test]
278fn returns_error_on_missing_minor_number() {
279	let version = "1";
280	let parts = get_version_parts(version);
281
282	if parts.is_ok() {
283		panic!("Didn't return an error on missing minor number.")
284	}
285}
286#[test]
287fn returns_error_on_invalid_patch_number() {
288	let version = "1.2.x";
289	let parts = get_version_parts(version);
290
291	if parts.is_ok() {
292		panic!("Didn't return an error on invalid patch number.")
293	}
294}
295#[test]
296fn returns_error_on_invalid_minor_number() {
297	let version = "1.x.3";
298	let parts = get_version_parts(version);
299
300	if parts.is_ok() {
301		panic!("Didn't return an error on invalid minor number.")
302	}
303}
304#[test]
305fn returns_error_on_invalid_major_number() {
306	let version = "x.2.3";
307	let parts = get_version_parts(version);
308
309	if parts.is_ok() {
310		panic!("Didn't return an error on invalid major number.")
311	}
312}