功能说明
依赖解析、插件兼容检查和灰度升级都需要判断版本是否落在某个范围内。手写字符串比较会踩到 1.10 小于 1.9 的坑,也处理不了预发布版本的排序。semver 库按语义化版本规范解析主版本、次版本、补丁和预发布标识,并提供插入符、波浪号、比较符等范围语法。
示例封装两个函数:matches 判断单个版本是否满足范围,best_match 在候选列表中挑选满足范围的最高版本,常用于从已发布版本中选择可升级目标。
关键代码
Version::parse 与 VersionReq::parse 都会返回 Result,调用方用问号向上传递错误。matches 直接调用 VersionReq::matches。best_match 遍历候选项,逐个解析并过滤,使用 map_or 在初始为空时直接接受第一个匹配项,之后比较大小保留更大者。main 展示插入符允许次版本升级、不允许主版本跨越,预发布版本默认不满足普通范围。
使用注意
预发布版本只在范围本身包含预发布标识时才匹配,这是规范要求而非缺陷。插入符范围 ^1.2 等价于大于等于 1.2.0 且小于 2.0.0,波浪号 ~1.2 只允许补丁升级。候选版本中的非法字符串会让整个函数失败,数据源不可信时可以先过滤。版本比较不包含构建元数据。不要用字符串排序代替语义化比较。
验证方式
1.9.0 满足 ^1.2 返回 true,2.0.0 不满足返回 false,预发布版本 2.1.0-rc.1 不满足 ^2.1 返回 false。best_match 在 ~1.2 范围内只选出 1.2.0,因为 1.4.3 超出补丁范围。非法范围返回错误,打印为 true。
代码示例
use semver::{Version, VersionReq};
fn matches(version: &str, requirement: &str) -> Result<bool, semver::Error> {
let parsed = Version::parse(version)?;
let requirement = VersionReq::parse(requirement)?;
Ok(requirement.matches(&parsed))
}
fn best_match(versions: &[&str], requirement: &str) -> Result<Option<Version>, semver::Error> {
let requirement = VersionReq::parse(requirement)?;
let mut best: Option<Version> = None;
for raw in versions {
let version = Version::parse(raw)?;
if !requirement.matches(&version) {
continue;
}
if best.as_ref().map_or(true, |current| version > *current) {
best = Some(version);
}
}
Ok(best)
}
fn main() {
let versions = ["1.2.0", "1.4.3", "2.0.0", "2.1.0-rc.1"];
println!("1.9.0 in ^1.2: {:?}", matches("1.9.0", "^1.2"));
println!("2.0.0 in ^1.2: {:?}", matches("2.0.0", "^1.2"));
println!("rc.1 in ^2.1: {:?}", matches("2.1.0-rc.1", "^2.1"));
println!("best in ~1.2: {:?}", best_match(&versions, "~1.2"));
println!("invalid range: {}", matches("1.0.0", "not-a-version").is_err());
}