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 }