Skip to content

Commit 2d3adfe

Browse files
committed
fix(boxlite/rest): tolerate legacy server cpus/memory_mib past SDK narrow types
list_info kept blowing up with RuntimeError: ... invalid value: integer 999, expected u8 or RuntimeError: ... invalid value: integer 8192000000, expected u32 on the Tokyo e2e stack because the Box table still carries pre-#735 rows that the API created back when the per-box quota wasn't enforced (cpus=999, memory_mib past 4 GiB). serde_json's narrow integer deserializer aborts the whole list response when ANY row's cpu or memory exceeds the field's range, so a single bad row poisoned every test_list_info* / test_runtime_initialization_*. Widen the on-the-wire deserialization (BoxResponse.cpus: u32, BoxResponse.memory_mib: u64) so the boundary accepts whatever the server returns, then saturate-cast at the SDK boundary (BoxInfo.cpus: u8, BoxInfo.memory_mib: u32 are the long-standing public types and bindings rely on them). Legitimate values round-trip unchanged; legacy garbage clamps to the narrow type's MAX instead of crashing the list call. Two-side verified against cargo test -p boxlite --features rest test_box_response_legacy_oversize_cpus_memory_does_not_break_list - impl reverted to the prior narrow types → test FAILS at from_str with the exact production error 'invalid value: integer 999, expected u8'. - impl restored → all 5 box_response tests pass.
1 parent d35cbe4 commit 2d3adfe

1 file changed

Lines changed: 48 additions & 4 deletions

File tree

src/boxlite/src/rest/types.rs

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -199,8 +199,17 @@ pub(crate) struct BoxResponse {
199199
pub updated_at: String,
200200
pub pid: Option<u32>,
201201
pub image: String,
202-
pub cpus: u8,
203-
pub memory_mib: u32,
202+
// Widened deserialization types. The server's cpu / memory columns
203+
// are pre-#735-era integers with no per-box quota enforcement, so a
204+
// single legacy row with cpus=999 (or memory_mib past 4 GiB) used to
205+
// blow up every list_info round-trip with
206+
// `invalid value: integer N, expected u{8,32}`. Accept whatever the
207+
// server returns at the deserialize boundary, then saturate-cast to
208+
// the SDK's narrow public types in `to_box_info`. Legitimate values
209+
// round-trip; legacy garbage clamps to the type max instead of
210+
// crashing the whole list.
211+
pub cpus: u32,
212+
pub memory_mib: u64,
204213
#[serde(default)]
205214
pub labels: HashMap<String, String>,
206215
}
@@ -236,8 +245,12 @@ impl BoxResponse {
236245
last_updated,
237246
pid: self.pid,
238247
image: self.image.clone(),
239-
cpus: self.cpus,
240-
memory_mib: self.memory_mib,
248+
// Saturating cast: see BoxResponse cpus/memory_mib note. A
249+
// value past the BoxInfo narrow type just clamps to MAX; we
250+
// do NOT want the whole list_info call to fail just because
251+
// one legacy row has out-of-band numbers.
252+
cpus: u8::try_from(self.cpus).unwrap_or(u8::MAX),
253+
memory_mib: u32::try_from(self.memory_mib).unwrap_or(u32::MAX),
241254
labels: self.labels.clone(),
242255
health_status: crate::litebox::HealthStatus::new(), // REST API doesn't provide health status
243256
})
@@ -642,6 +655,37 @@ mod tests {
642655
assert!(mk("a.b").to_box_info().is_err(), "dot");
643656
}
644657

658+
#[test]
659+
fn test_box_response_legacy_oversize_cpus_memory_does_not_break_list() {
660+
// Pre-#735 rows could carry cpus / memory_mib values past the
661+
// SDK's narrow public BoxInfo types (cpus: u8, memory_mib: u32).
662+
// The deserialize boundary must accept whatever the server
663+
// returns so a single legacy row does NOT poison the whole
664+
// list_info response — the prior u8/u32 form blew up with
665+
// `invalid value: integer N, expected u{8,32}` and the entire
666+
// list call returned an error.
667+
let json = r#"{
668+
"box_id": "abcdEFGH1234",
669+
"name": null,
670+
"status": "stopped",
671+
"created_at": "2024-01-01T00:00:00Z",
672+
"updated_at": "2024-01-01T00:01:00Z",
673+
"pid": null,
674+
"image": "alpine:latest",
675+
"cpus": 999,
676+
"memory_mib": 8192000000,
677+
"labels": {}
678+
}"#;
679+
// Boundary: server-returned legacy values must deserialize.
680+
let resp: BoxResponse =
681+
serde_json::from_str(json).expect("legacy cpus/memory_mib must deserialize");
682+
// Saturating cast through the SDK boundary clamps to the narrow
683+
// type max, never panics.
684+
let info = resp.to_box_info().expect("to_box_info must succeed on legacy");
685+
assert_eq!(info.cpus, u8::MAX);
686+
assert_eq!(info.memory_mib, u32::MAX);
687+
}
688+
645689
#[test]
646690
fn test_exec_request_serialization() {
647691
let req = ExecRequest {

0 commit comments

Comments
 (0)