From 3642f5d0baf59ca97c7c5f03a0c4e2e9d490e17f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8C=85=E5=AD=90?= Date: Mon, 27 Dec 2021 16:39:28 +0800 Subject: [PATCH] feat(cmd/kratos): generating API documentation using Gnostic (#1716) --- cmd/kratos/internal/proto/client/client.go | 1 + cmd/kratos/internal/upgrade/upgrade.go | 2 + examples/blog/openapi.yaml | 126 ++++++++++++++++++++ third_party/google/api/client.proto | 101 ++++++++++++++++ third_party/google/api/field_behavior.proto | 80 +++++++++++++ 5 files changed, 310 insertions(+) create mode 100644 examples/blog/openapi.yaml create mode 100644 third_party/google/api/client.proto create mode 100644 third_party/google/api/field_behavior.proto diff --git a/cmd/kratos/internal/proto/client/client.go b/cmd/kratos/internal/proto/client/client.go index 8ac1743eb..06c2f9250 100644 --- a/cmd/kratos/internal/proto/client/client.go +++ b/cmd/kratos/internal/proto/client/client.go @@ -95,6 +95,7 @@ func generate(proto string, args []string) error { "--go-grpc_out=paths=source_relative:.", "--go-http_out=paths=source_relative:.", "--go-errors_out=paths=source_relative:.", + "--openapi_out=paths=source_relative:.", } input = append(input, inputExt...) protoBytes, err := os.ReadFile(proto) diff --git a/cmd/kratos/internal/upgrade/upgrade.go b/cmd/kratos/internal/upgrade/upgrade.go index d981442e4..141c8cc5f 100644 --- a/cmd/kratos/internal/upgrade/upgrade.go +++ b/cmd/kratos/internal/upgrade/upgrade.go @@ -25,6 +25,8 @@ func Run(cmd *cobra.Command, args []string) { "google.golang.org/protobuf/cmd/protoc-gen-go", "google.golang.org/grpc/cmd/protoc-gen-go-grpc", "github.com/envoyproxy/protoc-gen-validate", + "github.com/google/gnostic", + "github.com/google/gnostic/apps/protoc-gen-openapi", ) if err != nil { fmt.Println(err) diff --git a/examples/blog/openapi.yaml b/examples/blog/openapi.yaml new file mode 100644 index 000000000..c4afd3ba2 --- /dev/null +++ b/examples/blog/openapi.yaml @@ -0,0 +1,126 @@ +# Generated with protoc-gen-openapi +# https://github.com/google/gnostic/tree/master/apps/protoc-gen-openapi + +openapi: 3.0.3 +info: + title: BlogService + version: 0.0.1 +paths: + /v1/article/: + get: + operationId: BlogService_ListArticle + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ListArticleReply' + post: + operationId: BlogService_CreateArticle + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/CreateArticleRequest' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/CreateArticleReply' + /v1/article/{id}: + get: + operationId: BlogService_GetArticle + parameters: + - name: id + in: query + schema: + type: string + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/GetArticleReply' + put: + operationId: BlogService_UpdateArticle + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateArticleRequest' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateArticleReply' + delete: + operationId: BlogService_DeleteArticle + parameters: + - name: id + in: query + schema: + type: string + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/DeleteArticleReply' +components: + schemas: + Article: + properties: + id: + type: integer + format: int64 + title: + type: string + content: + type: string + like: + type: integer + format: int64 + CreateArticleReply: + properties: + Article: + $ref: '#/components/schemas/Article' + CreateArticleRequest: + properties: + title: + type: string + content: + type: string + DeleteArticleReply: + properties: {} + GetArticleReply: + properties: + Article: + $ref: '#/components/schemas/Article' + ListArticleReply: + properties: + results: + type: array + items: + $ref: '#/components/schemas/Article' + UpdateArticleReply: + properties: + Article: + $ref: '#/components/schemas/Article' + UpdateArticleRequest: + properties: + id: + type: integer + format: int64 + title: + type: string + content: + type: string diff --git a/third_party/google/api/client.proto b/third_party/google/api/client.proto new file mode 100644 index 000000000..d3d1eadac --- /dev/null +++ b/third_party/google/api/client.proto @@ -0,0 +1,101 @@ +// Copyright 2019 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 +// +// https://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 google.api; + +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "ClientProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + + +extend google.protobuf.ServiceOptions { + // The hostname for this service. + // This should be specified with no prefix or protocol. + // + // Example: + // + // service Foo { + // option (google.api.default_host) = "foo.googleapi.com"; + // ... + // } + string default_host = 1049; + + // OAuth scopes needed for the client. + // + // Example: + // + // service Foo { + // option (google.api.oauth_scopes) = \ + // "https://www.googleapis.com/auth/cloud-platform"; + // ... + // } + // + // If there is more than one scope, use a comma-separated string: + // + // Example: + // + // service Foo { + // option (google.api.oauth_scopes) = \ + // "https://www.googleapis.com/auth/cloud-platform," + // "https://www.googleapis.com/auth/monitoring"; + // ... + // } + string oauth_scopes = 1050; +} + + +extend google.protobuf.MethodOptions { + // A definition of a client library method signature. + // + // In client libraries, each proto RPC corresponds to one or more methods + // which the end user is able to call, and calls the underlying RPC. + // Normally, this method receives a single argument (a struct or instance + // corresponding to the RPC request object). Defining this field will + // add one or more overloads providing flattened or simpler method signatures + // in some languages. + // + // The fields on the method signature are provided as a comma-separated + // string. + // + // For example, the proto RPC and annotation: + // + // rpc CreateSubscription(CreateSubscriptionRequest) + // returns (Subscription) { + // option (google.api.method_signature) = "name,topic"; + // } + // + // Would add the following Java overload (in addition to the method accepting + // the request object): + // + // public final Subscription createSubscription(String name, String topic) + // + // The following backwards-compatibility guidelines apply: + // + // * Adding this annotation to an unannotated method is backwards + // compatible. + // * Adding this annotation to a method which already has existing + // method signature annotations is backwards compatible if and only if + // the new method signature annotation is last in the sequence. + // * Modifying or removing an existing method signature annotation is + // a breaking change. + // * Re-ordering existing method signature annotations is a breaking + // change. + repeated string method_signature = 1051; +} \ No newline at end of file diff --git a/third_party/google/api/field_behavior.proto b/third_party/google/api/field_behavior.proto new file mode 100644 index 000000000..e9716a2fe --- /dev/null +++ b/third_party/google/api/field_behavior.proto @@ -0,0 +1,80 @@ +// Copyright 2019 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 +// +// https://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 google.api; + +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "FieldBehaviorProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + + +// An indicator of the behavior of a given field (for example, that a field +// is required in requests, or given as output but ignored as input). +// This **does not** change the behavior in protocol buffers itself; it only +// denotes the behavior and may affect how API tooling handles the field. +// +// Note: This enum **may** receive new values in the future. +enum FieldBehavior { + // Conventional default for enums. Do not use this. + FIELD_BEHAVIOR_UNSPECIFIED = 0; + + // Specifically denotes a field as optional. + // While all fields in protocol buffers are optional, this may be specified + // for emphasis if appropriate. + OPTIONAL = 1; + + // Denotes a field as required. + // This indicates that the field **must** be provided as part of the request, + // and failure to do so will cause an error (usually `INVALID_ARGUMENT`). + REQUIRED = 2; + + // Denotes a field as output only. + // This indicates that the field is provided in responses, but including the + // field in a request does nothing (the server *must* ignore it and + // *must not* throw an error as a result of the field's presence). + OUTPUT_ONLY = 3; + + // Denotes a field as input only. + // This indicates that the field is provided in requests, and the + // corresponding field is not included in output. + INPUT_ONLY = 4; + + // Denotes a field as immutable. + // This indicates that the field may be set once in a request to create a + // resource, but may not be changed thereafter. + IMMUTABLE = 5; +} + + +extend google.protobuf.FieldOptions { + // A designation of a specific field behavior (required, output only, etc.) + // in protobuf messages. + // + // Examples: + // + // string name = 1 [(google.api.field_behavior) = REQUIRED]; + // State state = 1 [(google.api.field_behavior) = OUTPUT_ONLY]; + // google.protobuf.Duration ttl = 1 + // [(google.api.field_behavior) = INPUT_ONLY]; + // google.protobuf.Timestamp expire_time = 1 + // [(google.api.field_behavior) = OUTPUT_ONLY, + // (google.api.field_behavior) = IMMUTABLE]; + repeated FieldBehavior field_behavior = 1052; +} \ No newline at end of file