1- //! Version checking utilities for comparing client and server versions.
1+ //! Version checking utilities for comparing client and server API versions.
22//!
3- //! This module provides functions to check version compatibility between
3+ //! This module provides functions to check API version compatibility between
44//! client applications and the torc-server, with appropriate warning levels.
5+ //!
6+ //! The HTTP API has its own semver version (e.g., "0.8.0") that is independent
7+ //! of the crate/binary version. This allows the client to change frequently
8+ //! without implying server incompatibility. The API version only bumps when the
9+ //! HTTP contract changes.
510
611use crate :: client:: apis:: configuration:: Configuration ;
712use crate :: client:: apis:: default_api;
813
914/// The current version of this binary, set at compile time.
1015pub const CLIENT_VERSION : & str = env ! ( "CARGO_PKG_VERSION" ) ;
1116
17+ /// The API version that this client expects from the server.
18+ ///
19+ /// Bump this only when the HTTP API contract changes:
20+ /// - Patch: bug fix in an existing endpoint (response field fix, etc.)
21+ /// - Minor: new endpoint, new optional field, new query parameter
22+ /// - Major: removed endpoint, renamed field, changed semantics
23+ pub const CLIENT_API_VERSION : & str = "0.8.0" ;
24+
1225/// The git commit hash of this binary, set at compile time via build.rs.
1326pub const GIT_HASH : & str = env ! ( "GIT_HASH" ) ;
1427
@@ -25,16 +38,16 @@ pub fn version_with_hash() -> String {
2538 format ! ( "{}-{}{}" , CLIENT_VERSION , GIT_HASH , GIT_DIRTY )
2639}
2740
28- /// Severity level for version mismatches.
41+ /// Severity level for API version mismatches.
2942#[ derive( Debug , Clone , Copy , PartialEq , Eq ) ]
3043pub enum VersionMismatchSeverity {
31- /// Versions match exactly - no warning needed.
44+ /// API versions match exactly - no warning needed.
3245 None ,
3346 /// Only patch version differs - minor warning.
3447 Patch ,
35- /// Minor version of client is higher than server - strong warning .
48+ /// Client API minor version is higher than server - some features may not work .
3649 Minor ,
37- /// Major version differs - error condition .
50+ /// API major version differs - incompatible .
3851 Major ,
3952}
4053
@@ -50,14 +63,27 @@ impl VersionMismatchSeverity {
5063 }
5164}
5265
53- /// Result of a version check operation.
66+ /// Information retrieved from the server's /version endpoint.
67+ #[ derive( Debug , Clone ) ]
68+ pub struct ServerInfo {
69+ /// The server's binary version (e.g., "0.14.0 (abc1234)").
70+ pub version : String ,
71+ /// The server's API version (e.g., "0.8.0").
72+ pub api_version : Option < String > ,
73+ }
74+
75+ /// Result of an API version check operation.
5476#[ derive( Debug , Clone ) ]
5577pub struct VersionCheckResult {
56- /// The client (local) version.
78+ /// The client binary version.
5779 pub client_version : String ,
58- /// The server version (if successfully retrieved).
80+ /// The server binary version (if successfully retrieved).
5981 pub server_version : Option < String > ,
60- /// The severity of any version mismatch.
82+ /// The client API version.
83+ pub client_api_version : String ,
84+ /// The server API version (if successfully retrieved).
85+ pub server_api_version : Option < String > ,
86+ /// The severity of any API version mismatch.
6187 pub severity : VersionMismatchSeverity ,
6288 /// A human-readable message describing the result.
6389 pub message : String ,
@@ -69,19 +95,41 @@ impl VersionCheckResult {
6995 Self {
7096 client_version : CLIENT_VERSION . to_string ( ) ,
7197 server_version : None ,
98+ client_api_version : CLIENT_API_VERSION . to_string ( ) ,
99+ server_api_version : None ,
72100 severity : VersionMismatchSeverity :: None ,
73101 message : "Could not check server version" . to_string ( ) ,
74102 }
75103 }
76104
77105 /// Creates a new result for a successful version check.
78- pub fn new ( client_version : & str , server_version : & str ) -> Self {
79- let severity = compare_versions ( client_version, server_version) ;
80- let message = format_version_message ( client_version, server_version, severity) ;
106+ pub fn from_server_info ( server_info : & ServerInfo ) -> Self {
107+ let ( severity, message) = match & server_info. api_version {
108+ Some ( server_api) => {
109+ let severity = compare_versions ( CLIENT_API_VERSION , server_api) ;
110+ let message = format_api_version_message (
111+ CLIENT_API_VERSION ,
112+ server_api,
113+ & server_info. version ,
114+ severity,
115+ ) ;
116+ ( severity, message)
117+ }
118+ None => {
119+ // Old server that doesn't report api_version — fall back to
120+ // comparing binary versions (pre-API-versioning behavior).
121+ let severity = compare_versions ( CLIENT_VERSION , & server_info. version ) ;
122+ let message =
123+ format_legacy_version_message ( CLIENT_VERSION , & server_info. version , severity) ;
124+ ( severity, message)
125+ }
126+ } ;
81127
82128 Self {
83- client_version : client_version. to_string ( ) ,
84- server_version : Some ( server_version. to_string ( ) ) ,
129+ client_version : CLIENT_VERSION . to_string ( ) ,
130+ server_version : Some ( server_info. version . clone ( ) ) ,
131+ client_api_version : CLIENT_API_VERSION . to_string ( ) ,
132+ server_api_version : server_info. api_version . clone ( ) ,
85133 severity,
86134 message,
87135 }
@@ -154,8 +202,47 @@ pub fn compare_versions(client_version: &str, server_version: &str) -> VersionMi
154202 VersionMismatchSeverity :: None
155203}
156204
157- /// Formats a human-readable message for the version check result.
158- fn format_version_message (
205+ /// Formats a human-readable message for an API version mismatch.
206+ fn format_api_version_message (
207+ client_api : & str ,
208+ server_api : & str ,
209+ server_version : & str ,
210+ severity : VersionMismatchSeverity ,
211+ ) -> String {
212+ match severity {
213+ VersionMismatchSeverity :: None => {
214+ format ! (
215+ "API version {} matches server (server {})" ,
216+ client_api, server_version
217+ )
218+ }
219+ VersionMismatchSeverity :: Patch => {
220+ format ! (
221+ "API version mismatch: client API {} vs server API {} \
222+ (server {}) - patch difference, should be compatible",
223+ client_api, server_api, server_version
224+ )
225+ }
226+ VersionMismatchSeverity :: Minor => {
227+ format ! (
228+ "API version mismatch: client API {} is newer than server API {} \
229+ (server {}) - some client features may not be supported by this server",
230+ client_api, server_api, server_version
231+ )
232+ }
233+ VersionMismatchSeverity :: Major => {
234+ format ! (
235+ "API version incompatible: client API {} vs server API {} \
236+ (server {}) - major version mismatch, client and server are not compatible",
237+ client_api, server_api, server_version
238+ )
239+ }
240+ }
241+ }
242+
243+ /// Formats a human-readable message when the server doesn't report an API version
244+ /// (pre-API-versioning server). Falls back to comparing binary versions.
245+ fn format_legacy_version_message (
159246 client_version : & str ,
160247 server_version : & str ,
161248 severity : VersionMismatchSeverity ,
@@ -172,42 +259,61 @@ fn format_version_message(
172259 }
173260 VersionMismatchSeverity :: Minor => {
174261 format ! (
175- "Warning: Client version {} is newer than server {} - some features may not work" ,
262+ "Client version {} is newer than server {} \
263+ - server does not report API version, some features may not work",
176264 client_version, server_version
177265 )
178266 }
179267 VersionMismatchSeverity :: Major => {
180268 format ! (
181- "Error: Major version mismatch - client {} vs server {} - incompatible versions" ,
269+ "Major version mismatch: client {} vs server {} \
270+ - server does not report API version, client and server are likely incompatible",
182271 client_version, server_version
183272 )
184273 }
185274 }
186275}
187276
188- /// Fetches the server version from the API .
189- pub fn get_server_version ( config : & Configuration ) -> Option < String > {
277+ /// Fetches server information from the /version endpoint .
278+ pub fn get_server_info ( config : & Configuration ) -> Option < ServerInfo > {
190279 match default_api:: get_version ( config) {
191280 Ok ( value) => {
192- // The server returns the version as a JSON string
193- if let Some ( version) = value. as_str ( ) {
194- Some ( version. to_string ( ) )
195- } else {
196- // Try to extract from object if wrapped
197- value
281+ if value. is_object ( ) {
282+ // New structured response: { "version": "...", "api_version": "...", ... }
283+ let version = value
198284 . get ( "version" )
199285 . and_then ( |v| v. as_str ( ) )
200- . map ( |s| s. to_string ( ) )
286+ . map ( |s| s. to_string ( ) ) ?;
287+ let api_version = value
288+ . get ( "api_version" )
289+ . and_then ( |v| v. as_str ( ) )
290+ . map ( |s| s. to_string ( ) ) ;
291+ Some ( ServerInfo {
292+ version,
293+ api_version,
294+ } )
295+ } else {
296+ // Legacy response: plain string (pre-API-versioning server)
297+ value. as_str ( ) . map ( |version| ServerInfo {
298+ version : version. to_string ( ) ,
299+ api_version : None ,
300+ } )
201301 }
202302 }
203303 Err ( _) => None ,
204304 }
205305}
206306
207- /// Performs a version check between the client and server.
307+ /// Fetches the server version string from the API.
308+ /// Returns the binary version for display purposes (e.g., in log messages).
309+ pub fn get_server_version ( config : & Configuration ) -> Option < String > {
310+ get_server_info ( config) . map ( |info| info. version )
311+ }
312+
313+ /// Performs an API version check between the client and server.
208314pub fn check_version ( config : & Configuration ) -> VersionCheckResult {
209- match get_server_version ( config) {
210- Some ( server_version ) => VersionCheckResult :: new ( CLIENT_VERSION , & server_version ) ,
315+ match get_server_info ( config) {
316+ Some ( server_info ) => VersionCheckResult :: from_server_info ( & server_info ) ,
211317 None => VersionCheckResult :: server_unreachable ( ) ,
212318 }
213319}
0 commit comments