From 9498a065104ea8476eeab290605e051b9ee5bfd5 Mon Sep 17 00:00:00 2001 From: Alex McKinney Date: Mon, 16 Mar 2026 09:51:55 -0400 Subject: [PATCH] feat(openapi): Preserve nested descriptor mapping --- .../examples/tests/enumoptions/message.proto | 9 +++++++++ .../examples/tests/enumoptions/openapi.yaml | 16 ++++++++++++++++ .../enumoptions/openapi_default_response.yaml | 16 ++++++++++++++++ .../enumoptions/openapi_flatten_oneofs.yaml | 16 ++++++++++++++++ .../enumoptions/openapi_fq_schema_naming.yaml | 16 ++++++++++++++++ .../examples/tests/enumoptions/openapi_json.yaml | 16 ++++++++++++++++ .../tests/enumoptions/openapi_string_enum.yaml | 16 ++++++++++++++++ .../examples/tests/protobuftypes/openapi.yaml | 6 +++--- .../protobuftypes/openapi_default_response.yaml | 6 +++--- .../protobuftypes/openapi_flatten_oneofs.yaml | 6 +++--- .../protobuftypes/openapi_fq_schema_naming.yaml | 6 +++--- .../tests/protobuftypes/openapi_json.yaml | 6 +++--- .../tests/protobuftypes/openapi_string_enum.yaml | 6 +++--- cmd/protoc-gen-openapi/generator/generator.go | 4 ++-- cmd/protoc-gen-openapi/generator/reflector.go | 4 ++++ .../generator/wellknown/schemas.go | 16 ++++++++++++++-- 16 files changed, 143 insertions(+), 22 deletions(-) diff --git a/cmd/protoc-gen-openapi/examples/tests/enumoptions/message.proto b/cmd/protoc-gen-openapi/examples/tests/enumoptions/message.proto index 22c7ff0e..f958f06f 100644 --- a/cmd/protoc-gen-openapi/examples/tests/enumoptions/message.proto +++ b/cmd/protoc-gen-openapi/examples/tests/enumoptions/message.proto @@ -32,6 +32,15 @@ service Messaging { } message Message { Kind kind = 1; + + // Nested enum inside a message. + enum Priority { + PRIORITY_UNSPECIFIED = 0; + LOW = 1; + MEDIUM = 2; + HIGH = 3; + } + Priority priority = 2; } enum Kind { UNKNOWN_KIND = 0; diff --git a/cmd/protoc-gen-openapi/examples/tests/enumoptions/openapi.yaml b/cmd/protoc-gen-openapi/examples/tests/enumoptions/openapi.yaml index 3219d8fd..fa0b8c38 100644 --- a/cmd/protoc-gen-openapi/examples/tests/enumoptions/openapi.yaml +++ b/cmd/protoc-gen-openapi/examples/tests/enumoptions/openapi.yaml @@ -22,6 +22,10 @@ paths: in: query schema: $ref: '#/components/schemas/Kind' + - name: priority + in: query + schema: + $ref: '#/components/schemas/Message_Priority' requestBody: content: application/json: {} @@ -85,7 +89,19 @@ components: properties: kind: $ref: '#/components/schemas/Kind' + priority: + $ref: '#/components/schemas/Message_Priority' x-fern-type-name: Message source: tests/enumoptions/message.proto + Message_Priority: + enum: + - PRIORITY_UNSPECIFIED + - LOW + - MEDIUM + - HIGH + type: string + format: enum + x-fern-type-name: Message_Priority + source: tests/enumoptions/message.proto tags: - name: Messaging diff --git a/cmd/protoc-gen-openapi/examples/tests/enumoptions/openapi_default_response.yaml b/cmd/protoc-gen-openapi/examples/tests/enumoptions/openapi_default_response.yaml index 3219d8fd..fa0b8c38 100644 --- a/cmd/protoc-gen-openapi/examples/tests/enumoptions/openapi_default_response.yaml +++ b/cmd/protoc-gen-openapi/examples/tests/enumoptions/openapi_default_response.yaml @@ -22,6 +22,10 @@ paths: in: query schema: $ref: '#/components/schemas/Kind' + - name: priority + in: query + schema: + $ref: '#/components/schemas/Message_Priority' requestBody: content: application/json: {} @@ -85,7 +89,19 @@ components: properties: kind: $ref: '#/components/schemas/Kind' + priority: + $ref: '#/components/schemas/Message_Priority' x-fern-type-name: Message source: tests/enumoptions/message.proto + Message_Priority: + enum: + - PRIORITY_UNSPECIFIED + - LOW + - MEDIUM + - HIGH + type: string + format: enum + x-fern-type-name: Message_Priority + source: tests/enumoptions/message.proto tags: - name: Messaging diff --git a/cmd/protoc-gen-openapi/examples/tests/enumoptions/openapi_flatten_oneofs.yaml b/cmd/protoc-gen-openapi/examples/tests/enumoptions/openapi_flatten_oneofs.yaml index 3219d8fd..fa0b8c38 100644 --- a/cmd/protoc-gen-openapi/examples/tests/enumoptions/openapi_flatten_oneofs.yaml +++ b/cmd/protoc-gen-openapi/examples/tests/enumoptions/openapi_flatten_oneofs.yaml @@ -22,6 +22,10 @@ paths: in: query schema: $ref: '#/components/schemas/Kind' + - name: priority + in: query + schema: + $ref: '#/components/schemas/Message_Priority' requestBody: content: application/json: {} @@ -85,7 +89,19 @@ components: properties: kind: $ref: '#/components/schemas/Kind' + priority: + $ref: '#/components/schemas/Message_Priority' x-fern-type-name: Message source: tests/enumoptions/message.proto + Message_Priority: + enum: + - PRIORITY_UNSPECIFIED + - LOW + - MEDIUM + - HIGH + type: string + format: enum + x-fern-type-name: Message_Priority + source: tests/enumoptions/message.proto tags: - name: Messaging diff --git a/cmd/protoc-gen-openapi/examples/tests/enumoptions/openapi_fq_schema_naming.yaml b/cmd/protoc-gen-openapi/examples/tests/enumoptions/openapi_fq_schema_naming.yaml index 3a107150..4a795ac1 100644 --- a/cmd/protoc-gen-openapi/examples/tests/enumoptions/openapi_fq_schema_naming.yaml +++ b/cmd/protoc-gen-openapi/examples/tests/enumoptions/openapi_fq_schema_naming.yaml @@ -22,6 +22,10 @@ paths: in: query schema: $ref: '#/components/schemas/Kind' + - name: priority + in: query + schema: + $ref: '#/components/schemas/Message_Priority' requestBody: content: application/json: {} @@ -65,6 +69,16 @@ components: format: enum x-fern-type-name: Kind source: tests/enumoptions/message.proto + Message_Priority: + enum: + - PRIORITY_UNSPECIFIED + - LOW + - MEDIUM + - HIGH + type: string + format: enum + x-fern-type-name: Message_Priority + source: tests/enumoptions/message.proto google.protobuf.Any: type: object properties: @@ -97,6 +111,8 @@ components: properties: kind: $ref: '#/components/schemas/Kind' + priority: + $ref: '#/components/schemas/Message_Priority' x-fern-type-name: tests.enumoptions.message.v1.Message source: tests/enumoptions/message.proto tags: diff --git a/cmd/protoc-gen-openapi/examples/tests/enumoptions/openapi_json.yaml b/cmd/protoc-gen-openapi/examples/tests/enumoptions/openapi_json.yaml index 44b565c5..766a29b4 100644 --- a/cmd/protoc-gen-openapi/examples/tests/enumoptions/openapi_json.yaml +++ b/cmd/protoc-gen-openapi/examples/tests/enumoptions/openapi_json.yaml @@ -22,6 +22,10 @@ paths: in: query schema: $ref: '#/components/schemas/Kind' + - name: priority + in: query + schema: + $ref: '#/components/schemas/Message_Priority' requestBody: content: application/json: {} @@ -85,7 +89,19 @@ components: properties: kind: $ref: '#/components/schemas/Kind' + priority: + $ref: '#/components/schemas/Message_Priority' x-fern-type-name: Message source: tests/enumoptions/message.proto + Message_Priority: + enum: + - PRIORITY_UNSPECIFIED + - LOW + - MEDIUM + - HIGH + type: string + format: enum + x-fern-type-name: Message_Priority + source: tests/enumoptions/message.proto tags: - name: Messaging diff --git a/cmd/protoc-gen-openapi/examples/tests/enumoptions/openapi_string_enum.yaml b/cmd/protoc-gen-openapi/examples/tests/enumoptions/openapi_string_enum.yaml index 3219d8fd..fa0b8c38 100644 --- a/cmd/protoc-gen-openapi/examples/tests/enumoptions/openapi_string_enum.yaml +++ b/cmd/protoc-gen-openapi/examples/tests/enumoptions/openapi_string_enum.yaml @@ -22,6 +22,10 @@ paths: in: query schema: $ref: '#/components/schemas/Kind' + - name: priority + in: query + schema: + $ref: '#/components/schemas/Message_Priority' requestBody: content: application/json: {} @@ -85,7 +89,19 @@ components: properties: kind: $ref: '#/components/schemas/Kind' + priority: + $ref: '#/components/schemas/Message_Priority' x-fern-type-name: Message source: tests/enumoptions/message.proto + Message_Priority: + enum: + - PRIORITY_UNSPECIFIED + - LOW + - MEDIUM + - HIGH + type: string + format: enum + x-fern-type-name: Message_Priority + source: tests/enumoptions/message.proto tags: - name: Messaging diff --git a/cmd/protoc-gen-openapi/examples/tests/protobuftypes/openapi.yaml b/cmd/protoc-gen-openapi/examples/tests/protobuftypes/openapi.yaml index ffab2e62..affcfb5c 100644 --- a/cmd/protoc-gen-openapi/examples/tests/protobuftypes/openapi.yaml +++ b/cmd/protoc-gen-openapi/examples/tests/protobuftypes/openapi.yaml @@ -145,7 +145,7 @@ paths: in: query schema: type: string - format: bytes + format: byte x-fern-encoding: proto: type: google.protobuf.BytesValue @@ -385,7 +385,7 @@ paths: in: query schema: type: string - format: bytes + format: byte x-fern-encoding: proto: type: google.protobuf.BytesValue @@ -635,7 +635,7 @@ components: type: google.protobuf.BoolValue bytes_value_type: type: string - format: bytes + format: byte x-fern-encoding: proto: type: google.protobuf.BytesValue diff --git a/cmd/protoc-gen-openapi/examples/tests/protobuftypes/openapi_default_response.yaml b/cmd/protoc-gen-openapi/examples/tests/protobuftypes/openapi_default_response.yaml index ffab2e62..affcfb5c 100644 --- a/cmd/protoc-gen-openapi/examples/tests/protobuftypes/openapi_default_response.yaml +++ b/cmd/protoc-gen-openapi/examples/tests/protobuftypes/openapi_default_response.yaml @@ -145,7 +145,7 @@ paths: in: query schema: type: string - format: bytes + format: byte x-fern-encoding: proto: type: google.protobuf.BytesValue @@ -385,7 +385,7 @@ paths: in: query schema: type: string - format: bytes + format: byte x-fern-encoding: proto: type: google.protobuf.BytesValue @@ -635,7 +635,7 @@ components: type: google.protobuf.BoolValue bytes_value_type: type: string - format: bytes + format: byte x-fern-encoding: proto: type: google.protobuf.BytesValue diff --git a/cmd/protoc-gen-openapi/examples/tests/protobuftypes/openapi_flatten_oneofs.yaml b/cmd/protoc-gen-openapi/examples/tests/protobuftypes/openapi_flatten_oneofs.yaml index ffab2e62..affcfb5c 100644 --- a/cmd/protoc-gen-openapi/examples/tests/protobuftypes/openapi_flatten_oneofs.yaml +++ b/cmd/protoc-gen-openapi/examples/tests/protobuftypes/openapi_flatten_oneofs.yaml @@ -145,7 +145,7 @@ paths: in: query schema: type: string - format: bytes + format: byte x-fern-encoding: proto: type: google.protobuf.BytesValue @@ -385,7 +385,7 @@ paths: in: query schema: type: string - format: bytes + format: byte x-fern-encoding: proto: type: google.protobuf.BytesValue @@ -635,7 +635,7 @@ components: type: google.protobuf.BoolValue bytes_value_type: type: string - format: bytes + format: byte x-fern-encoding: proto: type: google.protobuf.BytesValue diff --git a/cmd/protoc-gen-openapi/examples/tests/protobuftypes/openapi_fq_schema_naming.yaml b/cmd/protoc-gen-openapi/examples/tests/protobuftypes/openapi_fq_schema_naming.yaml index 4ba46ca3..96c68ec9 100644 --- a/cmd/protoc-gen-openapi/examples/tests/protobuftypes/openapi_fq_schema_naming.yaml +++ b/cmd/protoc-gen-openapi/examples/tests/protobuftypes/openapi_fq_schema_naming.yaml @@ -145,7 +145,7 @@ paths: in: query schema: type: string - format: bytes + format: byte x-fern-encoding: proto: type: google.protobuf.BytesValue @@ -385,7 +385,7 @@ paths: in: query schema: type: string - format: bytes + format: byte x-fern-encoding: proto: type: google.protobuf.BytesValue @@ -656,7 +656,7 @@ components: type: google.protobuf.BoolValue bytes_value_type: type: string - format: bytes + format: byte x-fern-encoding: proto: type: google.protobuf.BytesValue diff --git a/cmd/protoc-gen-openapi/examples/tests/protobuftypes/openapi_json.yaml b/cmd/protoc-gen-openapi/examples/tests/protobuftypes/openapi_json.yaml index 1fe1c8dc..8bfc00ba 100644 --- a/cmd/protoc-gen-openapi/examples/tests/protobuftypes/openapi_json.yaml +++ b/cmd/protoc-gen-openapi/examples/tests/protobuftypes/openapi_json.yaml @@ -145,7 +145,7 @@ paths: in: query schema: type: string - format: bytes + format: byte x-fern-encoding: proto: type: google.protobuf.BytesValue @@ -385,7 +385,7 @@ paths: in: query schema: type: string - format: bytes + format: byte x-fern-encoding: proto: type: google.protobuf.BytesValue @@ -635,7 +635,7 @@ components: type: google.protobuf.BoolValue bytes_value_type: type: string - format: bytes + format: byte x-fern-encoding: proto: type: google.protobuf.BytesValue diff --git a/cmd/protoc-gen-openapi/examples/tests/protobuftypes/openapi_string_enum.yaml b/cmd/protoc-gen-openapi/examples/tests/protobuftypes/openapi_string_enum.yaml index ffab2e62..affcfb5c 100644 --- a/cmd/protoc-gen-openapi/examples/tests/protobuftypes/openapi_string_enum.yaml +++ b/cmd/protoc-gen-openapi/examples/tests/protobuftypes/openapi_string_enum.yaml @@ -145,7 +145,7 @@ paths: in: query schema: type: string - format: bytes + format: byte x-fern-encoding: proto: type: google.protobuf.BytesValue @@ -385,7 +385,7 @@ paths: in: query schema: type: string - format: bytes + format: byte x-fern-encoding: proto: type: google.protobuf.BytesValue @@ -635,7 +635,7 @@ components: type: google.protobuf.BoolValue bytes_value_type: type: string - format: bytes + format: byte x-fern-encoding: proto: type: google.protobuf.BytesValue diff --git a/cmd/protoc-gen-openapi/generator/generator.go b/cmd/protoc-gen-openapi/generator/generator.go index fa0a9fe7..eccd61a8 100644 --- a/cmd/protoc-gen-openapi/generator/generator.go +++ b/cmd/protoc-gen-openapi/generator/generator.go @@ -856,7 +856,7 @@ func (g *OpenAPIv3Generator) addWellKnownTypeSchemaToDocumentV3(d *v3.Document, // addSchemaForEnumsToDocumentV3 adds enum schemas to the document func (g *OpenAPIv3Generator) addSchemaForEnumsToDocumentV3(d *v3.Document, enums []*protogen.Enum, filename string) { for _, enum := range enums { - enumName := string(enum.Desc.Name()) + enumName := g.reflect.getEnumName(enum.Desc) enumSchema := g.createEnumSchema(enumName, enum.Desc.Values()) g.addSchemaToDocumentV3(d, &v3.NamedSchemaOrReference{ Name: enumName, @@ -883,7 +883,7 @@ func (g *OpenAPIv3Generator) addSchemasForMessagesToDocumentV3(d *v3.Document, m // Skip map entries and enums since they are handled separately if len(message.Enums) > 0 { for _, enum := range message.Enums { - enumName := string(enum.Desc.Name()) + enumName := g.reflect.getEnumName(enum.Desc) enumSchema := g.createEnumSchema(enumName, enum.Desc.Values()) g.addSchemaToDocumentV3(d, &v3.NamedSchemaOrReference{ Name: enumName, diff --git a/cmd/protoc-gen-openapi/generator/reflector.go b/cmd/protoc-gen-openapi/generator/reflector.go index 13d3638a..703cffed 100644 --- a/cmd/protoc-gen-openapi/generator/reflector.go +++ b/cmd/protoc-gen-openapi/generator/reflector.go @@ -57,6 +57,10 @@ func (r *OpenAPIv3Reflector) getMessageName(message protoreflect.MessageDescript return prefix + string(message.Name()) } +func (r *OpenAPIv3Reflector) getEnumName(enum protoreflect.EnumDescriptor) string { + return wk.GetEnumName(enum) +} + func (r *OpenAPIv3Reflector) formatMessageName(message protoreflect.MessageDescriptor) string { typeName := r.fullMessageTypeName(message) diff --git a/cmd/protoc-gen-openapi/generator/wellknown/schemas.go b/cmd/protoc-gen-openapi/generator/wellknown/schemas.go index 8b855601..e198df75 100644 --- a/cmd/protoc-gen-openapi/generator/wellknown/schemas.go +++ b/cmd/protoc-gen-openapi/generator/wellknown/schemas.go @@ -65,7 +65,7 @@ func NewBytesSchema() *v3.SchemaOrReference { Oneof: &v3.SchemaOrReference_Schema{ Schema: &v3.Schema{ Type: "string", - Format: "bytes", + Format: "byte", SpecificationExtension: []*v3.NamedAny{ { Name: "x-fern-encoding", @@ -127,12 +127,24 @@ func NewEnumSchema(enum_type *string, field protoreflect.FieldDescriptor) *v3.Sc return &v3.SchemaOrReference{ Oneof: &v3.SchemaOrReference_Reference{ Reference: &v3.Reference{ - XRef: "#/components/schemas/" + string(field.Enum().Name()), + XRef: "#/components/schemas/" + GetEnumName(field.Enum()), }, }, } } +// GetEnumName returns the schema name for an enum, including the parent +// message name prefix for nested enums. For example, a nested enum +// `message InvoiceBundle { enum Status { ... } }` returns "InvoiceBundle_Status". +func GetEnumName(enum protoreflect.EnumDescriptor) string { + prefix := "" + parent := enum.Parent() + if _, ok := parent.(protoreflect.MessageDescriptor); ok { + prefix = string(parent.Name()) + "_" + } + return prefix + string(enum.Name()) +} + func NewListSchema(item_schema *v3.SchemaOrReference) *v3.SchemaOrReference { return &v3.SchemaOrReference{ Oneof: &v3.SchemaOrReference_Schema{