From 05a0ef713d66efb7cef8d4fd3e93b8961edf9d0b Mon Sep 17 00:00:00 2001 From: ppaanngggg Date: Sun, 22 May 2022 14:36:29 +0800 Subject: [PATCH] Feat/message inputonly outputonly (#354) * [+] add use allof wrapped schema to support inputonly and outputonly for message * [+] update readme * [!] fix test, --- cmd/protoc-gen-openapi/README.md | 2 +- .../examples/tests/allofwrap/message.proto | 45 +++++++++ .../examples/tests/allofwrap/openapi.yaml | 94 +++++++++++++++++++ .../examples/tests/protobuftypes/openapi.yaml | 4 +- .../openapi_default_response.yaml | 4 +- .../openapi_fq_schema_naming.yaml | 4 +- .../tests/protobuftypes/openapi_json.yaml | 4 +- .../protobuftypes/openapi_string_enum.yaml | 4 +- cmd/protoc-gen-openapi/generator/generator.go | 23 +++-- cmd/protoc-gen-openapi/plugin_test.go | 1 + 10 files changed, 171 insertions(+), 14 deletions(-) create mode 100644 cmd/protoc-gen-openapi/examples/tests/allofwrap/message.proto create mode 100644 cmd/protoc-gen-openapi/examples/tests/allofwrap/openapi.yaml diff --git a/cmd/protoc-gen-openapi/README.md b/cmd/protoc-gen-openapi/README.md index 5a018c7d..154d7202 100644 --- a/cmd/protoc-gen-openapi/README.md +++ b/cmd/protoc-gen-openapi/README.md @@ -10,7 +10,7 @@ Installation: Usage: - protoc sample.proto -I. --openapi_out=. + protoc sample.proto -I=. --openapi_out=. This runs the plugin for a file named `sample.proto` which refers to additional .proto files in the same directory as diff --git a/cmd/protoc-gen-openapi/examples/tests/allofwrap/message.proto b/cmd/protoc-gen-openapi/examples/tests/allofwrap/message.proto new file mode 100644 index 00000000..55049a70 --- /dev/null +++ b/cmd/protoc-gen-openapi/examples/tests/allofwrap/message.proto @@ -0,0 +1,45 @@ +// 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.allofwrap.message.v1; + +import "google/api/annotations.proto"; +import "google/api/field_behavior.proto"; + +option go_package = "github.com/google/gnostic/apps/protoc-gen-openapi/examples/tests/bodymapping/message/v1;message"; + +service Messaging { + rpc UpdateMessage(Message) returns(Message) { + option(google.api.http) = { + patch: "/v1/messages/{message_id}" + body: "*" + }; + } +} + +message Message { + message Sub { + string content = 1; + } + Sub sub = 1; + Sub sub_input = 2 [(google.api.field_behavior) = INPUT_ONLY]; + Sub sub_output = 3 [(google.api.field_behavior) = OUTPUT_ONLY]; + // this sub has a description + Sub sub_desc = 4; + // test repeated, should not allof wrapped + repeated Sub subs = 5 [(google.api.field_behavior) = OUTPUT_ONLY]; +} \ No newline at end of file diff --git a/cmd/protoc-gen-openapi/examples/tests/allofwrap/openapi.yaml b/cmd/protoc-gen-openapi/examples/tests/allofwrap/openapi.yaml new file mode 100644 index 00000000..c9c35391 --- /dev/null +++ b/cmd/protoc-gen-openapi/examples/tests/allofwrap/openapi.yaml @@ -0,0 +1,94 @@ +# 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}: + patch: + tags: + - Messaging + operationId: Messaging_UpdateMessage + parameters: + - name: message_id + in: path + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Message' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Message' + 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. + Message: + type: object + properties: + sub: + $ref: '#/components/schemas/Message_Sub' + sub_input: + writeOnly: true + allOf: + - $ref: '#/components/schemas/Message_Sub' + sub_output: + readOnly: true + allOf: + - $ref: '#/components/schemas/Message_Sub' + sub_desc: + allOf: + - $ref: '#/components/schemas/Message_Sub' + description: this sub has a description + subs: + readOnly: true + type: array + items: + $ref: '#/components/schemas/Message_Sub' + description: test repeated, should not allof wrapped + Message_Sub: + type: object + properties: + content: + type: string + 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).' +tags: + - name: Messaging diff --git a/cmd/protoc-gen-openapi/examples/tests/protobuftypes/openapi.yaml b/cmd/protoc-gen-openapi/examples/tests/protobuftypes/openapi.yaml index ef5dc1a9..14b82b38 100644 --- a/cmd/protoc-gen-openapi/examples/tests/protobuftypes/openapi.yaml +++ b/cmd/protoc-gen-openapi/examples/tests/protobuftypes/openapi.yaml @@ -318,7 +318,9 @@ components: items: type: object value_type: - $ref: '#/components/schemas/GoogleProtobufValue' + allOf: + - $ref: '#/components/schemas/GoogleProtobufValue' + description: Description of value repeated_value_type: type: array items: diff --git a/cmd/protoc-gen-openapi/examples/tests/protobuftypes/openapi_default_response.yaml b/cmd/protoc-gen-openapi/examples/tests/protobuftypes/openapi_default_response.yaml index 3099a367..c60cbf26 100644 --- a/cmd/protoc-gen-openapi/examples/tests/protobuftypes/openapi_default_response.yaml +++ b/cmd/protoc-gen-openapi/examples/tests/protobuftypes/openapi_default_response.yaml @@ -318,7 +318,9 @@ components: items: type: object valueType: - $ref: '#/components/schemas/GoogleProtobufValue' + allOf: + - $ref: '#/components/schemas/GoogleProtobufValue' + description: Description of value repeatedValueType: type: array items: diff --git a/cmd/protoc-gen-openapi/examples/tests/protobuftypes/openapi_fq_schema_naming.yaml b/cmd/protoc-gen-openapi/examples/tests/protobuftypes/openapi_fq_schema_naming.yaml index cd2c0dd9..187bba79 100644 --- a/cmd/protoc-gen-openapi/examples/tests/protobuftypes/openapi_fq_schema_naming.yaml +++ b/cmd/protoc-gen-openapi/examples/tests/protobuftypes/openapi_fq_schema_naming.yaml @@ -334,7 +334,9 @@ components: items: type: object valueType: - $ref: '#/components/schemas/google.protobuf.Value' + allOf: + - $ref: '#/components/schemas/google.protobuf.Value' + description: Description of value repeatedValueType: type: array items: diff --git a/cmd/protoc-gen-openapi/examples/tests/protobuftypes/openapi_json.yaml b/cmd/protoc-gen-openapi/examples/tests/protobuftypes/openapi_json.yaml index cd4064fb..c73847e1 100644 --- a/cmd/protoc-gen-openapi/examples/tests/protobuftypes/openapi_json.yaml +++ b/cmd/protoc-gen-openapi/examples/tests/protobuftypes/openapi_json.yaml @@ -318,7 +318,9 @@ components: items: type: object valueType: - $ref: '#/components/schemas/GoogleProtobufValue' + allOf: + - $ref: '#/components/schemas/GoogleProtobufValue' + description: Description of value repeatedValueType: type: array items: diff --git a/cmd/protoc-gen-openapi/examples/tests/protobuftypes/openapi_string_enum.yaml b/cmd/protoc-gen-openapi/examples/tests/protobuftypes/openapi_string_enum.yaml index 3099a367..c60cbf26 100644 --- a/cmd/protoc-gen-openapi/examples/tests/protobuftypes/openapi_string_enum.yaml +++ b/cmd/protoc-gen-openapi/examples/tests/protobuftypes/openapi_string_enum.yaml @@ -318,7 +318,9 @@ components: items: type: object valueType: - $ref: '#/components/schemas/GoogleProtobufValue' + allOf: + - $ref: '#/components/schemas/GoogleProtobufValue' + description: Description of value repeatedValueType: type: array items: diff --git a/cmd/protoc-gen-openapi/generator/generator.go b/cmd/protoc-gen-openapi/generator/generator.go index 7ed7afb3..563aabf0 100644 --- a/cmd/protoc-gen-openapi/generator/generator.go +++ b/cmd/protoc-gen-openapi/generator/generator.go @@ -758,6 +758,8 @@ func (g *OpenAPIv3Generator) addSchemasForMessagesToDocumentV3(d *v3.Document, m var required []string for _, field := range message.Fields { + // Get the field description from the comments. + description := g.filterCommentString(field.Comments.Leading, true) // Check the field annotations to see if this is a readonly or writeonly field. inputOnly := false outputOnly := false @@ -786,15 +788,20 @@ func (g *OpenAPIv3Generator) addSchemasForMessagesToDocumentV3(d *v3.Document, m continue } - if schema, ok := fieldSchema.Oneof.(*v3.SchemaOrReference_Schema); ok { - // Get the field description from the comments. - schema.Schema.Description = g.filterCommentString(field.Comments.Leading, true) - if outputOnly { - schema.Schema.ReadOnly = true - } - if inputOnly { - schema.Schema.WriteOnly = true + // If this field has siblings and is a $ref now, create a new schema use `allOf` to wrap it + wrapperNeeded := inputOnly || outputOnly || description != "" + if wrapperNeeded { + if _, ok := fieldSchema.Oneof.(*v3.SchemaOrReference_Reference); ok { + fieldSchema = &v3.SchemaOrReference{Oneof: &v3.SchemaOrReference_Schema{Schema: &v3.Schema{ + AllOf: []*v3.SchemaOrReference{fieldSchema}, + }}} } + } + + if schema, ok := fieldSchema.Oneof.(*v3.SchemaOrReference_Schema); ok { + schema.Schema.Description = description + schema.Schema.ReadOnly = outputOnly + schema.Schema.WriteOnly = inputOnly // Merge any `Property` annotations with the current extProperty := proto.GetExtension(field.Desc.Options(), v3.E_Property) diff --git a/cmd/protoc-gen-openapi/plugin_test.go b/cmd/protoc-gen-openapi/plugin_test.go index 49645dc4..0c41d903 100644 --- a/cmd/protoc-gen-openapi/plugin_test.go +++ b/cmd/protoc-gen-openapi/plugin_test.go @@ -38,6 +38,7 @@ var openapiTests = []struct { {name: "Ignore services without annotations", path: "examples/tests/noannotations/", protofile: "message.proto"}, {name: "Enum Options", path: "examples/tests/enumoptions/", protofile: "message.proto"}, {name: "OpenAPIv3 Annotations", path: "examples/tests/openapiv3annotations/", protofile: "message.proto"}, + {name: "AllOf Wrap Message", path: "examples/tests/allofwrap/", protofile: "message.proto"}, } // Set this to true to generate/overwrite the fixtures. Make sure you set it back