version_tool/lib.rs
1//! # version 版本号工具库
2//! 此工具库提供了处理版本号并提供版本号比较的工具函数
3//!
4//! ## 快速开始
5//! ```
6//! use version::Version;
7//! let version_old = Version::build_string("1.0.0").unwrap();
8//! let version_new = Version::build_string("2.1.0").unwrap();
9//!
10//! if version_old.is_newer(&version_new) {
11//! println!("{} 是新版本", version_new.to_string());
12//! } else {
13//! panic!("版本号判断错误")
14//! }
15//! ```
16
17use std::num::ParseIntError;
18use thiserror::Error;
19
20///
21/// 表示一个版本号的结构体
22///
23/// 包含了 major(主版本号) minor(次版本号) patch(补丁版本号) 和 可选的suffix(版本后缀)
24///
25/// ```
26/// use version::Version;
27///
28/// // 基于现有字符串
29/// let version_s = Version::build_string("1.0.0").unwrap();
30/// println!("{}", version_s.to_string())
31/// ```
32pub struct Version {
33 major: u8,
34 minor: u8,
35 patch: u8,
36 suffix: String,
37}
38
39///
40/// 表示在解析操作期间可能发生的错误。
41///
42/// 这个枚举包含两种变体:
43/// - `IntError`: 在解析整数时发生错误。它包装了标准的`ParseIntError`,以提供更多上下文特定的错误信息。
44/// - `LengthError`: 当拆分操作的长度出现问题时返回的错误,表示输入或输出不符合预期的长度要求。
45///
46#[derive(Error, Debug)]
47pub enum ParseError {
48
49 #[error("解析数字失败: {0}")]
50 IntError(#[from] ParseIntError),
51
52 #[error("分割长度错误")]
53 LengthError
54}
55
56impl Version {
57
58
59 /// 通过字符串构建 Version 结构体对象
60 ///
61 /// # 参数
62 /// `version` -
63 /// 字符串必须遵循此结构
64 /// ```"XX.XX.XX-YY"```
65 /// 或者
66 /// ```"XX.XX-YY"```
67 /// 其中 YY 部分可缺省,此时的形式为
68 /// ```"XX.XX.XX"```
69 ///
70 /// # 返回值
71 /// Ok(Version) - 版本号对象
72 /// Err(ParseError) - 解析错误
73 ///
74 /// # 示例
75 /// ```
76 /// use version::Version;
77 ///
78 /// let v = Version::build_string("1.0.0").unwrap(); // 主.副.补丁
79 /// let v_suffix = Version::build_string("2.0.0-beta").unwrap(); // 主.副.补丁-后缀
80 /// let v_major_minor = Version::build_string("1.2").unwrap(); // 主.副
81 /// ```
82 pub fn build_string(version: &str) -> Result<Version, ParseError> {
83 // 分割版本号和后缀
84 let version_suffix: Vec<&str> = version.split("-").collect();
85 // 分割版本号
86 let major_minor_patch: Vec<&str> = version_suffix[0].split(".").collect();
87 let suffix : String;
88
89 // 检查分割长度是否满足要求
90 if major_minor_patch.len() < 2 || major_minor_patch.len() > 3 {
91 // 如果不满足则报错
92 return Err(ParseError::LengthError)
93 }
94
95 // 检测版本号是否存在后缀
96 if version_suffix.len() == 1 {
97 suffix = "".to_string();
98 } else {
99 suffix = version_suffix[1].to_string(); // 后缀类型
100 }
101
102 // 解析版本号为整数
103 // 错误将传递上层
104 let major = major_minor_patch[0].parse::<u8>()?;
105 let minor = major_minor_patch[1].parse::<u8>()?;
106 // 对缺失补丁版本号特殊处理
107 let patch : u8;
108 if major_minor_patch.len() > 2 {
109 patch = major_minor_patch[2].parse::<u8>()?;
110 } else {
111 patch = 0;
112 }
113
114 // 返回Version对象
115 Ok(Version {
116 major,
117 minor,
118 patch,
119 suffix,
120 })
121 }
122
123 /// 比较传入的版本号是否为最新版本
124 ///
125 /// # 参数
126 /// - `other` - 传入要比较的版本号的地址
127 ///
128 /// # 返回值
129 /// `true` - 当传入版本号为最新时返回
130 /// `false` - 其他情况返回
131 ///
132 /// # 注意
133 /// 判断是否为新版本逻辑如下
134 /// 1. 判断主版本号、副版本号、补丁版本号
135 /// 2. 判断两者之一是否有后缀,有后缀的版本号默认被认为是新版本
136 ///
137 /// # 示例
138 /// ```
139 /// use version::Version;
140 ///
141 /// let v_old = Version::build_string("1.0.0").unwrap();
142 /// let v_new = Version::build_string("2.0.0").unwrap();
143 ///
144 /// assert_eq!(v_old.is_newer(&v_new), true)
145 /// ```
146 pub fn is_newer(&self, other: &Version) -> bool {
147 self.major < other.major // 判断大版本
148 || (self.major == other.major && self.minor < other.minor // 判断小版本
149 || (self.major == other.major && self.minor == other.minor && self.patch < other.patch // 判断补丁版本
150 || (self.major == other.major && self.minor == other.minor && self.patch == other.patch &&
151 (self.suffix.trim().is_empty() && !other.suffix.trim().is_empty()) // 判断后缀
152 )))
153 }
154
155 /// 将版本号转化为字符串。
156 ///
157 /// # 返回值
158 /// 以`[major].[minor].[patch]-[suffix]`或`[major].[minor].[patch]`形式输出
159 ///
160 ///
161 pub fn to_string(&self) -> String {
162 if self.suffix.trim().is_empty() {
163 format!("{}.{}.{}", self.major, self.minor, self.patch)
164 } else {
165 format!("{}.{}.{}-{}", self.major, self.minor, self.patch, self.suffix)
166 }
167 }
168}
169
170mod tests {
171 use crate::{Version};
172
173 /// 测试版本比较
174 #[test]
175 fn test_newer() {
176 let v_old = Version::build_string("1.0.0").unwrap();
177 let v_new = Version::build_string("1.1.0").unwrap();
178
179 // 断言比较
180 assert_eq!(v_old.is_newer(&v_new), true);
181
182 let v_new = Version::build_string("2.0.0").unwrap();
183
184 // 断言比较
185 assert_eq!(v_old.is_newer(&v_new), true);
186 }
187
188 /// 测试版本对象创建
189 #[test]
190 fn test_build() {
191 let v_not_suffix = Version::build_string("1.0.0").unwrap();
192 let v_has_suffix = Version::build_string("1.0.0-beta").unwrap();
193 let v_less_patch = Version::build_string("1.0-beta").unwrap();
194
195 println!("v_not_suffix: {}\nv_has_suffix: {}\nv_less_patch: {}\n",
196 v_not_suffix.to_string(), v_has_suffix.to_string(), v_less_patch.to_string()
197 )
198 }
199
200 /// 测试版本无后缀优先于后缀
201 #[test]
202 fn test_suffix() {
203 let v_has_suffix = Version::build_string("1.0.0").unwrap();
204 let v_has_not_suffix = Version::build_string("1.0.0-beta").unwrap();
205
206 // 断言比较
207 assert_eq!(v_has_suffix.is_newer(&v_has_not_suffix), true)
208 }
209
210 /// 测试错误的版本号数字
211 #[test]
212 #[should_panic]
213 fn test_error_number() {
214 let _ = Version::build_string("homo.114514.1919810").unwrap();
215 }
216
217 /// 测试错误长度
218 #[test]
219 #[should_panic]
220 fn test_error_length() {
221 let _ = Version::build_string("1-beta").unwrap();
222 }
223}