diff --git a/docs/docs/mapping/customizing_openapi_output.md b/docs/docs/mapping/customizing_openapi_output.md index 5ed03f7f132..d1017bf7748 100644 --- a/docs/docs/mapping/customizing_openapi_output.md +++ b/docs/docs/mapping/customizing_openapi_output.md @@ -631,14 +631,107 @@ info: title: helloproto/v1/example.proto version: version not set consumes: -- application/json + - application/json produces: -- application/json + - application/json +paths: + /api/hello: + get: + operationId: EchoService_Hello +``` + +### Disable default responses + +By default a 200 OK response is rendered for each service operation. But it is possible to disable this and explicitly define your service's responses, using the `disable_default_responses` option. Allowed values are: `true`, `false`. + +**Note**: This does not alter the behavior of the gateway itself and should be coupled with a `ForwardResponseWriter` when altering status codes, see [Controlling HTTP Response Codes](https://grpc-ecosystem.github.io/grpc-gateway/docs/mapping/customizing_your_gateway/#controlling-http-response-status-codes). + +For example, if you are using `buf`: + +```yaml +version: v1 +plugins: + - name: openapiv2 + out: . + opt: + - disable_default_responses=true +``` + +or with `protoc` + +```sh +protoc --openapiv2_out=. --openapiv2_opt=disable_default_responses=true ./path/to/file.proto +``` + +Input example: + +```protobuf +syntax = "proto3"; + +package helloproto.v1; + +import "google/api/annotations.proto"; +import "protoc-gen-openapiv2/options/annotations.proto"; + +option go_package = "helloproto/v1;helloproto"; + +service EchoService { + rpc Hello(HelloReq) returns (HelloResp) { + option (google.api.http) = {get: "/api/hello"}; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + responses: { + key: "201", + value: { + description: "Created"; + schema: { + json_schema: {ref: ".helloproto.v1.HelloResp"} + } + } + }; + }; + } +} + +message HelloReq { + string name = 1; +} + +message HelloResp { + string message = 1; +} +``` + +Output (default response not generated): + +```yaml +swagger: "2.0" +info: + title: helloproto/v1/hello.proto + version: version not set +consumes: + - application/json +produces: + - application/json paths: /api/hello: get: operationId: EchoService_Hello -... + responses: + "201": + description: Created + schema: + $ref: "#/definitions/v1HelloResp" + parameters: + - name: name + in: query + required: false + type: string +definitions: + v1HelloResp: + type: object + properties: + message: + type: string ``` {% endraw %} diff --git a/internal/descriptor/registry.go b/internal/descriptor/registry.go index 9080c2f312b..fdb7717fb8a 100644 --- a/internal/descriptor/registry.go +++ b/internal/descriptor/registry.go @@ -132,6 +132,10 @@ type Registry struct { // disableServiceTags disables the generation of service tags. // This is useful if you do not want to expose the names of your backend grpc services. disableServiceTags bool + + // disableDefaultResponses disables the generation of default responses. + // Useful if you have to support custom response codes that are not 200. + disableDefaultResponses bool } type repeatedFieldSeparator struct { @@ -758,3 +762,13 @@ func (r *Registry) SetDisableServiceTags(use bool) { func (r *Registry) GetDisableServiceTags() bool { return r.disableServiceTags } + +// SetDisableDefaultResponses setsdisableDefaultResponses +func (r *Registry) SetDisableDefaultResponses(use bool) { + r.disableDefaultResponses = use +} + +// GetDisableDefaultResponses returns disableDefaultResponses +func (r *Registry) GetDisableDefaultResponses() bool { + return r.disableDefaultResponses +} diff --git a/protoc-gen-openapiv2/internal/genopenapi/template.go b/protoc-gen-openapiv2/internal/genopenapi/template.go index 2b5d43ce19e..a77a4d7c6d3 100644 --- a/protoc-gen-openapiv2/internal/genopenapi/template.go +++ b/protoc-gen-openapiv2/internal/genopenapi/template.go @@ -1366,13 +1366,15 @@ func renderServices(services []*descriptor.Service, paths openapiPathsObject, re operationObject := &openapiOperationObject{ Parameters: parameters, - Responses: openapiResponsesObject{ - "200": openapiResponseObject{ - Description: desc, - Schema: responseSchema, - Headers: openapiHeadersObject{}, - }, - }, + Responses: openapiResponsesObject{}, + } + + if !reg.GetDisableDefaultResponses() { + operationObject.Responses["200"] = openapiResponseObject{ + Description: desc, + Schema: responseSchema, + Headers: openapiHeadersObject{}, + } } if !reg.GetDisableServiceTags() { diff --git a/protoc-gen-openapiv2/internal/genopenapi/template_test.go b/protoc-gen-openapiv2/internal/genopenapi/template_test.go index 3f1a105c385..f4733c22540 100644 --- a/protoc-gen-openapiv2/internal/genopenapi/template_test.go +++ b/protoc-gen-openapiv2/internal/genopenapi/template_test.go @@ -3316,6 +3316,17 @@ func TestApplyTemplateProtobufAny(t *testing.T) { }, wantNumDefinitions: 3, }, + { + // we have a protobufAny in a message but with automatic rendering of responses disabled + name: "protobufAny_referenced_in_message_with_default_responses_disabled", + args: args{ + msgContainsAny: true, + regConfig: func(reg *descriptor.Registry) { + reg.SetDisableDefaultResponses(true) + }, + }, + wantNumDefinitions: 4, + }, } for _, tt := range tests { diff --git a/protoc-gen-openapiv2/main.go b/protoc-gen-openapiv2/main.go index 61730718411..092d6fa9dbc 100644 --- a/protoc-gen-openapiv2/main.go +++ b/protoc-gen-openapiv2/main.go @@ -41,6 +41,7 @@ var ( outputFormat = flag.String("output_format", string(genopenapi.FormatJSON), fmt.Sprintf("output content format. Allowed values are: `%s`, `%s`", genopenapi.FormatJSON, genopenapi.FormatYAML)) visibilityRestrictionSelectors = utilities.StringArrayFlag(flag.CommandLine, "visibility_restriction_selectors", "list of `google.api.VisibilityRule` visibility labels to include in the generated output when a visibility annotation is defined. Repeat this option to supply multiple values. Elements without visibility annotations are unaffected by this setting.") disableServiceTags = flag.Bool("disable_service_tags", false, "if set, disables generation of service tags. This is useful if you do not want to expose the names of your backend grpc services.") + disableDefaultResponses = flag.Bool("disable_default_responses", false, "if set, disables generation of default responses. Useful if you have to support custom response codes that are not 200.") ) // Variables set by goreleaser at build time @@ -125,6 +126,7 @@ func main() { reg.SetOmitEnumDefaultValue(*omitEnumDefaultValue) reg.SetVisibilityRestrictionSelectors(*visibilityRestrictionSelectors) reg.SetDisableServiceTags(*disableServiceTags) + reg.SetDisableDefaultResponses(*disableDefaultResponses) if err := reg.SetRepeatedPathParamSeparator(*repeatedPathParamSeparator); err != nil { emitError(err) return