1#![allow(non_upper_case_globals)]
14#![allow(non_camel_case_types)]
15#![allow(non_snake_case)]
16
17pub mod ffi {
19 include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
20}
21
22pub use ffi::{
23 LIBVERSION_VERSION_MAJOR, LIBVERSION_VERSION_MINOR, LIBVERSION_VERSION_PATCH,
24 VERSIONFLAG_ANY_IS_PATCH, VERSIONFLAG_LOWER_BOUND, VERSIONFLAG_P_IS_PATCH,
25 VERSIONFLAG_UPPER_BOUND, version_compare2, version_compare4,
26};
27
28use std::cmp::Ordering;
29use std::ffi::CString;
30
31pub fn compare(v1: &str, v2: &str) -> Ordering {
47 let v1 = CString::new(v1).expect("v1 contains interior null byte");
48 let v2 = CString::new(v2).expect("v2 contains interior null byte");
49 let result = unsafe { ffi::version_compare2(v1.as_ptr(), v2.as_ptr()) };
50 result.cmp(&0)
51}
52
53pub fn compare_with_flags(v1: &str, v2: &str, v1_flags: u32, v2_flags: u32) -> Ordering {
75 let v1 = CString::new(v1).expect("v1 contains interior null byte");
76 let v2 = CString::new(v2).expect("v2 contains interior null byte");
77 let result = unsafe {
78 ffi::version_compare4(v1.as_ptr(), v2.as_ptr(), v1_flags as i32, v2_flags as i32)
79 };
80 result.cmp(&0)
81}
82
83pub fn version_string() -> &'static str {
85 std::str::from_utf8(&ffi::LIBVERSION_VERSION[..ffi::LIBVERSION_VERSION.len() - 1])
86 .expect("libversion version string is not valid UTF-8")
87}
88
89#[allow(clippy::absurd_extreme_comparisons)]
91pub const fn version_atleast(major: u32, minor: u32, patch: u32) -> bool {
92 (LIBVERSION_VERSION_MAJOR > major)
93 || (LIBVERSION_VERSION_MAJOR == major && LIBVERSION_VERSION_MINOR > minor)
94 || (LIBVERSION_VERSION_MAJOR == major
95 && LIBVERSION_VERSION_MINOR == minor
96 && LIBVERSION_VERSION_PATCH >= patch)
97}
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102
103 #[test]
104 fn basic_comparison() {
105 assert_eq!(compare("0.99", "1.11"), Ordering::Less);
106 assert_eq!(compare("1.0", "1.0.0"), Ordering::Equal);
107 assert_eq!(compare("1.0", "0.99"), Ordering::Greater);
108 }
109
110 #[test]
111 fn prerelease() {
112 assert_eq!(compare("1.0alpha1", "1.0"), Ordering::Less);
113 assert_eq!(compare("1.0alpha1", "1.0rc1"), Ordering::Less);
114 assert_eq!(compare("1.0rc1", "1.0"), Ordering::Less);
115 }
116
117 #[test]
118 fn postrelease() {
119 assert_eq!(compare("1.0patch1", "1.0"), Ordering::Greater);
120 assert_eq!(compare("1.0.1", "1.0"), Ordering::Greater);
121 }
122
123 #[test]
124 fn p_is_patch_flag() {
125 assert_eq!(compare("1.0p1", "1.0"), Ordering::Less);
127
128 assert_eq!(
130 compare_with_flags("1.0p1", "1.0", VERSIONFLAG_P_IS_PATCH, 0),
131 Ordering::Greater,
132 );
133 }
134
135 #[test]
136 fn any_is_patch_flag() {
137 assert_eq!(compare("1.0foopatchset1", "1.0"), Ordering::Less);
138 assert_eq!(
139 compare_with_flags("1.0foopatchset1", "1.0", VERSIONFLAG_ANY_IS_PATCH, 0),
140 Ordering::Greater,
141 );
142 }
143
144 #[test]
145 fn lower_bound_flag() {
146 assert_eq!(
147 compare_with_flags("1.0alpha1", "1.0", 0, VERSIONFLAG_LOWER_BOUND),
148 Ordering::Greater,
149 );
150 assert_eq!(
151 compare_with_flags("0.999", "1.0", 0, VERSIONFLAG_LOWER_BOUND),
152 Ordering::Less,
153 );
154 }
155
156 #[test]
157 fn upper_bound_flag() {
158 assert_eq!(
159 compare_with_flags("1.0.1", "1.0", 0, VERSIONFLAG_UPPER_BOUND),
160 Ordering::Less,
161 );
162 assert_eq!(
163 compare_with_flags("1.1", "1.0", 0, VERSIONFLAG_UPPER_BOUND),
164 Ordering::Greater,
165 );
166 }
167
168 #[test]
169 #[should_panic(expected = "v1 contains interior null byte")]
170 fn compare_rejects_interior_null() {
171 let _ = compare("1.0\0rc1", "1.0");
172 }
173
174 #[test]
175 #[should_panic(expected = "v2 contains interior null byte")]
176 fn compare_with_flags_rejects_interior_null() {
177 let _ = compare_with_flags("1.0", "1.0\0rc1", 0, 0);
178 }
179
180 #[test]
181 fn version_metadata() {
182 let parts = version_string()
183 .split('.')
184 .map(|part| part.parse::<u32>().unwrap())
185 .collect::<Vec<_>>();
186
187 assert_eq!(
188 parts,
189 vec![
190 LIBVERSION_VERSION_MAJOR,
191 LIBVERSION_VERSION_MINOR,
192 LIBVERSION_VERSION_PATCH,
193 ]
194 );
195 assert!(version_atleast(
196 LIBVERSION_VERSION_MAJOR,
197 LIBVERSION_VERSION_MINOR,
198 LIBVERSION_VERSION_PATCH,
199 ));
200 assert!(!version_string().is_empty());
201 }
202
203 #[test]
204 fn ffi_direct() {
205 let v1 = CString::new("1.0").unwrap();
206 let v2 = CString::new("2.0").unwrap();
207 let result = unsafe { ffi::version_compare2(v1.as_ptr(), v2.as_ptr()) };
208 assert_eq!(result, -1);
209 }
210
211 #[test]
212 fn ffi_compare4_direct() {
213 let v1 = CString::new("1.0p1").unwrap();
214 let v2 = CString::new("1.0").unwrap();
215 let result = unsafe {
216 ffi::version_compare4(v1.as_ptr(), v2.as_ptr(), VERSIONFLAG_P_IS_PATCH as i32, 0)
217 };
218 assert_eq!(result, 1);
219 }
220}