Skip to content

Add Enumeration::values() static slice for variant iteration#49

Merged
iainmcgin merged 2 commits intomainfrom
feat/enum-values-iter
Apr 17, 2026
Merged

Add Enumeration::values() static slice for variant iteration#49
iainmcgin merged 2 commits intomainfrom
feat/enum-values-iter

Conversation

@iainmcgin
Copy link
Copy Markdown
Collaborator

@iainmcgin iainmcgin commented Apr 16, 2026

Partially addresses #23 (at least, the most common use case for enum attributes).

Generated enums now expose Enumeration::values(), returning a &'static [Self] slice of every primary variant in proto declaration order. This subsumes the most common reason users reach for strum::EnumIter (the example in #23) and additionally enables .contains(), .len(), indexing, binary search, etc.

Example

For

enum Status {
  UNSPECIFIED = 0;
  ACTIVE = 1;
  INACTIVE = 2;
}

users can now write

for v in Status::values() {
    println!("{:?} = {}", v, v.to_i32());
}
assert!(Status::values().contains(&Status::ACTIVE));
assert_eq!(Status::values().len(), 3);

Behavior

  • Aliases (additional names sharing an existing value when option allow_alias = true) are skipped — they remain accessible as the existing pub const aliases on the enum, but they aren't enum variants in Rust so they don't belong in values().
  • Order matches the .proto declaration order, which is also the order from_i32 resolves to (so Status::values()[i].to_i32() lines up with from_i32 for unique values).

Trait change

Enumeration::values is added with a default implementation returning an empty slice so out-of-tree consumers implementing Enumeration against an older buffa version continue to compile. Codegen unconditionally overrides the default with the real impl.

Tests

  • New unit tests in tests/generation.rs: test_enum_values_emits_static_slice_in_declaration_order and test_enum_values_skips_aliases.
  • Workspace tests, clippy -D warnings, rustfmt, and markdownlint clean.
  • task gen-wkt-types and task gen-bootstrap-types run; the resulting drift in buffa-types/src/generated/ and buffa-descriptor/src/generated/ is just the new values() impls.

Follow-up

A separate PR will add enum_attribute (the symmetric inverse of message_attribute from #44) for users who want to inject Rust attributes onto enums but not messages.

Generated enums now implement `Enumeration::values()` which returns a
`&'static [Self]` slice of every primary variant in proto declaration
order. This subsumes the most common reason users reach for
`strum::EnumIter` (per #23), and additionally enables `.contains()`,
`.len()`, indexing, and binary search.

Aliases (additional names sharing an existing value when
`allow_alias = true`) are skipped; they remain accessible as the
existing `pub const` aliases.

The trait method has a default implementation returning an empty
slice so out-of-tree consumers implementing `Enumeration` against an
older buffa version continue to compile — they should override the
default after regenerating.

Closes #23.
@iainmcgin iainmcgin marked this pull request as ready for review April 16, 2026 23:26
@iainmcgin iainmcgin requested a review from asacamano April 16, 2026 23:26
iainmcgin added a commit that referenced this pull request Apr 17, 2026
Follow-up to #44, partly addressing #23.

The custom-attributes API from #44 has `type_attribute` (matches both
messages and enums) and `message_attribute` (struct-only), but no
symmetric enum-only filter. Users wanting to inject a derive like
`#[derive(strum::EnumIter)]` on enums had to either also paint every
matching message with the same attribute (which usually fails to
compile, since the derive only makes sense on enums) or hand-construct
an awkward union of multiple `.type_attribute(".pkg.MyEnum1", ...)` /
`.type_attribute(".pkg.MyEnum2", ...)` matchers.

`enum_attribute` is the symmetric inverse of `message_attribute`.

## API

```rust
buffa_build::Config::new()
    .enum_attribute(".my.pkg", "#[derive(strum::EnumIter)]")
    .files(&["proto/my_service.proto"])
    .includes(&["proto/"])
    .compile()
    .unwrap();
```

Same path-matching, normalization (leading-dot prepend, trailing-dot
trim, segment-aware prefix), insertion-order accumulation, and
`CodeGenError::InvalidCustomAttribute` semantics as the existing trio.

## Codegen

In `enumeration.rs`, after the existing `type_attribute` match, also
match `enum_attributes` and emit both attribute streams above the `pub
enum` declaration. `type_attribute` continues to apply to enums as well
— `enum_attribute` is additive, not exclusive.

## Tests

- `test_enum_attribute_on_enum_not_struct` — catch-all `enum_attribute`
lands on the enum but not on a sibling message struct.
- `test_enum_attribute_scoped_to_specific_enum` — prefix-targeted
attribute lands on the matched enum only.
- `test_enum_attribute_does_not_apply_to_struct` — defense-in-depth
check on the non-bleed property.
- `enum_attribute_forwards_normalized_path` — build-layer test
confirming path normalization and that the other three attribute lists
remain untouched.

Workspace tests, clippy `-D warnings`, rustfmt clean. No codegen drift.

## Relation to #23

#23 originally asked for `enum_attribute` as a way to inject
`#[derive(strum::EnumIter)]`. PR #49 (currently in flight) addresses the
underlying use case directly by adding `Enumeration::values()`, removing
the need for the third-party `EnumIter` derive in most cases. This PR
adds `enum_attribute` because the API symmetry is still genuinely useful
(e.g. for serde, schemars, custom derives) and the absence was an
asymmetry surprise.
@iainmcgin iainmcgin enabled auto-merge (squash) April 17, 2026 19:32
@iainmcgin iainmcgin merged commit b065d14 into main Apr 17, 2026
7 checks passed
@iainmcgin iainmcgin deleted the feat/enum-values-iter branch April 17, 2026 19:35
@github-actions github-actions bot locked and limited conversation to collaborators Apr 17, 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