Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for OpenAPI YAML annotations #1665

Merged
merged 17 commits into from
Sep 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@ RUNTIME_TEST_PROTO=runtime/internal/examplepb/example.proto \
runtime/internal/examplepb/non_standard_names.proto
RUNTIME_TEST_SRCS=$(RUNTIME_TEST_PROTO:.proto=.pb.go)

APICONFIG_PROTO=internal/descriptor/apiconfig/apiconfig.proto
APICONFIG_PROTO=internal/descriptor/apiconfig/apiconfig.proto \
internal/descriptor/openapiconfig/openapiconfig.proto
APICONFIG_SRCS=$(APICONFIG_PROTO:.proto=.pb.go)

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

$(GATEWAY_PLUGIN): $(GATEWAY_PLUGIN_SRC)
$(GATEWAY_PLUGIN): $(GATEWAY_PLUGIN_SRC) $(OPENAPIV2_GO)
go build -o $@ $(GATEWAY_PLUGIN_PKG)

$(OPENAPI_PLUGIN): $(OPENAPI_PLUGIN_SRC) $(OPENAPIV2_GO)
Expand All @@ -183,7 +184,7 @@ $(EXAMPLE_GWSRCS): $(GATEWAY_PLUGIN) $(EXAMPLES)
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)


$(EXAMPLE_OPENAPISRCS): ADDITIONAL_SWG_FLAGS:=$(ADDITIONAL_SWG_FLAGS),grpc_api_configuration=examples/internal/proto/examplepb/unannotated_echo_service.yaml
$(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
$(EXAMPLE_OPENAPISRCS): $(OPENAPI_PLUGIN) $(OPENAPI_EXAMPLES)
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)

Expand Down
33 changes: 27 additions & 6 deletions docs/_docs/grpcapiconfiguration.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,19 @@ order: 100
---

# gRPC API Configuration
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.
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.

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.

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.

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.

## Usage of gRPC API Configuration YAML files
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:

1. Define your service in gRPC as usual

your_service.proto:
```protobuf
syntax = "proto3";
Expand All @@ -24,7 +26,7 @@ The following is equivalent to the basic [usage example](usage.html) but without
message StringMessage {
string value = 1;
}

service YourService {
rpc Echo(StringMessage) returns (StringMessage) {}
}
Expand All @@ -45,11 +47,11 @@ The following is equivalent to the basic [usage example](usage.html) but without
Use a [linter](http://www.yamllint.com/) to validate your YAML.

3. Generate gRPC stub as before

```sh
protoc -I. --go_out=plugins=grpc,paths=source_relative:./gen/go/ your/service/v1/your_service.proto
```

It will generate a stub file with path `./gen/go/your/service/v1/your_service.pb.go`.

4. Implement your service in gRPC as usual
Expand All @@ -61,7 +63,26 @@ The following is equivalent to the basic [usage example](usage.html) but without
protoc -I. --grpc-gateway_out=logtostderr=true,paths=source_relative,grpc_api_configuration=path/to/your_service.yaml:./gen/go \
your/service/v1/your_service.proto
```

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.

6. Generate the optional your_service.swagger.json

```sh
protoc -I . --swagger_out ./gen/go \
--swagger_opt grpc_api_configuration=path/to/your_service.yaml \
your/service/v1/your_service.proto
```

or using an OpenAPI configuration file

