Tags: SebastienMelki/sebuf
Tags
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>
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
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>
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>
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>
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>
PreviousNext