Skip to content

Commit 1893dda

Browse files
authored
Merge pull request #113 from NatLabRockies/fix/split-api-version
Create new API version for server
2 parents 053c0c8 + 3a9c9ea commit 1893dda

File tree

8 files changed

+266
-47
lines changed

8 files changed

+266
-47
lines changed

src/bin/torc-dash.rs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2301,7 +2301,9 @@ async fn server_status_handler(State(state): State<Arc<AppState>>) -> impl IntoR
23012301
#[derive(Serialize)]
23022302
struct VersionResponse {
23032303
version: String,
2304+
api_version: String,
23042305
server_version: Option<String>,
2306+
server_api_version: Option<String>,
23052307
version_mismatch: Option<String>,
23062308
mismatch_severity: Option<String>,
23072309
}
@@ -2324,7 +2326,7 @@ async fn version_handler(State(state): State<Arc<AppState>>) -> impl IntoRespons
23242326
.await
23252327
.ok();
23262328

2327-
let (server_version, version_mismatch, mismatch_severity) = match result {
2329+
let (server_version, server_api_version, version_mismatch, mismatch_severity) = match result {
23282330
Some(result) => match &result.server_version {
23292331
Some(server_ver) => {
23302332
let severity_str = match result.severity {
@@ -2338,11 +2340,16 @@ async fn version_handler(State(state): State<Arc<AppState>>) -> impl IntoRespons
23382340
} else {
23392341
None
23402342
};
2341-
(Some(server_ver.clone()), mismatch_msg, severity_str)
2343+
(
2344+
Some(server_ver.clone()),
2345+
result.server_api_version.clone(),
2346+
mismatch_msg,
2347+
severity_str,
2348+
)
23422349
}
2343-
None => (None, None, None),
2350+
None => (None, None, None, None),
23442351
},
2345-
None => (None, None, None),
2352+
None => (None, None, None, None),
23462353
};
23472354

23482355
// Extract just the semver from server version (strip git hash suffix for display)
@@ -2352,7 +2359,9 @@ async fn version_handler(State(state): State<Arc<AppState>>) -> impl IntoRespons
23522359

23532360
Json(VersionResponse {
23542361
version: env!("CARGO_PKG_VERSION").to_string(),
2362+
api_version: version_check::CLIENT_API_VERSION.to_string(),
23552363
server_version: server_version_display,
2364+
server_api_version,
23562365
version_mismatch,
23572366
mismatch_severity,
23582367
})

src/client.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,5 +56,5 @@ pub use report_models::{
5656

5757
// Version checking utilities
5858
pub use version_check::{
59-
VersionCheckResult, VersionMismatchSeverity, check_and_warn, check_version,
59+
ServerInfo, VersionCheckResult, VersionMismatchSeverity, check_and_warn, check_version,
6060
};

src/client/job_runner.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -374,12 +374,19 @@ impl JobRunner {
374374
.server_version
375375
.clone()
376376
.unwrap_or_else(|| "unknown".to_string());
377+
let server_api_version = version_result
378+
.server_api_version
379+
.clone()
380+
.unwrap_or_else(|| "unknown".to_string());
377381

378382
info!(
379-
"Starting torc job runner version={} server_version={} workflow_id={} hostname={} output_dir={} resources={:?} rules={:?} \
383+
"Starting torc job runner version={} client_api_version={} server_version={} server_api_version={} \
384+
workflow_id={} hostname={} output_dir={} resources={:?} rules={:?} \
380385
job_completion_poll_interval={}s max_parallel_jobs={:?} end_time={:?} strict_scheduler_match={}",
381386
version,
387+
version_check::CLIENT_API_VERSION,
382388
server_version,
389+
server_api_version,
383390
self.workflow_id,
384391
hostname,
385392
self.output_dir.display(),

src/client/version_check.rs

Lines changed: 137 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,27 @@
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
611
use crate::client::apis::configuration::Configuration;
712
use crate::client::apis::default_api;
813

914
/// The current version of this binary, set at compile time.
1015
pub 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.
1326
pub 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)]
3043
pub 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)]
5577
pub 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.
208314
pub 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
}

src/server/api_types.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ use tokio::sync::broadcast;
3131
pub type ServiceError = Box<dyn Error + Send + Sync + 'static>;
3232

3333
pub const BASE_PATH: &str = "/torc-service/v1";
34-
pub const API_VERSION: &str = "v0.7.0";
34+
pub const API_VERSION: &str = "0.8.0";
3535

3636
#[derive(Debug, PartialEq, Serialize, Deserialize)]
3737
#[must_use]

src/server/http_server.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2685,9 +2685,11 @@ where
26852685
"get_version() - X-Span-ID: {:?}",
26862686
Has::<XSpanIdString>::get(context).0.clone()
26872687
);
2688-
Ok(GetVersionResponse::SuccessfulResponse(serde_json::json!(
2689-
full_version()
2690-
)))
2688+
Ok(GetVersionResponse::SuccessfulResponse(serde_json::json!({
2689+
"version": full_version(),
2690+
"api_version": API_VERSION,
2691+
"git_hash": GIT_HASH
2692+
})))
26912693
}
26922694

26932695
/// Retrieve all workflows.

src/tui/ui.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -274,10 +274,12 @@ fn draw_server_url(f: &mut Frame, area: Rect, app: &App) {
274274
version_check::VersionMismatchSeverity::Patch => Color::Yellow,
275275
version_check::VersionMismatchSeverity::None => Color::Green,
276276
};
277-
spans.push(Span::styled(
278-
format!(" (server v{})", server_version),
279-
Style::default().fg(version_color),
280-
));
277+
let display = if let Some(ref api_ver) = version_result.server_api_version {
278+
format!(" (server {} API {})", server_version, api_ver)
279+
} else {
280+
format!(" (server {})", server_version)
281+
};
282+
spans.push(Span::styled(display, Style::default().fg(version_color)));
281283
}
282284

283285
spans.extend(vec![

0 commit comments

Comments
 (0)