Skip to content

Commit 4ae780f

Browse files
committed
fix(rivetkit): decode legacy v4 actor schedule args
1 parent b81dab2 commit 4ae780f

2 files changed

Lines changed: 84 additions & 1 deletion

File tree

rivetkit-rust/packages/actor-persist/src/versioned.rs

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ impl OwnedVersionedData for Actor {
2929
1 => Ok(Self::V1(serde_bare::from_slice(payload)?)),
3030
2 => Ok(Self::V2(serde_bare::from_slice(payload)?)),
3131
3 => Ok(Self::V3(serde_bare::from_slice(payload)?)),
32-
4 => Ok(Self::V4(serde_bare::from_slice(payload)?)),
32+
4 => Ok(Self::V4(Self::decode_v4_with_legacy_raw_args_fallback(
33+
payload,
34+
)?)),
3335
_ => bail!("invalid actor persist version: {version}"),
3436
}
3537
}
@@ -54,6 +56,58 @@ impl OwnedVersionedData for Actor {
5456
}
5557

5658
impl Actor {
59+
fn decode_v4_with_legacy_raw_args_fallback(payload: &[u8]) -> Result<v4::Actor> {
60+
#[derive(serde::Deserialize)]
61+
struct LegacyRawV4Actor {
62+
input: Option<Vec<u8>>,
63+
has_initialized: bool,
64+
state: Vec<u8>,
65+
scheduled_events: Vec<LegacyRawV4ScheduleEvent>,
66+
}
67+
68+
#[derive(serde::Deserialize)]
69+
struct LegacyRawV4ScheduleEvent {
70+
event_id: String,
71+
timestamp_ms: i64,
72+
action: String,
73+
args: Vec<u8>,
74+
}
75+
76+
// serde_bare accepts any nonzero bool as true, so legacy raw args can
77+
// sometimes decode as current v4. Only accept canonical current-v4 bytes.
78+
let current_error = match serde_bare::from_slice::<v4::Actor>(payload) {
79+
Ok(actor) => {
80+
if serde_bare::to_vec(&actor).is_ok_and(|encoded| encoded == payload) {
81+
return Ok(actor);
82+
}
83+
None
84+
}
85+
Err(error) => Some(error),
86+
};
87+
88+
// A short-lived v4 writer stored schedule args as raw `data` while
89+
// the fixed v4 schema expects `optional<Cbor>`. Decode that reused
90+
// version so actors persisted by the bad writer can restart.
91+
match serde_bare::from_slice::<LegacyRawV4Actor>(payload) {
92+
Ok(actor) => Ok(v4::Actor {
93+
input: actor.input,
94+
has_initialized: actor.has_initialized,
95+
state: actor.state,
96+
scheduled_events: actor
97+
.scheduled_events
98+
.into_iter()
99+
.map(|event| v4::ScheduleEvent {
100+
event_id: event.event_id,
101+
timestamp: event.timestamp_ms,
102+
action: event.action,
103+
args: (!event.args.is_empty()).then_some(event.args),
104+
})
105+
.collect(),
106+
}),
107+
Err(legacy_error) => Err(current_error.unwrap_or(legacy_error).into()),
108+
}
109+
}
110+
57111
fn v1_to_v2(self) -> Result<Self> {
58112
let Self::V1(data) = self else {
59113
bail!("expected actor persist v1 Actor");
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
use rivetkit_actor_persist::versioned;
2+
use vbare::OwnedVersionedData;
3+
4+
#[test]
5+
fn actor_decodes_legacy_raw_v4_schedule_args() {
6+
let encoded = b"\x04\x00\x01\x05input\x01\x05state\x01\x07event-1\x2a\x00\x00\x00\x00\x00\x00\x00\x0fhandleTurnTimer\x01\x80";
7+
8+
let decoded = versioned::Actor::deserialize_with_embedded_version(encoded)
9+
.expect("legacy raw v4 actor should decode");
10+
11+
assert_eq!(decoded.input, Some(b"input".to_vec()));
12+
assert!(decoded.has_initialized);
13+
assert_eq!(decoded.state, b"state");
14+
assert_eq!(decoded.scheduled_events.len(), 1);
15+
assert_eq!(decoded.scheduled_events[0].event_id, "event-1");
16+
assert_eq!(decoded.scheduled_events[0].timestamp, 42);
17+
assert_eq!(decoded.scheduled_events[0].action, "handleTurnTimer");
18+
assert_eq!(decoded.scheduled_events[0].args, Some(vec![0x80]));
19+
}
20+
21+
#[test]
22+
fn actor_decodes_legacy_raw_v4_when_current_v4_accepts_bytes() {
23+
let encoded = b"\x04\x00\x00\x01\x05state\x01\x07event-1\x2a\x00\x00\x00\x00\x00\x00\x00\x0fhandleTurnTimer\x02\x01\x99";
24+
25+
let decoded = versioned::Actor::deserialize_with_embedded_version(encoded)
26+
.expect("legacy raw v4 actor accepted by current v4 should decode");
27+
28+
assert_eq!(decoded.scheduled_events[0].args, Some(vec![0x01, 0x99]));
29+
}

0 commit comments

Comments
 (0)