Hmbown/CodeWhale · error
roundtrip
Error message
roundtrip
What it means
The same test then parses the JSON back with `serde_json::from_str::<CatalogCache>(&json).expect("roundtrip")`, panicking if deserialization fails. A roundtrip failure means the serialized shape no longer matches `CatalogCache`'s `Deserialize` expectations — field type drift, missing `#[serde(default)]`, or an incompatible custom deserializer.
Solutions
- Make new fields optional with `#[serde(default)]` or add a cache format version
- Diff the serialized JSON against `CatalogCache`'s field set and types
- Remove `deny_unknown_fields` or align the custom Deserialize with the Serialize output
- Print the serde error (it names the field/path) with `unwrap_or_else(|e| panic!("{e}"))`
Example fix
// before
let parsed: CatalogCache = serde_json::from_str(&json).expect("roundtrip");
// after
let parsed: CatalogCache = serde_json::from_str(&json)
.unwrap_or_else(|e| panic!("cache roundtrip failed: {e}\njson={json}")); Defensive patterns
Strategy: validation
Validate before calling
// Confirm the serialized JSON parses before full roundtrip assertions
if let Err(e) = serde_json::from_str::<CatalogCache>(&json) {
panic!("roundtrip pre-check failed at {}: {e}", e);
} Try / catch
let parsed: CatalogCache = serde_json::from_str(&json)
.unwrap_or_else(|e| panic!("CatalogCache roundtrip failed: {e}\njson={json}")); Prevention
- Add `#[serde(default)]` to new optional cache fields so old JSON still parses
- Version the cache format (`version: u32`) and reject/upgrade mismatches explicitly
- Never mix `deny_unknown_fields` with forward-evolving cache files
When it happens
Trigger: Adding a required field to `CatalogCache` that old serialized JSON lacks; changing a field type (e.g. `u64` → enum) without migration; a custom `Deserialize` that rejects the output of the current `Serialize` impl.
Common situations: Cache-format evolution without a version field or serde defaults; hand-editing the JSON in another test fixture; enabling `deny_unknown_fields` while serialization emits extra fields.
Understand the failure class
Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.
Related errors
- deserialize
- Fleet task has an invalid durable member snapshot
- <serde_json deserialization error>
- (serde_json deserialization error wrapped as…
- (serde_json deserialization error wrapped as…
AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15).
Data as JSON: /api/errors/e190f7a52681bea5.
Report an issue: GitHub.
Appendix: source
Thrown at crates/models/src/model_catalog.rs:374
let mut entries = BTreeMap::new();
entries.insert(
"sample/model".to_string(),
CatalogEntry {
input_usd_per_million: Some(0.25),
output_usd_per_million: Some(1.25),
..entry("sample/model", 32_000, MetadataProvenance::ProviderApi)
},
);
let cache = cache(Utc::now(), 60, entries);
let json = serde_json::to_string_pretty(&cache).expect("serialize");
let lowered = json.to_lowercase();
for forbidden in ["api_key", "authorization", "token", "secret"] {
assert!(
!lowered.contains(forbidden),
"cache JSON must not contain auth field {forbidden}: {json}"
);
}
let parsed: CatalogCache = serde_json::from_str(&json).expect("roundtrip");
assert_eq!(parsed.entries.len(), 1);
}
}
View on GitHub (pinned to 433685b202)