Skip to content

fix(buffa): re-export serde_json so extension JSON deserialize compiles without a direct consumer dep#112

Merged
iainmcgin merged 1 commit into
mainfrom
fix-serde-json-reexport
May 13, 2026
Merged

fix(buffa): re-export serde_json so extension JSON deserialize compiles without a direct consumer dep#112
iainmcgin merged 1 commit into
mainfrom
fix-serde-json-reexport

Conversation

@iainmcgin
Copy link
Copy Markdown
Collaborator

@iainmcgin iainmcgin commented May 13, 2026

Summary

Part 1/3 of the buf/validate/validate.proto build fix (alongside #114 and #115 — issue context in #113).

Buf reported that the bufbuild_registry_anthropics_buffa SDK (generated from the BSR with the buffa plugin, json=true) fails to build because the generated buf.validate.rs references ::serde_json::Value but the SDK's Cargo.toml doesn't declare serde_json.

The path comes from the hand-written Deserialize impl emitted for messages with extensions N to M; ranges (validate.proto declares 21 of them). It buffers "[pkg.ext]" JSON keys into a serde_json::Value before dispatching to extension_registry::deserialize_extension_key. Because the emitted path is a bare ::serde_json::Value, every consumer of json=true codegen silently picks up a hard requirement on a direct serde_json dep — a footgun the BSR plugin config (which only declares buffa, buffa-types, serde, bytes) walked into.

Fix

Re-export serde_json from buffa (gated on the json feature, #[doc(hidden)], matching the existing bytes re-export) and route the codegen path through ::buffa::serde_json::Value. After this change the generated SDK compiles with only buffa, buffa-types, and serde declared.

serde itself deliberately stays a direct consumer dep: the #[derive(::serde::Serialize)] macro emits extern crate serde as _serde; by default, which a re-export alone cannot satisfy without stamping #[serde(crate = "...")] onto every generated derive (and rewriting every other emitted ::serde:: path). That's feasible but out of scope here; the BSR plugin already declares serde.

No checked-in generated code changes — none of the WKTs declare extension ranges, so task gen-wkt-types is a no-op.

Reproduction

Minimal repro: a crate that runs buffa-build against buf/validate/validate.proto with .generate_json(true):

Before: 21 × error[E0433]: cannot find serde_json in the crate root
After: those errors are gone.

(There are also 15 × error[E0433]: cannot find field_descriptor_proto in protobuf from the same proto — that's a separate gap in the default .google.protobuf::buffa_types::google::protobuf extern_path mapping, which doesn't cover descriptor.proto types. Tracked in #113 and addressed by #114 + #115.)

Notes

  • Added a codegen test asserting every ::serde_json:: occurrence is preceded by ::buffa:: (count-equality, so it catches turbofish/trait-bound contexts too).
  • Documented the load-bearing #[doc(hidden)] pub use re-exports in CONTRIBUTING.md so a future cleanup pass doesn't remove them.
  • Updated docs/guide.md to add serde to the example [dependencies] (it was always required but never shown) and clarify that serde_json is only needed by the consumer's own serde_json::to_string calls, not by generated code.
  • protoc-gen-buffa/buf.plugin.yaml is still pinned to 0.3 and predates json=true — it's stale and should be bumped at release time, not here.

…es without a direct consumer dep

Generated `Deserialize` impls for messages with `extensions N to M;`
ranges and `json=true` codegen buffer `"[pkg.ext]"` JSON keys into a
`serde_json::Value` before dispatching to
`extension_registry::deserialize_extension_key`. The emitted path was a
bare `::serde_json::Value`, which silently required every consumer of
`json=true` codegen to declare `serde_json` directly in its own
`Cargo.toml`.

`buf/validate/validate.proto` declares 21 such extension ranges, so any
SDK generated from a proto module that depends on protovalidate failed to
compile with `cannot find serde_json in the crate root` unless the
consumer happened to also use serde_json themselves.

Re-export `serde_json` from `buffa` (gated on the `json` feature,
`#[doc(hidden)]`, matching the existing `bytes` re-export) and route the
codegen path through `::buffa::serde_json::Value`. `serde` itself
deliberately stays a direct consumer dep: the
`#[derive(::serde::Serialize)]` macro emits
`extern crate serde as _serde;` by default, which a re-export alone
cannot satisfy without stamping `#[serde(crate = "...")]` on every
generated derive.

No checked-in generated code changes — none of the WKTs declare
extension ranges, so `task gen-wkt-types` is a no-op for this path.
@github-actions
Copy link
Copy Markdown

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@iainmcgin iainmcgin merged commit a3b9f4e into main May 13, 2026
7 checks passed
@iainmcgin iainmcgin deleted the fix-serde-json-reexport branch May 13, 2026 20:17
@github-actions github-actions Bot locked and limited conversation to collaborators May 13, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants