Skip to content

buffa-build: add oneof_attribute() so enums can take a different serde derive than oneofs/messages #166

@jlucaso1

Description

@jlucaso1

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)

  1. 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.
  2. 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.
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions