// TestMethodUsageFlags tests the MethodUsage function ensure it returns the // expected flags and errors. func TestMethodUsageFlags(t *testing.T) { t.Parallel() tests := []struct { name string method string err error flags btcjson.UsageFlag }{ { name: "unregistered type", method: "bogusmethod", err: btcjson.Error{ErrorCode: btcjson.ErrUnregisteredMethod}, }, { name: "getblock", method: "getblock", flags: 0, }, { name: "walletpassphrase", method: "walletpassphrase", flags: btcjson.UFWalletOnly, }, } t.Logf("Running %d tests", len(tests)) for i, test := range tests { flags, err := btcjson.MethodUsageFlags(test.method) if reflect.TypeOf(err) != reflect.TypeOf(test.err) { t.Errorf("Test #%d (%s) wrong error - got %T (%[3]v), "+ "want %T", i, test.name, err, test.err) continue } if err != nil { gotErrorCode := err.(btcjson.Error).ErrorCode if gotErrorCode != test.err.(btcjson.Error).ErrorCode { t.Errorf("Test #%d (%s) mismatched error code "+ "- got %v (%v), want %v", i, test.name, gotErrorCode, err, test.err.(btcjson.Error).ErrorCode) continue } continue } // Ensure flags match the expected value. if flags != test.flags { t.Errorf("Test #%d (%s) mismatched flags - got %v, "+ "want %v", i, test.name, flags, test.flags) continue } } }
// listCommands categorizes and lists all of the usable commands along with // their one-line usage. func listCommands() { const ( categoryChain uint8 = iota categoryWallet numCategories ) // Get a list of registered commands and categorize and filter them. cmdMethods := btcjson.RegisteredCmdMethods() categorized := make([][]string, numCategories) for _, method := range cmdMethods { flags, err := btcjson.MethodUsageFlags(method) if err != nil { // This should never happen since the method was just // returned from the package, but be safe. continue } // Skip the commands that aren't usable from this utility. if flags&unusableFlags != 0 { continue } usage, err := btcjson.MethodUsageText(method) if err != nil { // This should never happen since the method was just // returned from the package, but be safe. continue } // Categorize the command based on the usage flags. category := categoryChain if flags&btcjson.UFWalletOnly != 0 { category = categoryWallet } categorized[category] = append(categorized[category], usage) } // Display the command according to their categories. categoryTitles := make([]string, numCategories) categoryTitles[categoryChain] = "Chain Server Commands:" categoryTitles[categoryWallet] = "Wallet Server Commands (--wallet):" for category := uint8(0); category < numCategories; category++ { fmt.Println(categoryTitles[category]) for _, usage := range categorized[category] { fmt.Println(usage) } fmt.Println() } }
func main() { cfg, args, err := loadConfig() if err != nil { os.Exit(1) } if len(args) < 1 { usage("No command specified") os.Exit(1) } // Ensure the specified method identifies a valid registered command and // is one of the usable types. method := args[0] usageFlags, err := btcjson.MethodUsageFlags(method) if err != nil { fmt.Fprintf(os.Stderr, "Unrecognized command '%s'\n", method) fmt.Fprintln(os.Stderr, listCmdMessage) os.Exit(1) } if usageFlags&unusableFlags != 0 { fmt.Fprintf(os.Stderr, "The '%s' command can only be used via "+ "websockets\n", method) fmt.Fprintln(os.Stderr, listCmdMessage) os.Exit(1) } // Convert remaining command line args to a slice of interface values // to be passed along as parameters to new command creation function. // // Since some commands, such as submitblock, can involve data which is // too large for the Operating System to allow as a normal command line // parameter, support using '-' as an argument to allow the argument // to be read from a stdin pipe. bio := bufio.NewReader(os.Stdin) params := make([]interface{}, 0, len(args[1:])) for _, arg := range args[1:] { if arg == "-" { param, err := bio.ReadString('\n') if err != nil && err != io.EOF { fmt.Fprintf(os.Stderr, "Failed to read data "+ "from stdin: %v\n", err) os.Exit(1) } if err == io.EOF && len(param) == 0 { fmt.Fprintln(os.Stderr, "Not enough lines "+ "provided on stdin") os.Exit(1) } param = strings.TrimRight(param, "\r\n") params = append(params, param) continue } params = append(params, arg) } // Attempt to create the appropriate command using the arguments // provided by the user. cmd, err := btcjson.NewCmd(method, params...) if err != nil { // Show the error along with its error code when it's a // btcjson.Error as it reallistcally will always be since the // NewCmd function is only supposed to return errors of that // type. if jerr, ok := err.(btcjson.Error); ok { fmt.Fprintf(os.Stderr, "%s command: %v (code: %s)\n", method, err, jerr.ErrorCode) commandUsage(method) os.Exit(1) } // The error is not a btcjson.Error and this really should not // happen. Nevertheless, fallback to just showing the error // if it should happen due to a bug in the package. fmt.Fprintf(os.Stderr, "%s command: %v\n", method, err) commandUsage(method) os.Exit(1) } // Marshal the command into a JSON-RPC byte slice in preparation for // sending it to the RPC server. marshalledJSON, err := btcjson.MarshalCmd(1, cmd) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } // Send the JSON-RPC request to the server using the user-specified // connection configuration. result, err := sendPostRequest(marshalledJSON, cfg) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } // Choose how to display the result based on its type. strResult := string(result) if strings.HasPrefix(strResult, "{") || strings.HasPrefix(strResult, "[") { var dst bytes.Buffer if err := json.Indent(&dst, result, "", " "); err != nil { fmt.Fprintf(os.Stderr, "Failed to format result: %v", err) os.Exit(1) } fmt.Println(dst.String()) } else if strings.HasPrefix(strResult, `"`) { var str string if err := json.Unmarshal(result, &str); err != nil { fmt.Fprintf(os.Stderr, "Failed to unmarshal result: %v", err) os.Exit(1) } fmt.Println(str) } else if strResult != "null" { fmt.Println(strResult) } }