```sh
protoc -I . --swagger_out ./gen/go \
--swagger_opt grpc_api_configuration=path/to/your_service.yaml \
--swagger_opt openapi_configuration=path/to/your_service_swagger.yaml \
your/service/v1/your_service.proto
```

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).

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.
1 change: 1 addition & 0 deletions examples/internal/clients/unannotatedecho/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ go_library(
"api_unannotated_echo_service.go",
"client.go",
"configuration.go",
"model_examplepb_numeric_enum.go",
"model_examplepb_unannotated_embedded.go",
"model_examplepb_unannotated_simple_message.go",
"model_protobuf_any.go",
Expand Down
165 changes: 140 additions & 25 deletions examples/internal/clients/unannotatedecho/api/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,40 @@ info:
\ annotations. See\nunannotated_echo_service.yaml for the equivalent of the annotations\
\ in\ngRPC API configuration format.\n\nEcho Service API consists of a single\
\ service which returns\na message."
version: "version not set"
title: "examples/internal/proto/examplepb/unannotated_echo_service.proto"
version: "1.0"
title: "Unannotated Echo"
contact:
name: "gRPC-Gateway project"
url: "https://github.com/grpc-ecosystem/grpc-gateway"
email: "none@example.com"
license:
name: "BSD 3-Clause License"
url: "https://github.com/grpc-ecosystem/grpc-gateway/blob/master/LICENSE.txt"
x-something-something: "yadda"
schemes:
- "http"
- "https"
- "wss"
consumes:
- "application/json"
- "application/x-foo-mime"
produces:
- "application/json"
- "application/x-foo-mime"
security:
- ApiKeyAuth: []
BasicAuth: []
- ApiKeyAuth: []
OAuth2:
- "read"
- "write"
paths:
/v1/example/echo/{id}:
post:
tags:
- "UnannotatedEchoService"
summary: "Echo method receives a simple message and returns it."
description: "The message posted as the id parameter will also be\nreturned."
summary: "Summary: Echo rpc"
description: "Description Echo"
operationId: "UnannotatedEchoService_Echo"
parameters:
- name: "id"
Expand All @@ -29,18 +50,41 @@ paths:
responses:
200:
description: "A successful response."
examples:
application/json:
value: "the input value"
schema:
$ref: "#/definitions/examplepbUnannotatedSimpleMessage"
403:
description: "Returned when the user does not have permission to access\
\ the resource."
schema: {}
404:
description: "Returned when the resource does not exist."
schema:
type: "integer"
format: "integer"
418:
description: "I'm a teapot."
schema:
$ref: "#/definitions/examplepbNumericEnum"
503:
description: "Returned when the resource is temporarily unavailable."
schema: {}
x-number: 100
default:
description: "An unexpected error response."
schema:
$ref: "#/definitions/rpcStatus"
externalDocs:
description: "Find out more Echo"
url: "https://github.com/grpc-ecosystem/grpc-gateway"
/v1/example/echo/{id}/{num}:
get:
tags:
- "UnannotatedEchoService"
summary: "Echo method receives a simple message and returns it."
description: "The message posted as the id parameter will also be\nreturned."
summary: "Summary: Echo rpc"
description: "Description Echo"
operationId: "UnannotatedEchoService_Echo2"
parameters:
- name: "id"
Expand All @@ -51,8 +95,10 @@ paths:
x-exportParamName: "Id"
- name: "num"
in: "path"
description: "Int value field"
required: true
type: "string"
default: "42"
format: "int64"
x-exportParamName: "Num"
- name: "duration"
Expand Down Expand Up @@ -110,12 +156,35 @@ paths:
responses:
200:
description: "A successful response."
examples:
application/json:
value: "the input value"
schema:
$ref: "#/definitions/examplepbUnannotatedSimpleMessage"
403:
description: "Returned when the user does not have permission to access\
\ the resource."
schema: {}
404:
description: "Returned when the resource does not exist."
schema:
type: "integer"
format: "integer"
418:
description: "I'm a teapot."
schema:
$ref: "#/definitions/examplepbNumericEnum"
503:
description: "Returned when the resource is temporarily unavailable."
schema: {}
x-number: 100
default:
description: "An unexpected error response."
schema:
$ref: "#/definitions/rpcStatus"
externalDocs:
description: "Find out more Echo"
url: "https://github.com/grpc-ecosystem/grpc-gateway"
/v1/example/echo_body:
post:
tags:
Expand All @@ -134,6 +203,19 @@ paths:
description: "A successful response."
schema:
$ref: "#/definitions/examplepbUnannotatedSimpleMessage"
403:
description: "Returned when the user does not have permission to access\
\ the resource."
schema: {}
404:
description: "Returned when the resource does not exist."
schema:
type: "string"
format: "string"
418:
description: "I'm a teapot."
schema:
$ref: "#/definitions/examplepbNumericEnum"
default:
description: "An unexpected error response."
schema:
Expand All @@ -154,11 +236,12 @@ paths:
x-optionalDataType: "String"
- name: "num"
in: "query"
required: false
description: "Int value field"
required: true
type: "string"
default: "42"
format: "int64"
x-exportParamName: "Num"
x-optionalDataType: "String"
- name: "duration"
in: "query"
required: false
Expand Down Expand Up @@ -216,11 +299,43 @@ paths:
description: "A successful response."
schema:
$ref: "#/definitions/examplepbUnannotatedSimpleMessage"
403:
description: "Returned when the user does not have permission to access\
\ the resource."
schema: {}
404:
description: "Returned when the resource does not exist."
schema:
type: "string"
format: "string"
418:
description: "I'm a teapot."
schema:
$ref: "#/definitions/examplepbNumericEnum"
default:
description: "An unexpected error response."
schema:
$ref: "#/definitions/rpcStatus"
securityDefinitions:
ApiKeyAuth:
type: "apiKey"
name: "X-API-Key"
in: "header"
x-amazon-apigateway-authorizer:
authorizerResultTtlInSeconds: 60
type: "token"
x-amazon-apigateway-authtype: "oauth2"
BasicAuth:
type: "basic"
definitions:
examplepbNumericEnum:
type: "string"
description: "NumericEnum is one or zero.\n\n - ZERO: ZERO means 0\n - ONE: ONE\
\ means 1"
enum:
- "ZERO"
- "ONE"
default: "ZERO"
examplepbUnannotatedEmbedded:
type: "object"
properties:
Expand All @@ -230,18 +345,19 @@ definitions:
note:
type: "string"
description: "Embedded represents a message embedded in SimpleMessage."
example:
note: "note"
progress: "progress"
examplepbUnannotatedSimpleMessage:
type: "object"
required:
- "id"
properties:
id:
type: "string"
description: "Id represents the message identifier."
num:
type: "string"
format: "int64"
description: "Int value field"
default: "42"
duration:
type: "string"
lineNum:
Expand All @@ -256,21 +372,13 @@ definitions:
format: "int64"
"no":
$ref: "#/definitions/examplepbUnannotatedEmbedded"
description: "UnannotatedSimpleMessage represents a simple message sent to the\
\ unannotated Echo service."
externalDocs:
description: "Find out more about UnannotatedSimpleMessage"
url: "https://github.com/grpc-ecosystem/grpc-gateway"
title: "A bit of everything"
description: "A simple message with many types"
example:
duration: "duration"
"no":
note: "note"
progress: "progress"
num: "num"
lineNum: "lineNum"
en: "en"
id: "id"
lang: "lang"
status:
note: "note"
progress: "progress"
id: "myid"
protobufAny:
type: "object"
properties:
Expand Down Expand Up @@ -341,3 +449,10 @@ definitions:
type: "array"
items:
$ref: "#/definitions/protobufAny"
externalDocs:
description: "More about gRPC-Gateway"
url: "https://github.com/grpc-ecosystem/grpc-gateway"
x-grpc-gateway-baz-list:
- "one"
- true
x-grpc-gateway-foo: "bar"
Loading