Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for method's deprecated option #4373

Merged
merged 5 commits into from
Jun 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 109 additions & 0 deletions docs/docs/mapping/customizing_openapi_output.md
Original file line number Diff line number Diff line change
Expand Up @@ -934,4 +934,113 @@ plugins:
- preserve_rpc_order=true
```

### Enable RPC deprecation

With `enable_rpc_deprecation` option you can deprecate openapi method using standard method's option. Allowed values are: `true`, `false`.

For example, if you are using `buf`:

```yaml
version: v1
plugins:
- name: openapiv2
out: .
opt:
- enable_rpc_deprecation=true
```

or with `protoc`

```sh
protoc --openapiv2_out=. --openapiv2_opt=enable_rpc_deprecation=true ./path/to/file.proto
```

Input example:

```protobuf
syntax = "proto3";

package helloproto.v1;

import "google/api/annotations.proto";

option go_package = "helloproto/v1;helloproto";

service EchoService {
rpc Hello(HelloReq) returns (HelloResp) {
option deprecated = true;
option (google.api.http) = {get: "/api/hello"};
}
}

message HelloReq {
string name = 1;
}

message HelloResp {
string message = 1;
}
```

Output:

```yaml
swagger: "2.0"
info:
title: helloproto/v1/example.proto
version: version not set
tags:
- name: EchoService
consumes:
- application/json
produces:
- application/json
paths:
/api/hello:
get:
operationId: EchoService_Hello
responses:
"200":
description: A successful response.
schema:
$ref: '#/definitions/v1HelloResp'
default:
description: An unexpected error response.
schema:
$ref: '#/definitions/rpcStatus'
parameters:
- name: name
in: query
required: false
type: string
tags:
- EchoService
deprecated: true
definitions:
protobufAny:
type: object
properties:
'@type':
type: string
additionalProperties: {}
rpcStatus:
type: object
properties:
code:
type: integer
format: int32
message:
type: string
details:
type: array
items:
type: object
$ref: '#/definitions/protobufAny'
v1HelloResp:
type: object
properties:
message:
type: string
```

{% endraw %}
13 changes: 13 additions & 0 deletions internal/descriptor/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,9 @@ type Registry struct {
// preserveRPCOrder, if true, will ensure the order of paths emitted in openapi swagger files mirror
// the order of RPC methods found in proto files. If false, emitted paths will be ordered alphabetically.
preserveRPCOrder bool

// enableRpcDeprecation whether to process grpc method's deprecated option
enableRpcDeprecation bool
}

type repeatedFieldSeparator struct {
Expand Down Expand Up @@ -875,3 +878,13 @@ func (r *Registry) SetPreserveRPCOrder(preserve bool) {
func (r *Registry) IsPreserveRPCOrder() bool {
return r.preserveRPCOrder
}

// SetEnableRpcDeprecation sets enableRpcDeprecation
func (r *Registry) SetEnableRpcDeprecation(enable bool) {
r.enableRpcDeprecation = enable
}

// GetEnableRpcDeprecation returns enableRpcDeprecation
func (r *Registry) GetEnableRpcDeprecation() bool {
return r.enableRpcDeprecation
}
12 changes: 11 additions & 1 deletion protoc-gen-openapiv2/defs.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ def _run_proto_gen_openapi(
generate_unbound_methods,
visibility_restriction_selectors,
use_allof_for_refs,
disable_default_responses):
disable_default_responses,
enable_rpc_deprecation):
args = actions.args()

args.add("--plugin", "protoc-gen-openapiv2=%s" % protoc_gen_openapiv2.path)
Expand Down Expand Up @@ -148,6 +149,9 @@ def _run_proto_gen_openapi(
if disable_default_responses:
args.add("--openapiv2_opt", "disable_default_responses=true")

if enable_rpc_deprecation:
args.add("--openapiv2_opt", "enable_rpc_deprecation=true")

args.add("--openapiv2_opt", "repeated_path_param_separator=%s" % repeated_path_param_separator)

proto_file_infos = _direct_source_infos(proto_info)
Expand Down Expand Up @@ -255,6 +259,7 @@ def _proto_gen_openapi_impl(ctx):
visibility_restriction_selectors = ctx.attr.visibility_restriction_selectors,
use_allof_for_refs = ctx.attr.use_allof_for_refs,
disable_default_responses = ctx.attr.disable_default_responses,
enable_rpc_deprecation = ctx.attr.enable_rpc_deprecation,
),
),
),
Expand Down Expand Up @@ -410,6 +415,11 @@ protoc_gen_openapiv2 = rule(
" if you have to support custom response codes that are" +
" not 200.",
),
"enable_rpc_deprecation": attr.bool(
default = False,
mandatory = False,
doc = "whether to process grpc method's deprecated option.",
),
"_protoc": attr.label(
default = "@com_google_protobuf//:protoc",
executable = True,
Expand Down
10 changes: 8 additions & 2 deletions protoc-gen-openapiv2/internal/genopenapi/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -1163,6 +1163,8 @@ func renderServices(services []*descriptor.Service, paths *openapiPathsObject, r
continue
}

deprecated := reg.GetEnableRpcDeprecation() && meth.GetOptions().GetDeprecated()

for bIdx, b := range meth.Bindings {
operationFunc := operationForMethod(b.HTTPMethod)
// Iterate over all the OpenAPI parameters
Expand Down Expand Up @@ -1508,6 +1510,7 @@ func renderServices(services []*descriptor.Service, paths *openapiPathsObject, r
operationObject := &openapiOperationObject{
Parameters: parameters,
Responses: openapiResponsesObject{},
Deprecated: deprecated,
}

if !reg.GetDisableDefaultResponses() {
Expand Down Expand Up @@ -1566,14 +1569,17 @@ func renderServices(services []*descriptor.Service, paths *openapiPathsObject, r
grpclog.Error(err)
return err
}

opts, err := getMethodOpenAPIOption(reg, meth)
if opts != nil {
if err != nil {
panic(err)
}
operationObject.ExternalDocs = protoExternalDocumentationToOpenAPIExternalDocumentation(opts.ExternalDocs, reg, meth)
// TODO(ivucica): this would be better supported by looking whether the method is deprecated in the proto file
operationObject.Deprecated = opts.Deprecated

if opts.Deprecated {
operationObject.Deprecated = true
}

if opts.Summary != "" {
operationObject.Summary = opts.Summary
Expand Down
124 changes: 124 additions & 0 deletions protoc-gen-openapiv2/internal/genopenapi/template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10789,3 +10789,127 @@ func TestApiVisibilityOption(t *testing.T) {
t.Fatal("Definition should be excluded by api visibility option")
}
}

func TestRenderServicesOptionDeprecated(t *testing.T) {
testCases := [...]struct {
testName string
methodOptions descriptorpb.MethodOptions
openapiOperation *openapi_options.Operation
expectedDeprecated bool
}{
{
testName: "method option",
methodOptions: descriptorpb.MethodOptions{
Deprecated: proto.Bool(true),
},
expectedDeprecated: true,
},
{
testName: "openapi option",
openapiOperation: &openapi_options.Operation{
Deprecated: true,
},
expectedDeprecated: true,
},
{
testName: "empty openapi doesn't override method option",
methodOptions: descriptorpb.MethodOptions{
Deprecated: proto.Bool(true),
},
openapiOperation: &openapi_options.Operation{},
expectedDeprecated: true,
},
}

for _, tc := range testCases {
tc := tc

t.Run(tc.testName, func(t *testing.T) {
msgdesc := &descriptorpb.DescriptorProto{
Name: proto.String("ExampleMessage"),
}

meth := &descriptorpb.MethodDescriptorProto{
Name: proto.String("Example"),
InputType: proto.String("ExampleMessage"),
OutputType: proto.String("ExampleMessage"),
Options: &tc.methodOptions,
}

svc := &descriptorpb.ServiceDescriptorProto{
Name: proto.String("ExampleService"),
Method: []*descriptorpb.MethodDescriptorProto{meth},
}

msg := &descriptor.Message{
DescriptorProto: msgdesc,
}

file := descriptor.File{
FileDescriptorProto: &descriptorpb.FileDescriptorProto{
SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
Name: proto.String("example.proto"),
Package: proto.String("example"),
MessageType: []*descriptorpb.DescriptorProto{msgdesc},
Service: []*descriptorpb.ServiceDescriptorProto{svc},
Options: &descriptorpb.FileOptions{
GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"),
},
},
GoPkg: descriptor.GoPackage{
Path: "example.com/path/to/example/example.pb",
Name: "example_pb",
},
Messages: []*descriptor.Message{msg},
Services: []*descriptor.Service{
{
ServiceDescriptorProto: svc,
Methods: []*descriptor.Method{
{
MethodDescriptorProto: meth,
RequestType: msg,
ResponseType: msg,
Bindings: []*descriptor.Binding{
{
HTTPMethod: "GET",
PathTmpl: httprule.Template{
Version: 1,
OpCodes: []int{0, 0},
Template: "/v1/echo",
},
},
},
},
},
},
},
}

if tc.openapiOperation != nil {
proto.SetExtension(
proto.Message(file.Services[0].Methods[0].Options),
openapi_options.E_Openapiv2Operation,
tc.openapiOperation,
)
}

reg := descriptor.NewRegistry()
reg.SetEnableRpcDeprecation(true)
fileCL := crossLinkFixture(&file)

if err := reg.Load(reqFromFile(fileCL)); err != nil {
t.Errorf("reg.Load(%#v) failed with %v; want success", file, err)
}

result, err := applyTemplate(param{File: fileCL, reg: reg})
if err != nil {
t.Fatalf("applyTemplate(%#v) failed with %v; want success", file, err)
}

got := result.getPathItemObject("/v1/echo").Get.Deprecated
if got != tc.expectedDeprecated {
t.Fatalf("Wrong deprecated field, got %v want %v", got, tc.expectedDeprecated)
}
})
}
}
3 changes: 3 additions & 0 deletions protoc-gen-openapiv2/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ var (
useAllOfForRefs = flag.Bool("use_allof_for_refs", false, "if set, will use allOf as container for $ref to preserve same-level properties.")
allowPatchFeature = flag.Bool("allow_patch_feature", true, "whether to hide update_mask fields in PATCH requests from the generated swagger file.")
preserveRPCOrder = flag.Bool("preserve_rpc_order", false, "if true, will ensure the order of paths emitted in openapi swagger files mirror the order of RPC methods found in proto files. If false, emitted paths will be ordered alphabetically.")
enableRpcDeprecation = flag.Bool("enable_rpc_deprecation", false, "whether to process grpc method's deprecated option.")

_ = flag.Bool("logtostderr", false, "Legacy glog compatibility. This flag is a no-op, you can safely remove it")
)
Expand Down Expand Up @@ -172,6 +173,8 @@ func main() {
reg.SetUseAllOfForRefs(*useAllOfForRefs)
reg.SetAllowPatchFeature(*allowPatchFeature)
reg.SetPreserveRPCOrder(*preserveRPCOrder)
reg.SetEnableRpcDeprecation(*enableRpcDeprecation)

if err := reg.SetRepeatedPathParamSeparator(*repeatedPathParamSeparator); err != nil {
emitError(err)
return
Expand Down
Loading