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.get(0)
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}