Migrating a codebase from prost to buffa and hit a wall trying to control enum serde output. Wanted to write it up in case the fix is small.
What I need
The generated messages derive serde::Serialize (great), but I need the proto enums to serialize as their integer value (proto3-JSON style — also what prost emitted), not the variant name. A downstream JS consumer reads these as numbers, so "IMAGE" instead of 5 is a silent break.
#[repr(i32)] is already on every generated enum, so serde_repr::Serialize_repr would be exactly right. The natural move is to keep serde::Serialize on messages and put serde_repr only on enums:
.message_attribute(".", "#[derive(serde::Serialize)]")
.enum_attribute(".", "#[derive(serde_repr::Serialize_repr)]")
Why it doesn't work
Oneof types fall through the cracks. They aren't reached by message_attribute or enum_attribute — only by type_attribute. So the snippet above leaves every oneof (__buffa::oneof::*) without a Serialize impl, and any message containing a oneof field fails to compile:
error[E0277]: the trait bound `__buffa::oneof::message::buttons_message::Header: serde::Serialize` is not satisfied
And I can't fall back to type_attribute(".", "#[derive(serde::Serialize)]") to cover oneofs, because that also lands on the enums, colliding with the per-enum serde_repr derive.
I also looked at #[serde(into = "i32")] / #[serde(try_from = "i32")] as a workaround that's compatible with a blanket serde::Serialize, but the generated enums only expose to_i32/from_i32 (the Enumeration trait) — there's no impl From<Enum> for i32 / TryFrom<i32> for Enum, so that path is out too.
Proposed solutions (any one would unblock)
- Add
oneof_attribute(path, attr) to buffa-build, symmetric with the existing message_attribute / enum_attribute. Then each category can be targeted independently (serde on messages + oneofs, serde_repr on enums). This seems like the smallest, most general fix.
- A built-in "serialize enums as their numeric repr" codegen option (e.g.
serde_enum_repr(true)), since proto3-JSON ↔ integer is a common interop need and #[repr(i32)] is already emitted.
- Emit
From<Enum> for i32 (and TryFrom<i32>) for generated enums, which would make #[serde(into = "i32")] a clean, self-contained workaround.
Option 1 is the one I'd reach for. Happy to send a PR for oneof_attribute if that direction sounds good — it looks like it would mirror the enum_attribute plumbing closely.
Migrating a codebase from prost to buffa and hit a wall trying to control enum serde output. Wanted to write it up in case the fix is small.
What I need
The generated messages derive
serde::Serialize(great), but I need the proto enums to serialize as their integer value (proto3-JSON style — also what prost emitted), not the variant name. A downstream JS consumer reads these as numbers, so"IMAGE"instead of5is a silent break.#[repr(i32)]is already on every generated enum, soserde_repr::Serialize_reprwould be exactly right. The natural move is to keepserde::Serializeon messages and putserde_repronly on enums:Why it doesn't work
Oneof types fall through the cracks. They aren't reached by
message_attributeorenum_attribute— only bytype_attribute. So the snippet above leaves every oneof (__buffa::oneof::*) without aSerializeimpl, and any message containing a oneof field fails to compile:And I can't fall back to
type_attribute(".", "#[derive(serde::Serialize)]")to cover oneofs, because that also lands on the enums, colliding with the per-enumserde_reprderive.I also looked at
#[serde(into = "i32")]/#[serde(try_from = "i32")]as a workaround that's compatible with a blanketserde::Serialize, but the generated enums only exposeto_i32/from_i32(theEnumerationtrait) — there's noimpl From<Enum> for i32/TryFrom<i32> for Enum, so that path is out too.Proposed solutions (any one would unblock)
oneof_attribute(path, attr)tobuffa-build, symmetric with the existingmessage_attribute/enum_attribute. Then each category can be targeted independently (serde on messages + oneofs, serde_repr on enums). This seems like the smallest, most general fix.serde_enum_repr(true)), since proto3-JSON ↔ integer is a common interop need and#[repr(i32)]is already emitted.From<Enum> for i32(andTryFrom<i32>) for generated enums, which would make#[serde(into = "i32")]a clean, self-contained workaround.Option 1 is the one I'd reach for. Happy to send a PR for
oneof_attributeif that direction sounds good — it looks like it would mirror theenum_attributeplumbing closely.