cosmossdk.io/client/v2@v2.0.0-beta.1/autocli/query.go (about)

     1  package autocli
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"time"
     8  
     9  	autocliv1 "cosmossdk.io/api/cosmos/autocli/v1"
    10  	"cosmossdk.io/x/tx/signing/aminojson"
    11  	"github.com/cockroachdb/errors"
    12  	"github.com/cosmos/cosmos-sdk/client"
    13  	"github.com/spf13/cobra"
    14  	"google.golang.org/protobuf/reflect/protoreflect"
    15  
    16  	"cosmossdk.io/client/v2/internal/flags"
    17  	"cosmossdk.io/client/v2/internal/util"
    18  )
    19  
    20  // BuildQueryCommand builds the query commands for all the provided modules. If a custom command is provided for a
    21  // module, this is used instead of any automatically generated CLI commands. This allows apps to a fully dynamic client
    22  // with a more customized experience if a binary with custom commands is downloaded.
    23  func (b *Builder) BuildQueryCommand(appOptions AppOptions, customCmds map[string]*cobra.Command) (*cobra.Command, error) {
    24  	queryCmd := topLevelCmd("query", "Querying subcommands")
    25  	queryCmd.Aliases = []string{"q"}
    26  
    27  	if err := b.enhanceCommandCommon(queryCmd, queryCmdType, appOptions, customCmds); err != nil {
    28  		return nil, err
    29  	}
    30  
    31  	return queryCmd, nil
    32  }
    33  
    34  // AddQueryServiceCommands adds a sub-command to the provided command for each
    35  // method in the specified service and returns the command. This can be used in
    36  // order to add auto-generated commands to an existing command.
    37  func (b *Builder) AddQueryServiceCommands(cmd *cobra.Command, cmdDescriptor *autocliv1.ServiceCommandDescriptor) error {
    38  	for cmdName, subCmdDesc := range cmdDescriptor.SubCommands {
    39  		subCmd := findSubCommand(cmd, cmdName)
    40  		if subCmd == nil {
    41  			subCmd = topLevelCmd(cmdName, fmt.Sprintf("Querying commands for the %s service", subCmdDesc.Service))
    42  		}
    43  
    44  		if err := b.AddQueryServiceCommands(subCmd, subCmdDesc); err != nil {
    45  			return err
    46  		}
    47  
    48  		cmd.AddCommand(subCmd)
    49  	}
    50  
    51  	// skip empty command descriptors
    52  	if cmdDescriptor.Service == "" {
    53  		return nil
    54  	}
    55  
    56  	descriptor, err := b.FileResolver.FindDescriptorByName(protoreflect.FullName(cmdDescriptor.Service))
    57  	if err != nil {
    58  		return errors.Errorf("can't find service %s: %v", cmdDescriptor.Service, err)
    59  	}
    60  
    61  	service := descriptor.(protoreflect.ServiceDescriptor)
    62  	methods := service.Methods()
    63  
    64  	rpcOptMap := map[protoreflect.Name]*autocliv1.RpcCommandOptions{}
    65  	for _, option := range cmdDescriptor.RpcCommandOptions {
    66  		name := protoreflect.Name(option.RpcMethod)
    67  		rpcOptMap[name] = option
    68  		// make sure method exists
    69  		if m := methods.ByName(name); m == nil {
    70  			return fmt.Errorf("rpc method %q not found for service %q", name, service.FullName())
    71  		}
    72  	}
    73  
    74  	for i := 0; i < methods.Len(); i++ {
    75  		methodDescriptor := methods.Get(i)
    76  		methodOpts, ok := rpcOptMap[methodDescriptor.Name()]
    77  		if !ok {
    78  			methodOpts = &autocliv1.RpcCommandOptions{}
    79  		}
    80  
    81  		if methodOpts.Skip {
    82  			continue
    83  		}
    84  
    85  		if !util.IsSupportedVersion(util.DescriptorDocs(methodDescriptor)) {
    86  			continue
    87  		}
    88  
    89  		methodCmd, err := b.BuildQueryMethodCommand(methodDescriptor, methodOpts)
    90  		if err != nil {
    91  			return err
    92  		}
    93  
    94  		if findSubCommand(cmd, methodCmd.Name()) != nil {
    95  			// do not overwrite existing commands
    96  			// we do not display a warning because you may want to overwrite an autocli command
    97  			continue
    98  		}
    99  
   100  		cmd.AddCommand(methodCmd)
   101  	}
   102  
   103  	return nil
   104  }
   105  
   106  // BuildQueryMethodCommand creates a gRPC query command for the given service method. This can be used to auto-generate
   107  // just a single command for a single service rpc method.
   108  func (b *Builder) BuildQueryMethodCommand(descriptor protoreflect.MethodDescriptor, options *autocliv1.RpcCommandOptions) (*cobra.Command, error) {
   109  	getClientConn := b.GetClientConn
   110  	serviceDescriptor := descriptor.Parent().(protoreflect.ServiceDescriptor)
   111  	methodName := fmt.Sprintf("/%s/%s", serviceDescriptor.FullName(), descriptor.Name())
   112  	outputType := util.ResolveMessageType(b.TypeResolver, descriptor.Output())
   113  	encoderOptions := aminojson.EncoderOptions{
   114  		Indent:          "  ",
   115  		DoNotSortFields: true,
   116  		TypeResolver:    b.TypeResolver,
   117  		FileResolver:    b.FileResolver,
   118  	}
   119  
   120  	cmd, err := b.buildMethodCommandCommon(descriptor, options, func(cmd *cobra.Command, input protoreflect.Message) error {
   121  		cmd.SetContext(context.WithValue(context.Background(), client.ClientContextKey, &b.ClientCtx))
   122  
   123  		clientConn, err := getClientConn(cmd)
   124  		if err != nil {
   125  			return err
   126  		}
   127  
   128  		output := outputType.New()
   129  		if err := clientConn.Invoke(cmd.Context(), methodName, input.Interface(), output.Interface()); err != nil {
   130  			return err
   131  		}
   132  
   133  		if noIndent, _ := cmd.Flags().GetBool(flags.FlagNoIndent); noIndent {
   134  			encoderOptions.Indent = ""
   135  		}
   136  
   137  		enc := encoder(aminojson.NewEncoder(encoderOptions))
   138  		bz, err := enc.Marshal(output.Interface())
   139  		if err != nil {
   140  			return fmt.Errorf("cannot marshal response %v: %w", output.Interface(), err)
   141  		}
   142  
   143  		err = b.outOrStdoutFormat(cmd, bz)
   144  		return err
   145  	})
   146  	if err != nil {
   147  		return nil, err
   148  	}
   149  
   150  	if b.AddQueryConnFlags != nil {
   151  		b.AddQueryConnFlags(cmd)
   152  
   153  		cmd.Flags().BoolP(flags.FlagNoIndent, "", false, "Do not indent JSON output")
   154  	}
   155  
   156  	// silence usage only for inner txs & queries commands
   157  	if cmd != nil {
   158  		cmd.SilenceUsage = true
   159  	}
   160  
   161  	return cmd, nil
   162  }
   163  
   164  func encoder(encoder aminojson.Encoder) aminojson.Encoder {
   165  	return encoder.DefineTypeEncoding("google.protobuf.Duration", func(_ *aminojson.Encoder, msg protoreflect.Message, w io.Writer) error {
   166  		var (
   167  			secondsName protoreflect.Name = "seconds"
   168  			nanosName   protoreflect.Name = "nanos"
   169  		)
   170  
   171  		fields := msg.Descriptor().Fields()
   172  		secondsField := fields.ByName(secondsName)
   173  		if secondsField == nil {
   174  			return fmt.Errorf("expected seconds field")
   175  		}
   176  
   177  		seconds := msg.Get(secondsField).Int()
   178  
   179  		nanosField := fields.ByName(nanosName)
   180  		if nanosField == nil {
   181  			return fmt.Errorf("expected nanos field")
   182  		}
   183  
   184  		nanos := msg.Get(nanosField).Int()
   185  
   186  		_, err := fmt.Fprintf(w, `"%s"`, (time.Duration(seconds)*time.Second + (time.Duration(nanos) * time.Nanosecond)).String())
   187  		return err
   188  	})
   189  }