diff --git a/cmd/protoc-gen-openapi/examples/tests/responsebody/message.proto b/cmd/protoc-gen-openapi/examples/tests/responsebody/message.proto new file mode 100644 index 00000000..2e271f55 --- /dev/null +++ b/cmd/protoc-gen-openapi/examples/tests/responsebody/message.proto @@ -0,0 +1,44 @@ +// Copyright 2020 Google LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +syntax = "proto3"; + +package tests.responsebody.message.v1; + +import "google/api/annotations.proto"; + +option go_package = "github.com/google/gnostic/apps/protoc-gen-openapi/examples/tests/responsebody/message/v1;message"; + +service Messaging { + rpc GetMessage(GetMessageRequest) returns(GetMessageResponse) { + option(google.api.http) = { + get: "/v1/messages/{message_id}" + response_body: "text" + }; + } +} + +message GetMessageRequest { + string message_id = 1; +} + +message GetMessageResponse { + string message_id = 1; + Text text = 2; +} + +message Text { + string body = 1; +} diff --git a/cmd/protoc-gen-openapi/examples/tests/responsebody/openapi.yaml b/cmd/protoc-gen-openapi/examples/tests/responsebody/openapi.yaml new file mode 100644 index 00000000..5dc581c1 --- /dev/null +++ b/cmd/protoc-gen-openapi/examples/tests/responsebody/openapi.yaml @@ -0,0 +1,65 @@ +# Generated with protoc-gen-openapi +# https://github.com/google/gnostic/tree/master/cmd/protoc-gen-openapi + +openapi: 3.0.3 +info: + title: Messaging API + version: 0.0.1 +paths: + /v1/messages/{message_id}: + get: + tags: + - Messaging + operationId: Messaging_GetMessage + parameters: + - name: message_id + in: path + required: true + schema: + type: string + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Text' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' +components: + schemas: + GoogleProtobufAny: + type: object + properties: + '@type': + type: string + description: The type of the serialized message. + additionalProperties: true + description: Contains an arbitrary serialized message along with a @type that describes the type of the serialized message. + Status: + type: object + properties: + code: + type: integer + description: The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. + format: int32 + message: + type: string + description: A developer-facing error message, which should be in English. Any user-facing error message should be localized and sent in the [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. + details: + type: array + items: + $ref: '#/components/schemas/GoogleProtobufAny' + description: A list of messages that carry the error details. There is a common set of message types for APIs to use. + description: 'The `Status` type defines a logical error model that is suitable for different programming environments, including REST APIs and RPC APIs. It is used by [gRPC](https://github.com/grpc). Each `Status` message contains three pieces of data: error code, error message, and error details. You can find out more about this error model and how to work with it in the [API Design Guide](https://cloud.google.com/apis/design/errors).' + Text: + type: object + properties: + body: + type: string +tags: + - name: Messaging diff --git a/cmd/protoc-gen-openapi/examples/tests/responsebody/openapi_default_response.yaml b/cmd/protoc-gen-openapi/examples/tests/responsebody/openapi_default_response.yaml new file mode 100644 index 00000000..20151471 --- /dev/null +++ b/cmd/protoc-gen-openapi/examples/tests/responsebody/openapi_default_response.yaml @@ -0,0 +1,65 @@ +# Generated with protoc-gen-openapi +# https://github.com/google/gnostic/tree/master/cmd/protoc-gen-openapi + +openapi: 3.0.3 +info: + title: Messaging API + version: 0.0.1 +paths: + /v1/messages/{messageId}: + get: + tags: + - Messaging + operationId: Messaging_GetMessage + parameters: + - name: messageId + in: path + required: true + schema: + type: string + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Text' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' +components: + schemas: + GoogleProtobufAny: + type: object + properties: + '@type': + type: string + description: The type of the serialized message. + additionalProperties: true + description: Contains an arbitrary serialized message along with a @type that describes the type of the serialized message. + Status: + type: object + properties: + code: + type: integer + description: The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. + format: int32 + message: + type: string + description: A developer-facing error message, which should be in English. Any user-facing error message should be localized and sent in the [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. + details: + type: array + items: + $ref: '#/components/schemas/GoogleProtobufAny' + description: A list of messages that carry the error details. There is a common set of message types for APIs to use. + description: 'The `Status` type defines a logical error model that is suitable for different programming environments, including REST APIs and RPC APIs. It is used by [gRPC](https://github.com/grpc). Each `Status` message contains three pieces of data: error code, error message, and error details. You can find out more about this error model and how to work with it in the [API Design Guide](https://cloud.google.com/apis/design/errors).' + Text: + type: object + properties: + body: + type: string +tags: + - name: Messaging diff --git a/cmd/protoc-gen-openapi/examples/tests/responsebody/openapi_fq_schema_naming.yaml b/cmd/protoc-gen-openapi/examples/tests/responsebody/openapi_fq_schema_naming.yaml new file mode 100644 index 00000000..efa347c9 --- /dev/null +++ b/cmd/protoc-gen-openapi/examples/tests/responsebody/openapi_fq_schema_naming.yaml @@ -0,0 +1,65 @@ +# Generated with protoc-gen-openapi +# https://github.com/google/gnostic/tree/master/cmd/protoc-gen-openapi + +openapi: 3.0.3 +info: + title: Messaging API + version: 0.0.1 +paths: + /v1/messages/{messageId}: + get: + tags: + - Messaging + operationId: Messaging_GetMessage + parameters: + - name: messageId + in: path + required: true + schema: + type: string + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/tests.responsebody.message.v1.Text' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/google.rpc.Status' +components: + schemas: + google.protobuf.Any: + type: object + properties: + '@type': + type: string + description: The type of the serialized message. + additionalProperties: true + description: Contains an arbitrary serialized message along with a @type that describes the type of the serialized message. + google.rpc.Status: + type: object + properties: + code: + type: integer + description: The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. + format: int32 + message: + type: string + description: A developer-facing error message, which should be in English. Any user-facing error message should be localized and sent in the [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. + details: + type: array + items: + $ref: '#/components/schemas/google.protobuf.Any' + description: A list of messages that carry the error details. There is a common set of message types for APIs to use. + description: 'The `Status` type defines a logical error model that is suitable for different programming environments, including REST APIs and RPC APIs. It is used by [gRPC](https://github.com/grpc). Each `Status` message contains three pieces of data: error code, error message, and error details. You can find out more about this error model and how to work with it in the [API Design Guide](https://cloud.google.com/apis/design/errors).' + tests.responsebody.message.v1.Text: + type: object + properties: + body: + type: string +tags: + - name: Messaging diff --git a/cmd/protoc-gen-openapi/examples/tests/responsebody/openapi_json.yaml b/cmd/protoc-gen-openapi/examples/tests/responsebody/openapi_json.yaml new file mode 100644 index 00000000..6939aee3 --- /dev/null +++ b/cmd/protoc-gen-openapi/examples/tests/responsebody/openapi_json.yaml @@ -0,0 +1,65 @@ +# Generated with protoc-gen-openapi +# https://github.com/google/gnostic/tree/master/cmd/protoc-gen-openapi + +openapi: 3.0.3 +info: + title: Messaging API + version: 1.2.3 +paths: + /v1/messages/{messageId}: + get: + tags: + - Messaging + operationId: Messaging_GetMessage + parameters: + - name: messageId + in: path + required: true + schema: + type: string + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Text' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' +components: + schemas: + GoogleProtobufAny: + type: object + properties: + '@type': + type: string + description: The type of the serialized message. + additionalProperties: true + description: Contains an arbitrary serialized message along with a @type that describes the type of the serialized message. + Status: + type: object + properties: + code: + type: integer + description: The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. + format: int32 + message: + type: string + description: A developer-facing error message, which should be in English. Any user-facing error message should be localized and sent in the [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. + details: + type: array + items: + $ref: '#/components/schemas/GoogleProtobufAny' + description: A list of messages that carry the error details. There is a common set of message types for APIs to use. + description: 'The `Status` type defines a logical error model that is suitable for different programming environments, including REST APIs and RPC APIs. It is used by [gRPC](https://github.com/grpc). Each `Status` message contains three pieces of data: error code, error message, and error details. You can find out more about this error model and how to work with it in the [API Design Guide](https://cloud.google.com/apis/design/errors).' + Text: + type: object + properties: + body: + type: string +tags: + - name: Messaging diff --git a/cmd/protoc-gen-openapi/examples/tests/responsebody/openapi_string_enum.yaml b/cmd/protoc-gen-openapi/examples/tests/responsebody/openapi_string_enum.yaml new file mode 100644 index 00000000..20151471 --- /dev/null +++ b/cmd/protoc-gen-openapi/examples/tests/responsebody/openapi_string_enum.yaml @@ -0,0 +1,65 @@ +# Generated with protoc-gen-openapi +# https://github.com/google/gnostic/tree/master/cmd/protoc-gen-openapi + +openapi: 3.0.3 +info: + title: Messaging API + version: 0.0.1 +paths: + /v1/messages/{messageId}: + get: + tags: + - Messaging + operationId: Messaging_GetMessage + parameters: + - name: messageId + in: path + required: true + schema: + type: string + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Text' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' +components: + schemas: + GoogleProtobufAny: + type: object + properties: + '@type': + type: string + description: The type of the serialized message. + additionalProperties: true + description: Contains an arbitrary serialized message along with a @type that describes the type of the serialized message. + Status: + type: object + properties: + code: + type: integer + description: The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. + format: int32 + message: + type: string + description: A developer-facing error message, which should be in English. Any user-facing error message should be localized and sent in the [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. + details: + type: array + items: + $ref: '#/components/schemas/GoogleProtobufAny' + description: A list of messages that carry the error details. There is a common set of message types for APIs to use. + description: 'The `Status` type defines a logical error model that is suitable for different programming environments, including REST APIs and RPC APIs. It is used by [gRPC](https://github.com/grpc). Each `Status` message contains three pieces of data: error code, error message, and error details. You can find out more about this error model and how to work with it in the [API Design Guide](https://cloud.google.com/apis/design/errors).' + Text: + type: object + properties: + body: + type: string +tags: + - name: Messaging diff --git a/cmd/protoc-gen-openapi/generator/generator.go b/cmd/protoc-gen-openapi/generator/generator.go index e548ab21..bfc2534b 100644 --- a/cmd/protoc-gen-openapi/generator/generator.go +++ b/cmd/protoc-gen-openapi/generator/generator.go @@ -441,14 +441,15 @@ func (g *OpenAPIv3Generator) buildOperationV3( description string, defaultHost string, path string, - bodyField string, + reqBodyField string, + resBodyField string, inputMessage *protogen.Message, outputMessage *protogen.Message, ) (*v3.Operation, string) { // coveredParameters tracks the parameters that have been used in the body or path. coveredParameters := make([]string, 0) - if bodyField != "" { - coveredParameters = append(coveredParameters, bodyField) + if reqBodyField != "" { + coveredParameters = append(coveredParameters, reqBodyField) } // Initialize the list of operation parameters. parameters := []*v3.ParameterOrReference{} @@ -542,10 +543,10 @@ func (g *OpenAPIv3Generator) buildOperationV3( } // Add any unhandled fields in the request message as query parameters. - if bodyField != "*" && string(inputMessage.Desc.FullName()) != "google.api.HttpBody" { + if reqBodyField != "*" && string(inputMessage.Desc.FullName()) != "google.api.HttpBody" { for _, field := range inputMessage.Fields { fieldName := string(field.Desc.Name()) - if !contains(coveredParameters, fieldName) && fieldName != bodyField { + if !contains(coveredParameters, fieldName) && fieldName != reqBodyField { fieldParams := g.buildQueryParamsV3(field) parameters = append(parameters, fieldParams...) } @@ -553,6 +554,26 @@ func (g *OpenAPIv3Generator) buildOperationV3( } // Create the response. + if resBodyField != "" && resBodyField != "*" { + found := false + for _, field := range outputMessage.Fields { + if string(field.Desc.Name()) == resBodyField { + found = true + switch field.Desc.Kind() { + case protoreflect.MessageKind: + if field.Message != nil { + outputMessage = field.Message + } + default: + log.Printf("unsupported response_body field type %+v", field.Desc) + } + break + } + } + if !found { + log.Printf("response_body field %q not found in %s", resBodyField, outputMessage.Desc.FullName()) + } + } name, content := g.reflect.responseContentForMessage(outputMessage.Desc) responses := &v3.Responses{ ResponseOrReference: []*v3.NamedResponseOrReference{ @@ -615,17 +636,17 @@ func (g *OpenAPIv3Generator) buildOperationV3( } // If a body field is specified, we need to pass a message as the request body. - if bodyField != "" { + if reqBodyField != "" { var requestSchema *v3.SchemaOrReference - if bodyField == "*" { + if reqBodyField == "*" { // Pass the entire request message as the request body. requestSchema = g.reflect.schemaOrReferenceForMessage(inputMessage.Desc) } else { // If body refers to a message field, use that type. for _, field := range inputMessage.Fields { - if string(field.Desc.Name()) == bodyField { + if string(field.Desc.Name()) == reqBodyField { switch field.Desc.Kind() { case protoreflect.StringKind: requestSchema = &v3.SchemaOrReference{ @@ -722,9 +743,11 @@ func (g *OpenAPIv3Generator) addPathsToDocumentV3(d *v3.Document, services []*pr for _, rule := range rules { var path string var methodName string - var body string + var reqBody string + var resBody string - body = rule.Body + reqBody = rule.Body + resBody = rule.ResponseBody switch pattern := rule.Pattern.(type) { case *annotations.HttpRule_Get: path = pattern.Get @@ -751,7 +774,7 @@ func (g *OpenAPIv3Generator) addPathsToDocumentV3(d *v3.Document, services []*pr defaultHost := proto.GetExtension(service.Desc.Options(), annotations.E_DefaultHost).(string) op, path2 := g.buildOperationV3( - d, operationID, service.GoName, comment, defaultHost, path, body, inputMessage, outputMessage) + d, operationID, service.GoName, comment, defaultHost, path, reqBody, resBody, inputMessage, outputMessage) // Merge any `Operation` annotations with the current extOperation := proto.GetExtension(method.Desc.Options(), v3.E_Operation) diff --git a/cmd/protoc-gen-openapi/plugin_test.go b/cmd/protoc-gen-openapi/plugin_test.go index 767e9742..c79d6fa7 100644 --- a/cmd/protoc-gen-openapi/plugin_test.go +++ b/cmd/protoc-gen-openapi/plugin_test.go @@ -43,6 +43,7 @@ var openapiTests = []struct { {name: "OpenAPIv3 Annotations", path: "examples/tests/openapiv3annotations/", protofile: "message.proto"}, {name: "AllOf Wrap Message", path: "examples/tests/allofwrap/", protofile: "message.proto"}, {name: "Additional Bindings", path: "examples/tests/additional_bindings/", protofile: "message.proto"}, + {name: "Response Body", path: "examples/tests/responsebody/", protofile: "message.proto"}, } // Set this to true to generate/overwrite the fixtures. Make sure you set it back