use crate::client::apis;
use crate::client::apis::configuration::Configuration;
pub const CLIENT_VERSION: &str = env!("CARGO_PKG_VERSION");
pub const CLIENT_API_VERSION: &str = crate::api_version::HTTP_API_VERSION;
pub const GIT_HASH: &str = env!("GIT_HASH");
pub fn full_version() -> String {
format!("{} ({})", CLIENT_VERSION, GIT_HASH)
}
pub fn version_with_hash() -> String {
format!("{}-{}", CLIENT_VERSION, GIT_HASH)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VersionMismatchSeverity {
None,
Patch,
Minor,
Major,
}
impl VersionMismatchSeverity {
pub fn is_blocking(&self) -> bool {
matches!(self, VersionMismatchSeverity::Major)
}
pub fn has_warning(&self) -> bool {
!matches!(self, VersionMismatchSeverity::None)
}
}
#[derive(Debug, Clone)]
pub struct ServerInfo {
pub version: String,
pub api_version: Option<String>,
}
#[derive(Debug, Clone)]
pub struct VersionCheckResult {
pub client_version: String,
pub server_version: Option<String>,
pub client_api_version: String,
pub server_api_version: Option<String>,
pub severity: VersionMismatchSeverity,
pub message: String,
}
impl VersionCheckResult {
pub fn server_unreachable() -> Self {
Self {
client_version: CLIENT_VERSION.to_string(),
server_version: None,
client_api_version: CLIENT_API_VERSION.to_string(),
server_api_version: None,
severity: VersionMismatchSeverity::None,
message: "Could not check server version".to_string(),
}
}
pub fn from_server_info(server_info: &ServerInfo) -> Self {
let (severity, message) = match &server_info.api_version {
Some(server_api) => {
let severity = compare_versions(CLIENT_API_VERSION, server_api);
let message = format_api_version_message(
CLIENT_API_VERSION,
server_api,
&server_info.version,
severity,
);
(severity, message)
}
None => {
let severity = compare_versions(CLIENT_VERSION, &server_info.version);
let message =
format_legacy_version_message(CLIENT_VERSION, &server_info.version, severity);
(severity, message)
}
};
Self {
client_version: CLIENT_VERSION.to_string(),
server_version: Some(server_info.version.clone()),
client_api_version: CLIENT_API_VERSION.to_string(),
server_api_version: server_info.api_version.clone(),
severity,
message,
}
}
}
pub fn parse_version(version: &str) -> Option<(u32, u32, u32)> {
let version = version.strip_prefix('v').unwrap_or(version);
let version = version.split(" (").next().unwrap_or(version);
let parts: Vec<&str> = version.split('.').collect();
if parts.len() < 3 {
return None;
}
let major = parts[0].parse().ok()?;
let minor = parts[1].parse().ok()?;
let patch_str = parts[2].split('-').next().unwrap_or(parts[2]);
let patch = patch_str.parse().ok()?;
Some((major, minor, patch))
}
pub fn compare_versions(client_version: &str, server_version: &str) -> VersionMismatchSeverity {
let client = match parse_version(client_version) {
Some(v) => v,
None => {
eprintln!(
"Warning: failed to parse client version '{}'; skipping version comparison",
client_version
);
return VersionMismatchSeverity::None;
}
};
let server = match parse_version(server_version) {
Some(v) => v,
None => {
eprintln!(
"Warning: failed to parse server version '{}'; skipping version comparison",
server_version
);
return VersionMismatchSeverity::None;
}
};
if client.0 != server.0 {
return VersionMismatchSeverity::Major;
}
if client.1 != server.1 {
return VersionMismatchSeverity::Minor;
}
if client.2 != server.2 {
return VersionMismatchSeverity::Patch;
}
VersionMismatchSeverity::None
}
fn format_api_version_message(
client_api: &str,
server_api: &str,
server_version: &str,
severity: VersionMismatchSeverity,
) -> String {
match severity {
VersionMismatchSeverity::None => {
format!(
"API version {} matches server (server {})",
client_api, server_version
)
}
VersionMismatchSeverity::Patch => {
format!(
"API version mismatch: client API {} vs server API {} \
(server {}) - patch difference, should be compatible",
client_api, server_api, server_version
)
}
VersionMismatchSeverity::Minor => {
let client_parsed = parse_version(client_api);
let server_parsed = parse_version(server_api);
let direction = if client_parsed > server_parsed {
"client is newer than server"
} else {
"server is newer than client"
};
format!(
"API version mismatch: client API {} vs server API {} \
(server {}) - minor version difference ({direction}), should be compatible",
client_api, server_api, server_version
)
}
VersionMismatchSeverity::Major => {
format!(
"API version incompatible: client API {} vs server API {} \
(server {}) - major version mismatch, client and server are not compatible",
client_api, server_api, server_version
)
}
}
}
fn format_legacy_version_message(
client_version: &str,
server_version: &str,
severity: VersionMismatchSeverity,
) -> String {
match severity {
VersionMismatchSeverity::None => {
format!("Version {} matches server", client_version)
}
VersionMismatchSeverity::Patch => {
format!(
"Version mismatch: client {} vs server {} (patch difference)",
client_version, server_version
)
}
VersionMismatchSeverity::Minor => {
format!(
"Client version {} is newer than server {} \
- server does not report API version, some features may not work",
client_version, server_version
)
}
VersionMismatchSeverity::Major => {
format!(
"Major version mismatch: client {} vs server {} \
- server does not report API version, client and server are likely incompatible",
client_version, server_version
)
}
}
}
pub fn get_server_info(config: &Configuration) -> Option<ServerInfo> {
match apis::system_api::get_version(config) {
Ok(value) => Some(ServerInfo {
version: value.version,
api_version: Some(value.api_version),
}),
Err(_) => None,
}
}
pub fn get_server_version(config: &Configuration) -> Option<String> {
get_server_info(config).map(|info| info.version)
}
pub fn check_version(config: &Configuration) -> VersionCheckResult {
match get_server_info(config) {
Some(server_info) => VersionCheckResult::from_server_info(&server_info),
None => VersionCheckResult::server_unreachable(),
}
}
pub fn print_version_warning(result: &VersionCheckResult) -> VersionMismatchSeverity {
match result.severity {
VersionMismatchSeverity::None | VersionMismatchSeverity::Patch => {}
VersionMismatchSeverity::Minor => {
eprintln!("Warning: {}", result.message);
}
VersionMismatchSeverity::Major => {
eprintln!("Error: {}", result.message);
}
}
result.severity
}
pub fn check_and_warn(config: &Configuration) -> bool {
let result = check_version(config);
let severity = print_version_warning(&result);
!severity.is_blocking()
}