From c8ad0162ceb392e78f7988843a64478a8acc9690 Mon Sep 17 00:00:00 2001 From: Dmitry Doroginin Date: Fri, 27 Jul 2018 15:08:50 +0300 Subject: [PATCH] Add supporting `response_body` field in the `google.api.HttpRule` (#712) Also: * add flag `allow_repeated_fields_in_body` in `protoc-gen-grpc-gateway` * add flag `json_names_for_fields` in `protoc-gen-swagger` --- Gopkg.lock | 2 +- Gopkg.toml | 11 +- Makefile | 23 +- WORKSPACE | 31 ++- examples/clients/responsebody/.gitignore | 24 ++ .../responsebody/.swagger-codegen-ignore | 23 ++ examples/clients/responsebody/api_client.go | 164 ++++++++++++ examples/clients/responsebody/api_response.go | 44 ++++ .../clients/responsebody/configuration.go | 67 +++++ .../docs/ExamplepbResponseBodyMessage.md | 11 + .../ExamplepbResponseBodyMessageResponse.md | 10 + .../docs/ExamplepbResponseBodyOut.md | 10 + .../docs/ExamplepbResponseBodyOutResponse.md | 10 + .../docs/ExamplepbResponseBodyReq.md | 10 + .../docs/ResponseBodyServiceApi.md | 36 +++ .../examplepb_response_body_message.go | 18 ++ ...xamplepb_response_body_message_response.go | 16 ++ .../examplepb_response_body_out.go | 16 ++ .../examplepb_response_body_out_response.go | 16 ++ .../examplepb_response_body_req.go | 16 ++ .../responsebody/response_body_service_api.go | 99 +++++++ examples/gateway/gateway.go | 1 + examples/integration/integration_test.go | 35 ++- examples/proto/examplepb/BUILD.bazel | 2 + .../examplepb/response_body_service.pb.go | 242 ++++++++++++++++++ .../examplepb/response_body_service.pb.gw.go | 143 +++++++++++ .../examplepb/response_body_service.proto | 26 ++ .../response_body_service.swagger.json | 61 +++++ examples/server/BUILD.bazel | 1 + examples/server/main.go | 1 + examples/server/responsebody.go | 23 ++ .../descriptor/registry.go | 30 +++ .../descriptor/services.go | 20 +- protoc-gen-grpc-gateway/descriptor/types.go | 11 +- .../gengateway/template.go | 19 ++ protoc-gen-grpc-gateway/main.go | 16 +- protoc-gen-swagger/genswagger/template.go | 32 ++- protoc-gen-swagger/main.go | 14 +- repositories.bzl | 4 +- runtime/handler.go | 15 +- runtime/mux.go | 1 - third_party/googleapis/README.grpc-gateway | 2 +- third_party/googleapis/google/api/http.proto | 77 ++++-- 43 files changed, 1356 insertions(+), 77 deletions(-) create mode 100644 examples/clients/responsebody/.gitignore create mode 100644 examples/clients/responsebody/.swagger-codegen-ignore create mode 100644 examples/clients/responsebody/api_client.go create mode 100644 examples/clients/responsebody/api_response.go create mode 100644 examples/clients/responsebody/configuration.go create mode 100644 examples/clients/responsebody/docs/ExamplepbResponseBodyMessage.md create mode 100644 examples/clients/responsebody/docs/ExamplepbResponseBodyMessageResponse.md create mode 100644 examples/clients/responsebody/docs/ExamplepbResponseBodyOut.md create mode 100644 examples/clients/responsebody/docs/ExamplepbResponseBodyOutResponse.md create mode 100644 examples/clients/responsebody/docs/ExamplepbResponseBodyReq.md create mode 100644 examples/clients/responsebody/docs/ResponseBodyServiceApi.md create mode 100644 examples/clients/responsebody/examplepb_response_body_message.go create mode 100644 examples/clients/responsebody/examplepb_response_body_message_response.go create mode 100644 examples/clients/responsebody/examplepb_response_body_out.go create mode 100644 examples/clients/responsebody/examplepb_response_body_out_response.go create mode 100644 examples/clients/responsebody/examplepb_response_body_req.go create mode 100644 examples/clients/responsebody/response_body_service_api.go create mode 100644 examples/proto/examplepb/response_body_service.pb.go create mode 100644 examples/proto/examplepb/response_body_service.pb.gw.go create mode 100644 examples/proto/examplepb/response_body_service.proto create mode 100644 examples/proto/examplepb/response_body_service.swagger.json create mode 100644 examples/server/responsebody.go diff --git a/Gopkg.lock b/Gopkg.lock index 041bf73d54a..94cc9cc5d11 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -85,7 +85,7 @@ "googleapis/rpc/status", "protobuf/field_mask" ] - revision = "86e600f69ee4704c6efbf6a2a40a5c10700e76c2" + revision = "383e8b2c3b9e36c4076b235b32537292176bae20" [[projects]] name = "google.golang.org/grpc" diff --git a/Gopkg.toml b/Gopkg.toml index 539982e325f..cf3660628ae 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -23,6 +23,11 @@ required = [ revision = "6724a57986aff9bff1a1770e9347036def7c89f6" name = "github.com/rogpeppe/fastuuid" +[[constraint]] + # Also defined in WORKSPACE + revision = "383e8b2c3b9e36c4076b235b32537292176bae20" + name = "google.golang.org/genproto" + [[override]] # Also defined in WORKSPACE revision = "eb3733d160e74a9c7e442f435eb3bea458e1d19f" @@ -48,12 +53,6 @@ required = [ version = "1.10.3" name = "google.golang.org/grpc" -[[constraint]] - # Also defined in bazelbuild/rules_go - # https://github.com/bazelbuild/rules_go/blob/436452edc29a2f1e0edc22d180fbb57c27e6d0af/go/private/repositories.bzl#L131 - revision = "86e600f69ee4704c6efbf6a2a40a5c10700e76c2" - name = "google.golang.org/genproto" - [[constraint]] # Also defined in bazelbuild/rules_go # https://github.com/bazelbuild/rules_go/blob/436452edc29a2f1e0edc22d180fbb57c27e6d0af/go/private/repositories.bzl#L138 diff --git a/Makefile b/Makefile index ac1235ef267..14f1d673442 100644 --- a/Makefile +++ b/Makefile @@ -63,13 +63,17 @@ endif SWAGGER_EXAMPLES=examples/proto/examplepb/echo_service.proto \ examples/proto/examplepb/a_bit_of_everything.proto \ examples/proto/examplepb/wrappers.proto \ - examples/proto/examplepb/unannotated_echo_service.proto + examples/proto/examplepb/unannotated_echo_service.proto \ + examples/proto/examplepb/response_body_service.proto + EXAMPLES=examples/proto/examplepb/echo_service.proto \ examples/proto/examplepb/a_bit_of_everything.proto \ examples/proto/examplepb/stream.proto \ examples/proto/examplepb/flow_combination.proto \ examples/proto/examplepb/wrappers.proto \ - examples/proto/examplepb/unannotated_echo_service.proto + examples/proto/examplepb/unannotated_echo_service.proto \ + examples/proto/examplepb/response_body_service.proto + EXAMPLE_SVCSRCS=$(EXAMPLES:.proto=.pb.go) EXAMPLE_GWSRCS=$(EXAMPLES:.proto=.pb.gw.go) EXAMPLE_SWAGGERSRCS=$(SWAGGER_EXAMPLES:.proto=.swagger.json) @@ -105,7 +109,14 @@ UNANNOTATED_ECHO_EXAMPLE_SRCS=$(EXAMPLE_CLIENT_DIR)/unannotatedecho/api_client.g $(EXAMPLE_CLIENT_DIR)/unannotatedecho/configuration.go \ $(EXAMPLE_CLIENT_DIR)/unannotatedecho/examplepb_unannotated_simple_message.go \ $(EXAMPLE_CLIENT_DIR)/unannotatedecho/unannotated_echo_service_api.go -EXAMPLE_CLIENT_SRCS=$(ECHO_EXAMPLE_SRCS) $(ABE_EXAMPLE_SRCS) $(UNANNOTATED_ECHO_EXAMPLE_SRCS) +RESPONSE_BODY_EXAMPLE_SPEC=examples/proto/examplepb/response_body_service.swagger.json +RESPONSE_BODY_EXAMPLE_SRCS=$(EXAMPLE_CLIENT_DIR)/responsebody/api_client.go \ + $(EXAMPLE_CLIENT_DIR)/responsebody/api_response.go \ + $(EXAMPLE_CLIENT_DIR)/responsebody/configuration.go \ + $(EXAMPLE_CLIENT_DIR)/responsebody/examplepb_response_body.go \ + $(EXAMPLE_CLIENT_DIR)/responsebody/response_body_service_api.go + +EXAMPLE_CLIENT_SRCS=$(ECHO_EXAMPLE_SRCS) $(ABE_EXAMPLE_SRCS) $(UNANNOTATED_ECHO_EXAMPLE_SRCS) $(RESPONSE_BODY_EXAMPLE_SRCS) SWAGGER_CODEGEN=swagger-codegen PROTOC_INC_PATH=$(dir $(shell which protoc))/../include @@ -164,6 +175,12 @@ $(UNANNOTATED_ECHO_EXAMPLE_SRCS): $(UNANNOTATED_ECHO_EXAMPLE_SPEC) @rm -f $(EXAMPLE_CLIENT_DIR)/unannotatedecho/README.md \ $(EXAMPLE_CLIENT_DIR)/unannotatedecho/git_push.sh \ $(EXAMPLE_CLIENT_DIR)/unannotatedecho/.travis.yml +$(RESPONSE_BODY_EXAMPLE_SRCS): $(RESPONSE_BODY_EXAMPLE_SPEC) + $(SWAGGER_CODEGEN) generate -i $(RESPONSE_BODY_EXAMPLE_SPEC) \ + -l go -o examples/clients/responsebody --additional-properties packageName=responsebody + @rm -f $(EXAMPLE_CLIENT_DIR)/responsebody/README.md \ + $(EXAMPLE_CLIENT_DIR)/responsebody/git_push.sh \ + $(EXAMPLE_CLIENT_DIR)/responsebody/.travis.yml examples: $(EXAMPLE_SVCSRCS) $(EXAMPLE_GWSRCS) $(EXAMPLE_DEPSRCS) $(EXAMPLE_SWAGGERSRCS) $(EXAMPLE_CLIENT_SRCS) test: examples diff --git a/WORKSPACE b/WORKSPACE index 2984db657d8..bf8cb212f33 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -12,42 +12,51 @@ http_archive( sha256 = "d03625db67e9fb0905bbd206fa97e32ae9da894fe234a493e7517fd25faec914", ) +load("@io_bazel_rules_go//go:def.bzl", "go_repository") +load("//:repositories.bzl", "repositories") load("@io_bazel_rules_go//go:def.bzl", "go_rules_dependencies", "go_register_toolchains") -go_rules_dependencies() - -go_register_toolchains() - -load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies") - -gazelle_dependencies() - -load("@io_bazel_rules_go//go:def.bzl", "go_repository") +# Also define in Gopkg.toml +go_repository( + name = "org_golang_google_genproto", + commit = "383e8b2c3b9e36c4076b235b32537292176bae20", + importpath = "google.golang.org/genproto", +) +# Also define in Gopkg.toml go_repository( name = "com_github_rogpeppe_fastuuid", commit = "6724a57986aff9bff1a1770e9347036def7c89f6", importpath = "github.com/rogpeppe/fastuuid", ) +# Also define in Gopkg.toml go_repository( name = "com_github_go_resty_resty", commit = "f8815663de1e64d57cdd4ee9e2b2fa96977a030e", importpath = "github.com/go-resty/resty", ) +# Also define in Gopkg.toml go_repository( name = "com_github_ghodss_yaml", commit = "0ca9ea5df5451ffdf184b4428c902747c2c11cd7", importpath = "github.com/ghodss/yaml", ) +# Also define in Gopkg.toml go_repository( name = "in_gopkg_yaml_v2", commit = "eb3733d160e74a9c7e442f435eb3bea458e1d19f", importpath = "gopkg.in/yaml.v2", ) -load("//:repositories.bzl", "repositories") - repositories() + +go_rules_dependencies() + +go_register_toolchains() + +load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies") + +gazelle_dependencies() diff --git a/examples/clients/responsebody/.gitignore b/examples/clients/responsebody/.gitignore new file mode 100644 index 00000000000..daf913b1b34 --- /dev/null +++ b/examples/clients/responsebody/.gitignore @@ -0,0 +1,24 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof diff --git a/examples/clients/responsebody/.swagger-codegen-ignore b/examples/clients/responsebody/.swagger-codegen-ignore new file mode 100644 index 00000000000..c5fa491b4c5 --- /dev/null +++ b/examples/clients/responsebody/.swagger-codegen-ignore @@ -0,0 +1,23 @@ +# Swagger Codegen Ignore +# Generated by swagger-codegen https://github.com/swagger-api/swagger-codegen + +# Use this file to prevent files from being overwritten by the generator. +# The patterns follow closely to .gitignore or .dockerignore. + +# As an example, the C# client generator defines ApiClient.cs. +# You can make changes and tell Swagger Codgen to ignore just this file by uncommenting the following line: +#ApiClient.cs + +# You can match any string of characters against a directory, file or extension with a single asterisk (*): +#foo/*/qux +# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux + +# You can recursively match patterns against a directory, file or extension with a double asterisk (**): +#foo/**/qux +# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux + +# You can also negate patterns with an exclamation (!). +# For example, you can ignore all files in a docs folder with the file extension .md: +#docs/*.md +# Then explicitly reverse the ignore rule for a single file: +#!docs/README.md diff --git a/examples/clients/responsebody/api_client.go b/examples/clients/responsebody/api_client.go new file mode 100644 index 00000000000..1f7354b61dc --- /dev/null +++ b/examples/clients/responsebody/api_client.go @@ -0,0 +1,164 @@ +/* + * examples/proto/examplepb/response_body_service.proto + * + * No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen) + * + * OpenAPI spec version: version not set + * + * Generated by: https://github.com/swagger-api/swagger-codegen.git + */ + +package responsebody + +import ( + "bytes" + "fmt" + "path/filepath" + "reflect" + "strings" + "net/url" + "io/ioutil" + "github.com/go-resty/resty" +) + +type APIClient struct { + config *Configuration +} + +func (c *APIClient) SelectHeaderContentType(contentTypes []string) string { + + if len(contentTypes) == 0 { + return "" + } + if contains(contentTypes, "application/json") { + return "application/json" + } + return contentTypes[0] // use the first content type specified in 'consumes' +} + +func (c *APIClient) SelectHeaderAccept(accepts []string) string { + + if len(accepts) == 0 { + return "" + } + if contains(accepts, "application/json") { + return "application/json" + } + return strings.Join(accepts, ",") +} + +func contains(haystack []string, needle string) bool { + for _, a := range haystack { + if strings.ToLower(a) == strings.ToLower(needle) { + return true + } + } + return false +} + +func (c *APIClient) CallAPI(path string, method string, + postBody interface{}, + headerParams map[string]string, + queryParams url.Values, + formParams map[string]string, + fileName string, + fileBytes []byte) (*resty.Response, error) { + + rClient := c.prepareClient() + request := c.prepareRequest(rClient, postBody, headerParams, queryParams, formParams, fileName, fileBytes) + + switch strings.ToUpper(method) { + case "GET": + response, err := request.Get(path) + return response, err + case "POST": + response, err := request.Post(path) + return response, err + case "PUT": + response, err := request.Put(path) + return response, err + case "PATCH": + response, err := request.Patch(path) + return response, err + case "DELETE": + response, err := request.Delete(path) + return response, err + } + + return nil, fmt.Errorf("invalid method %v", method) +} + +func (c *APIClient) ParameterToString(obj interface{}, collectionFormat string) string { + delimiter := "" + switch collectionFormat { + case "pipes": + delimiter = "|" + case "ssv": + delimiter = " " + case "tsv": + delimiter = "\t" + case "csv": + delimiter = "," + } + + if reflect.TypeOf(obj).Kind() == reflect.Slice { + return strings.Trim(strings.Replace(fmt.Sprint(obj), " ", delimiter, -1), "[]") + } + + return fmt.Sprintf("%v", obj) +} + +func (c *APIClient) prepareClient() *resty.Client { + + rClient := resty.New() + + rClient.SetDebug(c.config.Debug) + if c.config.Transport != nil { + rClient.SetTransport(c.config.Transport) + } + + if c.config.Timeout != nil { + rClient.SetTimeout(*c.config.Timeout) + } + rClient.SetLogger(ioutil.Discard) + return rClient +} + +func (c *APIClient) prepareRequest( + rClient *resty.Client, + postBody interface{}, + headerParams map[string]string, + queryParams url.Values, + formParams map[string]string, + fileName string, + fileBytes []byte) *resty.Request { + + + request := rClient.R() + request.SetBody(postBody) + + if c.config.UserAgent != "" { + request.SetHeader("User-Agent", c.config.UserAgent) + } + + // add header parameter, if any + if len(headerParams) > 0 { + request.SetHeaders(headerParams) + } + + // add query parameter, if any + if len(queryParams) > 0 { + request.SetMultiValueQueryParams(queryParams) + } + + // add form parameter, if any + if len(formParams) > 0 { + request.SetFormData(formParams) + } + + if len(fileBytes) > 0 && fileName != "" { + _, fileNm := filepath.Split(fileName) + request.SetFileReader("file", fileNm, bytes.NewReader(fileBytes)) + } + return request +} diff --git a/examples/clients/responsebody/api_response.go b/examples/clients/responsebody/api_response.go new file mode 100644 index 00000000000..225d12176b3 --- /dev/null +++ b/examples/clients/responsebody/api_response.go @@ -0,0 +1,44 @@ +/* + * examples/proto/examplepb/response_body_service.proto + * + * No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen) + * + * OpenAPI spec version: version not set + * + * Generated by: https://github.com/swagger-api/swagger-codegen.git + */ + +package responsebody + +import ( + "net/http" +) + +type APIResponse struct { + *http.Response `json:"-"` + Message string `json:"message,omitempty"` + // Operation is the name of the swagger operation. + Operation string `json:"operation,omitempty"` + // RequestURL is the request URL. This value is always available, even if the + // embedded *http.Response is nil. + RequestURL string `json:"url,omitempty"` + // Method is the HTTP method used for the request. This value is always + // available, even if the embedded *http.Response is nil. + Method string `json:"method,omitempty"` + // Payload holds the contents of the response body (which may be nil or empty). + // This is provided here as the raw response.Body() reader will have already + // been drained. + Payload []byte `json:"-"` +} + +func NewAPIResponse(r *http.Response) *APIResponse { + + response := &APIResponse{Response: r} + return response +} + +func NewAPIResponseWithError(errorMessage string) *APIResponse { + + response := &APIResponse{Message: errorMessage} + return response +} diff --git a/examples/clients/responsebody/configuration.go b/examples/clients/responsebody/configuration.go new file mode 100644 index 00000000000..0ffa917b424 --- /dev/null +++ b/examples/clients/responsebody/configuration.go @@ -0,0 +1,67 @@ +/* + * examples/proto/examplepb/response_body_service.proto + * + * No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen) + * + * OpenAPI spec version: version not set + * + * Generated by: https://github.com/swagger-api/swagger-codegen.git + */ + +package responsebody + +import ( + "encoding/base64" + "net/http" + "time" +) + + +type Configuration struct { + Username string `json:"userName,omitempty"` + Password string `json:"password,omitempty"` + APIKeyPrefix map[string]string `json:"APIKeyPrefix,omitempty"` + APIKey map[string]string `json:"APIKey,omitempty"` + Debug bool `json:"debug,omitempty"` + DebugFile string `json:"debugFile,omitempty"` + OAuthToken string `json:"oAuthToken,omitempty"` + BasePath string `json:"basePath,omitempty"` + Host string `json:"host,omitempty"` + Scheme string `json:"scheme,omitempty"` + AccessToken string `json:"accessToken,omitempty"` + DefaultHeader map[string]string `json:"defaultHeader,omitempty"` + UserAgent string `json:"userAgent,omitempty"` + APIClient *APIClient + Transport *http.Transport + Timeout *time.Duration `json:"timeout,omitempty"` +} + +func NewConfiguration() *Configuration { + cfg := &Configuration{ + BasePath: "http://localhost", + DefaultHeader: make(map[string]string), + APIKey: make(map[string]string), + APIKeyPrefix: make(map[string]string), + UserAgent: "Swagger-Codegen/1.0.0/go", + APIClient: &APIClient{}, + } + + cfg.APIClient.config = cfg + return cfg +} + +func (c *Configuration) GetBasicAuthEncodedString() string { + return base64.StdEncoding.EncodeToString([]byte(c.Username + ":" + c.Password)) +} + +func (c *Configuration) AddDefaultHeader(key string, value string) { + c.DefaultHeader[key] = value +} + +func (c *Configuration) GetAPIKeyWithPrefix(APIKeyIdentifier string) string { + if c.APIKeyPrefix[APIKeyIdentifier] != "" { + return c.APIKeyPrefix[APIKeyIdentifier] + " " + c.APIKey[APIKeyIdentifier] + } + + return c.APIKey[APIKeyIdentifier] +} diff --git a/examples/clients/responsebody/docs/ExamplepbResponseBodyMessage.md b/examples/clients/responsebody/docs/ExamplepbResponseBodyMessage.md new file mode 100644 index 00000000000..5b7749a7be1 --- /dev/null +++ b/examples/clients/responsebody/docs/ExamplepbResponseBodyMessage.md @@ -0,0 +1,11 @@ +# ExamplepbResponseBodyMessage + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**Request** | **string** | | [optional] [default to null] +**Response** | [**ExamplepbResponseBodyMessageResponse**](examplepbResponseBodyMessageResponse.md) | | [optional] [default to null] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/examples/clients/responsebody/docs/ExamplepbResponseBodyMessageResponse.md b/examples/clients/responsebody/docs/ExamplepbResponseBodyMessageResponse.md new file mode 100644 index 00000000000..d0990f90fb0 --- /dev/null +++ b/examples/clients/responsebody/docs/ExamplepbResponseBodyMessageResponse.md @@ -0,0 +1,10 @@ +# ExamplepbResponseBodyMessageResponse + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**Data** | **string** | | [optional] [default to null] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/examples/clients/responsebody/docs/ExamplepbResponseBodyOut.md b/examples/clients/responsebody/docs/ExamplepbResponseBodyOut.md new file mode 100644 index 00000000000..a26ce7530d0 --- /dev/null +++ b/examples/clients/responsebody/docs/ExamplepbResponseBodyOut.md @@ -0,0 +1,10 @@ +# ExamplepbResponseBodyOut + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**Response** | [**ExamplepbResponseBodyOutResponse**](examplepbResponseBodyOutResponse.md) | | [optional] [default to null] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/examples/clients/responsebody/docs/ExamplepbResponseBodyOutResponse.md b/examples/clients/responsebody/docs/ExamplepbResponseBodyOutResponse.md new file mode 100644 index 00000000000..aff3fe461e4 --- /dev/null +++ b/examples/clients/responsebody/docs/ExamplepbResponseBodyOutResponse.md @@ -0,0 +1,10 @@ +# ExamplepbResponseBodyOutResponse + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**Data** | **string** | | [optional] [default to null] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/examples/clients/responsebody/docs/ExamplepbResponseBodyReq.md b/examples/clients/responsebody/docs/ExamplepbResponseBodyReq.md new file mode 100644 index 00000000000..84c87bf9a84 --- /dev/null +++ b/examples/clients/responsebody/docs/ExamplepbResponseBodyReq.md @@ -0,0 +1,10 @@ +# ExamplepbResponseBodyReq + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**Data** | **string** | | [optional] [default to null] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/examples/clients/responsebody/docs/ResponseBodyServiceApi.md b/examples/clients/responsebody/docs/ResponseBodyServiceApi.md new file mode 100644 index 00000000000..7c4506cc991 --- /dev/null +++ b/examples/clients/responsebody/docs/ResponseBodyServiceApi.md @@ -0,0 +1,36 @@ +# \ResponseBodyServiceApi + +All URIs are relative to *http://localhost* + +Method | HTTP request | Description +------------- | ------------- | ------------- +[**GetResponseBody**](ResponseBodyServiceApi.md#GetResponseBody) | **Get** /responsebody/{data} | + + +# **GetResponseBody** +> ExamplepbResponseBodyOutResponse GetResponseBody($data) + + + + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **data** | **string**| | + +### Return type + +[**ExamplepbResponseBodyOutResponse**](examplepbResponseBodyOutResponse.md) + +### Authorization + +No authorization required + +### HTTP request headers + + - **Content-Type**: application/json + - **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + diff --git a/examples/clients/responsebody/examplepb_response_body_message.go b/examples/clients/responsebody/examplepb_response_body_message.go new file mode 100644 index 00000000000..6dfcea3835c --- /dev/null +++ b/examples/clients/responsebody/examplepb_response_body_message.go @@ -0,0 +1,18 @@ +/* + * examples/proto/examplepb/response_body_service.proto + * + * No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen) + * + * OpenAPI spec version: version not set + * + * Generated by: https://github.com/swagger-api/swagger-codegen.git + */ + +package responsebody + +type ExamplepbResponseBodyMessage struct { + + Request string `json:"request,omitempty"` + + Response ExamplepbResponseBodyMessageResponse `json:"response,omitempty"` +} diff --git a/examples/clients/responsebody/examplepb_response_body_message_response.go b/examples/clients/responsebody/examplepb_response_body_message_response.go new file mode 100644 index 00000000000..436f1dff6cb --- /dev/null +++ b/examples/clients/responsebody/examplepb_response_body_message_response.go @@ -0,0 +1,16 @@ +/* + * examples/proto/examplepb/response_body_service.proto + * + * No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen) + * + * OpenAPI spec version: version not set + * + * Generated by: https://github.com/swagger-api/swagger-codegen.git + */ + +package responsebody + +type ExamplepbResponseBodyMessageResponse struct { + + Data string `json:"data,omitempty"` +} diff --git a/examples/clients/responsebody/examplepb_response_body_out.go b/examples/clients/responsebody/examplepb_response_body_out.go new file mode 100644 index 00000000000..72fae8bd52a --- /dev/null +++ b/examples/clients/responsebody/examplepb_response_body_out.go @@ -0,0 +1,16 @@ +/* + * examples/proto/examplepb/response_body_service.proto + * + * No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen) + * + * OpenAPI spec version: version not set + * + * Generated by: https://github.com/swagger-api/swagger-codegen.git + */ + +package responsebody + +type ExamplepbResponseBodyOut struct { + + Response ExamplepbResponseBodyOutResponse `json:"response,omitempty"` +} diff --git a/examples/clients/responsebody/examplepb_response_body_out_response.go b/examples/clients/responsebody/examplepb_response_body_out_response.go new file mode 100644 index 00000000000..ca14fa3e6a1 --- /dev/null +++ b/examples/clients/responsebody/examplepb_response_body_out_response.go @@ -0,0 +1,16 @@ +/* + * examples/proto/examplepb/response_body_service.proto + * + * No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen) + * + * OpenAPI spec version: version not set + * + * Generated by: https://github.com/swagger-api/swagger-codegen.git + */ + +package responsebody + +type ExamplepbResponseBodyOutResponse struct { + + Data string `json:"data,omitempty"` +} diff --git a/examples/clients/responsebody/examplepb_response_body_req.go b/examples/clients/responsebody/examplepb_response_body_req.go new file mode 100644 index 00000000000..5d9a56d9546 --- /dev/null +++ b/examples/clients/responsebody/examplepb_response_body_req.go @@ -0,0 +1,16 @@ +/* + * examples/proto/examplepb/response_body_service.proto + * + * No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen) + * + * OpenAPI spec version: version not set + * + * Generated by: https://github.com/swagger-api/swagger-codegen.git + */ + +package responsebody + +type ExamplepbResponseBodyReq struct { + + Data string `json:"data,omitempty"` +} diff --git a/examples/clients/responsebody/response_body_service_api.go b/examples/clients/responsebody/response_body_service_api.go new file mode 100644 index 00000000000..15fa4e5bb63 --- /dev/null +++ b/examples/clients/responsebody/response_body_service_api.go @@ -0,0 +1,99 @@ +/* + * examples/proto/examplepb/response_body_service.proto + * + * No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen) + * + * OpenAPI spec version: version not set + * + * Generated by: https://github.com/swagger-api/swagger-codegen.git + */ + +package responsebody + +import ( + "net/url" + "strings" + "encoding/json" + "fmt" +) + +type ResponseBodyServiceApi struct { + Configuration *Configuration +} + +func NewResponseBodyServiceApi() *ResponseBodyServiceApi { + configuration := NewConfiguration() + return &ResponseBodyServiceApi{ + Configuration: configuration, + } +} + +func NewResponseBodyServiceApiWithBasePath(basePath string) *ResponseBodyServiceApi { + configuration := NewConfiguration() + configuration.BasePath = basePath + + return &ResponseBodyServiceApi{ + Configuration: configuration, + } +} + +/** + * + * + * @param data + * @return *ExamplepbResponseBodyOutResponse + */ +func (a ResponseBodyServiceApi) GetResponseBody(data string) (*ExamplepbResponseBodyOutResponse, *APIResponse, error) { + + var localVarHttpMethod = strings.ToUpper("Get") + // create path and map variables + localVarPath := a.Configuration.BasePath + "/responsebody/{data}" + localVarPath = strings.Replace(localVarPath, "{"+"data"+"}", fmt.Sprintf("%v", data), -1) + + localVarHeaderParams := make(map[string]string) + localVarQueryParams := url.Values{} + localVarFormParams := make(map[string]string) + var localVarPostBody interface{} + var localVarFileName string + var localVarFileBytes []byte + // add default headers if any + for key := range a.Configuration.DefaultHeader { + localVarHeaderParams[key] = a.Configuration.DefaultHeader[key] + } + + // to determine the Content-Type header + localVarHttpContentTypes := []string{ "application/json", } + + // set Content-Type header + localVarHttpContentType := a.Configuration.APIClient.SelectHeaderContentType(localVarHttpContentTypes) + if localVarHttpContentType != "" { + localVarHeaderParams["Content-Type"] = localVarHttpContentType + } + // to determine the Accept header + localVarHttpHeaderAccepts := []string{ + "application/json", + } + + // set Accept header + localVarHttpHeaderAccept := a.Configuration.APIClient.SelectHeaderAccept(localVarHttpHeaderAccepts) + if localVarHttpHeaderAccept != "" { + localVarHeaderParams["Accept"] = localVarHttpHeaderAccept + } + var successPayload = new(ExamplepbResponseBodyOutResponse) + localVarHttpResponse, err := a.Configuration.APIClient.CallAPI(localVarPath, localVarHttpMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, localVarFileName, localVarFileBytes) + + var localVarURL, _ = url.Parse(localVarPath) + localVarURL.RawQuery = localVarQueryParams.Encode() + var localVarAPIResponse = &APIResponse{Operation: "GetResponseBody", Method: localVarHttpMethod, RequestURL: localVarURL.String()} + if localVarHttpResponse != nil { + localVarAPIResponse.Response = localVarHttpResponse.RawResponse + localVarAPIResponse.Payload = localVarHttpResponse.Body() + } + + if err != nil { + return successPayload, localVarAPIResponse, err + } + err = json.Unmarshal(localVarHttpResponse.Body(), &successPayload) + return successPayload, localVarAPIResponse, err +} + diff --git a/examples/gateway/gateway.go b/examples/gateway/gateway.go index ffdbe2ca246..297ebe63d8b 100644 --- a/examples/gateway/gateway.go +++ b/examples/gateway/gateway.go @@ -22,6 +22,7 @@ func newGateway(ctx context.Context, conn *grpc.ClientConn, opts []gwruntime.Ser examplepb.RegisterStreamServiceHandler, examplepb.RegisterABitOfEverythingServiceHandler, examplepb.RegisterFlowCombinationHandler, + examplepb.RegisterResponseBodyServiceHandler, } { if err := f(ctx, mux, conn); err != nil { return nil, err diff --git a/examples/integration/integration_test.go b/examples/integration/integration_test.go index 0c2885c4557..de4e8bb0ad8 100644 --- a/examples/integration/integration_test.go +++ b/examples/integration/integration_test.go @@ -18,7 +18,7 @@ import ( "github.com/golang/protobuf/proto" "github.com/golang/protobuf/ptypes/empty" gw "github.com/grpc-ecosystem/grpc-gateway/examples/proto/examplepb" - sub "github.com/grpc-ecosystem/grpc-gateway/examples/proto/sub" + "github.com/grpc-ecosystem/grpc-gateway/examples/proto/sub" "github.com/grpc-ecosystem/grpc-gateway/runtime" "google.golang.org/grpc/codes" ) @@ -980,3 +980,36 @@ func TestInvalidArgument(t *testing.T) { t.Logf("%s", buf) } } + +func TestResponseBody(t *testing.T) { + if testing.Short() { + t.Skip() + return + } + + testResponseBody(t, 8080) +} + +func testResponseBody(t *testing.T, port int) { + url := fmt.Sprintf("http://localhost:%d/responsebody/foo", port) + resp, err := http.Get(url) + if err != nil { + t.Errorf("http.Get(%q) failed with %v; want success", url, err) + return + } + defer resp.Body.Close() + buf, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Errorf("iotuil.ReadAll(resp.Body) failed with %v; want success", err) + return + } + + if got, want := resp.StatusCode, http.StatusOK; got != want { + t.Errorf("resp.StatusCode = %d; want %d", got, want) + t.Logf("%s", buf) + } + + if got, want := string(buf), `{"data":"foo"}`; got != want { + t.Errorf("response = %q; want %q", got, want) + } +} diff --git a/examples/proto/examplepb/BUILD.bazel b/examples/proto/examplepb/BUILD.bazel index 066ccd467a9..09b77d200ab 100644 --- a/examples/proto/examplepb/BUILD.bazel +++ b/examples/proto/examplepb/BUILD.bazel @@ -9,6 +9,7 @@ package(default_visibility = ["//visibility:public"]) # gazelle:exclude flow_combination.pb.gw.go # gazelle:exclude stream.pb.gw.go # gazelle:exclude wrappers.pb.gw.go +# gazelle:exclude response_body_service.pb.gw.go proto_library( name = "examplepb_proto", @@ -19,6 +20,7 @@ proto_library( "stream.proto", "unannotated_echo_service.proto", "wrappers.proto", + "response_body_service.proto", ], deps = [ "//examples/proto/sub:sub_proto", diff --git a/examples/proto/examplepb/response_body_service.pb.go b/examples/proto/examplepb/response_body_service.pb.go new file mode 100644 index 00000000000..fcad76e5898 --- /dev/null +++ b/examples/proto/examplepb/response_body_service.pb.go @@ -0,0 +1,242 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: examples/proto/examplepb/response_body_service.proto + +package examplepb + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" +import _ "google.golang.org/genproto/googleapis/api/annotations" + +import ( + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +type ResponseBodyIn struct { + Data string `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ResponseBodyIn) Reset() { *m = ResponseBodyIn{} } +func (m *ResponseBodyIn) String() string { return proto.CompactTextString(m) } +func (*ResponseBodyIn) ProtoMessage() {} +func (*ResponseBodyIn) Descriptor() ([]byte, []int) { + return fileDescriptor_response_body_service_5010ebf28bf7b965, []int{0} +} +func (m *ResponseBodyIn) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ResponseBodyIn.Unmarshal(m, b) +} +func (m *ResponseBodyIn) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ResponseBodyIn.Marshal(b, m, deterministic) +} +func (dst *ResponseBodyIn) XXX_Merge(src proto.Message) { + xxx_messageInfo_ResponseBodyIn.Merge(dst, src) +} +func (m *ResponseBodyIn) XXX_Size() int { + return xxx_messageInfo_ResponseBodyIn.Size(m) +} +func (m *ResponseBodyIn) XXX_DiscardUnknown() { + xxx_messageInfo_ResponseBodyIn.DiscardUnknown(m) +} + +var xxx_messageInfo_ResponseBodyIn proto.InternalMessageInfo + +func (m *ResponseBodyIn) GetData() string { + if m != nil { + return m.Data + } + return "" +} + +type ResponseBodyOut struct { + Response *ResponseBodyOut_Response `protobuf:"bytes,2,opt,name=response,proto3" json:"response,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ResponseBodyOut) Reset() { *m = ResponseBodyOut{} } +func (m *ResponseBodyOut) String() string { return proto.CompactTextString(m) } +func (*ResponseBodyOut) ProtoMessage() {} +func (*ResponseBodyOut) Descriptor() ([]byte, []int) { + return fileDescriptor_response_body_service_5010ebf28bf7b965, []int{1} +} +func (m *ResponseBodyOut) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ResponseBodyOut.Unmarshal(m, b) +} +func (m *ResponseBodyOut) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ResponseBodyOut.Marshal(b, m, deterministic) +} +func (dst *ResponseBodyOut) XXX_Merge(src proto.Message) { + xxx_messageInfo_ResponseBodyOut.Merge(dst, src) +} +func (m *ResponseBodyOut) XXX_Size() int { + return xxx_messageInfo_ResponseBodyOut.Size(m) +} +func (m *ResponseBodyOut) XXX_DiscardUnknown() { + xxx_messageInfo_ResponseBodyOut.DiscardUnknown(m) +} + +var xxx_messageInfo_ResponseBodyOut proto.InternalMessageInfo + +func (m *ResponseBodyOut) GetResponse() *ResponseBodyOut_Response { + if m != nil { + return m.Response + } + return nil +} + +type ResponseBodyOut_Response struct { + Data string `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ResponseBodyOut_Response) Reset() { *m = ResponseBodyOut_Response{} } +func (m *ResponseBodyOut_Response) String() string { return proto.CompactTextString(m) } +func (*ResponseBodyOut_Response) ProtoMessage() {} +func (*ResponseBodyOut_Response) Descriptor() ([]byte, []int) { + return fileDescriptor_response_body_service_5010ebf28bf7b965, []int{1, 0} +} +func (m *ResponseBodyOut_Response) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ResponseBodyOut_Response.Unmarshal(m, b) +} +func (m *ResponseBodyOut_Response) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ResponseBodyOut_Response.Marshal(b, m, deterministic) +} +func (dst *ResponseBodyOut_Response) XXX_Merge(src proto.Message) { + xxx_messageInfo_ResponseBodyOut_Response.Merge(dst, src) +} +func (m *ResponseBodyOut_Response) XXX_Size() int { + return xxx_messageInfo_ResponseBodyOut_Response.Size(m) +} +func (m *ResponseBodyOut_Response) XXX_DiscardUnknown() { + xxx_messageInfo_ResponseBodyOut_Response.DiscardUnknown(m) +} + +var xxx_messageInfo_ResponseBodyOut_Response proto.InternalMessageInfo + +func (m *ResponseBodyOut_Response) GetData() string { + if m != nil { + return m.Data + } + return "" +} + +func init() { + proto.RegisterType((*ResponseBodyIn)(nil), "grpc.gateway.examples.examplepb.ResponseBodyIn") + proto.RegisterType((*ResponseBodyOut)(nil), "grpc.gateway.examples.examplepb.ResponseBodyOut") + proto.RegisterType((*ResponseBodyOut_Response)(nil), "grpc.gateway.examples.examplepb.ResponseBodyOut.Response") +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// ResponseBodyServiceClient is the client API for ResponseBodyService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type ResponseBodyServiceClient interface { + GetResponseBody(ctx context.Context, in *ResponseBodyIn, opts ...grpc.CallOption) (*ResponseBodyOut, error) +} + +type responseBodyServiceClient struct { + cc *grpc.ClientConn +} + +func NewResponseBodyServiceClient(cc *grpc.ClientConn) ResponseBodyServiceClient { + return &responseBodyServiceClient{cc} +} + +func (c *responseBodyServiceClient) GetResponseBody(ctx context.Context, in *ResponseBodyIn, opts ...grpc.CallOption) (*ResponseBodyOut, error) { + out := new(ResponseBodyOut) + err := c.cc.Invoke(ctx, "/grpc.gateway.examples.examplepb.ResponseBodyService/GetResponseBody", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// ResponseBodyServiceServer is the server API for ResponseBodyService service. +type ResponseBodyServiceServer interface { + GetResponseBody(context.Context, *ResponseBodyIn) (*ResponseBodyOut, error) +} + +func RegisterResponseBodyServiceServer(s *grpc.Server, srv ResponseBodyServiceServer) { + s.RegisterService(&_ResponseBodyService_serviceDesc, srv) +} + +func _ResponseBodyService_GetResponseBody_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ResponseBodyIn) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ResponseBodyServiceServer).GetResponseBody(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/grpc.gateway.examples.examplepb.ResponseBodyService/GetResponseBody", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ResponseBodyServiceServer).GetResponseBody(ctx, req.(*ResponseBodyIn)) + } + return interceptor(ctx, in, info, handler) +} + +var _ResponseBodyService_serviceDesc = grpc.ServiceDesc{ + ServiceName: "grpc.gateway.examples.examplepb.ResponseBodyService", + HandlerType: (*ResponseBodyServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetResponseBody", + Handler: _ResponseBodyService_GetResponseBody_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "examples/proto/examplepb/response_body_service.proto", +} + +func init() { + proto.RegisterFile("examples/proto/examplepb/response_body_service.proto", fileDescriptor_response_body_service_5010ebf28bf7b965) +} + +var fileDescriptor_response_body_service_5010ebf28bf7b965 = []byte{ + // 257 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x32, 0x49, 0xad, 0x48, 0xcc, + 0x2d, 0xc8, 0x49, 0x2d, 0xd6, 0x2f, 0x28, 0xca, 0x2f, 0xc9, 0xd7, 0x87, 0x72, 0x0b, 0x92, 0xf4, + 0x8b, 0x52, 0x8b, 0x0b, 0xf2, 0xf3, 0x8a, 0x53, 0xe3, 0x93, 0xf2, 0x53, 0x2a, 0xe3, 0x8b, 0x53, + 0x8b, 0xca, 0x32, 0x93, 0x53, 0xf5, 0xc0, 0xaa, 0x84, 0xe4, 0xd3, 0x8b, 0x0a, 0x92, 0xf5, 0xd2, + 0x13, 0x4b, 0x52, 0xcb, 0x13, 0x2b, 0xf5, 0x60, 0x46, 0xe8, 0xc1, 0x35, 0x4b, 0xc9, 0xa4, 0xe7, + 0xe7, 0xa7, 0xe7, 0xa4, 0xea, 0x27, 0x16, 0x64, 0xea, 0x27, 0xe6, 0xe5, 0xe5, 0x97, 0x24, 0x96, + 0x64, 0xe6, 0xe7, 0x15, 0x43, 0xb4, 0x2b, 0xa9, 0x70, 0xf1, 0x05, 0x41, 0x4d, 0x77, 0xca, 0x4f, + 0xa9, 0xf4, 0xcc, 0x13, 0x12, 0xe2, 0x62, 0x49, 0x49, 0x2c, 0x49, 0x94, 0x60, 0x54, 0x60, 0xd4, + 0xe0, 0x0c, 0x02, 0xb3, 0x95, 0x3a, 0x18, 0xb9, 0xf8, 0x91, 0x95, 0xf9, 0x97, 0x96, 0x08, 0x85, + 0x72, 0x71, 0xc0, 0xdc, 0x25, 0xc1, 0xa4, 0xc0, 0xa8, 0xc1, 0x6d, 0x64, 0xa9, 0x47, 0xc0, 0x2d, + 0x7a, 0x68, 0x66, 0xc0, 0xf9, 0x41, 0x70, 0xa3, 0xa4, 0xe4, 0xb8, 0x38, 0x60, 0xa2, 0xd8, 0x9c, + 0x62, 0xb4, 0x85, 0x91, 0x4b, 0x18, 0xd9, 0x98, 0x60, 0x48, 0x68, 0x08, 0xcd, 0x61, 0xe4, 0xe2, + 0x77, 0x4f, 0x2d, 0x41, 0x96, 0x12, 0xd2, 0x27, 0xc9, 0x41, 0x9e, 0x79, 0x52, 0x06, 0xa4, 0xfa, + 0x40, 0x49, 0xad, 0xe9, 0xf2, 0x93, 0xc9, 0x4c, 0x0a, 0x42, 0x22, 0xf0, 0x48, 0x02, 0xc5, 0x91, + 0x7e, 0x35, 0xc8, 0xad, 0xb5, 0x49, 0x70, 0x6f, 0x39, 0x71, 0x47, 0x71, 0xc2, 0x0d, 0x49, 0x62, + 0x03, 0x87, 0xbd, 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0xe8, 0x4a, 0x08, 0x5b, 0xf2, 0x01, 0x00, + 0x00, +} diff --git a/examples/proto/examplepb/response_body_service.pb.gw.go b/examples/proto/examplepb/response_body_service.pb.gw.go new file mode 100644 index 00000000000..ac15844a045 --- /dev/null +++ b/examples/proto/examplepb/response_body_service.pb.gw.go @@ -0,0 +1,143 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: examples/proto/examplepb/response_body_service.proto + +/* +Package examplepb is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package examplepb + +import ( + "io" + "net/http" + + "github.com/golang/protobuf/proto" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/grpc-ecosystem/grpc-gateway/utilities" + "golang.org/x/net/context" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/status" +) + +var _ codes.Code +var _ io.Reader +var _ status.Status +var _ = runtime.String +var _ = utilities.NewDoubleArray + +func request_ResponseBodyService_GetResponseBody_0(ctx context.Context, marshaler runtime.Marshaler, client ResponseBodyServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ResponseBodyIn + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["data"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "data") + } + + protoReq.Data, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "data", err) + } + + msg, err := client.GetResponseBody(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +// RegisterResponseBodyServiceHandlerFromEndpoint is same as RegisterResponseBodyServiceHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterResponseBodyServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.Dial(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterResponseBodyServiceHandler(ctx, mux, conn) +} + +// RegisterResponseBodyServiceHandler registers the http handlers for service ResponseBodyService to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterResponseBodyServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterResponseBodyServiceHandlerClient(ctx, mux, NewResponseBodyServiceClient(conn)) +} + +// RegisterResponseBodyServiceHandlerClient registers the http handlers for service ResponseBodyService +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "ResponseBodyServiceClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "ResponseBodyServiceClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "ResponseBodyServiceClient" to call the correct interceptors. +func RegisterResponseBodyServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client ResponseBodyServiceClient) error { + + mux.Handle("GET", pattern_ResponseBodyService_GetResponseBody_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + if cn, ok := w.(http.CloseNotifier); ok { + go func(done <-chan struct{}, closed <-chan bool) { + select { + case <-done: + case <-closed: + cancel() + } + }(ctx.Done(), cn.CloseNotify()) + } + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_ResponseBodyService_GetResponseBody_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_ResponseBodyService_GetResponseBody_0(ctx, mux, outboundMarshaler, w, req, response_ResponseBodyService_GetResponseBody_0{resp}, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +type response_ResponseBodyService_GetResponseBody_0 struct { + proto.Message +} + +func (m response_ResponseBodyService_GetResponseBody_0) XXX_ResponseBody() interface{} { + response := m.Message.(*ResponseBodyOut) + return response.Response +} + +var ( + pattern_ResponseBodyService_GetResponseBody_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1}, []string{"responsebody", "data"}, "")) +) + +var ( + forward_ResponseBodyService_GetResponseBody_0 = runtime.ForwardResponseMessage +) diff --git a/examples/proto/examplepb/response_body_service.proto b/examples/proto/examplepb/response_body_service.proto new file mode 100644 index 00000000000..65d430e348d --- /dev/null +++ b/examples/proto/examplepb/response_body_service.proto @@ -0,0 +1,26 @@ +syntax = "proto3"; +option go_package = "examplepb"; + +package grpc.gateway.examples.examplepb; + +import "google/api/annotations.proto"; + +message ResponseBodyIn { + string data = 1; +} + +message ResponseBodyOut { + message Response { + string data = 1; + } + Response response = 2; +} + +service ResponseBodyService { + rpc GetResponseBody(ResponseBodyIn) returns (ResponseBodyOut) { + option (google.api.http) = { + get: "/responsebody/{data}" + response_body: "response" + }; + } +} diff --git a/examples/proto/examplepb/response_body_service.swagger.json b/examples/proto/examplepb/response_body_service.swagger.json new file mode 100644 index 00000000000..c13e7646bcf --- /dev/null +++ b/examples/proto/examplepb/response_body_service.swagger.json @@ -0,0 +1,61 @@ +{ + "swagger": "2.0", + "info": { + "title": "examples/proto/examplepb/response_body_service.proto", + "version": "version not set" + }, + "schemes": [ + "http", + "https" + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "paths": { + "/responsebody/{data}": { + "get": { + "operationId": "GetResponseBody", + "responses": { + "200": { + "description": "", + "schema": { + "$ref": "#/definitions/examplepbResponseBodyOutResponse" + } + } + }, + "parameters": [ + { + "name": "data", + "in": "path", + "required": true, + "type": "string" + } + ], + "tags": [ + "ResponseBodyService" + ] + } + } + }, + "definitions": { + "examplepbResponseBodyOut": { + "type": "object", + "properties": { + "response": { + "$ref": "#/definitions/examplepbResponseBodyOutResponse" + } + } + }, + "examplepbResponseBodyOutResponse": { + "type": "object", + "properties": { + "data": { + "type": "string" + } + } + } + } +} diff --git a/examples/server/BUILD.bazel b/examples/server/BUILD.bazel index 61fff8ab6ec..afd74848a09 100644 --- a/examples/server/BUILD.bazel +++ b/examples/server/BUILD.bazel @@ -10,6 +10,7 @@ go_library( "flow_combination.go", "main.go", "unannotatedecho.go", + "responsebody.go", ], importpath = "github.com/grpc-ecosystem/grpc-gateway/examples/server", deps = [ diff --git a/examples/server/main.go b/examples/server/main.go index 4cd385f879d..179485a3601 100644 --- a/examples/server/main.go +++ b/examples/server/main.go @@ -29,6 +29,7 @@ func Run(ctx context.Context, network, address string) error { abe := newABitOfEverythingServer() examples.RegisterABitOfEverythingServiceServer(s, abe) examples.RegisterStreamServiceServer(s, abe) + examples.RegisterResponseBodyServiceServer(s, newResponseBodyServer()) go func() { defer s.GracefulStop() diff --git a/examples/server/responsebody.go b/examples/server/responsebody.go new file mode 100644 index 00000000000..62cff2f47db --- /dev/null +++ b/examples/server/responsebody.go @@ -0,0 +1,23 @@ +package server + +import ( + "context" + + examples "github.com/grpc-ecosystem/grpc-gateway/examples/proto/examplepb" +) + +// Implements of ResponseBodyServiceServer + +type responseBodyServer struct{} + +func newResponseBodyServer() examples.ResponseBodyServiceServer { + return new(responseBodyServer) +} + +func (s *responseBodyServer) GetResponseBody(ctx context.Context, req *examples.ResponseBodyIn) (*examples.ResponseBodyOut, error) { + return &examples.ResponseBodyOut{ + Response: &examples.ResponseBodyOut_Response{ + Data: req.Data, + }, + }, nil +} diff --git a/protoc-gen-grpc-gateway/descriptor/registry.go b/protoc-gen-grpc-gateway/descriptor/registry.go index b09e193c3da..9132e0a6d4d 100644 --- a/protoc-gen-grpc-gateway/descriptor/registry.go +++ b/protoc-gen-grpc-gateway/descriptor/registry.go @@ -46,6 +46,14 @@ type Registry struct { // mergeFileName target swagger file name after merge mergeFileName string + + // allowRepeatedFieldsInBody permits repeated field in body field path of `google.api.http` annotation option + allowRepeatedFieldsInBody bool + + // useJSONNamesForFields if true json tag name is used for generating fields in swagger definitions, + // otherwise the original proto name is used. It's helpful for synchronizing the swagger definition + // with grpc-gateway response, if it uses json tags for marshaling. + useJSONNamesForFields bool } // NewRegistry returns a new Registry. @@ -318,6 +326,28 @@ func (r *Registry) SetMergeFileName(mergeFileName string) { r.mergeFileName = mergeFileName } +// SetAllowRepeatedFieldsInBody controls whether repeated field can be used +// in `body` and `response_body` (`google.api.http` annotation option) field path or not +func (r *Registry) SetAllowRepeatedFieldsInBody(allow bool) { + r.allowRepeatedFieldsInBody = allow +} + +// IsAllowRepeatedFieldsInBody checks if repeated field can be used +// in `body` and `response_body` (`google.api.http` annotation option) field path or not +func (r *Registry) IsAllowRepeatedFieldsInBody() bool { + return r.allowRepeatedFieldsInBody +} + +// SetUseJSONNamesForFields sets useJSONNamesForFields +func (r *Registry) SetUseJSONNamesForFields(use bool) { + r.useJSONNamesForFields = use +} + +// GetUseJSONNamesForFields returns useJSONNamesForFields +func (r *Registry) GetUseJSONNamesForFields() bool { + return r.useJSONNamesForFields +} + // GetMergeFileName return the target merge swagger file name func (r *Registry) GetMergeFileName() string { return r.mergeFileName diff --git a/protoc-gen-grpc-gateway/descriptor/services.go b/protoc-gen-grpc-gateway/descriptor/services.go index c200e57e275..56dd9a8da2d 100644 --- a/protoc-gen-grpc-gateway/descriptor/services.go +++ b/protoc-gen-grpc-gateway/descriptor/services.go @@ -143,6 +143,11 @@ func (r *Registry) newMethod(svc *Service, md *descriptor.MethodDescriptorProto, return nil, err } + b.ResponseBody, err = r.newResponse(meth, opts.ResponseBody) + if err != nil { + return nil, err + } + return b, nil } @@ -238,6 +243,19 @@ func (r *Registry) newBody(meth *Method, path string) (*Body, error) { return &Body{FieldPath: FieldPath(fields)}, nil } +func (r *Registry) newResponse(meth *Method, path string) (*Body, error) { + msg := meth.ResponseType + switch path { + case "", "*": + return nil, nil + } + fields, err := r.resolveFieldPath(msg, path) + if err != nil { + return nil, err + } + return &Body{FieldPath: FieldPath(fields)}, nil +} + // lookupField looks up a field named "name" within "msg". // It returns nil if no such field found. func lookupField(msg *Message, name string) *Field { @@ -277,7 +295,7 @@ func (r *Registry) resolveFieldPath(msg *Message, path string) ([]FieldPathCompo if f == nil { return nil, fmt.Errorf("no field %q found in %s", path, root.GetName()) } - if f.GetLabel() == descriptor.FieldDescriptorProto_LABEL_REPEATED { + if !r.allowRepeatedFieldsInBody && f.GetLabel() == descriptor.FieldDescriptorProto_LABEL_REPEATED { return nil, fmt.Errorf("repeated field not allowed in field path: %s in %s", f.GetName(), path) } result = append(result, FieldPathComponent{Name: c, Target: f}) diff --git a/protoc-gen-grpc-gateway/descriptor/types.go b/protoc-gen-grpc-gateway/descriptor/types.go index 6346b68e956..1a6e1cce527 100644 --- a/protoc-gen-grpc-gateway/descriptor/types.go +++ b/protoc-gen-grpc-gateway/descriptor/types.go @@ -4,7 +4,7 @@ import ( "fmt" "strings" - descriptor "github.com/golang/protobuf/protoc-gen-go/descriptor" + "github.com/golang/protobuf/protoc-gen-go/descriptor" gogen "github.com/golang/protobuf/protoc-gen-go/generator" "github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway/httprule" ) @@ -175,6 +175,8 @@ type Binding struct { PathParams []Parameter // Body describes parameters provided in HTTP request body. Body *Body + // ResponseBody describes field in response struct to marshal in HTTP response body. + ResponseBody *Body } // ExplicitParams returns a list of explicitly bound parameters of "b", @@ -227,10 +229,11 @@ func (p Parameter) ConvertFuncExpr() (string, error) { return conv, nil } -// Body describes a http requtest body to be sent to the method. +// Body describes a http (request|response) body to be sent to the (method|client). +// This is used in body and response_body options in google.api.HttpRule type Body struct { - // FieldPath is a path to a proto field which the request body is mapped to. - // The request body is mapped to the request type itself if FieldPath is empty. + // FieldPath is a path to a proto field which the (request|response) body is mapped to. + // The (request|response) body is mapped to the (request|response) type itself if FieldPath is empty. FieldPath FieldPath } diff --git a/protoc-gen-grpc-gateway/gengateway/template.go b/protoc-gen-grpc-gateway/gengateway/template.go index 36435750d66..42c42c3ccaf 100644 --- a/protoc-gen-grpc-gateway/gengateway/template.go +++ b/protoc-gen-grpc-gateway/gengateway/template.go @@ -385,14 +385,33 @@ func Register{{$svc.GetName}}{{$.RegisterFuncSuffix}}Client(ctx context.Context, {{if $m.GetServerStreaming}} forward_{{$svc.GetName}}_{{$m.GetName}}_{{$b.Index}}(ctx, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...) {{else}} + {{ if $b.ResponseBody }} + forward_{{$svc.GetName}}_{{$m.GetName}}_{{$b.Index}}(ctx, mux, outboundMarshaler, w, req, response_{{$svc.GetName}}_{{$m.GetName}}_{{$b.Index}}{resp}, mux.GetForwardResponseOptions()...) + {{ else }} forward_{{$svc.GetName}}_{{$m.GetName}}_{{$b.Index}}(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) {{end}} + {{end}} }) {{end}} {{end}} return nil } +{{range $m := $svc.Methods}} +{{range $b := $m.Bindings}} +{{if $b.ResponseBody}} +type response_{{$svc.GetName}}_{{$m.GetName}}_{{$b.Index}} struct { + proto.Message +} + +func (m response_{{$svc.GetName}}_{{$m.GetName}}_{{$b.Index}}) XXX_ResponseBody() interface{} { + response := m.Message.(*{{$m.ResponseType.GoType $m.Service.File.GoPkg.Path}}) + return {{$b.ResponseBody.AssignableExpr "response"}} +} +{{end}} +{{end}} +{{end}} + var ( {{range $m := $svc.Methods}} {{range $b := $m.Bindings}} diff --git a/protoc-gen-grpc-gateway/main.go b/protoc-gen-grpc-gateway/main.go index b9d2dcfca6f..276e8fc9df0 100644 --- a/protoc-gen-grpc-gateway/main.go +++ b/protoc-gen-grpc-gateway/main.go @@ -22,13 +22,14 @@ import ( ) var ( - importPrefix = flag.String("import_prefix", "", "prefix to be added to go package paths for imported proto files") - importPath = flag.String("import_path", "", "used as the package if no input files declare go_package. If it contains slashes, everything up to the rightmost slash is ignored.") - registerFuncSuffix = flag.String("register_func_suffix", "Handler", "used to construct names of generated Register* methods.") - useRequestContext = flag.Bool("request_context", true, "determine whether to use http.Request's context or not") - allowDeleteBody = flag.Bool("allow_delete_body", false, "unless set, HTTP DELETE methods may not have a body") - grpcAPIConfiguration = flag.String("grpc_api_configuration", "", "path to gRPC API Configuration in YAML format") - pathType = flag.String("paths", "", "specifies how the paths of generated files are structured") + importPrefix = flag.String("import_prefix", "", "prefix to be added to go package paths for imported proto files") + importPath = flag.String("import_path", "", "used as the package if no input files declare go_package. If it contains slashes, everything up to the rightmost slash is ignored.") + registerFuncSuffix = flag.String("register_func_suffix", "Handler", "used to construct names of generated Register* methods.") + useRequestContext = flag.Bool("request_context", true, "determine whether to use http.Request's context or not") + allowDeleteBody = flag.Bool("allow_delete_body", false, "unless set, HTTP DELETE methods may not have a body") + grpcAPIConfiguration = flag.String("grpc_api_configuration", "", "path to gRPC API Configuration in YAML format") + pathType = flag.String("paths", "", "specifies how the paths of generated files are structured") + allowRepeatedFieldsInBody = flag.Bool("allow_repeated_fields_in_body", false, "allows to use repeated field in `body` and `response_body` field of `google.api.http` annotation option") ) func main() { @@ -75,6 +76,7 @@ func main() { reg.SetPrefix(*importPrefix) reg.SetImportPath(*importPath) reg.SetAllowDeleteBody(*allowDeleteBody) + reg.SetAllowRepeatedFieldsInBody(*allowRepeatedFieldsInBody) if err := reg.Load(req); err != nil { emitError(err) return diff --git a/protoc-gen-swagger/genswagger/template.go b/protoc-gen-swagger/genswagger/template.go index 99170d72dab..3f82a52a7a5 100644 --- a/protoc-gen-swagger/genswagger/template.go +++ b/protoc-gen-swagger/genswagger/template.go @@ -283,7 +283,13 @@ func renderMessagesAsDefinition(messages messageMap, d swaggerDefinitionsObject, panic(err) } - schema.Properties = append(schema.Properties, keyVal{f.GetName(), fieldValue}) + kv := keyVal{Value: fieldValue} + if reg.GetUseJSONNamesForFields() { + kv.Key = f.GetJsonName() + } else { + kv.Key = f.GetName() + } + schema.Properties = append(schema.Properties, kv) } d[fullyQualifiedNameToSwaggerName(msg.FQMN(), reg)] = schema } @@ -659,6 +665,24 @@ func renderServices(services []*descriptor.Service, paths swaggerPathsObject, re methProtoPath := protoPathIndex(reflect.TypeOf((*pbdescriptor.ServiceDescriptorProto)(nil)), "Method") desc := "" + var responseSchema swaggerSchemaObject + + if b.ResponseBody == nil || len(b.ResponseBody.FieldPath) == 0 { + responseSchema = swaggerSchemaObject{ + schemaCore: schemaCore{ + Ref: fmt.Sprintf("#/definitions/%s", fullyQualifiedNameToSwaggerName(meth.ResponseType.FQMN(), reg)), + }, + } + } else { + // This is resolving the value of response_body in the google.api.HttpRule + lastField := b.ResponseBody.FieldPath[len(b.ResponseBody.FieldPath)-1] + responseSchema = schemaOfField(lastField.Target, reg, customRefs) + if responseSchema.Description != "" { + desc = responseSchema.Description + } else { + desc = fieldProtoComments(reg, lastField.Target.Message, lastField.Target) + } + } if meth.GetServerStreaming() { desc += "(streaming responses)" } @@ -668,11 +692,7 @@ func renderServices(services []*descriptor.Service, paths swaggerPathsObject, re Responses: swaggerResponsesObject{ "200": swaggerResponseObject{ Description: desc, - Schema: swaggerSchemaObject{ - schemaCore: schemaCore{ - Ref: fmt.Sprintf("#/definitions/%s", fullyQualifiedNameToSwaggerName(meth.ResponseType.FQMN(), reg)), - }, - }, + Schema: responseSchema, }, }, } diff --git a/protoc-gen-swagger/main.go b/protoc-gen-swagger/main.go index 674af5c8b7c..6c6cccb8d4a 100644 --- a/protoc-gen-swagger/main.go +++ b/protoc-gen-swagger/main.go @@ -15,12 +15,13 @@ import ( ) var ( - importPrefix = flag.String("import_prefix", "", "prefix to be added to go package paths for imported proto files") - file = flag.String("file", "-", "where to load data from") - allowDeleteBody = flag.Bool("allow_delete_body", false, "unless set, HTTP DELETE methods may not have a body") - grpcAPIConfiguration = flag.String("grpc_api_configuration", "", "path to gRPC API Configuration in YAML format") - allowMerge = flag.Bool("allow_merge", false, "if set, generation one swagger file out of multiple protos") - mergeFileName = flag.String("merge_file_name", "apidocs", "target swagger file name prefix after merge") + importPrefix = flag.String("import_prefix", "", "prefix to be added to go package paths for imported proto files") + file = flag.String("file", "-", "where to load data from") + allowDeleteBody = flag.Bool("allow_delete_body", false, "unless set, HTTP DELETE methods may not have a body") + grpcAPIConfiguration = flag.String("grpc_api_configuration", "", "path to gRPC API Configuration in YAML format") + allowMerge = flag.Bool("allow_merge", false, "if set, generation one swagger file out of multiple protos") + mergeFileName = flag.String("merge_file_name", "apidocs", "target swagger file name prefix after merge") + useJSONNamesForFields = flag.Bool("json_names_for_fields", false, "if it sets Field.GetJsonName() will be used for generating swagger definitions, otherwise Field.GetName() will be used") ) func main() { @@ -56,6 +57,7 @@ func main() { reg.SetAllowDeleteBody(*allowDeleteBody) reg.SetAllowMerge(*allowMerge) reg.SetMergeFileName(*mergeFileName) + reg.SetUseJSONNamesForFields(*useJSONNamesForFields) for k, v := range pkgMap { reg.AddPkgMap(k, v) } diff --git a/repositories.bzl b/repositories.bzl index 959b20cfffa..dc49d60eeee 100644 --- a/repositories.bzl +++ b/repositories.bzl @@ -28,12 +28,12 @@ go_library( """ def _googleapis_repository_impl(ctx): - googleapis_commit = "e1c0c726290a55065c0c46a62dacc9372939973b" + googleapis_commit = "3e68e19410baa7d78cdacc45b034eafe7467b439" ctx.download_and_extract( url = "https://github.com/googleapis/googleapis/archive/{commit}.tar.gz".format( commit = googleapis_commit, ), - sha256 = "9508971cb4a7c0fe03bc1bfafbd0abc9654c80b4c70e360a6c534938d06d8fb9", + sha256 = "eb98f16b037a27fd5fa6b3420a1e344014396d8ffbbab83c1adfe1296ea8572e", stripPrefix = "googleapis-{}".format(googleapis_commit), ) diff --git a/runtime/handler.go b/runtime/handler.go index 9d6bbc2a473..8ad9d766e1c 100644 --- a/runtime/handler.go +++ b/runtime/handler.go @@ -106,6 +106,12 @@ func handleForwardResponseTrailer(w http.ResponseWriter, md ServerMetadata) { } } +// responseBody interface contains method for getting field for marshaling to the response body +// this method is generated for response struct from the value of `response_body` in the `google.api.HttpRule` +type responseBody interface { + XXX_ResponseBody() interface{} +} + // ForwardResponseMessage forwards the message "resp" from gRPC server to REST client. func ForwardResponseMessage(ctx context.Context, mux *ServeMux, marshaler Marshaler, w http.ResponseWriter, req *http.Request, resp proto.Message, opts ...func(context.Context, http.ResponseWriter, proto.Message) error) { md, ok := ServerMetadataFromContext(ctx) @@ -120,8 +126,13 @@ func ForwardResponseMessage(ctx context.Context, mux *ServeMux, marshaler Marsha HTTPError(ctx, mux, marshaler, w, req, err) return } - - buf, err := marshaler.Marshal(resp) + var buf []byte + var err error + if rb, ok := resp.(responseBody); ok { + buf, err = marshaler.Marshal(rb.XXX_ResponseBody()) + } else { + buf, err = marshaler.Marshal(resp) + } if err != nil { grpclog.Infof("Marshal error: %v", err) HTTPError(ctx, mux, marshaler, w, req, err) diff --git a/runtime/mux.go b/runtime/mux.go index 6c06ab8c222..a184291e16a 100644 --- a/runtime/mux.go +++ b/runtime/mux.go @@ -5,7 +5,6 @@ import ( "net/http" "net/textproto" "strings" - "context" "github.com/golang/protobuf/proto" diff --git a/third_party/googleapis/README.grpc-gateway b/third_party/googleapis/README.grpc-gateway index cd723e52079..5c77aece630 100644 --- a/third_party/googleapis/README.grpc-gateway +++ b/third_party/googleapis/README.grpc-gateway @@ -3,7 +3,7 @@ Google APIs Project: Google APIs URL: https://github.com/google/googleapis -Revision: a9fb190cdb78ed9bb2d6bb3fb5b9ef46effa5df3 +Revision: 3544ab16c3342d790b00764251e348705991ea4b License: Apache License 2.0 diff --git a/third_party/googleapis/google/api/http.proto b/third_party/googleapis/google/api/http.proto index 5f8538a0164..2bd3a19bfa5 100644 --- a/third_party/googleapis/google/api/http.proto +++ b/third_party/googleapis/google/api/http.proto @@ -1,4 +1,4 @@ -// Copyright 2016 Google Inc. +// Copyright 2018 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -24,7 +24,7 @@ option java_package = "com.google.api"; option objc_class_prefix = "GAPI"; -// Defines the HTTP configuration for a service. It contains a list of +// Defines the HTTP configuration for an API service. It contains a list of // [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method // to one or more HTTP REST API methods. message Http { @@ -32,14 +32,22 @@ message Http { // // **NOTE:** All service configuration rules follow "last one wins" order. repeated HttpRule rules = 1; + + // When set to true, URL path parmeters will be fully URI-decoded except in + // cases of single segment matches in reserved expansion, where "%2F" will be + // left encoded. + // + // The default behavior is to not decode RFC 6570 reserved characters in multi + // segment matches. + bool fully_decode_reserved_expansion = 2; } // `HttpRule` defines the mapping of an RPC method to one or more HTTP -// REST APIs. The mapping determines what portions of the request -// message are populated from the path, query parameters, or body of -// the HTTP request. The mapping is typically specified as an -// `google.api.http` annotation, see "google/api/annotations.proto" -// for details. +// REST API methods. The mapping specifies how different portions of the RPC +// request message are mapped to URL path, URL query parameters, and +// HTTP request body. The mapping is typically specified as an +// `google.api.http` annotation on the RPC method, +// see "google/api/annotations.proto" for details. // // The mapping consists of a field specifying the path template and // method kind. The path template can refer to fields in the request @@ -87,6 +95,11 @@ message Http { // parameters. Assume the following definition of the request message: // // +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http).get = "/v1/messages/{message_id}"; +// } +// } // message GetMessageRequest { // message SubMessage { // string subfield = 1; @@ -199,7 +212,7 @@ message Http { // to the request message are as follows: // // 1. The `body` field specifies either `*` or a field path, or is -// omitted. If omitted, it assumes there is no HTTP body. +// omitted. If omitted, it indicates there is no HTTP request body. // 2. Leaf fields (recursive expansion of nested messages in the // request) can be classified into three types: // (a) Matched in the URL template. @@ -218,28 +231,34 @@ message Http { // FieldPath = IDENT { "." IDENT } ; // Verb = ":" LITERAL ; // -// The syntax `*` matches a single path segment. It follows the semantics of -// [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String -// Expansion. -// -// The syntax `**` matches zero or more path segments. It follows the semantics -// of [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.3 Reserved -// Expansion. NOTE: it must be the last segment in the path except the Verb. +// The syntax `*` matches a single path segment. The syntax `**` matches zero +// or more path segments, which must be the last part of the path except the +// `Verb`. The syntax `LITERAL` matches literal text in the path. // -// The syntax `LITERAL` matches literal text in the URL path. -// -// The syntax `Variable` matches the entire path as specified by its template; -// this nested template must not contain further variables. If a variable +// The syntax `Variable` matches part of the URL path as specified by its +// template. A variable template must not contain other variables. If a variable // matches a single path segment, its template may be omitted, e.g. `{var}` // is equivalent to `{var=*}`. // +// If a variable contains exactly one path segment, such as `"{var}"` or +// `"{var=*}"`, when such a variable is expanded into a URL path, all characters +// except `[-_.~0-9a-zA-Z]` are percent-encoded. Such variables show up in the +// Discovery Document as `{var}`. +// +// If a variable contains one or more path segments, such as `"{var=foo/*}"` +// or `"{var=**}"`, when such a variable is expanded into a URL path, all +// characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. Such variables +// show up in the Discovery Document as `{+var}`. +// +// NOTE: While the single segment variable matches the semantics of +// [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 +// Simple String Expansion, the multi segment variable **does not** match +// RFC 6570 Reserved Expansion. The reason is that the Reserved Expansion +// does not expand special characters like `?` and `#`, which would lead +// to invalid URLs. +// // NOTE: the field paths in variables and in the `body` must not refer to // repeated fields or map fields. -// -// Use CustomHttpPattern to specify any HTTP method that is not included in the -// `pattern` field, such as HEAD, or "*" to leave the HTTP method unspecified for -// a given URL path rule. The wild-card rule is useful for services that provide -// content to Web (HTML) clients. message HttpRule { // Selects methods to which this rule applies. // @@ -265,7 +284,10 @@ message HttpRule { // Used for updating a resource. string patch = 6; - // Custom pattern is used for defining custom verbs. + // The custom pattern is used for specifying an HTTP method that is not + // included in the `pattern` field, such as HEAD, or "*" to leave the + // HTTP method unspecified for this rule. The wild-card rule is useful + // for services that provide content to Web (HTML) clients. CustomHttpPattern custom = 8; } @@ -275,6 +297,11 @@ message HttpRule { // present at the top-level of request message type. string body = 7; + // Optional. The name of the response field whose value is mapped to the HTTP + // body of response. Other response fields are ignored. When + // not set, the response message will be used as HTTP body of response. + string response_body = 12; + // Additional HTTP bindings for the selector. Nested bindings must // not contain an `additional_bindings` field themselves (that is, // the nesting may only be one level deep).