Skip to content

Commit e808889

Browse files
committed
fix: fix taking taking serializers for serializing older verions
1 parent 3ae474a commit e808889

File tree

3 files changed

+157
-26
lines changed

3 files changed

+157
-26
lines changed

rust/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,8 @@ pub enum MyTypeVersioned {
6060
impl OwnedVersionedData for MyTypeVersioned {
6161
type Latest = schemas::v2::MyType;
6262

63-
fn latest(latest: Self::Latest) -> Self { Self::V2(latest) }
64-
fn into_latest(self) -> Result<Self::Latest> {
63+
fn wrap_latest(latest: Self::Latest) -> Self { Self::V2(latest) }
64+
fn unwrap_latest(self) -> Result<Self::Latest> {
6565
match self { Self::V2(x) => Ok(x), _ => bail!("not latest") }
6666
}
6767

rust/vbare/src/lib.rs

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use anyhow::{bail, Result};
1+
use anyhow::{bail, Context, Result};
22

33
pub trait VersionedData<'a>: Sized {
44
type Latest;
@@ -29,9 +29,12 @@ pub trait VersionedData<'a>: Sized {
2929
fn deserialize(payload: &'a [u8], version: u16) -> Result<Self::Latest> {
3030
let mut data = Self::deserialize_version(payload, version)?;
3131

32+
let skip_count = version
33+
.checked_sub(1)
34+
.with_context(|| format!("proto version ({version}) must be > 0"))?;
3235
for converter in Self::deserialize_converters()
3336
.iter()
34-
.skip(version.saturating_sub(1) as usize)
37+
.skip(skip_count as usize)
3538
{
3639
data = converter(data)?;
3740
}
@@ -42,10 +45,16 @@ pub trait VersionedData<'a>: Sized {
4245
fn serialize(self, version: u16) -> Result<Vec<u8>> {
4346
let mut data = self;
4447

45-
for converter in Self::serialize_converters()
46-
.iter()
47-
.skip(version.saturating_sub(1) as usize)
48-
{
48+
let converters = Self::serialize_converters();
49+
let take_count = (converters.len() + 1)
50+
.checked_sub(version as usize)
51+
.with_context(|| {
52+
format!(
53+
"proto version ({version}) greater than latest version ({})",
54+
converters.len() + 1
55+
)
56+
})?;
57+
for converter in converters.iter().take(take_count) {
4958
data = converter(data)?;
5059
}
5160

@@ -103,9 +112,12 @@ pub trait OwnedVersionedData: Sized {
103112
fn deserialize(payload: &[u8], version: u16) -> Result<Self::Latest> {
104113
let mut data = Self::deserialize_version(payload, version)?;
105114

115+
let skip_count = version
116+
.checked_sub(1)
117+
.with_context(|| format!("proto version ({version}) must be > 0"))?;
106118
for converter in Self::deserialize_converters()
107119
.iter()
108-
.skip(version.saturating_sub(1) as usize)
120+
.skip(skip_count as usize)
109121
{
110122
data = converter(data)?;
111123
}
@@ -116,10 +128,16 @@ pub trait OwnedVersionedData: Sized {
116128
fn serialize(self, version: u16) -> Result<Vec<u8>> {
117129
let mut data = self;
118130

119-
for converter in Self::serialize_converters()
120-
.iter()
121-
.skip(version.saturating_sub(1) as usize)
122-
{
131+
let converters = Self::serialize_converters();
132+
let take_count = (converters.len() + 1)
133+
.checked_sub(version as usize)
134+
.with_context(|| {
135+
format!(
136+
"proto version ({version}) greater than latest version ({})",
137+
converters.len() + 1
138+
)
139+
})?;
140+
for converter in converters.iter().take(take_count) {
123141
data = converter(data)?;
124142
}
125143

rust/vbare/tests/test.rs

Lines changed: 126 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,22 +15,31 @@ struct TestDataV2 {
1515
description: String,
1616
}
1717

18+
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
19+
struct TestDataV3 {
20+
id: u32,
21+
name: String,
22+
description: String,
23+
tags: Vec<String>,
24+
}
25+
1826
#[derive(Clone)]
1927
enum TestData {
2028
V1(TestDataV1),
2129
V2(TestDataV2),
30+
V3(TestDataV3),
2231
}
2332

2433
impl OwnedVersionedData for TestData {
25-
type Latest = TestDataV2;
34+
type Latest = TestDataV3;
2635

27-
fn wrap_latest(latest: TestDataV2) -> Self {
28-
TestData::V2(latest)
36+
fn wrap_latest(latest: TestDataV3) -> Self {
37+
TestData::V3(latest)
2938
}
3039

3140
fn unwrap_latest(self) -> Result<Self::Latest> {
3241
#[allow(irrefutable_let_patterns)]
33-
if let TestData::V2(data) = self {
42+
if let TestData::V3(data) = self {
3443
Ok(data)
3544
} else {
3645
bail!("version not latest");
@@ -41,6 +50,7 @@ impl OwnedVersionedData for TestData {
4150
match version {
4251
1 => Ok(TestData::V1(serde_bare::from_slice(payload)?)),
4352
2 => Ok(TestData::V2(serde_bare::from_slice(payload)?)),
53+
3 => Ok(TestData::V3(serde_bare::from_slice(payload)?)),
4454
_ => bail!("invalid version: {version}"),
4555
}
4656
}
@@ -49,15 +59,16 @@ impl OwnedVersionedData for TestData {
4959
match self {
5060
TestData::V1(data) => serde_bare::to_vec(&data).map_err(Into::into),
5161
TestData::V2(data) => serde_bare::to_vec(&data).map_err(Into::into),
62+
TestData::V3(data) => serde_bare::to_vec(&data).map_err(Into::into),
5263
}
5364
}
5465

5566
fn deserialize_converters() -> Vec<impl Fn(Self) -> Result<Self>> {
56-
vec![Self::v1_to_v2]
67+
vec![Self::v1_to_v2, Self::v2_to_v3]
5768
}
5869

5970
fn serialize_converters() -> Vec<impl Fn(Self) -> Result<Self>> {
60-
vec![Self::v2_to_v1]
71+
vec![Self::v3_to_v2, Self::v2_to_v1]
6172
}
6273
}
6374

@@ -110,7 +121,30 @@ impl TestData {
110121
name: v1.name,
111122
description: "default".to_string(),
112123
})),
113-
v2 => Ok(v2),
124+
other => Ok(other),
125+
}
126+
}
127+
128+
fn v2_to_v3(self) -> Result<Self> {
129+
match self {
130+
TestData::V2(v2) => Ok(TestData::V3(TestDataV3 {
131+
id: v2.id,
132+
name: v2.name,
133+
description: v2.description,
134+
tags: vec![],
135+
})),
136+
other => Ok(other),
137+
}
138+
}
139+
140+
fn v3_to_v2(self) -> Result<Self> {
141+
match self {
142+
TestData::V3(v3) => Ok(TestData::V2(TestDataV2 {
143+
id: v3.id,
144+
name: v3.name,
145+
description: v3.description,
146+
})),
147+
other => Ok(other),
114148
}
115149
}
116150

@@ -120,7 +154,7 @@ impl TestData {
120154
id: v2.id,
121155
name: v2.name,
122156
})),
123-
v1 => Ok(v1),
157+
other => Ok(other),
124158
}
125159
}
126160
}
@@ -151,37 +185,112 @@ fn test_v2_to_v2() {
151185

152186
let payload = TestData::V2(data.clone()).serialize(2).unwrap();
153187

188+
// V2 data deserialized with version 2 will be converted to V3 (latest)
154189
let deserialized = TestData::deserialize(&payload, 2).unwrap();
155190
assert_eq!(deserialized.id, 456);
156191
assert_eq!(deserialized.name, "test");
157192
assert_eq!(deserialized.description, "data");
193+
assert_eq!(deserialized.tags.len(), 0); // Tags default to empty vec
158194
}
159195

160196
#[test]
161197
fn test_unsupported_version() {
162198
assert!(TestData::deserialize(&[], 99).is_err());
163199
}
164200

201+
#[test]
202+
fn test_v3_to_v2() {
203+
// This is the critical test case: serializing from V3 to V2
204+
// With 3 versions and 2 serialize converters (V3->V2, V2->V1),
205+
// serializing to version 2 should apply 1 converter (V3->V2)
206+
let data = TestDataV3 {
207+
id: 789,
208+
name: "v3_test".to_string(),
209+
description: "test description".to_string(),
210+
tags: vec!["tag1".to_string(), "tag2".to_string()],
211+
};
212+
213+
// Serialize V3 data to V2 format (should strip tags)
214+
let payload = TestData::V3(data.clone()).serialize(2).unwrap();
215+
216+
// Deserialize as V2 and verify tags were stripped
217+
let deserialized = TestData::deserialize(&payload, 2).unwrap();
218+
assert_eq!(deserialized.id, 789);
219+
assert_eq!(deserialized.name, "v3_test");
220+
assert_eq!(deserialized.description, "test description");
221+
// Tags should not be present in V2
222+
}
223+
224+
#[test]
225+
fn test_v3_to_v1() {
226+
// Test serializing from V3 all the way down to V1
227+
// Should apply both converters: V3->V2, then V2->V1
228+
let data = TestDataV3 {
229+
id: 999,
230+
name: "v3_to_v1_test".to_string(),
231+
description: "should be stripped".to_string(),
232+
tags: vec!["will be removed".to_string()],
233+
};
234+
235+
// Serialize V3 data to V1 format
236+
let payload = TestData::V3(data.clone()).serialize(1).unwrap();
237+
238+
// Deserialize as V1 and verify both description and tags were stripped
239+
let deserialized = TestData::deserialize(&payload, 1).unwrap();
240+
assert_eq!(deserialized.id, 999);
241+
assert_eq!(deserialized.name, "v3_to_v1_test");
242+
assert_eq!(deserialized.description, "default");
243+
assert_eq!(deserialized.tags.len(), 0);
244+
}
245+
246+
#[test]
247+
fn test_v3_to_v3() {
248+
// Test that serializing V3 to V3 preserves all data
249+
let data = TestDataV3 {
250+
id: 123,
251+
name: "v3_same".to_string(),
252+
description: "preserved".to_string(),
253+
tags: vec!["keep".to_string()],
254+
};
255+
256+
let payload = TestData::V3(data.clone()).serialize(3).unwrap();
257+
258+
let deserialized = TestData::deserialize(&payload, 3).unwrap();
259+
assert_eq!(deserialized.id, 123);
260+
assert_eq!(deserialized.name, "v3_same");
261+
assert_eq!(deserialized.description, "preserved");
262+
assert_eq!(deserialized.tags, vec!["keep".to_string()]);
263+
}
264+
165265
#[test]
166266
fn test_serialize() {
167-
let data = TestData::V2(TestDataV2 {
267+
let data = TestData::V3(TestDataV3 {
168268
id: 456,
169269
name: "serialize_test".to_string(),
170270
description: "will be stripped".to_string(),
271+
tags: vec!["tag1".to_string()],
171272
});
172273

173-
// Test serializing to V1 (should convert V2 -> V1)
274+
// Test serializing to V1 (should convert V3 -> V2 -> V1)
174275
let result = data.clone().serialize(1).unwrap();
175276
let deserialized: TestDataV1 = serde_bare::from_slice(&result).unwrap();
176277
assert_eq!(deserialized.id, 456);
177278
assert_eq!(deserialized.name, "serialize_test");
178279

179-
// Test serializing to V2
180-
let result = data.serialize(2).unwrap();
280+
// Test serializing to V2 (should convert V3 -> V2)
281+
let result = data.clone().serialize(2).unwrap();
181282
let deserialized: TestDataV2 = serde_bare::from_slice(&result).unwrap();
182283
assert_eq!(deserialized.id, 456);
183284
assert_eq!(deserialized.name, "serialize_test");
184285
assert_eq!(deserialized.description, "will be stripped");
286+
287+
// Test serializing to V3 (no conversion)
288+
let result = data.serialize(3).unwrap();
289+
let deserialized: TestDataV3 = serde_bare::from_slice(&result).unwrap();
290+
assert_eq!(deserialized.id, 456);
291+
assert_eq!(deserialized.name, "serialize_test");
292+
assert_eq!(deserialized.description, "will be stripped");
293+
assert_eq!(deserialized.tags, vec!["tag1".to_string()]);
185294
}
186295

187296
#[test]
@@ -196,14 +305,16 @@ fn test_embedded_v2_to_v1_to_v2() {
196305
.serialize_with_embedded_version(1)
197306
.unwrap();
198307

199-
// First 2 bytes should be the version (2 in little-endian)
308+
// First 2 bytes should be the version (1 in little-endian)
200309
assert_eq!(payload[0], 1u8);
201310
assert_eq!(payload[1], 0u8);
202311

312+
// Deserializing V1 data converts it to V3 (latest)
203313
let deserialized = TestData::deserialize_with_embedded_version(&payload).unwrap();
204314
assert_eq!(deserialized.id, 456);
205315
assert_eq!(deserialized.name, "test");
206316
assert_eq!(deserialized.description, "default");
317+
assert_eq!(deserialized.tags.len(), 0);
207318
}
208319

209320
#[test]
@@ -222,10 +333,12 @@ fn test_embedded_v2_to_v2() {
222333
assert_eq!(payload[0], 2u8);
223334
assert_eq!(payload[1], 0u8);
224335

336+
// Deserializing V2 data converts it to V3 (latest)
225337
let deserialized = TestData::deserialize_with_embedded_version(&payload).unwrap();
226338
assert_eq!(deserialized.id, 456);
227339
assert_eq!(deserialized.name, "test");
228340
assert_eq!(deserialized.description, "data");
341+
assert_eq!(deserialized.tags.len(), 0);
229342
}
230343

231344
#[test]

0 commit comments

Comments
 (0)