Skip to content

Commit 8a07810

Browse files
authored
feat(openapi): Add include_all_methods plugin option (#21)
1 parent 334199f commit 8a07810

5 files changed

Lines changed: 238 additions & 37 deletions

File tree

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
# Generated with protoc-gen-openapi
2+
# https://github.com/fern-api/protoc-gen-openapi/tree/master/cmd/protoc-gen-openapi
3+
4+
openapi: 3.0.3
5+
info:
6+
title: ""
7+
version: 0.0.1
8+
paths:
9+
/tests.noannotations.message.v1.Messaging2/UpdateMessage:
10+
post:
11+
tags:
12+
- Messaging2
13+
operationId: Messaging2_UpdateMessage
14+
requestBody:
15+
content:
16+
application/json:
17+
schema:
18+
$ref: '#/components/schemas/Message'
19+
required: true
20+
responses:
21+
"200":
22+
description: OK
23+
content:
24+
application/json:
25+
schema:
26+
$ref: '#/components/schemas/Message'
27+
default:
28+
description: Default error response
29+
content:
30+
application/json:
31+
schema:
32+
$ref: '#/components/schemas/GoogleRpcStatus'
33+
x-fern-sdk-group-name: Messaging2
34+
x-fern-sdk-method-name: UpdateMessage
35+
/v1/messages/{message_id}:
36+
patch:
37+
tags:
38+
- Messaging1
39+
operationId: Messaging1_UpdateMessage
40+
parameters:
41+
- name: message_id
42+
in: path
43+
required: true
44+
schema:
45+
type: string
46+
requestBody:
47+
content:
48+
application/json:
49+
schema:
50+
$ref: '#/components/schemas/Message'
51+
required: true
52+
responses:
53+
"200":
54+
description: OK
55+
content:
56+
application/json:
57+
schema:
58+
$ref: '#/components/schemas/Message'
59+
default:
60+
description: Default error response
61+
content:
62+
application/json:
63+
schema:
64+
$ref: '#/components/schemas/GoogleRpcStatus'
65+
x-fern-sdk-group-name: Messaging1
66+
x-fern-sdk-method-name: UpdateMessage
67+
components:
68+
schemas:
69+
GoogleProtobufAny:
70+
type: object
71+
properties:
72+
'@type':
73+
type: string
74+
description: The type of the serialized message.
75+
additionalProperties: true
76+
description: Contains an arbitrary serialized message along with a @type that describes the type of the serialized message.
77+
x-fern-encoding:
78+
proto:
79+
type: google.protobuf.Any
80+
GoogleRpcStatus:
81+
type: object
82+
properties:
83+
code:
84+
type: integer
85+
description: The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code].
86+
format: int32
87+
message:
88+
type: string
89+
description: A developer-facing error message, which should be in English. Any user-facing error message should be localized and sent in the [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client.
90+
details:
91+
type: array
92+
items:
93+
$ref: '#/components/schemas/GoogleProtobufAny'
94+
description: A list of messages that carry the error details. There is a common set of message types for APIs to use.
95+
description: 'The `Status` type defines a logical error model that is suitable for different programming environments, including REST APIs and RPC APIs. It is used by [gRPC](https://github.com/grpc). Each `Status` message contains three pieces of data: error code, error message, and error details. You can find out more about this error model and how to work with it in the [API Design Guide](https://cloud.google.com/apis/design/errors).'
96+
Message:
97+
type: object
98+
properties:
99+
id:
100+
type: integer
101+
format: int64
102+
x-fern-encoding:
103+
proto:
104+
type: google.protobuf.Int64Value
105+
label:
106+
type: string
107+
x-fern-encoding:
108+
proto:
109+
type: google.protobuf.StringValue
110+
x-fern-type-name: Message
111+
source: tests/noannotations/message.proto
112+
tags:
113+
- name: Messaging1
114+
- name: Messaging2

cmd/protoc-gen-openapi/generator/generator.go

Lines changed: 84 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -37,17 +37,18 @@ import (
3737
)
3838

3939
type Configuration struct {
40-
Version *string
41-
Title *string
42-
Description *string
43-
Naming *string
44-
FQSchemaNaming *bool
45-
EnumType *string
46-
CircularDepth *int
47-
DefaultResponse *bool
48-
OutputMode *string
49-
SourceRoot *string
50-
FlattenOneofs *bool
40+
Version *string
41+
Title *string
42+
Description *string
43+
Naming *string
44+
FQSchemaNaming *bool
45+
EnumType *string
46+
CircularDepth *int
47+
DefaultResponse *bool
48+
OutputMode *string
49+
SourceRoot *string
50+
FlattenOneofs *bool
51+
IncludeAllMethods *bool
5152
}
5253

5354
const (
@@ -740,6 +741,54 @@ func (g *OpenAPIv3Generator) addPathsToDocumentV3(d *v3.Document, services []*pr
740741
rules = append(rules, rule.AdditionalBindings...)
741742
}
742743

744+
// When include_all_methods is enabled and there are no HTTP annotations,
745+
// generate a default POST path using the fully-qualified gRPC method name.
746+
if len(rules) == 0 && g.conf.IncludeAllMethods != nil && *g.conf.IncludeAllMethods {
747+
annotationsCount++
748+
path := "/" + string(service.Desc.FullName()) + "/" + string(method.Desc.Name())
749+
methodName := "POST"
750+
body := "*"
751+
752+
defaultHost := proto.GetExtension(service.Desc.Options(), annotations.E_DefaultHost).(string)
753+
754+
op, path2 := g.buildOperationV3(
755+
d, operationID, service.GoName, comment, defaultHost, path, body, inputMessage, outputMessage)
756+
757+
extFernSummary := proto.GetExtension(method.Desc.Options(), fernoptions.E_Summary)
758+
if extFernSummary != nil {
759+
if summary, ok := extFernSummary.(string); ok && summary != "" {
760+
op.Summary = summary
761+
}
762+
}
763+
764+
op.SpecificationExtension = append(op.SpecificationExtension,
765+
&v3.NamedAny{
766+
Name: "x-fern-sdk-group-name",
767+
Value: &v3.Any{Yaml: service.GoName},
768+
},
769+
&v3.NamedAny{
770+
Name: "x-fern-sdk-method-name",
771+
Value: &v3.Any{Yaml: method.GoName},
772+
},
773+
)
774+
775+
if requestMessageUsageCount[string(inputMessage.Desc.FullName())] == 1 {
776+
op.SpecificationExtension = append(op.SpecificationExtension,
777+
&v3.NamedAny{
778+
Name: "x-fern-request-name",
779+
Value: &v3.Any{Yaml: string(inputMessage.Desc.Name())},
780+
},
781+
)
782+
}
783+
784+
extOperation := proto.GetExtension(method.Desc.Options(), v3.E_Operation)
785+
if extOperation != nil {
786+
proto.Merge(op, extOperation.(*v3.Operation))
787+
}
788+
789+
g.addOperationToDocumentV3(d, op, path2, methodName)
790+
}
791+
743792
for _, rule := range rules {
744793
var path string
745794
var methodName string
@@ -916,15 +965,15 @@ func (g *OpenAPIv3Generator) addSchemasForMessagesToDocumentV3(d *v3.Document, m
916965
}
917966

918967
var required []string
919-
968+
920969
for _, field := range message.Fields {
921970
// Skip fields that are part of an explicit oneOf (unless flattening is enabled).
922971
// Proto3 optional fields create synthetic oneofs that should be
923972
// treated as regular optional fields, not as oneOf variants.
924973
if field.Oneof != nil && !field.Oneof.Desc.IsSynthetic() && !*g.conf.FlattenOneofs {
925974
continue
926975
}
927-
976+
928977
// Get the field description from the comments.
929978
description := g.filterCommentString(field.Comments.Leading)
930979
// Check the field annotations to see if this is a readonly or writeonly field.
@@ -1055,7 +1104,6 @@ func (g *OpenAPIv3Generator) createEnumSchema(enumName string, enumValues protor
10551104
}
10561105
}
10571106

1058-
10591107
// addOneOfFieldsToSchema adds oneOf fields to the schema
10601108
func (g *OpenAPIv3Generator) addOneOfFieldsToSchema(d *v3.Document, oneofs []*protogen.Oneof, schema *v3.Schema, schemaName string, filename string) {
10611109
if oneofs == nil {
@@ -1079,17 +1127,17 @@ func (g *OpenAPIv3Generator) addOneOfFieldsToSchema(d *v3.Document, oneofs []*pr
10791127
if len(schema.Properties.AdditionalProperties) == 0 {
10801128
// Flatten all oneofs to the message level
10811129
allOneOfSchemas := make([]*v3.SchemaOrReference, 0)
1082-
1130+
10831131
for _, oneOfProto := range oneofs {
10841132
oneOfSchemas := make([]*v3.SchemaOrReference, 0, len(oneOfProto.Fields))
1085-
1133+
10861134
for _, fieldProto := range oneOfProto.Fields {
10871135
fieldName := g.reflect.formatFieldName(fieldProto.Desc)
1088-
1136+
10891137
// For google.protobuf.Empty, create a simple object schema
1090-
if fieldProto.Desc.Kind() == protoreflect.MessageKind &&
1091-
g.reflect.fullMessageTypeName(fieldProto.Desc.Message()) == ".google.protobuf.Empty" {
1092-
1138+
if fieldProto.Desc.Kind() == protoreflect.MessageKind &&
1139+
g.reflect.fullMessageTypeName(fieldProto.Desc.Message()) == ".google.protobuf.Empty" {
1140+
10931141
emptySchema := &v3.SchemaOrReference{
10941142
Oneof: &v3.SchemaOrReference_Schema{
10951143
Schema: &v3.Schema{
@@ -1113,11 +1161,11 @@ func (g *OpenAPIv3Generator) addOneOfFieldsToSchema(d *v3.Document, oneofs []*pr
11131161
},
11141162
}
11151163
oneOfSchemas = append(oneOfSchemas, emptySchema)
1116-
1164+
11171165
} else {
11181166
// For other field types, wrap the field schema in an object with the specific property name
11191167
fieldSchema := g.reflect.schemaOrReferenceForField(fieldProto.Desc)
1120-
1168+
11211169
objectSchema := &v3.SchemaOrReference{
11221170
Oneof: &v3.SchemaOrReference_Schema{
11231171
Schema: &v3.Schema{
@@ -1142,11 +1190,11 @@ func (g *OpenAPIv3Generator) addOneOfFieldsToSchema(d *v3.Document, oneofs []*pr
11421190
oneOfSchemas = append(oneOfSchemas, objectSchema)
11431191
}
11441192
}
1145-
1193+
11461194
// Add all schemas from this oneof to the combined list
11471195
allOneOfSchemas = append(allOneOfSchemas, oneOfSchemas...)
11481196
}
1149-
1197+
11501198
// Add null as an option
11511199
nullSchema := &v3.SchemaOrReference{
11521200
Oneof: &v3.SchemaOrReference_Schema{
@@ -1156,27 +1204,27 @@ func (g *OpenAPIv3Generator) addOneOfFieldsToSchema(d *v3.Document, oneofs []*pr
11561204
},
11571205
}
11581206
allOneOfSchemas = append(allOneOfSchemas, nullSchema)
1159-
1207+
11601208
// Replace the schema with the oneOf at the top level
11611209
schema.OneOf = allOneOfSchemas
11621210
schema.Type = ""
11631211
schema.Properties = nil
1164-
1212+
11651213
return
11661214
}
11671215

11681216
// Handle regular oneofs as nested properties when message has other fields
11691217
for _, oneOfProto := range oneofs {
11701218
oneOfFieldName := string(oneOfProto.Desc.Name())
11711219
oneOfSchemas := make([]*v3.SchemaOrReference, 0, len(oneOfProto.Fields))
1172-
1220+
11731221
for _, fieldProto := range oneOfProto.Fields {
11741222
fieldName := g.reflect.formatFieldName(fieldProto.Desc)
1175-
1223+
11761224
// For google.protobuf.Empty, create a simple object schema
1177-
if fieldProto.Desc.Kind() == protoreflect.MessageKind &&
1178-
g.reflect.fullMessageTypeName(fieldProto.Desc.Message()) == ".google.protobuf.Empty" {
1179-
1225+
if fieldProto.Desc.Kind() == protoreflect.MessageKind &&
1226+
g.reflect.fullMessageTypeName(fieldProto.Desc.Message()) == ".google.protobuf.Empty" {
1227+
11801228
emptySchema := &v3.SchemaOrReference{
11811229
Oneof: &v3.SchemaOrReference_Schema{
11821230
Schema: &v3.Schema{
@@ -1185,11 +1233,11 @@ func (g *OpenAPIv3Generator) addOneOfFieldsToSchema(d *v3.Document, oneofs []*pr
11851233
},
11861234
}
11871235
oneOfSchemas = append(oneOfSchemas, emptySchema)
1188-
1236+
11891237
} else {
11901238
// For other field types, wrap the field schema in an object with the specific property name
11911239
fieldSchema := g.reflect.schemaOrReferenceForField(fieldProto.Desc)
1192-
1240+
11931241
objectSchema := &v3.SchemaOrReference{
11941242
Oneof: &v3.SchemaOrReference_Schema{
11951243
Schema: &v3.Schema{
@@ -1214,7 +1262,7 @@ func (g *OpenAPIv3Generator) addOneOfFieldsToSchema(d *v3.Document, oneofs []*pr
12141262
oneOfSchemas = append(oneOfSchemas, objectSchema)
12151263
}
12161264
}
1217-
1265+
12181266
// Add null as an option
12191267
nullSchema := &v3.SchemaOrReference{
12201268
Oneof: &v3.SchemaOrReference_Schema{
@@ -1224,7 +1272,7 @@ func (g *OpenAPIv3Generator) addOneOfFieldsToSchema(d *v3.Document, oneofs []*pr
12241272
},
12251273
}
12261274
oneOfSchemas = append(oneOfSchemas, nullSchema)
1227-
1275+
12281276
// Create the oneOf schema
12291277
oneOfSchema := &v3.SchemaOrReference{
12301278
Oneof: &v3.SchemaOrReference_Schema{
@@ -1233,7 +1281,7 @@ func (g *OpenAPIv3Generator) addOneOfFieldsToSchema(d *v3.Document, oneofs []*pr
12331281
},
12341282
},
12351283
}
1236-
1284+
12371285
schema.Properties.AdditionalProperties = append(
12381286
schema.Properties.AdditionalProperties,
12391287
&v3.NamedSchemaOrReference{

cmd/protoc-gen-openapi/main.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ func main() {
4444
DefaultResponse: flags.Bool("default_response", true, `add default response. If "true", automatically adds a default response to operations which use the google.rpc.Status message. Useful if you use envoy or grpc-gateway to transcode as they use this type for their default error responses.`),
4545
OutputMode: flags.String("output_mode", "merged", `output generation mode. By default, a single openapi.yaml is generated at the out folder. Use "source_relative' to generate a separate '[inputfile].openapi.yaml' next to each '[inputfile].proto'.`),
4646
SourceRoot: flags.String("source_root", "", `root directory of the source Protobuf files. This is used to add source information to the openapi.yaml file.`),
47-
FlattenOneofs: flags.Bool("flatten_oneofs", false, `flatten oneof fields as regular properties on the parent message instead of generating a oneOf union`),
47+
FlattenOneofs: flags.Bool("flatten_oneofs", false, `flatten oneof fields as regular properties on the parent message instead of generating a oneOf union`),
48+
IncludeAllMethods: flags.Bool("include_all_methods", false, `include RPCs that lack google.api.http annotations by generating default POST paths using the pattern /<package.ServiceName>/<MethodName>`),
4849
}
4950

5051
opts := protogen.Options{

0 commit comments

Comments
 (0)