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

     1  package autocli
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  
     7  	"github.com/spf13/cobra"
     8  	"google.golang.org/protobuf/reflect/protoreflect"
     9  	"sigs.k8s.io/yaml"
    10  
    11  	autocliv1 "cosmossdk.io/api/cosmos/autocli/v1"
    12  	"cosmossdk.io/client/v2/internal/flags"
    13  	"cosmossdk.io/client/v2/internal/util"
    14  )
    15  
    16  type cmdType int
    17  
    18  const (
    19  	queryCmdType cmdType = iota
    20  	msgCmdType
    21  )
    22  
    23  func (b *Builder) buildMethodCommandCommon(descriptor protoreflect.MethodDescriptor, options *autocliv1.RpcCommandOptions, exec func(cmd *cobra.Command, input protoreflect.Message) error) (*cobra.Command, error) {
    24  	if options == nil {
    25  		// use the defaults
    26  		options = &autocliv1.RpcCommandOptions{}
    27  	}
    28  
    29  	short := options.Short
    30  	if short == "" {
    31  		short = fmt.Sprintf("Execute the %s RPC method", descriptor.Name())
    32  	}
    33  
    34  	long := options.Long
    35  	if long == "" {
    36  		long = util.DescriptorDocs(descriptor)
    37  	}
    38  
    39  	inputDesc := descriptor.Input()
    40  	inputType := util.ResolveMessageType(b.TypeResolver, inputDesc)
    41  
    42  	use := options.Use
    43  	if use == "" {
    44  		use = protoNameToCliName(descriptor.Name())
    45  	}
    46  
    47  	cmd := &cobra.Command{
    48  		SilenceUsage: false,
    49  		Use:          use,
    50  		Long:         long,
    51  		Short:        short,
    52  		Example:      options.Example,
    53  		Aliases:      options.Alias,
    54  		SuggestFor:   options.SuggestFor,
    55  		Deprecated:   options.Deprecated,
    56  		Version:      options.Version,
    57  	}
    58  
    59  	cmd.SetContext(context.Background())
    60  	binder, err := b.AddMessageFlags(cmd.Context(), cmd.Flags(), inputType, options)
    61  	if err != nil {
    62  		return nil, err
    63  	}
    64  
    65  	cmd.Args = binder.CobraArgs
    66  
    67  	cmd.RunE = func(cmd *cobra.Command, args []string) error {
    68  		input, err := binder.BuildMessage(args)
    69  		if err != nil {
    70  			return err
    71  		}
    72  
    73  		// signer related logic, triggers only when there is a signer defined
    74  		if binder.SignerInfo.FieldName != "" {
    75  			// mark the signer flag as required if defined
    76  			// TODO(@julienrbrt): UX improvement by only marking the flag as required when there is more than one key in the keyring;
    77  			// when there is only one key, use that key by default.
    78  			if binder.SignerInfo.IsFlag {
    79  				if err := cmd.MarkFlagRequired(binder.SignerInfo.FieldName); err != nil {
    80  					return err
    81  				}
    82  
    83  				// the client context uses the from flag to determine the signer.
    84  				// this sets the signer flags to the from flag value if a custom signer flag is set.
    85  				if binder.SignerInfo.FieldName != flags.FlagFrom {
    86  					signer, err := cmd.Flags().GetString(binder.SignerInfo.FieldName)
    87  					if err != nil {
    88  						return fmt.Errorf("failed to get signer flag: %w", err)
    89  					}
    90  
    91  					if err := cmd.Flags().Set(flags.FlagFrom, signer); err != nil {
    92  						return err
    93  					}
    94  				}
    95  			} else {
    96  				// if the signer is not a flag, it is a positional argument
    97  				// we need to get the correct positional arguments
    98  				if err := cmd.Flags().Set(flags.FlagFrom, args[binder.SignerInfo.PositionalArgIndex]); err != nil {
    99  					return err
   100  				}
   101  			}
   102  		}
   103  
   104  		return exec(cmd, input)
   105  	}
   106  
   107  	return cmd, nil
   108  }
   109  
   110  // enhanceCommandCommon enhances the provided query or msg command with either generated commands based on the provided module
   111  // options or the provided custom commands for each module. If the provided query command already contains a command
   112  // for a module, that command is not over-written by this method. This allows a graceful addition of autocli to
   113  // automatically fill in missing commands.
   114  func (b *Builder) enhanceCommandCommon(
   115  	cmd *cobra.Command,
   116  	cmdType cmdType,
   117  	appOptions AppOptions,
   118  	customCmds map[string]*cobra.Command,
   119  ) error {
   120  	moduleOptions := appOptions.ModuleOptions
   121  	if len(moduleOptions) == 0 {
   122  		moduleOptions = make(map[string]*autocliv1.ModuleOptions)
   123  	}
   124  	for name, module := range appOptions.Modules {
   125  		if _, ok := moduleOptions[name]; !ok {
   126  			if module, ok := module.(HasAutoCLIConfig); ok {
   127  				moduleOptions[name] = module.AutoCLIOptions()
   128  			} else {
   129  				moduleOptions[name] = nil
   130  			}
   131  		}
   132  	}
   133  
   134  	for moduleName, modOpts := range moduleOptions {
   135  		hasModuleOptions := modOpts != nil
   136  
   137  		// if we have an existing command skip adding one here
   138  		if subCmd := findSubCommand(cmd, moduleName); subCmd != nil {
   139  			if hasModuleOptions { // check if we need to enhance the existing command
   140  				if err := enhanceCustomCmd(b, subCmd, cmdType, modOpts); err != nil {
   141  					return err
   142  				}
   143  			}
   144  
   145  			continue
   146  		}
   147  
   148  		// if we have a custom command use that instead of generating one
   149  		if custom, ok := customCmds[moduleName]; ok {
   150  			if hasModuleOptions { // check if we need to enhance the existing command
   151  				if err := enhanceCustomCmd(b, custom, cmdType, modOpts); err != nil {
   152  					return err
   153  				}
   154  			}
   155  
   156  			cmd.AddCommand(custom)
   157  			continue
   158  		}
   159  
   160  		// if we don't have module options, skip adding a command as we don't have anything to add
   161  		if !hasModuleOptions {
   162  			continue
   163  		}
   164  
   165  		switch cmdType {
   166  		case queryCmdType:
   167  			if err := enhanceQuery(b, moduleName, cmd, modOpts); err != nil {
   168  				return err
   169  			}
   170  		case msgCmdType:
   171  			if err := enhanceMsg(b, moduleName, cmd, modOpts); err != nil {
   172  				return err
   173  			}
   174  		}
   175  	}
   176  
   177  	return nil
   178  }
   179  
   180  // enhanceQuery enhances the provided query command with the autocli commands for a module.
   181  func enhanceQuery(builder *Builder, moduleName string, cmd *cobra.Command, modOpts *autocliv1.ModuleOptions) error {
   182  	if queryCmdDesc := modOpts.Query; queryCmdDesc != nil {
   183  		subCmd := topLevelCmd(moduleName, fmt.Sprintf("Querying commands for the %s module", moduleName))
   184  		if err := builder.AddQueryServiceCommands(subCmd, queryCmdDesc); err != nil {
   185  			return err
   186  		}
   187  
   188  		cmd.AddCommand(subCmd)
   189  	}
   190  
   191  	return nil
   192  }
   193  
   194  // enhanceMsg enhances the provided msg command with the autocli commands for a module.
   195  func enhanceMsg(builder *Builder, moduleName string, cmd *cobra.Command, modOpts *autocliv1.ModuleOptions) error {
   196  	if txCmdDesc := modOpts.Tx; txCmdDesc != nil {
   197  		subCmd := topLevelCmd(moduleName, fmt.Sprintf("Transactions commands for the %s module", moduleName))
   198  		if err := builder.AddMsgServiceCommands(subCmd, txCmdDesc); err != nil {
   199  			return err
   200  		}
   201  
   202  		cmd.AddCommand(subCmd)
   203  	}
   204  
   205  	return nil
   206  }
   207  
   208  // enhanceCustomCmd enhances the provided custom query or msg command autocli commands for a module.
   209  func enhanceCustomCmd(builder *Builder, cmd *cobra.Command, cmdType cmdType, modOpts *autocliv1.ModuleOptions) error {
   210  	switch cmdType {
   211  	case queryCmdType:
   212  		if modOpts.Query != nil && modOpts.Query.EnhanceCustomCommand {
   213  			if err := builder.AddQueryServiceCommands(cmd, modOpts.Query); err != nil {
   214  				return err
   215  			}
   216  		}
   217  	case msgCmdType:
   218  		if modOpts.Tx != nil && modOpts.Tx.EnhanceCustomCommand {
   219  			if err := builder.AddMsgServiceCommands(cmd, modOpts.Tx); err != nil {
   220  				return err
   221  			}
   222  		}
   223  	}
   224  
   225  	return nil
   226  }
   227  
   228  // outOrStdoutFormat formats the output based on the output flag and writes it to the command's output stream.
   229  func (b *Builder) outOrStdoutFormat(cmd *cobra.Command, out []byte) error {
   230  	var err error
   231  	outputType := cmd.Flag(flags.FlagOutput)
   232  	// if the output type is text, convert the json to yaml
   233  	// if output type is json or nil, default to json
   234  	if outputType != nil && outputType.Value.String() == flags.OutputFormatText {
   235  		out, err = yaml.JSONToYAML(out)
   236  		if err != nil {
   237  			return err
   238  		}
   239  	}
   240  
   241  	_, err = fmt.Fprintln(cmd.OutOrStdout(), string(out))
   242  	return err
   243  }