Skip to main content

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}