cloudflare / cloudflare/workers-rs

[BUG] Queue: typed `MessageBatch<T>` deserialize via `serde_wasm_bindgen::from_value` mangles payloads — offer a JSON-based path

Open
#1,013 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
Rust
Stars
3.7k
Forks
429
Avg merge
20h 28m
Merged PRs (30d)
7

Description

### Is there an existing issue for this?

- [x] I have searched the existing issues

### What version of `workers-rs` are you using?

0.8.4

### What version of `wrangler` are you using?

4.100.0

### Describe the bug

### Summary

The typed queue-consumer path (`MessageBatch` / `Message::try_from(RawMessage)`) deserializes each body with `serde_wasm_bindgen::from_value`. For real-world payloads we found its V8↔serde type coercion **unreliable** — it produces wrong data or an opaque deserialize error. Bypassing it with `JSON.stringify(body)` + `serde_json::from_str` works reliably for the exact same payloads.

### Environment
- `worker` **0.8.4**, `wasm32-unknown-unknown`, Rust 1.96.
- Payload: a struct with `serde_json::Value`s and tuples, e.g. `substitutes: Vec<(String, serde_json::Value)>`, plus `Vec`, strings.

### Reliable workaround (consumer side)
```rust
use js_sys::JSON;
fn parse(body: &JsValue) -> Result {
let s = JSON::stringify(body).ok().and_then(|s| s.as_string())
.ok_or("JSON.stringify produced no string")?;
serde_json::from_str(&s).map_err(|e| e.to_string())
}
```
Round-trips through plain JSON and Just Works where `serde_wasm_bindgen::from_value` mangled the data. (This is also what the previous generation of our consumer hand-rolled — the footgun is long-standing.)

### Asks
- Document the limitation + this `JSON.stringify` + `serde_json` escape hatch in the Queues guide — it was the single biggest footgun we hit.
- Consider a `content_type`-aware / JSON-based deserialize helper on `MessageBatch` / `Message`, e.g. `msg.body_json::()`, that uses `JSON.stringify` + `serde_json` for `json`-typed bodies instead of `serde_wasm_bindgen::from_value`.
- More generally: surface the message's `content_type` to the consumer so it can choose the right decoder.

### Steps To Reproduce

```rust
// worker-0.8.4/src/queue.rs
impl TryFrom for Message where T: DeserializeOwned {
fn try_from(value: RawMessage) -> Result {
let body = serde_wasm_bindgen::from_value(value.body())?; // <-- V8 coercion
...
}
}
```

`serde_wasm_bindgen`'s coercion misbehaves on `serde_json::Value`, tuples (`Vec<(String, Value)>`), maps, and number / `undefined` edge cases.

Related to #1012

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.