Skip to content

Commit ae3f3cc

Browse files
authored
Add support for OpenAPI YAML annotations (#1665)
* Add support for OpenAPI YAML annotations * Fix Bazel rules * Fix more Bazel rules * Fix Bazel rule syntax * Fix Bazel with changes from docker run * Split OpenAPI config from apiconfig * Add unannotated_echo_service swagger YAML example * Update Bazel build * Always prepend "." to proto selectors to mirror HTTP selector behavior * Add OpenAPIv2 go proto stubs as dependency of protoc-gen-grpc-gateway * Add generated files and Bazel rules * Fix YAML annotation Don't set the method option "tag" so examples/internal/clients/unannotatedecho/api/swagger.yaml generates a examples/internal/clients/unannotatedecho/api_unannotated_echo_service.go that works with existing tests. * Fix Bazel rule after rebasing * Document using OpenAPI YAML annotations * Fix grpcapiconfiguration.md links * Update docs/_docs/grpcapiconfiguration.md Co-authored-by: Johan Brandhorst-Satzkorn <[email protected]> * Update docs/_docs/grpcapiconfiguration.md Co-authored-by: Johan Brandhorst-Satzkorn <[email protected]> Co-authored-by: Johan Brandhorst-Satzkorn <[email protected]> Fixes #701
1 parent 49d2a43 commit ae3f3cc

28 files changed

+2607
-342
lines changed

Makefile

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,8 @@ RUNTIME_TEST_PROTO=runtime/internal/examplepb/example.proto \
9494
runtime/internal/examplepb/non_standard_names.proto
9595
RUNTIME_TEST_SRCS=$(RUNTIME_TEST_PROTO:.proto=.pb.go)
9696

97-
APICONFIG_PROTO=internal/descriptor/apiconfig/apiconfig.proto
97+
APICONFIG_PROTO=internal/descriptor/apiconfig/apiconfig.proto \
98+
internal/descriptor/openapiconfig/openapiconfig.proto
9899
APICONFIG_SRCS=$(APICONFIG_PROTO:.proto=.pb.go)
99100

100101
EXAMPLE_CLIENT_DIR=examples/internal/clients
@@ -156,7 +157,7 @@ $(GO_GRPC_PLUGIN):
156157
$(OPENAPIV2_GO): $(OPENAPIV2_PROTO) $(GO_PLUGIN)
157158
protoc -I $(PROTOC_INC_PATH) --plugin=$(GO_PLUGIN) -I. --go_out=paths=source_relative:. $(OPENAPIV2_PROTO)
158159

159-
$(GATEWAY_PLUGIN): $(GATEWAY_PLUGIN_SRC)
160+
$(GATEWAY_PLUGIN): $(GATEWAY_PLUGIN_SRC) $(OPENAPIV2_GO)
160161
go build -o $@ $(GATEWAY_PLUGIN_PKG)
161162

162163
$(OPENAPI_PLUGIN): $(OPENAPI_PLUGIN_SRC) $(OPENAPIV2_GO)
@@ -183,7 +184,7 @@ $(EXAMPLE_GWSRCS): $(GATEWAY_PLUGIN) $(EXAMPLES)
183184
protoc -I $(PROTOC_INC_PATH) -I. -I$(GOOGLEAPIS_DIR) --plugin=$(GATEWAY_PLUGIN) --grpc-gateway_out=logtostderr=true,allow_repeated_fields_in_body=true,paths=source_relative$(ADDITIONAL_GW_FLAGS):. $(EXAMPLES)
184185

185186

186-
$(EXAMPLE_OPENAPISRCS): ADDITIONAL_SWG_FLAGS:=$(ADDITIONAL_SWG_FLAGS),grpc_api_configuration=examples/internal/proto/examplepb/unannotated_echo_service.yaml
187+
$(EXAMPLE_OPENAPISRCS): ADDITIONAL_SWG_FLAGS:=$(ADDITIONAL_SWG_FLAGS),grpc_api_configuration=examples/internal/proto/examplepb/unannotated_echo_service.yaml,openapi_configuration=examples/internal/proto/examplepb/unannotated_echo_service.swagger.yaml
187188
$(EXAMPLE_OPENAPISRCS): $(OPENAPI_PLUGIN) $(OPENAPI_EXAMPLES)
188189
protoc -I $(PROTOC_INC_PATH) -I. -I$(GOOGLEAPIS_DIR) --plugin=$(OPENAPI_PLUGIN) --openapiv2_out=logtostderr=true,allow_repeated_fields_in_body=true,use_go_templates=true$(ADDITIONAL_SWG_FLAGS):. $(OPENAPI_EXAMPLES)
189190

docs/_docs/grpcapiconfiguration.md

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,19 @@ order: 100
55
---
66

77
# gRPC API Configuration
8-
In some sitations annotating the .proto file of a service is not an option. For example you might not have control over the .proto file or you might want to expose the same gRPC API multiple times in completely different ways.
8+
In some situations annotating the .proto file of a service is not an option. For example you might not have control over the .proto file or you might want to expose the same gRPC API multiple times in completely different ways.
99

1010
Google Cloud Platform offers a way to do this for services hosted with them called ["gRPC API Configuration"](https://cloud.google.com/endpoints/docs/grpc/grpc-service-config). It can be used to define the behavior of a gRPC API service without modifications to the service itself in the form of [YAML](https://en.wikipedia.org/wiki/YAML) configuration files.
1111

1212
grpc-gateway generators implement the [HTTP rules part](https://cloud.google.com/endpoints/docs/grpc-service-config/reference/rpc/google.api#httprule) of this specification. This allows you to take a completely unannotated service proto file, add a YAML file describing its HTTP endpoints and use them together like a annotated proto file with the grpc-gateway generators.
1313

14+
OpenAPI options may also be configured via ["OpenAPI Configuration"](https://github.com/grpc-ecosystem/grpc-gateway/tree/v2/internal/descriptor/openapiconfig/openapiconfig.proto) in the form of YAML configuration files.
15+
1416
## Usage of gRPC API Configuration YAML files
1517
The following is equivalent to the basic [usage example](usage.html) but without direct annotation for grpc-gateway in the .proto file. Only some steps require minor changes to use a gRPC API Configuration YAML file instead:
1618

1719
1. Define your service in gRPC as usual
18-
20+
1921
your_service.proto:
2022
```protobuf
2123
syntax = "proto3";
@@ -24,7 +26,7 @@ The following is equivalent to the basic [usage example](usage.html) but without
2426
message StringMessage {
2527
string value = 1;
2628
}
27-
29+
2830
service YourService {
2931
rpc Echo(StringMessage) returns (StringMessage) {}
3032
}
@@ -45,11 +47,11 @@ The following is equivalent to the basic [usage example](usage.html) but without
4547
Use a [linter](http://www.yamllint.com/) to validate your YAML.
4648
4749
3. Generate gRPC stub as before
48-
50+
4951
```sh
5052
protoc -I. --go_out=plugins=grpc,paths=source_relative:./gen/go/ your/service/v1/your_service.proto
5153
```
52-
54+
5355
It will generate a stub file with path `./gen/go/your/service/v1/your_service.pb.go`.
5456
5557
4. Implement your service in gRPC as usual
@@ -61,7 +63,26 @@ The following is equivalent to the basic [usage example](usage.html) but without
6163
protoc -I. --grpc-gateway_out=logtostderr=true,paths=source_relative,grpc_api_configuration=path/to/your_service.yaml:./gen/go \
6264
your/service/v1/your_service.proto
6365
```
64-
66+
6567
This will generate a reverse proxy `gen/go/your/service/v1/your_service.pb.gw.go` that is identical to the one produced for the annotated proto.
6668
69+
6. Generate the optional your_service.swagger.json
70+
71+
```sh
72+
protoc -I . --swagger_out ./gen/go \
73+
--swagger_opt grpc_api_configuration=path/to/your_service.yaml \
74+
your/service/v1/your_service.proto
75+
```
76+
77+
or using an OpenAPI configuration file
78+
79+
```sh
80+
protoc -I . --swagger_out ./gen/go \
81+
--swagger_opt grpc_api_configuration=path/to/your_service.yaml \
82+
--swagger_opt openapi_configuration=path/to/your_service_swagger.yaml \
83+
your/service/v1/your_service.proto
84+
```
85+
86+
For an example of an OpenAPI configuration file, see [unannotated_echo_service.swagger.yaml](https://github.com/grpc-ecosystem/grpc-gateway/tree/v2/examples/internal/proto/examplepb/unannotated_echo_service.swagger.yaml), which adds OpenAPI options to [unannotated_echo_service.proto](https://github.com/grpc-ecosystem/grpc-gateway/tree/v2/examples/internal/proto/examplepb/unannotated_echo_service.proto).
87+
6788
All other steps work as before. If you want you can remove the googleapis include path in step 3 and 4 as the unannotated proto no longer requires them.

examples/internal/clients/unannotatedecho/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ go_library(
88
"api_unannotated_echo_service.go",
99
"client.go",
1010
"configuration.go",
11+
"model_examplepb_numeric_enum.go",
1112
"model_examplepb_unannotated_embedded.go",
1213
"model_examplepb_unannotated_simple_message.go",
1314
"model_protobuf_any.go",

examples/internal/clients/unannotatedecho/api/swagger.yaml

Lines changed: 140 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,40 @@ info:
55
\ annotations. See\nunannotated_echo_service.yaml for the equivalent of the annotations\
66
\ in\ngRPC API configuration format.\n\nEcho Service API consists of a single\
77
\ service which returns\na message."
8-
version: "version not set"
9-
title: "examples/internal/proto/examplepb/unannotated_echo_service.proto"
8+
version: "1.0"
9+
title: "Unannotated Echo"
10+
contact:
11+
name: "gRPC-Gateway project"
12+
url: "https://github.com/grpc-ecosystem/grpc-gateway"
13+
14+
license:
15+
name: "BSD 3-Clause License"
16+
url: "https://github.com/grpc-ecosystem/grpc-gateway/blob/master/LICENSE.txt"
17+
x-something-something: "yadda"
18+
schemes:
19+
- "http"
20+
- "https"
21+
- "wss"
1022
consumes:
1123
- "application/json"
24+
- "application/x-foo-mime"
1225
produces:
1326
- "application/json"
27+
- "application/x-foo-mime"
28+
security:
29+
- ApiKeyAuth: []
30+
BasicAuth: []
31+
- ApiKeyAuth: []
32+
OAuth2:
33+
- "read"
34+
- "write"
1435
paths:
1536
/v1/example/echo/{id}:
1637
post:
1738
tags:
1839
- "UnannotatedEchoService"
19-
summary: "Echo method receives a simple message and returns it."
20-
description: "The message posted as the id parameter will also be\nreturned."
40+
summary: "Summary: Echo rpc"
41+
description: "Description Echo"
2142
operationId: "UnannotatedEchoService_Echo"
2243
parameters:
2344
- name: "id"
@@ -29,18 +50,41 @@ paths:
2950
responses:
3051
200:
3152
description: "A successful response."
53+
examples:
54+
application/json:
55+
value: "the input value"
3256
schema:
3357
$ref: "#/definitions/examplepbUnannotatedSimpleMessage"
58+
403:
59+
description: "Returned when the user does not have permission to access\
60+
\ the resource."
61+
schema: {}
62+
404:
63+
description: "Returned when the resource does not exist."
64+
schema:
65+
type: "integer"
66+
format: "integer"
67+
418:
68+
description: "I'm a teapot."
69+
schema:
70+
$ref: "#/definitions/examplepbNumericEnum"
71+
503:
72+
description: "Returned when the resource is temporarily unavailable."
73+
schema: {}
74+
x-number: 100
3475
default:
3576
description: "An unexpected error response."
3677
schema:
3778
$ref: "#/definitions/rpcStatus"
79+
externalDocs:
80+
description: "Find out more Echo"
81+
url: "https://github.com/grpc-ecosystem/grpc-gateway"
3882
/v1/example/echo/{id}/{num}:
3983
get:
4084
tags:
4185
- "UnannotatedEchoService"
42-
summary: "Echo method receives a simple message and returns it."
43-
description: "The message posted as the id parameter will also be\nreturned."
86+
summary: "Summary: Echo rpc"
87+
description: "Description Echo"
4488
operationId: "UnannotatedEchoService_Echo2"
4589
parameters:
4690
- name: "id"
@@ -51,8 +95,10 @@ paths:
5195
x-exportParamName: "Id"
5296
- name: "num"
5397
in: "path"
98+
description: "Int value field"
5499
required: true
55100
type: "string"
101+
default: "42"
56102
format: "int64"
57103
x-exportParamName: "Num"
58104
- name: "duration"
@@ -110,12 +156,35 @@ paths:
110156
responses:
111157
200:
112158
description: "A successful response."
159+
examples:
160+
application/json:
161+
value: "the input value"
113162
schema:
114163
$ref: "#/definitions/examplepbUnannotatedSimpleMessage"
164+
403:
165+
description: "Returned when the user does not have permission to access\
166+
\ the resource."
167+
schema: {}
168+
404:
169+
description: "Returned when the resource does not exist."
170+
schema:
171+
type: "integer"
172+
format: "integer"
173+
418:
174+
description: "I'm a teapot."
175+
schema:
176+
$ref: "#/definitions/examplepbNumericEnum"
177+
503:
178+
description: "Returned when the resource is temporarily unavailable."
179+
schema: {}
180+
x-number: 100
115181
default:
116182
description: "An unexpected error response."
117183
schema:
118184
$ref: "#/definitions/rpcStatus"
185+
externalDocs:
186+
description: "Find out more Echo"
187+
url: "https://github.com/grpc-ecosystem/grpc-gateway"
119188
/v1/example/echo_body:
120189
post:
121190
tags:
@@ -134,6 +203,19 @@ paths:
134203
description: "A successful response."
135204
schema:
136205
$ref: "#/definitions/examplepbUnannotatedSimpleMessage"
206+
403:
207+
description: "Returned when the user does not have permission to access\
208+
\ the resource."
209+
schema: {}
210+
404:
211+
description: "Returned when the resource does not exist."
212+
schema:
213+
type: "string"
214+
format: "string"
215+
418:
216+
description: "I'm a teapot."
217+
schema:
218+
$ref: "#/definitions/examplepbNumericEnum"
137219
default:
138220
description: "An unexpected error response."
139221
schema:
@@ -154,11 +236,12 @@ paths:
154236
x-optionalDataType: "String"
155237
- name: "num"
156238
in: "query"
157-
required: false
239+
description: "Int value field"
240+
required: true
158241
type: "string"
242+
default: "42"
159243
format: "int64"
160244
x-exportParamName: "Num"
161-
x-optionalDataType: "String"
162245
- name: "duration"
163246
in: "query"
164247
required: false
@@ -216,11 +299,43 @@ paths:
216299
description: "A successful response."
217300
schema:
218301
$ref: "#/definitions/examplepbUnannotatedSimpleMessage"
302+
403:
303+
description: "Returned when the user does not have permission to access\
304+
\ the resource."
305+
schema: {}
306+
404:
307+
description: "Returned when the resource does not exist."
308+
schema:
309+
type: "string"
310+
format: "string"
311+
418:
312+
description: "I'm a teapot."
313+
schema:
314+
$ref: "#/definitions/examplepbNumericEnum"
219315
default:
220316
description: "An unexpected error response."
221317
schema:
222318
$ref: "#/definitions/rpcStatus"
319+
securityDefinitions:
320+
ApiKeyAuth:
321+
type: "apiKey"
322+
name: "X-API-Key"
323+
in: "header"
324+
x-amazon-apigateway-authorizer:
325+
authorizerResultTtlInSeconds: 60
326+
type: "token"
327+
x-amazon-apigateway-authtype: "oauth2"
328+
BasicAuth:
329+
type: "basic"
223330
definitions:
331+
examplepbNumericEnum:
332+
type: "string"
333+
description: "NumericEnum is one or zero.\n\n - ZERO: ZERO means 0\n - ONE: ONE\
334+
\ means 1"
335+
enum:
336+
- "ZERO"
337+
- "ONE"
338+
default: "ZERO"
224339
examplepbUnannotatedEmbedded:
225340
type: "object"
226341
properties:
@@ -230,18 +345,19 @@ definitions:
230345
note:
231346
type: "string"
232347
description: "Embedded represents a message embedded in SimpleMessage."
233-
example:
234-
note: "note"
235-
progress: "progress"
236348
examplepbUnannotatedSimpleMessage:
237349
type: "object"
350+
required:
351+
- "id"
238352
properties:
239353
id:
240354
type: "string"
241355
description: "Id represents the message identifier."
242356
num:
243357
type: "string"
244358
format: "int64"
359+
description: "Int value field"
360+
default: "42"
245361
duration:
246362
type: "string"
247363
lineNum:
@@ -256,21 +372,13 @@ definitions:
256372
format: "int64"
257373
"no":
258374
$ref: "#/definitions/examplepbUnannotatedEmbedded"
259-
description: "UnannotatedSimpleMessage represents a simple message sent to the\
260-
\ unannotated Echo service."
375+
externalDocs:
376+
description: "Find out more about UnannotatedSimpleMessage"
377+
url: "https://github.com/grpc-ecosystem/grpc-gateway"
378+
title: "A bit of everything"
379+
description: "A simple message with many types"
261380
example:
262-
duration: "duration"
263-
"no":
264-
note: "note"
265-
progress: "progress"
266-
num: "num"
267-
lineNum: "lineNum"
268-
en: "en"
269-
id: "id"
270-
lang: "lang"
271-
status:
272-
note: "note"
273-
progress: "progress"
381+
id: "myid"
274382
protobufAny:
275383
type: "object"
276384
properties:
@@ -341,3 +449,10 @@ definitions:
341449
type: "array"
342450
items:
343451
$ref: "#/definitions/protobufAny"
452+
externalDocs:
453+
description: "More about gRPC-Gateway"
454+
url: "https://github.com/grpc-ecosystem/grpc-gateway"
455+
x-grpc-gateway-baz-list:
456+
- "one"
457+
- true
458+
x-grpc-gateway-foo: "bar"

0 commit comments

Comments
 (0)