From 1313c0e1afd679d27005c4f976fb8564fb02598a Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 6 Nov 2023 11:38:18 +0000 Subject: [PATCH] fix(client/v2): fix short command description if not set and skip unsupported commands (backport #18324) (#18371) Co-authored-by: Julien Robert --- client/v2/autocli/common.go | 7 +- client/v2/autocli/msg.go | 5 + client/v2/autocli/query.go | 4 + .../autocli/testdata/help-deprecated.golden | 2 + .../autocli/testdata/help-toplevel-msg.golden | 8 +- client/v2/internal/util/util.go | 62 +++++++ client/v2/internal/util/util_test.go | 161 ++++++++++++++++++ 7 files changed, 244 insertions(+), 5 deletions(-) create mode 100644 client/v2/internal/util/util_test.go diff --git a/client/v2/autocli/common.go b/client/v2/autocli/common.go index 93d399ff9d42..8fdecf69cf8e 100644 --- a/client/v2/autocli/common.go +++ b/client/v2/autocli/common.go @@ -26,6 +26,11 @@ func (b *Builder) buildMethodCommandCommon(descriptor protoreflect.MethodDescrip options = &autocliv1.RpcCommandOptions{} } + short := options.Short + if short == "" { + short = fmt.Sprintf("Execute the %s RPC method", descriptor.Name()) + } + long := options.Long if long == "" { long = util.DescriptorDocs(descriptor) @@ -43,7 +48,7 @@ func (b *Builder) buildMethodCommandCommon(descriptor protoreflect.MethodDescrip SilenceUsage: false, Use: use, Long: long, - Short: options.Short, + Short: short, Example: options.Example, Aliases: options.Alias, SuggestFor: options.SuggestFor, diff --git a/client/v2/autocli/msg.go b/client/v2/autocli/msg.go index 5bad6e1817ff..6e8b237c3584 100644 --- a/client/v2/autocli/msg.go +++ b/client/v2/autocli/msg.go @@ -12,6 +12,7 @@ import ( autocliv1 "cosmossdk.io/api/cosmos/autocli/v1" "cosmossdk.io/client/v2/autocli/flag" + "cosmossdk.io/client/v2/internal/util" "github.com/cosmos/cosmos-sdk/client" clienttx "github.com/cosmos/cosmos-sdk/client/tx" @@ -85,6 +86,10 @@ func (b *Builder) AddMsgServiceCommands(cmd *cobra.Command, cmdDescriptor *autoc continue } + if !util.IsSupportedVersion(util.DescriptorDocs(methodDescriptor)) { + continue + } + methodCmd, err := b.BuildMsgMethodCommand(methodDescriptor, methodOpts) if err != nil { return err diff --git a/client/v2/autocli/query.go b/client/v2/autocli/query.go index 30305e40649a..05665e048fac 100644 --- a/client/v2/autocli/query.go +++ b/client/v2/autocli/query.go @@ -82,6 +82,10 @@ func (b *Builder) AddQueryServiceCommands(cmd *cobra.Command, cmdDescriptor *aut continue } + if !util.IsSupportedVersion(util.DescriptorDocs(methodDescriptor)) { + continue + } + methodCmd, err := b.BuildQueryMethodCommand(methodDescriptor, methodOpts) if err != nil { return err diff --git a/client/v2/autocli/testdata/help-deprecated.golden b/client/v2/autocli/testdata/help-deprecated.golden index 6dda3365422f..94586620952a 100644 --- a/client/v2/autocli/testdata/help-deprecated.golden +++ b/client/v2/autocli/testdata/help-deprecated.golden @@ -1,4 +1,6 @@ Command "echo" is deprecated, don't use this +Execute the Echo RPC method + Usage: test deprecatedecho echo [flags] diff --git a/client/v2/autocli/testdata/help-toplevel-msg.golden b/client/v2/autocli/testdata/help-toplevel-msg.golden index 51d33ed95e69..f4afbd999bb4 100644 --- a/client/v2/autocli/testdata/help-toplevel-msg.golden +++ b/client/v2/autocli/testdata/help-toplevel-msg.golden @@ -5,13 +5,13 @@ Usage: test [command] Available Commands: - burn + burn Execute the Burn RPC method completion Generate the autocompletion script for the specified shell help Help about any command - multi-send + multi-send Execute the MultiSend RPC method send Send coins from one account to another - set-send-enabled - update-params + set-send-enabled Execute the SetSendEnabled RPC method + update-params Execute the UpdateParams RPC method Flags: -h, --help help for test diff --git a/client/v2/internal/util/util.go b/client/v2/internal/util/util.go index 31d748a8f7b3..1ec988142278 100644 --- a/client/v2/internal/util/util.go +++ b/client/v2/internal/util/util.go @@ -1,6 +1,10 @@ package util import ( + "regexp" + "runtime/debug" + "strings" + "google.golang.org/protobuf/reflect/protoreflect" "google.golang.org/protobuf/reflect/protoregistry" "google.golang.org/protobuf/types/dynamicpb" @@ -8,10 +12,18 @@ import ( "cosmossdk.io/client/v2/internal/strcase" ) +// get build info to verify later if comment is supported +// this is a hack in because of the global api module package +// later versions unsupported by the current version can be added +var buildInfo, _ = debug.ReadBuildInfo() + +// DescriptorName returns the name of the descriptor in kebab case. func DescriptorKebabName(descriptor protoreflect.Descriptor) string { return strcase.ToKebab(string(descriptor.Name())) } +// DescriptorDocs returns the leading comments of the descriptor. +// TODO this does not work, to fix. func DescriptorDocs(descriptor protoreflect.Descriptor) string { return descriptor.ParentFile().SourceLocations().ByDescriptor(descriptor).LeadingComments } @@ -24,3 +36,53 @@ func ResolveMessageType(resolver protoregistry.MessageTypeResolver, descriptor p return dynamicpb.NewMessageType(descriptor) } + +// IsSupportedVersion is used to determine in which version of a module / sdk a rpc was introduced. +// It returns false if the rpc has comment for an higher version than the current one. +func IsSupportedVersion(input string) bool { + return isSupportedVersion(input, buildInfo) +} + +// isSupportedVersion is used to determine in which version of a module / sdk a rpc was introduced. +// It returns false if the rpc has comment for an higher version than the current one. +// It takes a buildInfo as argument to be able to test it. +func isSupportedVersion(input string, buildInfo *debug.BuildInfo) bool { + if input == "" || buildInfo == nil { + return true + } + + moduleName, version := parseSinceComment(input) + for _, dep := range buildInfo.Deps { + if !strings.Contains(dep.Path, moduleName) { + continue + } + + return version <= dep.Version + } + + return true // if cannot find the module consider it's supported +} + +var sinceCommentRegex = regexp.MustCompile(`\/\/\s*since: (\S+) (\S+)`) + +// parseSinceComment parses the `// Since: cosmos-sdk v0.xx` comment on rpc. +func parseSinceComment(input string) (string, string) { + var ( + moduleName string + version string + ) + + input = strings.ToLower(input) + input = strings.ReplaceAll(input, "cosmos sdk", "cosmos-sdk") + + matches := sinceCommentRegex.FindStringSubmatch(input) + if len(matches) >= 3 { + moduleName, version = matches[1], matches[2] + + if !strings.HasPrefix(version, "v") { + version = "v" + version + } + } + + return moduleName, version +} diff --git a/client/v2/internal/util/util_test.go b/client/v2/internal/util/util_test.go new file mode 100644 index 000000000000..52a7b16f88a7 --- /dev/null +++ b/client/v2/internal/util/util_test.go @@ -0,0 +1,161 @@ +package util + +import ( + "runtime/debug" + "testing" +) + +func TestIsSupportedVersion(t *testing.T) { + mockBuildInfo := &debug.BuildInfo{ + Deps: []*debug.Module{ + { + Path: "github.com/cosmos/cosmos-sdk", + Version: "v0.50.0", + }, + { + Path: "cosmossdk.io/feegrant", + Version: "v0.1.0", + }, + }, + } + + cases := []struct { + input string + expected bool + }{ + { + input: "", + expected: true, + }, + { + input: "not a since comment", + expected: true, + }, + { + input: "// Since: cosmos-sdk v0.47", + expected: true, + }, + { + input: "// since: Cosmos-SDK 0.50", + expected: true, + }, + { + input: "// Since: cosmos-sdk v0.51", + expected: false, + }, + { + input: "// Since: cosmos-sdk v1.0.0", + expected: false, + }, + { + input: "// since: x/feegrant v0.1.0", + expected: true, + }, + { + input: "// since: feegrant v0.0.1", + expected: true, + }, + { + input: "// since: feegrant v0.1.0", + expected: true, + }, + { + input: "// since: feegrant v0.1", + expected: true, + }, + { + input: "// since: feegrant v0.1.1", + expected: false, + }, + { + input: "// since: feegrant v0.2.0", + expected: false, + }, + } + + for _, tc := range cases { + resp := isSupportedVersion(tc.input, mockBuildInfo) + if resp != tc.expected { + t.Errorf("expected %v, got %v", tc.expected, resp) + } + + resp = isSupportedVersion(tc.input, &debug.BuildInfo{}) + if !resp { + t.Errorf("expected %v, got %v", true, resp) + } + } +} + +func TestParseSinceComment(t *testing.T) { + cases := []struct { + input string + expectedModuleName string + expectedVersion string + }{ + { + input: "", + expectedModuleName: "", + expectedVersion: "", + }, + { + input: "not a since comment", + expectedModuleName: "", + expectedVersion: "", + }, + { + input: "// Since: Cosmos SDK 0.50", + expectedModuleName: "cosmos-sdk", + expectedVersion: "v0.50", + }, + { + input: "// since: Cosmos SDK 0.50", + expectedModuleName: "cosmos-sdk", + expectedVersion: "v0.50", + }, + { + input: "// since: cosmos sdk 0.50", + expectedModuleName: "cosmos-sdk", + expectedVersion: "v0.50", + }, + { + input: "// since: Cosmos-SDK 0.50", + expectedModuleName: "cosmos-sdk", + expectedVersion: "v0.50", + }, + { + input: "// Since: cosmos-sdk v0.50", + expectedModuleName: "cosmos-sdk", + expectedVersion: "v0.50", + }, + { + input: "//since: cosmos-sdk v0.50.1", + expectedModuleName: "cosmos-sdk", + expectedVersion: "v0.50.1", + }, + { + input: "// since: cosmos-sdk 0.47.0-veronica", + expectedModuleName: "cosmos-sdk", + expectedVersion: "v0.47.0-veronica", + }, + { + input: "// Since: x/feegrant v0.1.0", + expectedModuleName: "x/feegrant", + expectedVersion: "v0.1.0", + }, + { + input: "// since: x/feegrant 0.1", + expectedModuleName: "x/feegrant", + expectedVersion: "v0.1", + }, + } + + for _, tc := range cases { + moduleName, version := parseSinceComment(tc.input) + if moduleName != tc.expectedModuleName { + t.Errorf("expected module name %s, got %s", tc.expectedModuleName, moduleName) + } + if version != tc.expectedVersion { + t.Errorf("expected version %s, got %s", tc.expectedVersion, version) + } + } +}