Skip to content

codegen: emit Default + #[serde(default)] for proto enum scalars#137

Open
prasanna-anchorage wants to merge 2 commits into
tkhq:mainfrom
anchorageoss:prasanna/codegen-serde-default-enum-scalars
Open

codegen: emit Default + #[serde(default)] for proto enum scalars#137
prasanna-anchorage wants to merge 2 commits into
tkhq:mainfrom
anchorageoss:prasanna/codegen-serde-default-enum-scalars

Conversation

@prasanna-anchorage
Copy link
Copy Markdown

Problem

Bare-enum scalar fields in the generated turnkey_client crate fail to deserialize when the server response (legitimately) omits them.

Protobuf's canonical JSON mapping drops enum fields whose value is the zero / *_UNSPECIFIED variant. With the current codegen, those fields are emitted as bare enum types with neither a Default impl on the enum nor #[serde(default)] on the field, so a spec-compliant response without the key fails with:

Failed to decode response { ... } (missing field `<name>` ...)

This surfaced live for me on tvc deploy approve against a freshly-created deployment: the field was TvcDeployment.stage set to TVC_DEPLOYMENT_STAGE_UNSPECIFIED. That specific field has since been removed from the proto, but the same defect affects every other bare enum scalar in the current generated client — Outcome, InvitationStatus, TagType, Curve, AddressFormat, Effect, AccessType, Oauth2Provider, FiatOnRampProvider, CredentialType, ApiKeyCurve, and friends — any of which can fail the same way for the same reason.

Fix

Both changes live in codegen/src/transform.rs; regenerated client files in the second commit.

mutate_struct — when an enum-scalar field is rewritten from i32 to the named enum type, push #[serde(default)] for the bare (non-Vec, non-Option) case. The existing should_add_default allowlist already covers Vec<Enum> / Option<Enum>.

mutate_enum — find the variant whose explicit discriminant is 0 (the proto3 unspecified default), annotate it with #[default], and add #[derive(Default)] to the enum. Skip the flatten-style sum enums (Inner, TokenOrClaims) that already get special treatment — they aren't simple discriminated enums.

The regen diff is purely additive: #[serde(default)] on the field, #[derive(Default)] on the enum, #[default] on the zero variant. No proto changes.

Verification

  • cargo build --workspace
  • cargo test --workspace ✅ (all existing tests pass)
  • cargo clippy --all-targets --all-features -- -D warnings
  • cargo fmt -- --check
  • End-to-end: rebuilt tvc from this branch and re-ran the originally-failing tvc deploy approve --deploy-id … --operator-id … — now decodes the response and reaches the interactive approval prompt instead of erroring on a missing field.

Notes

  • A codegen-template fix is the right level: the generated .rs would otherwise be wiped on the next make generate.
  • make generate needed PROTOC_INCLUDE=/usr/local/include on my machine to find google/protobuf/descriptor.proto. Not a change in this PR; flagging in case it's worth documenting in the README.

🤖 Generated with Claude Code

prasanna-anchorage and others added 2 commits May 16, 2026 06:07
Protobuf canonical JSON omits enum fields whose value is the zero /
*_UNSPECIFIED variant. The current codegen emits these fields as bare
enum types with neither a Default impl on the enum nor #[serde(default)]
on the field, so a spec-compliant server response that omits a zero-valued
enum scalar fails to deserialize with `missing field <name>`.

Two changes in codegen/src/transform.rs:

- mutate_struct: when an enum-scalar field is rewritten from i32 to the
  named enum type, also push #[serde(default)] for the bare (non-Vec,
  non-Option) case. The existing should_add_default() allowlist covers
  Vec<Enum> / Option<Enum> already.

- mutate_enum: identify the variant whose explicit discriminant is 0
  (the proto3 unspecified default), annotate it with #[default], and
  add #[derive(Default)] to the enum. Skip the flatten-style sum enums
  (Inner, TokenOrClaims) that already get special treatment.

Surfaced when a real TVC deployment response omitted a TvcDeploymentStage
field set to UNSPECIFIED. That specific field has since been removed from
the proto, but the same defect currently affects several other scalar
enum fields (Outcome, InvitationStatus, TagType, Curve, AddressFormat,
Effect, AccessType, etc.) - any of which the server can legitimately
serialize without a key when the value is the unspecified default.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`make generate` output after the codegen change. Adds `#[serde(default)]`
to bare enum scalar fields and `#[derive(Default)] + #[default]` on the
zero-discriminant variant of simple enums. No proto changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant