Skip to content

Commit 0757102

Browse files
authored
feat: add comprehensive SemVer implementation with full test coverage (#30)
1 parent 2218276 commit 0757102

File tree

6 files changed

+1748
-0
lines changed

6 files changed

+1748
-0
lines changed

src/version/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
pub mod pep440;
2+
pub mod semver;
23

34
pub use pep440::{PEP440Version, PreReleaseLabel};
5+
pub use semver::{BuildMetadata, PreReleaseIdentifier, SemVerVersion};

src/version/semver/core.rs

Lines changed: 316 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,316 @@
1+
#[derive(Debug, Clone, PartialEq, Eq)]
2+
pub enum PreReleaseIdentifier {
3+
String(String),
4+
Integer(u64),
5+
}
6+
7+
#[derive(Debug, Clone, PartialEq, Eq)]
8+
pub enum BuildMetadata {
9+
String(String),
10+
Integer(u64),
11+
}
12+
13+
#[derive(Debug, Clone)]
14+
pub struct SemVerVersion {
15+
pub major: u64,
16+
pub minor: u64,
17+
pub patch: u64,
18+
pub pre_release: Option<Vec<PreReleaseIdentifier>>,
19+
pub build_metadata: Option<Vec<BuildMetadata>>,
20+
}
21+
22+
impl SemVerVersion {
23+
pub fn new(major: u64, minor: u64, patch: u64) -> Self {
24+
Self {
25+
major,
26+
minor,
27+
patch,
28+
pre_release: None,
29+
build_metadata: None,
30+
}
31+
}
32+
33+
pub fn with_pre_release(mut self, pre_release: Vec<PreReleaseIdentifier>) -> Self {
34+
self.pre_release = Some(pre_release);
35+
self
36+
}
37+
38+
pub fn with_build_metadata(mut self, build_metadata: Vec<BuildMetadata>) -> Self {
39+
self.build_metadata = Some(build_metadata);
40+
self
41+
}
42+
43+
pub fn is_pre_release(&self) -> bool {
44+
self.pre_release.is_some()
45+
}
46+
47+
pub fn is_stable(&self) -> bool {
48+
!self.is_pre_release()
49+
}
50+
}
51+
52+
impl Default for SemVerVersion {
53+
fn default() -> Self {
54+
Self::new(0, 0, 0)
55+
}
56+
}
57+
58+
#[cfg(test)]
59+
mod tests {
60+
use super::*;
61+
use rstest::rstest;
62+
63+
mod construction {
64+
use super::*;
65+
66+
#[test]
67+
fn test_new() {
68+
let version = SemVerVersion::new(1, 2, 3);
69+
assert_eq!(version.major, 1);
70+
assert_eq!(version.minor, 2);
71+
assert_eq!(version.patch, 3);
72+
assert!(version.pre_release.is_none());
73+
assert!(version.build_metadata.is_none());
74+
}
75+
76+
#[test]
77+
fn test_default() {
78+
let version = SemVerVersion::default();
79+
assert_eq!(version.major, 0);
80+
assert_eq!(version.minor, 0);
81+
assert_eq!(version.patch, 0);
82+
assert!(version.pre_release.is_none());
83+
assert!(version.build_metadata.is_none());
84+
}
85+
86+
#[test]
87+
fn test_with_pre_release() {
88+
let pre_release = vec![
89+
PreReleaseIdentifier::String("alpha".to_string()),
90+
PreReleaseIdentifier::Integer(1),
91+
];
92+
let version = SemVerVersion::new(1, 0, 0).with_pre_release(pre_release.clone());
93+
assert_eq!(version.pre_release, Some(pre_release));
94+
}
95+
96+
#[test]
97+
fn test_with_build_metadata() {
98+
let build_metadata = vec![
99+
BuildMetadata::String("build".to_string()),
100+
BuildMetadata::Integer(123),
101+
];
102+
let version = SemVerVersion::new(1, 0, 0).with_build_metadata(build_metadata.clone());
103+
assert_eq!(version.build_metadata, Some(build_metadata));
104+
}
105+
106+
#[test]
107+
fn test_method_chaining() {
108+
let pre_release = vec![PreReleaseIdentifier::String("alpha".to_string())];
109+
let build_metadata = vec![BuildMetadata::String("build".to_string())];
110+
111+
let version = SemVerVersion::new(1, 2, 3)
112+
.with_pre_release(pre_release.clone())
113+
.with_build_metadata(build_metadata.clone());
114+
115+
assert_eq!(version.major, 1);
116+
assert_eq!(version.minor, 2);
117+
assert_eq!(version.patch, 3);
118+
assert_eq!(version.pre_release, Some(pre_release));
119+
assert_eq!(version.build_metadata, Some(build_metadata));
120+
}
121+
}
122+
123+
mod properties {
124+
use super::*;
125+
126+
#[test]
127+
fn test_is_stable() {
128+
let stable = SemVerVersion::new(1, 0, 0);
129+
assert!(stable.is_stable());
130+
assert!(!stable.is_pre_release());
131+
}
132+
133+
#[test]
134+
fn test_is_pre_release() {
135+
let pre_release = SemVerVersion::new(1, 0, 0)
136+
.with_pre_release(vec![PreReleaseIdentifier::String("alpha".to_string())]);
137+
assert!(pre_release.is_pre_release());
138+
assert!(!pre_release.is_stable());
139+
}
140+
141+
#[test]
142+
fn test_build_metadata_does_not_affect_stability() {
143+
let version = SemVerVersion::new(1, 0, 0)
144+
.with_build_metadata(vec![BuildMetadata::String("build".to_string())]);
145+
assert!(version.is_stable());
146+
assert!(!version.is_pre_release());
147+
}
148+
}
149+
150+
mod edge_cases {
151+
use super::*;
152+
153+
#[test]
154+
fn test_zero_version() {
155+
let version = SemVerVersion::new(0, 0, 0);
156+
assert_eq!(version.major, 0);
157+
assert_eq!(version.minor, 0);
158+
assert_eq!(version.patch, 0);
159+
}
160+
161+
#[test]
162+
fn test_max_values() {
163+
let version = SemVerVersion::new(u64::MAX, u64::MAX, u64::MAX);
164+
assert_eq!(version.major, u64::MAX);
165+
assert_eq!(version.minor, u64::MAX);
166+
assert_eq!(version.patch, u64::MAX);
167+
}
168+
169+
#[test]
170+
fn test_empty_pre_release() {
171+
let version = SemVerVersion::new(1, 0, 0).with_pre_release(vec![]);
172+
assert_eq!(version.pre_release, Some(vec![]));
173+
assert!(version.is_pre_release());
174+
}
175+
176+
#[test]
177+
fn test_empty_build_metadata() {
178+
let version = SemVerVersion::new(1, 0, 0).with_build_metadata(vec![]);
179+
assert_eq!(version.build_metadata, Some(vec![]));
180+
}
181+
182+
#[test]
183+
fn test_overwrite_pre_release() {
184+
let first = vec![PreReleaseIdentifier::String("alpha".to_string())];
185+
let second = vec![PreReleaseIdentifier::String("beta".to_string())];
186+
187+
let version = SemVerVersion::new(1, 0, 0)
188+
.with_pre_release(first)
189+
.with_pre_release(second.clone());
190+
191+
assert_eq!(version.pre_release, Some(second));
192+
}
193+
194+
#[test]
195+
fn test_overwrite_build_metadata() {
196+
let first = vec![BuildMetadata::String("build1".to_string())];
197+
let second = vec![BuildMetadata::String("build2".to_string())];
198+
199+
let version = SemVerVersion::new(1, 0, 0)
200+
.with_build_metadata(first)
201+
.with_build_metadata(second.clone());
202+
203+
assert_eq!(version.build_metadata, Some(second));
204+
}
205+
}
206+
207+
mod identifiers {
208+
use super::*;
209+
210+
#[rstest]
211+
#[case("alpha")]
212+
#[case("beta")]
213+
#[case("rc")]
214+
#[case("x")]
215+
#[case("")]
216+
fn test_pre_release_string_identifier(#[case] value: &str) {
217+
let identifier = PreReleaseIdentifier::String(value.to_string());
218+
match identifier {
219+
PreReleaseIdentifier::String(s) => assert_eq!(s, value),
220+
_ => panic!("Expected string identifier"),
221+
}
222+
}
223+
224+
#[rstest]
225+
#[case(0)]
226+
#[case(1)]
227+
#[case(123)]
228+
#[case(u64::MAX)]
229+
fn test_pre_release_integer_identifier(#[case] value: u64) {
230+
let identifier = PreReleaseIdentifier::Integer(value);
231+
match identifier {
232+
PreReleaseIdentifier::Integer(n) => assert_eq!(n, value),
233+
_ => panic!("Expected integer identifier"),
234+
}
235+
}
236+
237+
#[rstest]
238+
#[case("build")]
239+
#[case("commit")]
240+
#[case("sha")]
241+
#[case("")]
242+
fn test_build_metadata_string(#[case] value: &str) {
243+
let metadata = BuildMetadata::String(value.to_string());
244+
match metadata {
245+
BuildMetadata::String(s) => assert_eq!(s, value),
246+
_ => panic!("Expected string metadata"),
247+
}
248+
}
249+
250+
#[rstest]
251+
#[case(0)]
252+
#[case(1)]
253+
#[case(20240101)]
254+
#[case(u64::MAX)]
255+
fn test_build_metadata_integer(#[case] value: u64) {
256+
let metadata = BuildMetadata::Integer(value);
257+
match metadata {
258+
BuildMetadata::Integer(n) => assert_eq!(n, value),
259+
_ => panic!("Expected integer metadata"),
260+
}
261+
}
262+
}
263+
264+
mod complex_versions {
265+
use super::*;
266+
267+
#[test]
268+
fn test_complex_pre_release() {
269+
let pre_release = vec![
270+
PreReleaseIdentifier::String("alpha".to_string()),
271+
PreReleaseIdentifier::Integer(1),
272+
PreReleaseIdentifier::String("build".to_string()),
273+
PreReleaseIdentifier::Integer(456),
274+
];
275+
276+
let version = SemVerVersion::new(2, 0, 0).with_pre_release(pre_release.clone());
277+
assert_eq!(version.pre_release, Some(pre_release));
278+
}
279+
280+
#[test]
281+
fn test_complex_build_metadata() {
282+
let build_metadata = vec![
283+
BuildMetadata::String("commit".to_string()),
284+
BuildMetadata::String("abc123".to_string()),
285+
BuildMetadata::Integer(20240101),
286+
];
287+
288+
let version = SemVerVersion::new(1, 5, 0).with_build_metadata(build_metadata.clone());
289+
assert_eq!(version.build_metadata, Some(build_metadata));
290+
}
291+
292+
#[test]
293+
fn test_full_version() {
294+
let pre_release = vec![
295+
PreReleaseIdentifier::String("rc".to_string()),
296+
PreReleaseIdentifier::Integer(2),
297+
];
298+
let build_metadata = vec![
299+
BuildMetadata::String("build".to_string()),
300+
BuildMetadata::Integer(789),
301+
];
302+
303+
let version = SemVerVersion::new(3, 1, 4)
304+
.with_pre_release(pre_release.clone())
305+
.with_build_metadata(build_metadata.clone());
306+
307+
assert_eq!(version.major, 3);
308+
assert_eq!(version.minor, 1);
309+
assert_eq!(version.patch, 4);
310+
assert_eq!(version.pre_release, Some(pre_release));
311+
assert_eq!(version.build_metadata, Some(build_metadata));
312+
assert!(version.is_pre_release());
313+
assert!(!version.is_stable());
314+
}
315+
}
316+
}

0 commit comments

Comments
 (0)