Skip to content

Tags: SebastienMelki/sebuf

Tags

v0.23.2

Toggle v0.23.2's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
fix(clientgen): avoid shared JSON mapping collisions

* fix(clientgen): stop emitting shared JSON mapping files

* test(examples): refresh generation after client JSON ownership change

* test(clientgen): broaden combined JSON mapping coverage

v0.23.1

Toggle v0.23.1's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
fix(openapiv3): tag YAML example scalars

Fixes #244

v0.23.0

Toggle v0.23.0's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
chore(deps): bump github.com/pb33f/libopenapi from 0.38.6 to 0.38.7 (#…

…208)

Bumps [github.com/pb33f/libopenapi](https://github.com/pb33f/libopenapi) from 0.38.6 to 0.38.7.
- [Release notes](https://github.com/pb33f/libopenapi/releases)
- [Commits](pb33f/libopenapi@v0.38.6...v0.38.7)

---
updated-dependencies:
- dependency-name: github.com/pb33f/libopenapi
  dependency-version: 0.38.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

v0.22.2

Toggle v0.22.2's commit message
fix(openapiv3): map Google well-known wrapper types to scalar OpenAPI…

… schemas

v0.22.1

Toggle v0.22.1's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
fix(codegen): generate transitive int64 NUMBER marshalers across file…

…s and at any depth (#220)

The Go generators emitted a transitive MarshalJSONSebuf for a message nesting an
int64_encoding=NUMBER type only when both were declared in the same .proto file, and only at
exactly one level. Otherwise no wrapper was generated, protojson owned serialization, and the
int64 came out as a quoted string:

  {"reading":{"timestampMs":"1715000000000"}}   instead of
  {"reading":{"timestampMs":1715000000000}}

Root cause: collectWrapperMessages qualified a nested field by membership in a name set built
from collectInt64EncodingContext(file) -- messages declared in the file being generated with a
direct NUMBER field. Imported types were never in that set, and wrapper messages were never fed
back into it.

messageTransitivelyHasInt64Number now walks field.Message (resolved across files by protogen) to
unbounded depth with a visited-set cycle guard, mirroring the existing
messageTransitivelyHasCustomEnum. This retires the directMsgNames parameter rather than patching
it. nestsInt64NumberMessage is widened in lockstep so the enum conflict check cannot go stale.

A message with direct NUMBER fields also carries its nested fields: patching only its own fields
left a child that reached an annotated field owned by protojson, so the child's int64 stayed
quoted. Node{Node child; int64 id [NUMBER]} is the smallest case. The per-field nested emission
is extracted into emitNestedFieldsMarshal/emitNestedFieldsUnmarshal, shared by the direct and
wrapper paths.

Reachability deliberately does not follow map values: the emitters cannot re-serialize a map
field, so counting that path would mark a parent as a wrapper whose child never gets a
marshaler, and the dead wrapper could raise a false MarshalJSON conflict. Documented, with a
fixture pinning it.

Three further defects surfaced, each of which left the fix non-functional in practice:

- A wrapper-only encoding file -- exactly what cross-file nesting produces -- imported strconv
  without using it, so the generated code did not compile. Previously unreachable; golden tests
  cannot catch it since they never compile the output.
- go-client skipped the encoding file for service-less protos, but the message carrying the
  annotation routinely lives in a pure types file imported by the RPC's file. Moved
  generateInt64EncodingFile above the services guard, matching go-http.
- go-client's transitive UnmarshalJSONSebuf had no repeated branch (its marshal side did), so it
  decoded a JSON array into a single message.

Widening detection widens the set of messages that can collide with another MarshalJSON-owning
annotation, which previously surfaced as a duplicate-method compile error.
checkInt64WrapperMarshalJSONConflict now rejects those at generation time with an actionable
message. Note this makes generation newly fail for such combinations.

Tests: cross-file, depth>1, mixed direct+nested, and map-only-path proto fixtures across both Go
generators; conflict fixtures driven through both in-process; and an integration test that
compiles the generated code and asserts the actual JSON bytes, round-trips them, and pins a
control that raw protojson still quotes the value. TestGoGeneratorsProduceIdenticalInt64Encoding
now compares five goldens instead of one -- comparing only the direct-field golden is what let
the two generators drift apart on the wrapper emitters.

No pre-existing golden changed.

Fixes #217

v0.22.0

Toggle v0.22.0's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
fix(codegen): reject non-scalar query and path parameters at generati…

…on time (#218)

A singular message-kind field annotated with (sebuf.http.query) was mishandled by
every generator: annotations.GetQueryParams returned it with FieldKind "message"
and each consumer's kind switch fell through to a string-shaped default.

  go-client  emitted `if req.X != ""` on a *Msg -> did not compile
  py-client  emitted `req.x != 0` + str(req.x) -> sent the dataclass repr
  ts-client  emitted String(req.x)             -> sent "[object Object]"
  ts-server  assigned a string into a message-typed property
  go-http    returned a runtime 400 "unsupported field type: message"
  openapiv3  documented `type: string`, a contract nothing implemented

Rather than inventing a wire-format rule that flattens single-scalar wrapper
messages, reject these types at generation time. Rejection is reversible, it
aligns query params with the stance isPathParamCompatible already took for path
params, and it breaks no working code -- all six consumers were already broken.

Rejects message, group and bytes kinds; maps report MessageKind and are covered
by the same predicate. Also rejects repeated fields bound to path variables: a
path variable matches one URL segment, and accepting one made the generated Go
server panic ("type mismatch: cannot convert string to list") when bindPathParams
called reflectMsg.Set on a list field. Repeated scalars remain valid query params.

internal/annotations/url_params.go is the single source of truth:
IsURLParamKindCompatible (kind), ValidatePathParamField (kind + cardinality) and
ValidateFileURLParams (per-file entry point). httpgen routes it through its
existing aggregating ValidateMethodConfig; the other five call it before emitting
anything, so an invalid proto never produces partial output. httpgen's local
isPathParamCompatible and findFieldByProtoName are deleted in favour of the
shared helpers.

A shared helper alone does not prevent drift -- ValidateTimestampFormatAnnotation
is shared too and only two of six generators ever wired it in. internal/urlparamtest
is the guard: one set of fixtures driven through all six generators, asserting
they reject and accept identically.

Verified: no golden file changes in any generator, and the full six-plugin by
six-fixture matrix exercised end to end through protoc.

The typed-ID pattern (UserClientID { string value = 1 }) is no longer valid as a
query or path param; use a scalar field with (buf.validate.field).string.uuid.

BREAKING: query annotations are not gated by HTTP method in httpgen or openapiv3,
so a (sebuf.http.query) sitting inertly on a message field in a POST request
compiled before and now fails generation. Warrants a minor version bump.

Closes #216
Refs #219

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

v0.21.1

Toggle v0.21.1's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
fix(enum): apply enum_value custom strings through nested messages (t…

…ransitive) (#215)

* fix(enum): apply enum_value custom strings through nested messages (transitive)

v0.21.0 only patched enums on the directly-marshaled message. When an RPC returns
a wrapper whose custom enums live on nested/repeated child messages (e.g.
GetEasyOptionsResponse.data[].risk_level and data[].contract.type), protojson
serializes the children itself and never invokes their MarshalJSONSebuf, so the
raw proto names leaked (RISK_LEVEL_LOW, OPTION_TYPE_CALL).

The enum-field marshaler is now unified and recursive: each message that directly
holds custom enums OR nests (at any depth) a message that does gets one
MarshalJSONSebuf/UnmarshalJSONSebuf that both patches its direct enum fields and
re-serializes nested singular/repeated message fields through the child's
marshaler (mirroring the int64 wrapper delegation). Custom strings now propagate
through the whole message tree, under default and UseProtoNames output.

Guards: nested custom enums inside map<_, message> values and cross-package
direct enums fail loudly (rather than silently leaking); combining with another
MarshalJSON-generating feature (including int64 nested wrappers) still fails fast.

Adds a three-level golden fixture (enum_nested.proto) plus an unannotated enum to
verify it is left untouched, and rebuilds examples/enum-encoding to cover the full
matrix: annotated vs unannotated x direct vs nested, validated end-to-end.
Verified against the real sarwa-co/contracts protos.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore: gofmt example main.go

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(enum): patch both JSON keys for nested message fields (UseProtoNames)

Codex review: direct enum fields already patched both the camelCase JSON name and
the proto snake_case name, but the nested message re-serialization (mirrored from
the int64 wrapper) keyed only on JSONName(). Under UseProtoNames, protojson emits a
multi-word nested field under its snake_case name; the generated code wrote the
patched child under the camelCase key instead, leaving the snake_case field with raw
proto enum names and adding a duplicate key. On unmarshal, snake_case nested fields
were skipped entirely.

Nested marshal/unmarshal now iterate the same key list as direct enum fields and
update whichever key is present. The golden fixture and example use multi-word nested
field names (item_list/lead_item/item_group, option_suggestions/options_contract) so
this path is regression-tested and asserted end-to-end (including no duplicate
camelCase key under UseProtoNames).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

v0.21.0

Toggle v0.21.0's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
fix(enum): apply enum_value custom strings on Go server & client JSON (…

…#214)

* fix(enum): apply enum_value custom strings on Go server & client JSON

The OpenAPI generator (and TS/Python clients) honor (sebuf.http.enum_value)
and emit "low"/"medium"/"high", but the generated Go HTTP server emitted the
raw proto names ("RISK_LEVEL_LOW"). Both Go surfaces serialize at the message
level with protojson, which never invokes the Go enum type's MarshalJSON — so
the enum-type marshalers in *_enum_encoding.pb.go are dead code server-side.

Add a message-level marshaler (*_enum_field_encoding.pb.go) for any message
with a custom-enum field. It reuses the existing xToJSON/xFromJSON lookup maps
to rewrite enum fields between proto value names and custom strings, in both
directions (MarshalJSONSebuf emits "low"; UnmarshalJSONSebuf accepts "low"),
across all shapes: singular, proto3 optional, repeated, and map<_, enum>.

Because a Go type can own only one MarshalJSON, register enum_value in the
existing fail-fast conflict web (flatten, oneof) plus a new
checkEnumMarshalJSONConflict, so combining a custom enum with another
JSON-mapping annotation errors clearly instead of emitting duplicate methods.

Mirrored in clientgen for Go-client request parity. Adds examples/enum-encoding
with an end-to-end test proving the wire format. Also regenerates the
TimestampFormat OpenAPI golden, which was already stale on current deps from a
recent protobuf bump (unrelated to this change).

Follow-ups tracked in #213 (transitive nesting, generator composition,
dual-plugin file collision, cross-package enums).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(lint): suppress dupl on generated enum marshaler helpers

golangci-lint flags the message-level marshaler/unmarshaler generators as
duplicates of the flatten/timestamp equivalents. Add //nolint:dupl where dupl
actually fires (matching the existing bytes_encoding.go convention), placed
asymmetrically to avoid an unused-directive nolintlint error.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(enum): handle UseProtoNames keys and fail loudly on cross-package enums

Addresses Codex review feedback on the enum_value marshaler:

1. UseProtoNames regression: the patcher keyed only on the camelCase JSON name,
   so a server/client configured with protojson UseProtoNames (snake_case keys)
   left enum fields unpatched and leaked raw proto names. Marshal/unmarshal now
   patch both the JSON name and the proto name key.

2. Cross-package enums were silently skipped (silent wrong output). Generation
   now fails loudly via validateEnumFieldEncoding when a custom-enum field
   references an enum from another Go package, since the marshaler relies on that
   package's private lookup maps. Full cross-package support remains tracked in #213.

Adds a UseProtoNames end-to-end assertion to examples/enum-encoding and updates
the consistency test for the two-key patch shape.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

v0.20.0

Toggle v0.20.0's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
fix(openapiv3): regenerate TimestampFormatService goldens to match re…

…al protoc output (#193)

v0.19.0

Toggle v0.19.0's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
fix(ts-client): name request param "req" for empty-body RPCs; lock em…

…pty-request consistency across generators (#192)

* feat(tsclientgen): add support for empty request bodies in TypeScript client generation

- Introduced a new method `requestParamName` in `rpcMethodConfig` to determine the request parameter name based on the presence of body, path, or query parameters.
- Updated `generateRPCMethod` and `generateSSERPCMethod` to utilize the new method for determining the request parameter name.
- Added a new golden test case for handling empty request bodies, ensuring the generated TypeScript client correctly names the request parameter as "req" when applicable.
- Created a new proto file and corresponding TypeScript client file to validate the changes.

* test(generators): cover empty request bodies consistently across generators

Add a shared `empty_request_body` fixture (an empty-field POST plus a
no-parameter GET) as a golden test case across every generator —
go-client, go-http, py-client, ts-client, ts-server, and openapiv3 — so
all clients and the OpenAPI doc stay consistent with the server for
requests whose message has no fields: a `{}` body is sent/expected on
POST, and no body on GET.

Also add an in-process unit test for `requestParamName`, covering all
branches (body / path / query / none). The generator golden tests invoke
protoc as a subprocess, which coverage tooling cannot observe, so this
unit test gives real coverage of the request-parameter naming logic.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>