Skip to content

Add type_attribute, field_attribute, and message_attribute support#44

Merged
iainmcgin merged 4 commits intoanthropics:mainfrom
jlucaso1:feat/custom-attributes
Apr 16, 2026
Merged

Add type_attribute, field_attribute, and message_attribute support#44
iainmcgin merged 4 commits intoanthropics:mainfrom
jlucaso1:feat/custom-attributes

Conversation

@jlucaso1
Copy link
Copy Markdown
Contributor

@jlucaso1 jlucaso1 commented Apr 7, 2026

Adds prost-compatible type_attribute(), field_attribute(), and message_attribute() builder methods to buffa_build::Config, allowing custom attributes on generated types, fields, and message structs.

This is the main missing piece for migrating from prost when projects rely on custom derives or per-field serde attributes (feature-gated derives, #[serde(skip)] on specific fields, struct-only #[serde(default)], etc.).

Changes

Crate File What
buffa-codegen lib.rs 3 new Vec<(String, String)> fields on CodeGenConfig
buffa-codegen context.rs matching_attributes() — prefix-matches FQN, parses into TokenStream
buffa-codegen message.rs Inject type/message attrs on structs, field attrs on fields
buffa-codegen enumeration.rs Inject type attrs on enums
buffa-build lib.rs 3 builder methods with docs and examples

Path matching follows the same convention as prost: "." matches everything, ".my.pkg" matches a package prefix, ".my.pkg.MyMessage" matches a specific type. Partial segment matches are rejected (.my.pk does not match .my.pkg).

Follow-up

enum_attribute() (enum-only counterpart to message_attribute()) could be added in a separate PR if there's demand.

Closes #43

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 7, 2026

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

@jlucaso1
Copy link
Copy Markdown
Contributor Author

jlucaso1 commented Apr 7, 2026

I have read the CLA Document and I hereby sign the CLA

github-actions bot added a commit that referenced this pull request Apr 7, 2026
- Extend type_attribute / field_attribute coverage to oneof enums and
  their variants via the oneof's fully-qualified proto path
  (e.g. .pkg.Msg.my_oneof and .pkg.Msg.my_oneof.variant_a).
- Return a CodeGenError::InvalidCustomAttribute when an attribute string
  fails to parse, instead of silently emitting a doc-comment fallback
  that could go unnoticed in CI.
- Demote CodeGenContext::matching_attributes from pub to pub(crate) — it
  is a codegen-internal helper.
- Extract a shared matches_proto_prefix helper so use_bytes_type gets
  the same proto-segment-aware matching (previously a plain starts_with
  matched '.my.pk' against '.my.pkg.Msg.data').
- Normalize user-supplied attribute paths: trim trailing dots and keep
  the bare catch-all '.' intact.
- Document the duplicate-derive pitfall and oneof coverage in the
  buffa-build rustdoc; add build-layer tests for normalization and
  codegen-layer tests for oneof coverage + the parse-error path.
Mark the legacy Person.address.freeform_address oneof variant as
#[deprecated] via buffa-build's new field_attribute hook, and update
the addressbook CLI to stop offering it on new writes while still
reading existing records that contain it. This is the canonical
deprecation-migration pattern and exercises the oneof-variant path
of the custom-attributes support end-to-end.

The write path now only creates structured_address entries; the read
path uses a scoped #[allow(deprecated)] so warnings still fire on
any accidental writes elsewhere. The generated module is wrapped in
#[allow(deprecated)] because codegen's own encode/decode paths match
on every variant regardless of deprecation.
Copy link
Copy Markdown
Collaborator

@iainmcgin iainmcgin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved. All review findings addressed in the maintainer-side fix-up; example demonstrates the feature end-to-end; 7/7 CI green.

@iainmcgin iainmcgin merged commit 687bee7 into anthropics:main Apr 16, 2026
7 checks passed
@github-actions github-actions bot locked and limited conversation to collaborators Apr 16, 2026
@iainmcgin
Copy link
Copy Markdown
Collaborator

[claude code]

Thanks for this, @jlucaso1type_attribute/field_attribute/message_attribute was a real gap for users migrating from prost, and having the matcher shape mirror prost-build exactly will make those migrations painless. Merged!

A summary of the changes I made on top of your branch before merging, in case you're curious about the follow-up work:

Visibility and error hygiene

  • Demoted CodeGenContext::matching_attributes from pub to pub(crate) — it's a codegen-internal helper and keeping it in the public API would have committed us to semver-stability we don't need.
  • Added a typed CodeGenError::InvalidCustomAttribute { path, attribute, detail } variant and routed malformed attribute strings to it via ? propagation. Previously an unparseable attribute became #[doc = "buffa: failed to parse..."], which compiled fine and would slip past CI. Now the build fails loudly at the offending call site.

Oneof coverage

Wired type_attribute and field_attribute into oneof codegen:

  • type_attribute(".pkg.Msg.my_oneof", ...) now reaches the generated oneof enum.
  • field_attribute(".pkg.Msg.my_oneof.variant_a", ...) now reaches individual variants.

Previously both silently no-op'd, which would have been a surprising regression from prost for anyone migrating.

Matcher hardening

  • Extracted matches_proto_prefix(prefix, fqn_dotted) -> bool so the proto-segment-aware logic (prevents ".my.pk" from matching ".my.pkg.Msg.data") is shared. Incidentally, this also fixed a pre-existing bug in use_bytes_type which was using plain starts_with.
  • Trimmed trailing dots in the buffa-build normalizer via a shared normalize_attr_path helper, so users passing "my.pkg." (which could never match anything otherwise) get the same stored path as "my.pkg".

Tests and docs

  • Added build-layer tests for the normalizer (leading-dot, trailing-dot, catch-all, insertion-order preservation) and codegen tests for the oneof coverage plus the parse-error path.
  • Documented the duplicate-derive pitfall, the normalization rules, and the oneof reach in the type_attribute/field_attribute/message_attribute rustdoc.

Example

Added a demo in examples/addressbook: the Person.address.freeform_address oneof variant is now marked #[deprecated(note = "...")] via field_attribute, and the CLI stops offering it for new entries while still reading existing records that use it. This is the canonical deprecation-migration pattern and exercises the oneof-variant plumbing end-to-end.

Also worth flagging a semi-related change that landed between your PR's original push and this merge: #34 switched oneof enums to a uniform {Name}Oneof naming convention (previously only-on-conflict). If you had any downstream code referencing the old unsuffixed names, you'll want to update after pulling main.

Thanks again!

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.

Support type_attribute and field_attribute for custom derive/attribute injection

2 participants