1use std::str::FromStr;
2
3use crate::error::{VersionBumpError, VersionParseError};
4use crate::version::{VersionEditor, Versioned};
5
6#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Ord, PartialOrd)]
7enum SemanticVersion {
8 Patch,
9 Minor,
10 Major,
11}
12
13#[derive(Clone, Debug, Eq, Hash, PartialEq, Ord, PartialOrd)]
14pub struct Semantic {
15 r#type: SemanticVersion,
16}
17
18impl Semantic {
19 pub fn minor() -> Self {
20 Semantic {
21 r#type: SemanticVersion::Minor,
22 }
23 }
24 pub fn major() -> Self {
25 Semantic {
26 r#type: SemanticVersion::Major,
27 }
28 }
29 pub fn patch() -> Self {
30 Semantic {
31 r#type: SemanticVersion::Patch,
32 }
33 }
34}
35
36impl Versioned for Semantic {
37 fn apply(&self, current: &str) -> Result<String, VersionBumpError> {
38 let mut current = current.split('.');
39
40 let major = current
41 .next()
42 .map(|val| val.parse::<i32>().ok())
43 .flatten()
44 .ok_or(VersionBumpError)?;
45 let minor = current
46 .next()
47 .map(|val| val.parse::<i32>().ok())
48 .flatten()
49 .ok_or(VersionBumpError)?;
50 let patch = current
52 .next()
53 .map(|val| val.parse::<i32>().ok())
54 .flatten()
55 .ok_or(VersionBumpError)?;
56
57 Ok(match self.r#type {
58 SemanticVersion::Major => format!("{}.{}.{}", major + 1, 0, 0),
59 SemanticVersion::Minor => format!("{}.{}.{}", major, minor + 1, 0),
60 SemanticVersion::Patch => format!("{}.{}.{}", major, minor, patch + 1),
61 })
62 }
63}
64
65impl VersionEditor for Semantic {
66 fn mask<'a>(mask: &str, version: &'a str) -> &'a str {
68 &version[..mask.len()]
69 }
70
71 fn r#match(mask: &str, version: &str) -> bool {
72 Self::mask(mask, version) == mask
73 }
74
75 fn options() -> Vec<Self> {
76 vec![Self::patch(), Self::minor(), Self::major()]
77 }
78}
79
80impl Default for Semantic {
81 fn default() -> Self {
82 Semantic::patch()
83 }
84}
85
86impl From<SemanticVersion> for Semantic {
87 fn from(r#type: SemanticVersion) -> Self {
88 Semantic { r#type }
89 }
90}
91
92impl FromStr for Semantic {
93 type Err = VersionParseError;
94 fn from_str(value: &str) -> Result<Self, Self::Err> {
95 match value.to_lowercase().as_str() {
96 "patch" => Ok(Semantic::patch()),
97 "minor" => Ok(Semantic::minor()),
98 "major" => Ok(Semantic::major()),
99 _ => Err(VersionParseError::from(value.to_string())),
100 }
101 }
102}
103
104impl ToString for Semantic {
105 fn to_string(&self) -> String {
106 match self.r#type {
107 SemanticVersion::Patch => "patch",
108 SemanticVersion::Minor => "minor",
109 SemanticVersion::Major => "major",
110 }
111 .to_owned()
112 }
113}
114
115#[cfg(test)]
116mod tests {
117
118 use super::*;
119 use crate::version::{VersionMod, Versioned};
120
121 #[test]
122 fn from_str() {
123 let strings = vec!["patch", "minor", "minor", "major"];
124
125 let versions: Vec<VersionMod<Semantic>> = strings
126 .iter()
127 .filter_map(|item| VersionMod::from_str(item).ok())
128 .collect();
129
130 assert_eq!(
131 versions,
132 vec![
133 VersionMod::new(Semantic::patch()),
134 VersionMod::new(Semantic::minor()),
135 VersionMod::new(Semantic::minor()),
136 VersionMod::new(Semantic::major()),
137 ]
138 );
139 }
140
141 #[test]
142 fn to_str() {
143 let versions = vec![
144 VersionMod::new(Semantic::patch()),
145 VersionMod::new(Semantic::minor()),
146 VersionMod::new(Semantic::minor()),
147 VersionMod::new(Semantic::major()),
148 ];
149
150 let strings: Vec<String> = versions.iter().map(|item| item.to_string()).collect();
151
152 assert_eq!(strings, vec!["patch", "minor", "minor", "major"]);
153 }
154
155 #[test]
168 fn major_apply() {
169 let version = VersionMod::new(Semantic::major());
170
171 let bumped = version.apply("0.4.1");
172
173 assert!(bumped.is_ok());
174
175 assert_eq!(bumped.unwrap(), "1.0.0".to_owned())
176 }
177
178 #[test]
179 fn minor_apply() {
180 let version = VersionMod::new(Semantic::minor());
181
182 let bumped = version.apply("4.1.1");
183
184 assert!(bumped.is_ok());
185
186 assert_eq!(bumped.unwrap(), "4.2.0".to_owned())
187 }
188
189 #[test]
190 fn patch_apply() {
191 let version = VersionMod::new(Semantic::patch());
192
193 let bumped = version.apply("0.4.1");
194
195 assert!(bumped.is_ok());
196
197 assert_eq!(bumped.unwrap(), "0.4.2".to_owned())
198 }
199}