收录版本号比较方法

This commit is contained in:
zqm
2026-04-08 16:15:03 +08:00
parent 7e56c823c6
commit 97d4d5e8d6
2 changed files with 152 additions and 0 deletions

View File

@@ -5,6 +5,7 @@
//! ## Modules
//!
//! - [`websocket`] - WebSocket client and server implementations
//! - [`version`] - Version string comparison utilities
//!
//! ## Quick Start
//!
@@ -27,7 +28,19 @@
//! client.connect().await;
//! }
//! ```
//!
//! ## Version Comparison
//!
//! ```rust
//! use cube_lib::version::{version_less_than, needs_update};
//!
//! // Check if update is needed
//! assert!(version_less_than("1.0.0", "1.0.1"));
//! assert!(needs_update("1.0.0", "1.0.1"));
//! ```
pub mod websocket;
pub mod version;
pub use websocket::{WebSocketClient, WebSocketConfig, WebSocketMessage, OutgoingMessage, ConnectionStatus, ReconnectedCallback, MessageSender};
pub use version::{version_less_than, compare_versions, needs_update, parse_version};

View File

@@ -0,0 +1,139 @@
//! # Version utilities
//!
//! Provides version string comparison utilities for semver-like version strings.
use std::cmp::Ordering;
/// Parse a version string into a vector of unsigned integers.
///
/// "1.2.3" → [1, 2, 3]
/// "1.2" → [1, 2]
/// "1.2.3.4.5" → [1, 2, 3, 4, 5]
/// "" or invalid parts are skipped.
pub fn parse_version(version: &str) -> Vec<u32> {
version
.split('.')
.filter(|s| !s.is_empty())
.filter_map(|s| s.parse().ok())
.collect()
}
/// Compare two version strings.
///
/// Returns `true` if `current < required` (i.e. current needs to be updated).
///
/// Comparison is done part-by-part (split by `.`).
/// Missing parts are treated as `0`.
/// Examples:
/// - `"1.0.0"` < `"1.0.1"` → true
/// - `"1.0.0"` < `"1.1.0"` → true
/// - `"1.0.0"` < `"2.0.0"` → true
/// - `"1.0.0"` < `"1.0.0"` → false (equal)
/// - `"1.0.1"` < `"1.0.0"` → false
///
/// # Arguments
/// * `current` - The current/local version string
/// * `required` - The required/server version string
pub fn version_less_than(current: &str, required: &str) -> bool {
compare_versions(current, required) == Ordering::Less
}
/// Compare two version strings, returning the standard `Ordering` result.
///
/// This is the canonical comparison function. Use [`version_less_than`] if you
/// only need a boolean upgrade check.
pub fn compare_versions(a: &str, b: &str) -> Ordering {
let a_parts = parse_version(a);
let b_parts = parse_version(b);
let max_len = a_parts.len().max(b_parts.len());
for i in 0..max_len {
let av = a_parts.get(i).copied().unwrap_or(0);
let bv = b_parts.get(i).copied().unwrap_or(0);
match av.cmp(&bv) {
Ordering::Less => return Ordering::Less,
Ordering::Greater => return Ordering::Greater,
Ordering::Equal => continue,
}
}
Ordering::Equal
}
/// Check if a local version is outdated compared to a server version.
///
/// Returns `true` if:
/// - local is "0.0.0" / empty (file does not exist)
/// - local version < server version
pub fn needs_update(local_version: &str, server_version: &str) -> bool {
local_version == "0.0.0"
|| local_version.is_empty()
|| version_less_than(local_version, server_version)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_version() {
assert_eq!(parse_version("1.2.3"), vec![1, 2, 3]);
assert_eq!(parse_version("1.2"), vec![1, 2]);
assert_eq!(parse_version("1.2.3.4.5"), vec![1, 2, 3, 4, 5]);
assert_eq!(parse_version(""), Vec::<u32>::new());
assert_eq!(parse_version("1..3"), vec![1, 3]);
assert_eq!(parse_version("abc"), Vec::<u32>::new());
}
#[test]
fn test_version_less_than() {
// Basic comparisons
assert!(version_less_than("1.0.0", "1.0.1"));
assert!(version_less_than("1.0.0", "1.1.0"));
assert!(version_less_than("1.0.0", "2.0.0"));
assert!(version_less_than("0.0.1", "0.0.2"));
// Equal versions
assert!(!version_less_than("1.0.0", "1.0.0"));
assert!(!version_less_than("1.2.3", "1.2.3"));
// Newer local version
assert!(!version_less_than("1.0.1", "1.0.0"));
assert!(!version_less_than("2.0.0", "1.0.0"));
// Different part counts
assert!(version_less_than("1.0", "1.0.1"));
assert!(version_less_than("1", "1.0.1"));
assert!(version_less_than("1.0.0", "1.0.0.1"));
assert!(version_less_than("1", "2"));
// Treats missing parts as 0
assert!(version_less_than("1", "1.0.1"));
assert!(!version_less_than("1.0.0", "1"));
}
#[test]
fn test_compare_versions() {
use std::cmp::Ordering;
assert_eq!(compare_versions("1.0.0", "1.0.1"), Ordering::Less);
assert_eq!(compare_versions("1.0.1", "1.0.0"), Ordering::Greater);
assert_eq!(compare_versions("1.0.0", "1.0.0"), Ordering::Equal);
}
#[test]
fn test_needs_update() {
// Missing local
assert!(needs_update("0.0.0", "1.0.0"));
assert!(needs_update("", "1.0.0"));
// Needs update
assert!(needs_update("1.0.0", "1.0.1"));
assert!(needs_update("1.0.0", "2.0.0"));
// Already up to date
assert!(!needs_update("1.0.0", "1.0.0"));
assert!(!needs_update("1.0.1", "1.0.0"));
}
}