I want to add a simple version scheme + check to my struct:
#[derive(Serialize, Deserialize)]
struct Versioned {
version: u32,
other_field: String,
}
impl Versioned {
const FORMAT_VERSION: u32 = 42;
}
The idea is rather simple: the version field is deserialized (0 if not present*), and an error is thrown if it does not equal FORMAT_VERSION. Then, the rest of the struct is processed. The order here is important: otherwise, the deserialization error might not be correct (you might get something like "missing field new_field" instead of "version too old").
I've tried a few ideas with wrapper types and custom deserializers, but none of them provided a good user experience. My best so far:
#[derive(Serialize, Deserialize)]
struct Payload {
some_field: String,
}
#[derive(Serialize, Deserialize)]
#[serde(tag = "version")]
enum Versioned {
#[serde(rename = "0")]
Inner(Payload),
}
It uses an enum wrapper for the version check, and two From implementations for easy type conversion. The downsides are:
- No default value for the field* (= not backwards compatible with unversioned data)
- Bad error message on old version ("Error: unknown variant
42, expected0at line 2 column 17" instead of something like "invalid version")
* Notice: the backwards compatibility requirement makes things significantly more difficult. So remember folks, always add a version field to all your data right away!