feat(autocli): add simple msg support (#14832)
Co-authored-by: Aaron Craelius <aaron@regen.network> Co-authored-by: Aaron Craelius <aaronc@users.noreply.github.com>
This commit is contained in:
parent
1edc60b871
commit
ee458eb6c4
@ -4,6 +4,7 @@ import (
|
||||
autocliv1 "cosmossdk.io/api/cosmos/autocli/v1"
|
||||
"cosmossdk.io/core/appmodule"
|
||||
"cosmossdk.io/depinject"
|
||||
"fmt"
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||
"github.com/spf13/cobra"
|
||||
@ -79,19 +80,41 @@ func (appOptions AppOptions) EnhanceRootCommandWithBuilder(rootCmd *cobra.Comman
|
||||
}
|
||||
|
||||
customQueryCmds := map[string]*cobra.Command{}
|
||||
customMsgCmds := map[string]*cobra.Command{}
|
||||
for name, module := range appOptions.Modules {
|
||||
if module, ok := module.(HasCustomQueryCommand); ok {
|
||||
cmd := module.GetQueryCmd()
|
||||
if queryModule, ok := module.(HasCustomQueryCommand); ok {
|
||||
queryCmd := queryModule.GetQueryCmd()
|
||||
// filter any nil commands
|
||||
if cmd != nil {
|
||||
customQueryCmds[name] = cmd
|
||||
if queryCmd != nil {
|
||||
customQueryCmds[name] = queryCmd
|
||||
}
|
||||
}
|
||||
if msgModule, ok := module.(HasCustomTxCommand); ok {
|
||||
msgCmd := msgModule.GetTxCmd()
|
||||
// filter any nil commands
|
||||
if msgCmd != nil {
|
||||
customMsgCmds[name] = msgCmd
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if we have an existing query command, enhance it or build a custom one
|
||||
enhanceQuery := func(cmd *cobra.Command, modOpts *autocliv1.ModuleOptions, moduleName string) error {
|
||||
queryCmdDesc := modOpts.Query
|
||||
if queryCmdDesc != nil {
|
||||
subCmd := topLevelCmd(moduleName, fmt.Sprintf("Querying commands for the %s module", moduleName))
|
||||
err := builder.AddQueryServiceCommands(cmd, queryCmdDesc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.AddCommand(subCmd)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if queryCmd := findSubCommand(rootCmd, "query"); queryCmd != nil {
|
||||
if err := builder.EnhanceQueryCommand(queryCmd, moduleOptions, customQueryCmds); err != nil {
|
||||
if err := builder.enhanceCommandCommon(queryCmd, moduleOptions, customQueryCmds, enhanceQuery); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
@ -103,5 +126,32 @@ func (appOptions AppOptions) EnhanceRootCommandWithBuilder(rootCmd *cobra.Comman
|
||||
rootCmd.AddCommand(queryCmd)
|
||||
}
|
||||
|
||||
enhanceMsg := func(cmd *cobra.Command, modOpts *autocliv1.ModuleOptions, moduleName string) error {
|
||||
txCmdDesc := modOpts.Tx
|
||||
if txCmdDesc != nil {
|
||||
subCmd := topLevelCmd(moduleName, fmt.Sprintf("Transations commands for the %s module", moduleName))
|
||||
err := builder.AddQueryServiceCommands(cmd, txCmdDesc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.AddCommand(subCmd)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if msgCmd := findSubCommand(rootCmd, "tx"); msgCmd != nil {
|
||||
if err := builder.enhanceCommandCommon(msgCmd, moduleOptions, customQueryCmds, enhanceMsg); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
subCmd, err := builder.BuildMsgCommand(moduleOptions, customQueryCmds)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rootCmd.AddCommand(subCmd)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
110
client/v2/autocli/common.go
Normal file
110
client/v2/autocli/common.go
Normal file
@ -0,0 +1,110 @@
|
||||
package autocli
|
||||
|
||||
import (
|
||||
autocliv1 "cosmossdk.io/api/cosmos/autocli/v1"
|
||||
"github.com/spf13/cobra"
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
|
||||
"cosmossdk.io/client/v2/internal/util"
|
||||
)
|
||||
|
||||
func (b *Builder) buildMethodCommandCommon(descriptor protoreflect.MethodDescriptor, options *autocliv1.RpcCommandOptions, exec func(cmd *cobra.Command, input protoreflect.Message) error) (*cobra.Command, error) {
|
||||
if options == nil {
|
||||
// use the defaults
|
||||
options = &autocliv1.RpcCommandOptions{}
|
||||
}
|
||||
|
||||
if options.Skip {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
long := options.Long
|
||||
if long == "" {
|
||||
long = util.DescriptorDocs(descriptor)
|
||||
}
|
||||
|
||||
inputDesc := descriptor.Input()
|
||||
inputType := util.ResolveMessageType(b.TypeResolver, inputDesc)
|
||||
|
||||
use := options.Use
|
||||
if use == "" {
|
||||
use = protoNameToCliName(descriptor.Name())
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: use,
|
||||
Long: long,
|
||||
Short: options.Short,
|
||||
Example: options.Example,
|
||||
Aliases: options.Alias,
|
||||
SuggestFor: options.SuggestFor,
|
||||
Deprecated: options.Deprecated,
|
||||
Version: options.Version,
|
||||
}
|
||||
|
||||
binder, err := b.AddMessageFlags(cmd.Context(), cmd.Flags(), inputType, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cmd.Args = binder.CobraArgs
|
||||
|
||||
cmd.RunE = func(cmd *cobra.Command, args []string) error {
|
||||
input, err := binder.BuildMessage(args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return exec(cmd, input)
|
||||
}
|
||||
|
||||
if b.AddQueryConnFlags != nil {
|
||||
b.AddQueryConnFlags(cmd)
|
||||
}
|
||||
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
// enhanceCommandCommon enhances the provided query or msg command with either generated commands based on the provided module
|
||||
// options or the provided custom commands for each module. If the provided query command already contains a command
|
||||
// for a module, that command is not over-written by this method. This allows a graceful addition of autocli to
|
||||
// automatically fill in missing commands.
|
||||
func (b *Builder) enhanceCommandCommon(cmd *cobra.Command, moduleOptions map[string]*autocliv1.ModuleOptions, customCmds map[string]*cobra.Command, buildModuleCommand func(*cobra.Command, *autocliv1.ModuleOptions, string) error) error {
|
||||
allModuleNames := map[string]bool{}
|
||||
for moduleName := range moduleOptions {
|
||||
allModuleNames[moduleName] = true
|
||||
}
|
||||
for moduleName := range customCmds {
|
||||
allModuleNames[moduleName] = true
|
||||
}
|
||||
|
||||
for moduleName := range allModuleNames {
|
||||
// if we have an existing command skip adding one here
|
||||
if cmd.HasSubCommands() {
|
||||
if _, _, err := cmd.Find([]string{moduleName}); err == nil {
|
||||
// command already exists, skip
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// if we have a custom command use that instead of generating one
|
||||
if custom := customCmds[moduleName]; custom != nil {
|
||||
// custom commands get added lower down
|
||||
cmd.AddCommand(custom)
|
||||
continue
|
||||
}
|
||||
|
||||
// check for autocli options
|
||||
modOpts := moduleOptions[moduleName]
|
||||
if modOpts == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
err := buildModuleCommand(cmd, modOpts, moduleName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
56
client/v2/autocli/common_test.go
Normal file
56
client/v2/autocli/common_test.go
Normal file
@ -0,0 +1,56 @@
|
||||
package autocli
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"cosmossdk.io/client/v2/internal/testpb"
|
||||
"github.com/spf13/cobra"
|
||||
"google.golang.org/grpc"
|
||||
"gotest.tools/v3/assert"
|
||||
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func testExecCommon(t *testing.T, buildModuleCommand func(string, *Builder) (*cobra.Command, error), args ...string) *testClientConn {
|
||||
server := grpc.NewServer()
|
||||
testpb.RegisterQueryServer(server, &testEchoServer{})
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
assert.NilError(t, err)
|
||||
go func() {
|
||||
err := server.Serve(listener)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
|
||||
clientConn, err := grpc.Dial(listener.Addr().String(), grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
assert.NilError(t, err)
|
||||
defer func() {
|
||||
err := clientConn.Close()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
|
||||
conn := &testClientConn{
|
||||
ClientConn: clientConn,
|
||||
t: t,
|
||||
out: &bytes.Buffer{},
|
||||
errorOut: &bytes.Buffer{},
|
||||
}
|
||||
b := &Builder{
|
||||
GetClientConn: func(*cobra.Command) (grpc.ClientConnInterface, error) {
|
||||
return conn, nil
|
||||
},
|
||||
}
|
||||
|
||||
cmd, err := buildModuleCommand("test", b)
|
||||
assert.NilError(t, err)
|
||||
assert.NilError(t, err)
|
||||
cmd.SetArgs(args)
|
||||
cmd.SetOut(conn.out)
|
||||
cmd.SetErr(conn.errorOut)
|
||||
cmd.Execute()
|
||||
return conn
|
||||
}
|
||||
116
client/v2/autocli/msg.go
Normal file
116
client/v2/autocli/msg.go
Normal file
@ -0,0 +1,116 @@
|
||||
package autocli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
autocliv1 "cosmossdk.io/api/cosmos/autocli/v1"
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
"google.golang.org/protobuf/reflect/protoregistry"
|
||||
)
|
||||
|
||||
// BuildMsgCommand builds the msg commands for all the provided modules. If a custom command is provided for a
|
||||
// module, this is used instead of any automatically generated CLI commands. This allows apps to a fully dynamic client
|
||||
// with a more customized experience if a binary with custom commands is downloaded.
|
||||
func (b *Builder) BuildMsgCommand(moduleOptions map[string]*autocliv1.ModuleOptions, customCmds map[string]*cobra.Command) (*cobra.Command, error) {
|
||||
msgCmd := topLevelCmd("tx", "Transaction subcommands")
|
||||
enhanceMsg := func(cmd *cobra.Command, modOpts *autocliv1.ModuleOptions, moduleName string) error {
|
||||
txCmdDesc := modOpts.Tx
|
||||
if txCmdDesc != nil {
|
||||
subCmd := topLevelCmd(moduleName, fmt.Sprintf("Transations commands for the %s module", moduleName))
|
||||
err := b.AddMsgServiceCommands(cmd, txCmdDesc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.AddCommand(subCmd)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if err := b.enhanceCommandCommon(msgCmd, moduleOptions, customCmds, enhanceMsg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return msgCmd, nil
|
||||
}
|
||||
|
||||
// AddMsgServiceCommands adds a sub-command to the provided command for each
|
||||
// method in the specified service and returns the command. This can be used in
|
||||
// order to add auto-generated commands to an existing command.
|
||||
func (b *Builder) AddMsgServiceCommands(cmd *cobra.Command, cmdDescriptor *autocliv1.ServiceCommandDescriptor) error {
|
||||
for cmdName, subCmdDescriptor := range cmdDescriptor.SubCommands {
|
||||
subCmd := topLevelCmd(cmdName, fmt.Sprintf("Tx commands for the %s service", subCmdDescriptor.Service))
|
||||
// Add recursive sub-commands if there are any. This is used for nested services.
|
||||
err := b.AddMsgServiceCommands(subCmd, subCmdDescriptor)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd.AddCommand(subCmd)
|
||||
}
|
||||
|
||||
if cmdDescriptor.Service == "" {
|
||||
// skip empty command descriptor
|
||||
return nil
|
||||
}
|
||||
|
||||
resolver := b.FileResolver
|
||||
if b.FileResolver == nil {
|
||||
resolver = protoregistry.GlobalFiles
|
||||
}
|
||||
|
||||
descriptor, err := resolver.FindDescriptorByName(protoreflect.FullName(cmdDescriptor.Service))
|
||||
if err != nil {
|
||||
return errors.Errorf("can't find service %s: %v", cmdDescriptor.Service, err)
|
||||
}
|
||||
service := descriptor.(protoreflect.ServiceDescriptor)
|
||||
methods := service.Methods()
|
||||
|
||||
rpcOptMap := map[protoreflect.Name]*autocliv1.RpcCommandOptions{}
|
||||
for _, option := range cmdDescriptor.RpcCommandOptions {
|
||||
methodName := protoreflect.Name(option.RpcMethod)
|
||||
// validate that methods exist
|
||||
if m := methods.ByName(methodName); m == nil {
|
||||
return fmt.Errorf("rpc method %q not found for service %q", methodName, service.FullName())
|
||||
|
||||
}
|
||||
rpcOptMap[methodName] = option
|
||||
|
||||
}
|
||||
|
||||
methodsLength := methods.Len()
|
||||
for i := 0; i < methodsLength; i++ {
|
||||
methodDescriptor := methods.Get(i)
|
||||
methodOpts := rpcOptMap[methodDescriptor.Name()]
|
||||
methodCmd, err := b.BuildMsgMethodCommand(methodDescriptor, methodOpts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if methodCmd != nil {
|
||||
cmd.AddCommand(methodCmd)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Builder) BuildMsgMethodCommand(descriptor protoreflect.MethodDescriptor, options *autocliv1.RpcCommandOptions) (*cobra.Command, error) {
|
||||
jsonMarshalOptions := protojson.MarshalOptions{
|
||||
Indent: " ",
|
||||
UseProtoNames: true,
|
||||
UseEnumNumbers: false,
|
||||
EmitUnpopulated: true,
|
||||
Resolver: b.TypeResolver,
|
||||
}
|
||||
|
||||
return b.buildMethodCommandCommon(descriptor, options, func(cmd *cobra.Command, input protoreflect.Message) error {
|
||||
bz, err := jsonMarshalOptions.Marshal(input.Interface())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = fmt.Fprintln(cmd.OutOrStdout(), string(bz))
|
||||
return err
|
||||
})
|
||||
}
|
||||
359
client/v2/autocli/msg_test.go
Normal file
359
client/v2/autocli/msg_test.go
Normal file
@ -0,0 +1,359 @@
|
||||
package autocli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gotest.tools/v3/golden"
|
||||
|
||||
autocliv1 "cosmossdk.io/api/cosmos/autocli/v1"
|
||||
"github.com/spf13/cobra"
|
||||
"gotest.tools/v3/assert"
|
||||
|
||||
"cosmossdk.io/client/v2/internal/testpb"
|
||||
)
|
||||
|
||||
var buildModuleMsgCommand = func(moduleName string, b *Builder) (*cobra.Command, error) {
|
||||
cmd := topLevelCmd(moduleName, fmt.Sprintf("Transations commands for the %s module", moduleName))
|
||||
|
||||
err := b.AddMsgServiceCommands(cmd, testCmdMsgDesc)
|
||||
return cmd, err
|
||||
}
|
||||
|
||||
var testCmdMsgDesc = &autocliv1.ServiceCommandDescriptor{
|
||||
Service: testpb.Msg_ServiceDesc.ServiceName,
|
||||
RpcCommandOptions: []*autocliv1.RpcCommandOptions{
|
||||
{
|
||||
RpcMethod: "Send",
|
||||
Use: "send [pos1] [pos2] [pos3...]",
|
||||
Version: "1.0",
|
||||
Alias: []string{"s"},
|
||||
SuggestFor: []string{"send"},
|
||||
Example: "send 1 abc {}",
|
||||
Short: "send msg the value provided by the user",
|
||||
Long: "send msg the value provided by the user as a proto JSON object with populated with the provided fields and positional arguments",
|
||||
PositionalArgs: []*autocliv1.PositionalArgDescriptor{
|
||||
{
|
||||
ProtoField: "positional1",
|
||||
},
|
||||
{
|
||||
ProtoField: "positional2",
|
||||
},
|
||||
{
|
||||
ProtoField: "positional3_varargs",
|
||||
Varargs: true,
|
||||
},
|
||||
},
|
||||
FlagOptions: map[string]*autocliv1.FlagOptions{
|
||||
"u32": {
|
||||
Name: "uint32",
|
||||
Shorthand: "u",
|
||||
Usage: "some random uint32",
|
||||
},
|
||||
"i32": {
|
||||
Usage: "some random int32",
|
||||
DefaultValue: "3",
|
||||
},
|
||||
"u64": {
|
||||
Usage: "some random uint64",
|
||||
DefaultValue: "5",
|
||||
},
|
||||
"deprecated_field": {
|
||||
Deprecated: "don't use this",
|
||||
},
|
||||
"shorthand_deprecated_field": {
|
||||
Shorthand: "d",
|
||||
Deprecated: "bad idea",
|
||||
},
|
||||
"hidden_bool": {
|
||||
Hidden: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
SubCommands: map[string]*autocliv1.ServiceCommandDescriptor{
|
||||
// we test the sub-command functionality using the same service with different options
|
||||
"deprecatedmsg": {
|
||||
Service: testpb.Msg_ServiceDesc.ServiceName,
|
||||
RpcCommandOptions: []*autocliv1.RpcCommandOptions{
|
||||
{
|
||||
RpcMethod: "Send",
|
||||
Deprecated: "dont use this",
|
||||
Short: "deprecated subcommand",
|
||||
},
|
||||
},
|
||||
},
|
||||
"skipmsg": {
|
||||
Service: testpb.Msg_ServiceDesc.ServiceName,
|
||||
RpcCommandOptions: []*autocliv1.RpcCommandOptions{
|
||||
{
|
||||
RpcMethod: "Send",
|
||||
Skip: true,
|
||||
Short: "skip subcommand",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestMsgOptions(t *testing.T) {
|
||||
conn := testExecCommon(t,
|
||||
buildModuleMsgCommand,
|
||||
"send", "5", "6", `{"denom":"foo","amount":"1"}`,
|
||||
"--uint32", "7",
|
||||
"--u64", "8",
|
||||
)
|
||||
response := conn.out.String()
|
||||
var output testpb.MsgRequest
|
||||
err := protojson.Unmarshal([]byte(response), &output)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, output.GetU32(), uint32(7))
|
||||
assert.Equal(t, output.GetPositional1(), int32(5))
|
||||
assert.Equal(t, output.GetPositional2(), "6")
|
||||
}
|
||||
|
||||
func TestMsgOptionsError(t *testing.T) {
|
||||
conn := testExecCommon(t, buildModuleMsgCommand,
|
||||
"send", "5",
|
||||
"--uint32", "7",
|
||||
"--u64", "8",
|
||||
)
|
||||
|
||||
assert.Assert(t, strings.Contains(conn.errorOut.String(), "requires at least 3 arg"))
|
||||
|
||||
conn = testExecCommon(t, buildModuleMsgCommand,
|
||||
"send", "5", "6", `{"denom":"foo","amount":"1"}`,
|
||||
"--uint32", "7",
|
||||
"--u64", "abc",
|
||||
)
|
||||
assert.Assert(t, strings.Contains(conn.errorOut.String(), "invalid argument "))
|
||||
|
||||
}
|
||||
|
||||
func TestDeprecatedMsg(t *testing.T) {
|
||||
conn := testExecCommon(t, buildModuleMsgCommand, "send",
|
||||
"1", "abc", `{"denom":"foo","amount":"1"}`,
|
||||
"--deprecated-field", "foo")
|
||||
assert.Assert(t, strings.Contains(conn.out.String(), "--deprecated-field has been deprecated"))
|
||||
|
||||
conn = testExecCommon(t, buildModuleMsgCommand, "send",
|
||||
"1", "abc", `{"denom":"foo","amount":"1"}`,
|
||||
"-d", "foo")
|
||||
assert.Assert(t, strings.Contains(conn.out.String(), "--shorthand-deprecated-field has been deprecated"))
|
||||
}
|
||||
|
||||
func TestEverythingMsg(t *testing.T) {
|
||||
conn := testExecCommon(t, buildModuleMsgCommand,
|
||||
"send",
|
||||
"1",
|
||||
"abc",
|
||||
`{"denom":"foo","amount":"1234"}`,
|
||||
`{"denom":"bar","amount":"4321"}`,
|
||||
"--a-bool",
|
||||
"--an-enum", "two",
|
||||
"--a-message", `{"bar":"abc", "baz":-3}`,
|
||||
"--duration", "4h3s",
|
||||
"--uint32", "27",
|
||||
"--u64", "3267246890",
|
||||
"--i32", "-253",
|
||||
"--i64", "-234602347",
|
||||
"--str", "def",
|
||||
"--timestamp", "2019-01-02T00:01:02Z",
|
||||
"--a-coin", `{"denom":"foo","amount":"100000"}`,
|
||||
"--an-address", "cosmossdghdsfoi2134sdgh",
|
||||
"--bz", "c2RncXdlZndkZ3NkZw==",
|
||||
"--page-count-total",
|
||||
"--page-key", "MTIzNTQ4N3NnaGRhcw==",
|
||||
"--page-limit", "1000",
|
||||
"--page-offset", "10",
|
||||
"--page-reverse",
|
||||
"--bools", "true",
|
||||
"--bools", "false,false,true",
|
||||
"--enums", "one",
|
||||
"--enums", "five",
|
||||
"--enums", "two",
|
||||
"--strings", "abc",
|
||||
"--strings", "xyz",
|
||||
"--strings", "xyz,qrs",
|
||||
"--durations", "3s",
|
||||
"--durations", "5s",
|
||||
"--durations", "10h",
|
||||
"--some-messages", "{}",
|
||||
"--some-messages", `{"bar":"baz"}`,
|
||||
"--some-messages", `{"baz":-1}`,
|
||||
"--uints", "1,2,3",
|
||||
"--uints", "4",
|
||||
)
|
||||
response := conn.out.String()
|
||||
var output testpb.MsgRequest
|
||||
err := protojson.Unmarshal([]byte(response), &output)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, output.GetU32(), uint32(27))
|
||||
assert.Equal(t, output.GetU64(), uint64(3267246890))
|
||||
assert.Equal(t, output.GetPositional1(), int32(1))
|
||||
assert.Equal(t, output.GetPositional2(), "abc")
|
||||
assert.Equal(t, output.GetABool(), true)
|
||||
assert.Equal(t, output.GetAnEnum(), testpb.Enum_ENUM_TWO)
|
||||
}
|
||||
|
||||
func TestHelpMsg(t *testing.T) {
|
||||
conn := testExecCommon(t, buildModuleMsgCommand, "-h")
|
||||
golden.Assert(t, conn.out.String(), "help-toplevel-msg.golden")
|
||||
|
||||
conn = testExecCommon(t, buildModuleMsgCommand, "send", "-h")
|
||||
golden.Assert(t, conn.out.String(), "help-echo-msg.golden")
|
||||
|
||||
conn = testExecCommon(t, buildModuleMsgCommand, "deprecatedmsg", "send", "-h")
|
||||
golden.Assert(t, conn.out.String(), "help-deprecated-msg.golden")
|
||||
}
|
||||
|
||||
func TestBuildCustomMsgCommand(t *testing.T) {
|
||||
b := &Builder{}
|
||||
customCommandCalled := false
|
||||
cmd, err := b.BuildMsgCommand(map[string]*autocliv1.ModuleOptions{
|
||||
"test": {
|
||||
Tx: testCmdMsgDesc,
|
||||
},
|
||||
}, map[string]*cobra.Command{
|
||||
"test": {Use: "test", Run: func(cmd *cobra.Command, args []string) {
|
||||
customCommandCalled = true
|
||||
}},
|
||||
})
|
||||
assert.NilError(t, err)
|
||||
cmd.SetArgs([]string{"test", "tx"})
|
||||
assert.NilError(t, cmd.Execute())
|
||||
assert.Assert(t, customCommandCalled)
|
||||
}
|
||||
|
||||
func TestErrorBuildMsgCommand(t *testing.T) {
|
||||
b := &Builder{}
|
||||
|
||||
commandDescriptor := &autocliv1.ServiceCommandDescriptor{
|
||||
Service: testpb.Msg_ServiceDesc.ServiceName,
|
||||
RpcCommandOptions: []*autocliv1.RpcCommandOptions{
|
||||
{
|
||||
RpcMethod: "Send",
|
||||
PositionalArgs: []*autocliv1.PositionalArgDescriptor{
|
||||
{
|
||||
ProtoField: "un-existent-proto-field",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
opts := map[string]*autocliv1.ModuleOptions{
|
||||
"test": {
|
||||
Tx: commandDescriptor,
|
||||
},
|
||||
}
|
||||
_, err := b.BuildMsgCommand(opts, nil)
|
||||
assert.ErrorContains(t, err, "can't find field un-existent-proto-field")
|
||||
|
||||
nonExistentService := &autocliv1.ServiceCommandDescriptor{Service: "un-existent-service"}
|
||||
opts = map[string]*autocliv1.ModuleOptions{
|
||||
"test": {
|
||||
Tx: nonExistentService,
|
||||
},
|
||||
}
|
||||
_, err = b.BuildMsgCommand(opts, nil)
|
||||
assert.ErrorContains(t, err, "can't find service un-existent-service")
|
||||
}
|
||||
|
||||
func TestNotFoundErrorsMsg(t *testing.T) {
|
||||
b := &Builder{}
|
||||
buildModuleMsgCommand := func(moduleName string, cmdDescriptor *autocliv1.ServiceCommandDescriptor) (*cobra.Command, error) {
|
||||
cmd := topLevelCmd(moduleName, fmt.Sprintf("Transations commands for the %s module", moduleName))
|
||||
|
||||
err := b.AddMsgServiceCommands(cmd, cmdDescriptor)
|
||||
return cmd, err
|
||||
}
|
||||
|
||||
// Query non existent service
|
||||
_, err := buildModuleMsgCommand("test", &autocliv1.ServiceCommandDescriptor{Service: "un-existent-service"})
|
||||
assert.ErrorContains(t, err, "can't find service un-existent-service")
|
||||
|
||||
_, err = buildModuleMsgCommand("test", &autocliv1.ServiceCommandDescriptor{
|
||||
Service: testpb.Query_ServiceDesc.ServiceName,
|
||||
RpcCommandOptions: []*autocliv1.RpcCommandOptions{{RpcMethod: "un-existent-method"}},
|
||||
})
|
||||
assert.ErrorContains(t, err, "rpc method \"un-existent-method\" not found")
|
||||
|
||||
_, err = buildModuleMsgCommand("test", &autocliv1.ServiceCommandDescriptor{
|
||||
Service: testpb.Msg_ServiceDesc.ServiceName,
|
||||
RpcCommandOptions: []*autocliv1.RpcCommandOptions{
|
||||
{
|
||||
RpcMethod: "Send",
|
||||
PositionalArgs: []*autocliv1.PositionalArgDescriptor{
|
||||
{
|
||||
ProtoField: "un-existent-proto-field",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
assert.ErrorContains(t, err, "can't find field un-existent-proto-field")
|
||||
|
||||
_, err = buildModuleMsgCommand("test", &autocliv1.ServiceCommandDescriptor{
|
||||
Service: testpb.Msg_ServiceDesc.ServiceName,
|
||||
RpcCommandOptions: []*autocliv1.RpcCommandOptions{
|
||||
{
|
||||
RpcMethod: "Send",
|
||||
FlagOptions: map[string]*autocliv1.FlagOptions{
|
||||
"un-existent-flag": {},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
assert.ErrorContains(t, err, "can't find field un-existent-flag")
|
||||
|
||||
}
|
||||
|
||||
func TestEnhanceMessageCommand(t *testing.T) {
|
||||
b := &Builder{}
|
||||
enhanceMsg := func(cmd *cobra.Command, modOpts *autocliv1.ModuleOptions, moduleName string) error {
|
||||
txCmdDesc := modOpts.Tx
|
||||
if txCmdDesc != nil {
|
||||
subCmd := topLevelCmd(moduleName, fmt.Sprintf("Transations commands for the %s module", moduleName))
|
||||
err := b.AddMsgServiceCommands(cmd, txCmdDesc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.AddCommand(subCmd)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Test that the command has a subcommand
|
||||
cmd := &cobra.Command{Use: "test"}
|
||||
cmd.AddCommand(&cobra.Command{Use: "test"})
|
||||
options := map[string]*autocliv1.ModuleOptions{
|
||||
"test": {},
|
||||
}
|
||||
err := b.enhanceCommandCommon(cmd, options, map[string]*cobra.Command{}, enhanceMsg)
|
||||
assert.NilError(t, err)
|
||||
|
||||
cmd = &cobra.Command{Use: "test"}
|
||||
options = map[string]*autocliv1.ModuleOptions{}
|
||||
customCommands := map[string]*cobra.Command{
|
||||
"test2": {Use: "test"},
|
||||
}
|
||||
err = b.enhanceCommandCommon(cmd, options, customCommands, enhanceMsg)
|
||||
assert.NilError(t, err)
|
||||
|
||||
cmd = &cobra.Command{Use: "test"}
|
||||
options = map[string]*autocliv1.ModuleOptions{
|
||||
"test": {Tx: nil},
|
||||
}
|
||||
customCommands = map[string]*cobra.Command{}
|
||||
err = b.enhanceCommandCommon(cmd, options, customCommands, enhanceMsg)
|
||||
assert.NilError(t, err)
|
||||
|
||||
}
|
||||
|
||||
type testMessageServer struct {
|
||||
testpb.UnimplementedMsgServer
|
||||
}
|
||||
@ -10,7 +10,6 @@ import (
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
"google.golang.org/protobuf/reflect/protoregistry"
|
||||
|
||||
"cosmossdk.io/client/v2/internal/strcase"
|
||||
"cosmossdk.io/client/v2/internal/util"
|
||||
)
|
||||
|
||||
@ -20,66 +19,25 @@ import (
|
||||
func (b *Builder) BuildQueryCommand(moduleOptions map[string]*autocliv1.ModuleOptions, customCmds map[string]*cobra.Command) (*cobra.Command, error) {
|
||||
queryCmd := topLevelCmd("query", "Querying subcommands")
|
||||
queryCmd.Aliases = []string{"q"}
|
||||
if err := b.EnhanceQueryCommand(queryCmd, moduleOptions, customCmds); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return queryCmd, nil
|
||||
}
|
||||
|
||||
// EnhanceQueryCommand enhances the provided query command with either generated commands based on the provided module
|
||||
// options or the provided custom commands for each module. If the provided query command already contains a command
|
||||
// for a module, that command is not over-written by this method. This allows a graceful addition of autocli to
|
||||
// automatically fill in missing commands.
|
||||
func (b *Builder) EnhanceQueryCommand(queryCmd *cobra.Command, moduleOptions map[string]*autocliv1.ModuleOptions, customCmds map[string]*cobra.Command) error {
|
||||
allModuleNames := map[string]bool{}
|
||||
for moduleName := range moduleOptions {
|
||||
allModuleNames[moduleName] = true
|
||||
}
|
||||
for moduleName := range customCmds {
|
||||
allModuleNames[moduleName] = true
|
||||
}
|
||||
|
||||
for moduleName := range allModuleNames {
|
||||
// if we have an existing command skip adding one here
|
||||
if existing := findSubCommand(queryCmd, moduleName); existing != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// if we have a custom command use that instead of generating one
|
||||
if custom := customCmds[moduleName]; custom != nil {
|
||||
// custom commands get added lower down
|
||||
queryCmd.AddCommand(custom)
|
||||
continue
|
||||
}
|
||||
|
||||
// check for autocli options
|
||||
modOpts := moduleOptions[moduleName]
|
||||
if modOpts == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
queryCmdDesc := modOpts.Query
|
||||
if queryCmdDesc != nil {
|
||||
cmd, err := b.BuildModuleQueryCommand(moduleName, queryCmdDesc)
|
||||
enhanceMsg := func(cmd *cobra.Command, modOpts *autocliv1.ModuleOptions, moduleName string) error {
|
||||
txQueryDesc := modOpts.Query
|
||||
if txQueryDesc != nil {
|
||||
subCmd := topLevelCmd(moduleName, fmt.Sprintf("Querying commands for the %s module", moduleName))
|
||||
err := b.AddQueryServiceCommands(subCmd, txQueryDesc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
queryCmd.AddCommand(cmd)
|
||||
cmd.AddCommand(subCmd)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if err := b.enhanceCommandCommon(queryCmd, moduleOptions, customCmds, enhanceMsg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// BuildModuleQueryCommand builds the query command for a single module.
|
||||
func (b *Builder) BuildModuleQueryCommand(moduleName string, cmdDescriptor *autocliv1.ServiceCommandDescriptor) (*cobra.Command, error) {
|
||||
cmd := topLevelCmd(moduleName, fmt.Sprintf("Querying commands for the %s module", moduleName))
|
||||
|
||||
err := b.AddQueryServiceCommands(cmd, cmdDescriptor)
|
||||
|
||||
return cmd, err
|
||||
return queryCmd, nil
|
||||
}
|
||||
|
||||
// AddQueryServiceCommands adds a sub-command to the provided command for each
|
||||
@ -143,52 +101,10 @@ func (b *Builder) AddQueryServiceCommands(cmd *cobra.Command, cmdDescriptor *aut
|
||||
// BuildQueryMethodCommand creates a gRPC query command for the given service method. This can be used to auto-generate
|
||||
// just a single command for a single service rpc method.
|
||||
func (b *Builder) BuildQueryMethodCommand(descriptor protoreflect.MethodDescriptor, options *autocliv1.RpcCommandOptions) (*cobra.Command, error) {
|
||||
if options == nil {
|
||||
// use the defaults
|
||||
options = &autocliv1.RpcCommandOptions{}
|
||||
}
|
||||
|
||||
if options.Skip {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
serviceDescriptor := descriptor.Parent().(protoreflect.ServiceDescriptor)
|
||||
|
||||
long := options.Long
|
||||
if long == "" {
|
||||
long = util.DescriptorDocs(descriptor)
|
||||
}
|
||||
|
||||
getClientConn := b.GetClientConn
|
||||
serviceDescriptor := descriptor.Parent().(protoreflect.ServiceDescriptor)
|
||||
methodName := fmt.Sprintf("/%s/%s", serviceDescriptor.FullName(), descriptor.Name())
|
||||
|
||||
inputDesc := descriptor.Input()
|
||||
inputType := util.ResolveMessageType(b.TypeResolver, inputDesc)
|
||||
outputType := util.ResolveMessageType(b.TypeResolver, descriptor.Output())
|
||||
|
||||
use := options.Use
|
||||
if use == "" {
|
||||
use = protoNameToCliName(descriptor.Name())
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: use,
|
||||
Long: long,
|
||||
Short: options.Short,
|
||||
Example: options.Example,
|
||||
Aliases: options.Alias,
|
||||
SuggestFor: options.SuggestFor,
|
||||
Deprecated: options.Deprecated,
|
||||
Version: options.Version,
|
||||
}
|
||||
|
||||
binder, err := b.AddMessageFlags(cmd.Context(), cmd.Flags(), inputType, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cmd.Args = binder.CobraArgs
|
||||
|
||||
jsonMarshalOptions := protojson.MarshalOptions{
|
||||
Indent: " ",
|
||||
UseProtoNames: true,
|
||||
@ -197,17 +113,12 @@ func (b *Builder) BuildQueryMethodCommand(descriptor protoreflect.MethodDescript
|
||||
Resolver: b.TypeResolver,
|
||||
}
|
||||
|
||||
cmd.RunE = func(cmd *cobra.Command, args []string) error {
|
||||
cmd, err := b.buildMethodCommandCommon(descriptor, options, func(cmd *cobra.Command, input protoreflect.Message) error {
|
||||
clientConn, err := getClientConn(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
input, err := binder.BuildMessage(args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
output := outputType.New()
|
||||
ctx := cmd.Context()
|
||||
err = clientConn.Invoke(ctx, methodName, input.Interface(), output.Interface())
|
||||
@ -222,6 +133,9 @@ func (b *Builder) BuildQueryMethodCommand(descriptor protoreflect.MethodDescript
|
||||
|
||||
_, err = fmt.Fprintln(cmd.OutOrStdout(), string(bz))
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if b.AddQueryConnFlags != nil {
|
||||
@ -230,17 +144,3 @@ func (b *Builder) BuildQueryMethodCommand(descriptor protoreflect.MethodDescript
|
||||
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
func protoNameToCliName(name protoreflect.Name) string {
|
||||
return strcase.ToKebab(string(name))
|
||||
}
|
||||
|
||||
func topLevelCmd(use, short string) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: use,
|
||||
Short: short,
|
||||
DisableFlagParsing: false,
|
||||
SuggestionsMinimumDistance: 2,
|
||||
RunE: validateCmd,
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,14 +3,13 @@ package autocli
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"net"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
autocliv1 "cosmossdk.io/api/cosmos/autocli/v1"
|
||||
"github.com/spf13/cobra"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"google.golang.org/protobuf/testing/protocmp"
|
||||
"gotest.tools/v3/assert"
|
||||
"gotest.tools/v3/golden"
|
||||
@ -18,6 +17,14 @@ import (
|
||||
"cosmossdk.io/client/v2/internal/testpb"
|
||||
)
|
||||
|
||||
var buildModuleQueryCommand = func(moduleName string, b *Builder) (*cobra.Command, error) {
|
||||
|
||||
cmd := topLevelCmd(moduleName, fmt.Sprintf("Querying commands for the %s module", moduleName))
|
||||
|
||||
err := b.AddQueryServiceCommands(cmd, testCmdDesc)
|
||||
return cmd, err
|
||||
}
|
||||
|
||||
var testCmdDesc = &autocliv1.ServiceCommandDescriptor{
|
||||
Service: testpb.Query_ServiceDesc.ServiceName,
|
||||
RpcCommandOptions: []*autocliv1.RpcCommandOptions{
|
||||
@ -92,47 +99,8 @@ var testCmdDesc = &autocliv1.ServiceCommandDescriptor{
|
||||
},
|
||||
}
|
||||
|
||||
func testExec(t *testing.T, args ...string) *testClientConn {
|
||||
server := grpc.NewServer()
|
||||
testpb.RegisterQueryServer(server, &testEchoServer{})
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
assert.NilError(t, err)
|
||||
go func() {
|
||||
err := server.Serve(listener)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
defer server.GracefulStop()
|
||||
clientConn, err := grpc.Dial(listener.Addr().String(), grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
assert.NilError(t, err)
|
||||
defer func() {
|
||||
err := clientConn.Close()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
|
||||
conn := &testClientConn{
|
||||
ClientConn: clientConn,
|
||||
t: t,
|
||||
out: &bytes.Buffer{},
|
||||
}
|
||||
b := &Builder{
|
||||
GetClientConn: func(*cobra.Command) (grpc.ClientConnInterface, error) {
|
||||
return conn, nil
|
||||
},
|
||||
}
|
||||
cmd, err := b.BuildModuleQueryCommand("test", testCmdDesc)
|
||||
assert.NilError(t, err)
|
||||
cmd.SetArgs(args)
|
||||
cmd.SetOut(conn.out)
|
||||
assert.NilError(t, cmd.Execute())
|
||||
return conn
|
||||
}
|
||||
|
||||
func TestEverything(t *testing.T) {
|
||||
conn := testExec(t,
|
||||
conn := testExecCommon(t, buildModuleQueryCommand,
|
||||
"echo",
|
||||
"1",
|
||||
"abc",
|
||||
@ -177,11 +145,11 @@ func TestEverything(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestOptions(t *testing.T) {
|
||||
conn := testExec(t,
|
||||
conn := testExecCommon(t, buildModuleQueryCommand,
|
||||
"echo",
|
||||
"1", "abc", `{"denom":"foo","amount":"1"}`,
|
||||
"-u", "27", // shorthand
|
||||
"--u64", // no opt default value
|
||||
"--u64", "5", // no opt default value
|
||||
)
|
||||
lastReq := conn.lastRequest.(*testpb.EchoRequest)
|
||||
assert.Equal(t, uint32(27), lastReq.U32) // shorthand got set
|
||||
@ -190,26 +158,26 @@ func TestOptions(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestHelp(t *testing.T) {
|
||||
conn := testExec(t, "-h")
|
||||
conn := testExecCommon(t, buildModuleQueryCommand, "-h")
|
||||
golden.Assert(t, conn.out.String(), "help-toplevel.golden")
|
||||
|
||||
conn = testExec(t, "echo", "-h")
|
||||
conn = testExecCommon(t, buildModuleQueryCommand, "echo", "-h")
|
||||
golden.Assert(t, conn.out.String(), "help-echo.golden")
|
||||
|
||||
conn = testExec(t, "deprecatedecho", "echo", "-h")
|
||||
conn = testExecCommon(t, buildModuleQueryCommand, "deprecatedecho", "echo", "-h")
|
||||
golden.Assert(t, conn.out.String(), "help-deprecated.golden")
|
||||
|
||||
conn = testExec(t, "skipecho", "-h")
|
||||
conn = testExecCommon(t, buildModuleQueryCommand, "skipecho", "-h")
|
||||
golden.Assert(t, conn.out.String(), "help-skip.golden")
|
||||
}
|
||||
|
||||
func TestDeprecated(t *testing.T) {
|
||||
conn := testExec(t, "echo",
|
||||
conn := testExecCommon(t, buildModuleQueryCommand, "echo",
|
||||
"1", "abc", `{}`,
|
||||
"--deprecated-field", "foo")
|
||||
assert.Assert(t, strings.Contains(conn.out.String(), "--deprecated-field has been deprecated"))
|
||||
|
||||
conn = testExec(t, "echo",
|
||||
conn = testExecCommon(t, buildModuleQueryCommand, "echo",
|
||||
"1", "abc", `{}`,
|
||||
"-s", "foo")
|
||||
assert.Assert(t, strings.Contains(conn.out.String(), "--shorthand-deprecated-field has been deprecated"))
|
||||
@ -236,19 +204,26 @@ func TestBuildCustomQueryCommand(t *testing.T) {
|
||||
func TestNotFoundErrors(t *testing.T) {
|
||||
b := &Builder{}
|
||||
|
||||
buildModuleQueryCommand := func(moduleName string, cmdDescriptor *autocliv1.ServiceCommandDescriptor) (*cobra.Command, error) {
|
||||
cmd := topLevelCmd("query", "Querying subcommands")
|
||||
|
||||
err := b.AddMsgServiceCommands(cmd, cmdDescriptor)
|
||||
return cmd, err
|
||||
}
|
||||
|
||||
// bad service
|
||||
_, err := b.BuildModuleQueryCommand("test", &autocliv1.ServiceCommandDescriptor{Service: "foo"})
|
||||
_, err := buildModuleQueryCommand("test", &autocliv1.ServiceCommandDescriptor{Service: "foo"})
|
||||
assert.ErrorContains(t, err, "can't find service foo")
|
||||
|
||||
// bad method
|
||||
_, err = b.BuildModuleQueryCommand("test", &autocliv1.ServiceCommandDescriptor{
|
||||
_, err = buildModuleQueryCommand("test", &autocliv1.ServiceCommandDescriptor{
|
||||
Service: testpb.Query_ServiceDesc.ServiceName,
|
||||
RpcCommandOptions: []*autocliv1.RpcCommandOptions{{RpcMethod: "bar"}},
|
||||
})
|
||||
assert.ErrorContains(t, err, "rpc method \"bar\" not found")
|
||||
|
||||
// bad positional field
|
||||
_, err = b.BuildModuleQueryCommand("test", &autocliv1.ServiceCommandDescriptor{
|
||||
_, err = buildModuleQueryCommand("test", &autocliv1.ServiceCommandDescriptor{
|
||||
Service: testpb.Query_ServiceDesc.ServiceName,
|
||||
RpcCommandOptions: []*autocliv1.RpcCommandOptions{
|
||||
{
|
||||
@ -264,7 +239,7 @@ func TestNotFoundErrors(t *testing.T) {
|
||||
assert.ErrorContains(t, err, "can't find field foo")
|
||||
|
||||
// bad flag field
|
||||
_, err = b.BuildModuleQueryCommand("test", &autocliv1.ServiceCommandDescriptor{
|
||||
_, err = buildModuleQueryCommand("test", &autocliv1.ServiceCommandDescriptor{
|
||||
Service: testpb.Query_ServiceDesc.ServiceName,
|
||||
RpcCommandOptions: []*autocliv1.RpcCommandOptions{
|
||||
{
|
||||
@ -284,6 +259,7 @@ type testClientConn struct {
|
||||
lastRequest interface{}
|
||||
lastResponse interface{}
|
||||
out *bytes.Buffer
|
||||
errorOut *bytes.Buffer
|
||||
}
|
||||
|
||||
func (t *testClientConn) Invoke(ctx context.Context, method string, args interface{}, reply interface{}, opts ...grpc.CallOption) error {
|
||||
|
||||
38
client/v2/autocli/testdata/help-deprecated-msg.golden
vendored
Normal file
38
client/v2/autocli/testdata/help-deprecated-msg.golden
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
Command "send" is deprecated, dont use this
|
||||
deprecated subcommand
|
||||
|
||||
Usage:
|
||||
test deprecatedmsg send [flags]
|
||||
|
||||
Flags:
|
||||
--a-bool
|
||||
--a-coin cosmos.base.v1beta1.Coin (json)
|
||||
--a-message testpb.AMessage (json)
|
||||
--an-address bech32 account address key name
|
||||
--an-enum Enum (unspecified | one | two | five | neg-three) (default unspecified)
|
||||
--bools bools (default [])
|
||||
--bz bytesBase64
|
||||
--deprecated-field string
|
||||
--duration duration
|
||||
--durations duration (repeated)
|
||||
--enums Enum (unspecified | one | two | five | neg-three) (repeated)
|
||||
-h, --help help for send
|
||||
--hidden-bool
|
||||
--i32 int32
|
||||
--i64 int
|
||||
--page-count-total
|
||||
--page-key bytesBase64
|
||||
--page-limit uint
|
||||
--page-offset uint
|
||||
--page-reverse
|
||||
--positional1 int32
|
||||
--positional2 string
|
||||
--positional3-varargs cosmos.base.v1beta1.Coin (json) (repeated)
|
||||
--shorthand-deprecated-field string
|
||||
--some-messages testpb.AMessage (json) (repeated)
|
||||
--str string
|
||||
--strings strings
|
||||
--timestamp timestamp (RFC 3339)
|
||||
--u32 uint32
|
||||
--u64 uint
|
||||
--uints uints (default [])
|
||||
40
client/v2/autocli/testdata/help-echo-msg.golden
vendored
Normal file
40
client/v2/autocli/testdata/help-echo-msg.golden
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
send msg the value provided by the user as a proto JSON object with populated with the provided fields and positional arguments
|
||||
|
||||
Usage:
|
||||
test send [pos1] [pos2] [pos3...] [flags]
|
||||
|
||||
Aliases:
|
||||
send, s
|
||||
|
||||
Examples:
|
||||
send 1 abc {}
|
||||
|
||||
Flags:
|
||||
--a-bool
|
||||
--a-coin cosmos.base.v1beta1.Coin (json)
|
||||
--a-message testpb.AMessage (json)
|
||||
--an-address bech32 account address key name
|
||||
--an-enum Enum (unspecified | one | two | five | neg-three) (default unspecified)
|
||||
--bools bools (default [])
|
||||
--bz bytesBase64
|
||||
--deprecated-field string (DEPRECATED: don't use this)
|
||||
--duration duration
|
||||
--durations duration (repeated)
|
||||
--enums Enum (unspecified | one | two | five | neg-three) (repeated)
|
||||
-h, --help help for send
|
||||
--i32 int32 some random int32
|
||||
--i64 int
|
||||
--page-count-total
|
||||
--page-key bytesBase64
|
||||
--page-limit uint
|
||||
--page-offset uint
|
||||
--page-reverse
|
||||
-d, --shorthand-deprecated-field string (DEPRECATED: bad idea)
|
||||
--some-messages testpb.AMessage (json) (repeated)
|
||||
--str string
|
||||
--strings strings
|
||||
--timestamp timestamp (RFC 3339)
|
||||
--u64 uint some random uint64
|
||||
-u, --uint32 uint32 some random uint32
|
||||
--uints uints (default [])
|
||||
-v, --version version for send
|
||||
2
client/v2/autocli/testdata/help-echo.golden
vendored
2
client/v2/autocli/testdata/help-echo.golden
vendored
@ -34,7 +34,7 @@ Flags:
|
||||
--str string
|
||||
--strings strings
|
||||
--timestamp timestamp (RFC 3339)
|
||||
--u64 uint[=5] some random uint64
|
||||
--u64 uint some random uint64
|
||||
-u, --uint32 uint32 some random uint32
|
||||
--uints uints (default [])
|
||||
-v, --version version for echo
|
||||
|
||||
17
client/v2/autocli/testdata/help-toplevel-msg.golden
vendored
Normal file
17
client/v2/autocli/testdata/help-toplevel-msg.golden
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
Transations commands for the test module
|
||||
|
||||
Usage:
|
||||
test [flags]
|
||||
test [command]
|
||||
|
||||
Available Commands:
|
||||
completion Generate the autocompletion script for the specified shell
|
||||
deprecatedmsg Tx commands for the testpb.Msg service
|
||||
help Help about any command
|
||||
send send msg the value provided by the user
|
||||
skipmsg Tx commands for the testpb.Msg service
|
||||
|
||||
Flags:
|
||||
-h, --help help for test
|
||||
|
||||
Use "test [command] --help" for more information about a command.
|
||||
@ -1,8 +1,10 @@
|
||||
package autocli
|
||||
|
||||
import (
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
"strings"
|
||||
|
||||
"cosmossdk.io/client/v2/internal/strcase"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@ -17,3 +19,20 @@ func findSubCommand(cmd *cobra.Command, subCmdName string) *cobra.Command {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// topLevelCmd creates a new top-level command with the provided name and
|
||||
// description. The command will have DisableFlagParsing set to false and
|
||||
// SuggestionsMinimumDistance set to 2.
|
||||
func topLevelCmd(use, short string) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: use,
|
||||
Short: short,
|
||||
DisableFlagParsing: false,
|
||||
SuggestionsMinimumDistance: 2,
|
||||
RunE: validateCmd,
|
||||
}
|
||||
}
|
||||
|
||||
func protoNameToCliName(name protoreflect.Name) string {
|
||||
return strcase.ToKebab(string(name))
|
||||
}
|
||||
|
||||
53
client/v2/internal/testpb/msg.proto
Normal file
53
client/v2/internal/testpb/msg.proto
Normal file
@ -0,0 +1,53 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package testpb;
|
||||
|
||||
import "google/protobuf/timestamp.proto";
|
||||
import "google/protobuf/duration.proto";
|
||||
import "cosmos_proto/cosmos.proto";
|
||||
import "cosmos/base/query/v1beta1/pagination.proto";
|
||||
import "cosmos/base/v1beta1/coin.proto";
|
||||
import "testpb/query.proto";
|
||||
|
||||
service Msg {
|
||||
// Send a request and returns the request as a response.
|
||||
rpc Send(MsgRequest) returns (MsgResponse);
|
||||
}
|
||||
|
||||
message MsgRequest {
|
||||
// u32 is an uint32
|
||||
uint32 u32 = 1;
|
||||
uint64 u64 = 2;
|
||||
string str = 3;
|
||||
bytes bz = 4;
|
||||
google.protobuf.Timestamp timestamp = 5;
|
||||
google.protobuf.Duration duration = 6;
|
||||
int32 i32 = 7;
|
||||
int64 i64 = 10;
|
||||
bool a_bool = 15;
|
||||
testpb.Enum an_enum = 16;
|
||||
testpb.AMessage a_message = 17;
|
||||
cosmos.base.v1beta1.Coin a_coin = 18;
|
||||
string an_address = 19 [(cosmos_proto.scalar) = "cosmos.AddressString"];
|
||||
cosmos.base.query.v1beta1.PageRequest page = 20;
|
||||
repeated bool bools = 21;
|
||||
repeated uint32 uints = 22;
|
||||
repeated string strings = 23;
|
||||
repeated testpb.Enum enums = 24;
|
||||
repeated google.protobuf.Duration durations = 25;
|
||||
repeated testpb.AMessage some_messages = 26;
|
||||
|
||||
int32 positional1 = 27;
|
||||
string positional2 = 28;
|
||||
repeated cosmos.base.v1beta1.Coin positional3_varargs = 29;
|
||||
|
||||
string deprecated_field = 30;
|
||||
string shorthand_deprecated_field = 31;
|
||||
bool hidden_bool = 32;
|
||||
}
|
||||
|
||||
|
||||
message MsgResponse {
|
||||
MsgRequest request = 1;
|
||||
}
|
||||
|
||||
3553
client/v2/internal/testpb/msg.pulsar.go
Normal file
3553
client/v2/internal/testpb/msg.pulsar.go
Normal file
File diff suppressed because it is too large
Load Diff
107
client/v2/internal/testpb/msg_grpc.pb.go
Normal file
107
client/v2/internal/testpb/msg_grpc.pb.go
Normal file
@ -0,0 +1,107 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.2.0
|
||||
// - protoc (unknown)
|
||||
// source: testpb/msg.proto
|
||||
|
||||
package testpb
|
||||
|
||||
import (
|
||||
context "context"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
// Requires gRPC-Go v1.32.0 or later.
|
||||
const _ = grpc.SupportPackageIsVersion7
|
||||
|
||||
// MsgClient is the client API for Msg service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
type MsgClient interface {
|
||||
// Send a request and returns the request as a response.
|
||||
Send(ctx context.Context, in *MsgRequest, opts ...grpc.CallOption) (*MsgResponse, error)
|
||||
}
|
||||
|
||||
type msgClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewMsgClient(cc grpc.ClientConnInterface) MsgClient {
|
||||
return &msgClient{cc}
|
||||
}
|
||||
|
||||
func (c *msgClient) Send(ctx context.Context, in *MsgRequest, opts ...grpc.CallOption) (*MsgResponse, error) {
|
||||
out := new(MsgResponse)
|
||||
err := c.cc.Invoke(ctx, "/testpb.Msg/Send", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// MsgServer is the server API for Msg service.
|
||||
// All implementations must embed UnimplementedMsgServer
|
||||
// for forward compatibility
|
||||
type MsgServer interface {
|
||||
// Send a request and returns the request as a response.
|
||||
Send(context.Context, *MsgRequest) (*MsgResponse, error)
|
||||
mustEmbedUnimplementedMsgServer()
|
||||
}
|
||||
|
||||
// UnimplementedMsgServer must be embedded to have forward compatible implementations.
|
||||
type UnimplementedMsgServer struct {
|
||||
}
|
||||
|
||||
func (UnimplementedMsgServer) Send(context.Context, *MsgRequest) (*MsgResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Send not implemented")
|
||||
}
|
||||
func (UnimplementedMsgServer) mustEmbedUnimplementedMsgServer() {}
|
||||
|
||||
// UnsafeMsgServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to MsgServer will
|
||||
// result in compilation errors.
|
||||
type UnsafeMsgServer interface {
|
||||
mustEmbedUnimplementedMsgServer()
|
||||
}
|
||||
|
||||
func RegisterMsgServer(s grpc.ServiceRegistrar, srv MsgServer) {
|
||||
s.RegisterService(&Msg_ServiceDesc, srv)
|
||||
}
|
||||
|
||||
func _Msg_Send_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(MsgRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(MsgServer).Send(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/testpb.Msg/Send",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(MsgServer).Send(ctx, req.(*MsgRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
// Msg_ServiceDesc is the grpc.ServiceDesc for Msg service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
var Msg_ServiceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "testpb.Msg",
|
||||
HandlerType: (*MsgServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "Send",
|
||||
Handler: _Msg_Send_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "testpb/msg.proto",
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user