You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Filed by Claude (via Claude Code) on behalf of @SebastienMelki.
Bug
protoc-gen-openapiv3 has two independent proto-type → OpenAPI-schema mappers, and path/query parameters go through the impoverished one. The result is that the same field is documented correctly inside components.schemas and incorrectly in the parameters block of the operation that actually uses it.
Everything extractValidationConstraints contributes — enum (from string.in), format, pattern, minLength/maxLength, numeric bounds, const — is silently dropped on parameters. Enum-typed fields and int64_encoding are mishandled too.
On a GET, ListItemsRequest is never $ref'd by anything, so the correct schema is dead weight in the document and the wrong one is the only thing a consumer reads.
Root cause
internal/openapiv3/generator.go — both buildPathParameters (~L814) and buildQueryParameters (~L835) call g.createFieldSchema, which delegates to createScalarFieldSchema (~L1076):
This is a parallel reimplementation of the real converter in internal/openapiv3/types.go, which does call extractValidationConstraints at three sites (L43, L225, L323) and has proper handling for enums, maps, timestamps and well-known types.
Three distinct defects follow from the duplication:
No validation constraints.extractValidationConstraints is never reached, so string.in → enum (validation.go:120), string.email/uuid/uri → format, pattern, and the int32/int64/float/double gte/lte bounds all vanish.
int64_encoding is ignored. The int64 case hardcodes type: string. A message annotated int64_encoding = NUMBER correctly emits type: integer in components.schemas but type: string as a parameter — the two halves of the same document contradict each other.
Why this is a problem
The parameters block is the part consumers actually use. For GET/DELETE there is no request body, so it is the only description of the input. Being wrong there is worse than being wrong in an unreferenced component.
Self-contradictory documents. Shipping both a correct and an incorrect description of one field means any tool's behaviour depends on which half it reads. Validation middleware built from parameters accepts input the server rejects.
Lost path-parameter constraints are a routing concern, not just docs. Where a {placeholder} is constrained to a small set of values, the document presents it as a free-form string, so a generated client will happily build unroutable URLs.
It makes buf.validate look unsupported. The constraints are implemented and are enforced at runtime — they just do not reach the one place most consumers look.
Note on the enum-in-query workaround
Because sebuf cannot bind a proto enum to a query parameter, the documented workaround is to type such fields as string + buf.validate.string.in. That workaround is exactly what this bug erases — so for query parameters the allowed value set is unavailable both ways.
Suggested fix
Delete createScalarFieldSchema / createFieldSchema from generator.go and have buildPathParameters / buildQueryParameters call the same converter components.schemas uses in types.go. That fixes all three defects at once and removes the class of bug where the two mappers drift apart again.
If a full unification is too large for one change, the minimum viable fix is to call extractValidationConstraints(field, schema) before the return in createScalarFieldSchema, and add an "enum" case — but the duplication would remain, and this is at least the third issue (#161, #216) rooted in the parameter path diverging from the schema path.
A regression test asserting that every field appearing in both parameters and components.schemas has an identical schema in both places would lock this down.
Bug
protoc-gen-openapiv3has two independent proto-type → OpenAPI-schema mappers, and path/query parameters go through the impoverished one. The result is that the same field is documented correctly insidecomponents.schemasand incorrectly in theparametersblock of the operation that actually uses it.Everything
extractValidationConstraintscontributes —enum(fromstring.in),format,pattern,minLength/maxLength, numeric bounds,const— is silently dropped on parameters. Enum-typed fields andint64_encodingare mishandled too.Reproduction
Generated — note the same field documented two different ways:
On a
GET,ListItemsRequestis never$ref'd by anything, so the correct schema is dead weight in the document and the wrong one is the only thing a consumer reads.Root cause
internal/openapiv3/generator.go— bothbuildPathParameters(~L814) andbuildQueryParameters(~L835) callg.createFieldSchema, which delegates tocreateScalarFieldSchema(~L1076):This is a parallel reimplementation of the real converter in
internal/openapiv3/types.go, which does callextractValidationConstraintsat three sites (L43, L225, L323) and has proper handling for enums, maps, timestamps and well-known types.Three distinct defects follow from the duplication:
extractValidationConstraintsis never reached, sostring.in→enum(validation.go:120),string.email/uuid/uri→format,pattern, and the int32/int64/float/doublegte/ltebounds all vanish.default:.protoreflect's kind string for an enum is"enum", which matches no case, so an enum parameter is emitted as a baretype: stringwith no values — theenum_valuecustom strings never appear. (Related to codegen: enum_value custom strings are ignored in URL parameter encoding, so the TS client cannot reach the Go server #219, but a different code path: that issue is about client-side URL encoding; this is the document.)int64_encodingis ignored. Theint64case hardcodestype: string. A message annotatedint64_encoding = NUMBERcorrectly emitstype: integerincomponents.schemasbuttype: stringas a parameter — the two halves of the same document contradict each other.Why this is a problem
GET/DELETEthere is no request body, so it is the only description of the input. Being wrong there is worse than being wrong in an unreferenced component.parametersaccepts input the server rejects.{placeholder}is constrained to a small set of values, the document presents it as a free-form string, so a generated client will happily build unroutable URLs.buf.validatelook unsupported. The constraints are implemented and are enforced at runtime — they just do not reach the one place most consumers look.Note on the enum-in-query workaround
Because sebuf cannot bind a proto enum to a query parameter, the documented workaround is to type such fields as
string+buf.validate.string.in. That workaround is exactly what this bug erases — so for query parameters the allowed value set is unavailable both ways.Suggested fix
Delete
createScalarFieldSchema/createFieldSchemafromgenerator.goand havebuildPathParameters/buildQueryParameterscall the same convertercomponents.schemasuses intypes.go. That fixes all three defects at once and removes the class of bug where the two mappers drift apart again.If a full unification is too large for one change, the minimum viable fix is to call
extractValidationConstraints(field, schema)before thereturnincreateScalarFieldSchema, and add an"enum"case — but the duplication would remain, and this is at least the third issue (#161, #216) rooted in the parameter path diverging from the schema path.A regression test asserting that every field appearing in both
parametersandcomponents.schemashas an identical schema in both places would lock this down.