feat(autocli): add support for binary file hex encoding (#15504)

Co-authored-by: Marko <marbar3778@yahoo.com>
This commit is contained in:
Jeancarlo Barrios 2023-04-06 10:13:55 -06:00 committed by GitHub
parent a5093dc7cb
commit 401d0d72c9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 148 additions and 11 deletions

View File

@ -0,0 +1,63 @@
package flag
import (
"context"
"encoding/base64"
"encoding/hex"
"github.com/cockroachdb/errors"
"google.golang.org/protobuf/reflect/protoreflect"
"os"
)
type binaryType struct{}
var _ Value = (*fileBinaryValue)(nil)
func (f binaryType) NewValue(_ context.Context, _ *Builder) Value {
return &fileBinaryValue{}
}
func (f binaryType) DefaultValue() string {
return ""
}
// fileBinaryValue is a Value that holds a binary file.
type fileBinaryValue struct {
value []byte
}
func (f *fileBinaryValue) Get(protoreflect.Value) (protoreflect.Value, error) {
return protoreflect.ValueOfBytes(f.value), nil
}
func (f *fileBinaryValue) String() string {
return string(f.value)
}
// Set implements the flag.Value interface for binary files, with exceptions.
// If the input string is a valid file path, the value will be the content of that file.
// If the input string is a valid hex or base64 string, the value will be the decoded form of that string.
// If the input string is not a valid file path, hex string, or base64 string, Set will return an error.
func (f *fileBinaryValue) Set(s string) error {
if data, err := os.ReadFile(s); err == nil {
f.value = data
return nil
}
if data, err := hex.DecodeString(s); err == nil {
f.value = data
return nil
}
if data, err := base64.StdEncoding.DecodeString(s); err == nil {
f.value = data
return nil
}
return errors.New("input string is neither a valid file path, hex, or base64 encoded")
}
func (f *fileBinaryValue) Type() string {
return "binary"
}

View File

@ -98,6 +98,8 @@ func (b *Builder) resolveFlagTypeBasic(field protoreflect.FieldDescriptor) Type
}
switch field.Kind() {
case protoreflect.BytesKind:
return binaryType{}
case protoreflect.EnumKind:
return enumType{enum: field.Enum()}
case protoreflect.MessageKind:

View File

@ -7,9 +7,6 @@ import (
func bindSimpleFlag(flagSet *pflag.FlagSet, kind protoreflect.Kind, name, shorthand, usage string) HasValue {
switch kind {
case protoreflect.BytesKind:
val := flagSet.BytesBase64P(name, shorthand, nil, usage)
return newSimpleValue(val, protoreflect.ValueOfBytes)
case protoreflect.StringKind:
val := flagSet.StringP(name, shorthand, "", usage)
return newSimpleValue(val, protoreflect.ValueOfString)

View File

@ -4,6 +4,7 @@ import (
"bytes"
"context"
"fmt"
"os"
"strings"
"testing"
@ -78,6 +79,9 @@ var testCmdDesc = &autocliv1.ServiceCommandDescriptor{
"duration": {
Usage: "some random duration",
},
"bz": {
Usage: "some bytes",
},
},
},
},
@ -196,6 +200,75 @@ func TestOptions(t *testing.T) {
assert.Equal(t, uint64(5), lastReq.U64) // no opt default value got set
}
func TestBinaryFlag(t *testing.T) {
// Create a temporary file with some content
tempFile, err := os.Open("testdata/file.test")
if err != nil {
t.Fatal(err)
}
content := []byte("this is just a test file")
if err := tempFile.Close(); err != nil {
t.Fatal(err)
}
// Test cases
tests := []struct {
name string
input string
expected []byte
hasError bool
err string
}{
{
name: "Valid file path with extension",
input: tempFile.Name(),
expected: content,
hasError: false,
err: "",
},
{
name: "Valid hex-encoded string",
input: "68656c6c6f20776f726c64",
expected: []byte("hello world"),
hasError: false,
err: "",
},
{
name: "Valid base64-encoded string",
input: "SGVsbG8gV29ybGQ=",
expected: []byte("Hello World"),
hasError: false,
err: "",
},
{
name: "Invalid input (not a file path or encoded string)",
input: "not a file or encoded string",
expected: nil,
hasError: true,
err: "input string is neither a valid file path, hex, or base64 encoded",
},
}
// Run test cases
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
conn := testExecCommon(t, buildModuleQueryCommand,
"echo",
"1", "abc", `{"denom":"foo","amount":"1"}`,
"--bz", tc.input,
)
errorOut := conn.errorOut.String()
if errorOut == "" {
lastReq := conn.lastRequest.(*testpb.EchoRequest)
assert.DeepEqual(t, tc.expected, lastReq.Bz)
} else {
assert.Assert(t, strings.Contains(conn.errorOut.String(), tc.err))
}
})
}
}
func TestAddressValidation(t *testing.T) {
conn := testExecCommon(t, buildModuleQueryCommand,
"echo",
@ -217,6 +290,7 @@ func TestAddressValidation(t *testing.T) {
"--an-address", "cosmps1BAD_ENCODING",
)
assert.Assert(t, strings.Contains(conn.errorOut.String(), "Error: invalid argument"))
}
func TestOutputFormat(t *testing.T) {

1
client/v2/autocli/testdata/file.test vendored Normal file
View File

@ -0,0 +1 @@
this is just a test file

View File

@ -14,7 +14,7 @@ Flags:
--aux Generate aux signer data instead of sending a tx
--bools bools (default [])
-b, --broadcast-mode string Transaction broadcasting mode (sync|async) (default "sync")
--bz bytesBase64
--bz binary
--chain-id string The network chain ID
--deprecated-field string
--dry-run ignore the --gas flag and perform a simulation of a transaction, but don't broadcast it (when enabled, the local Keybase is not accessible)
@ -41,7 +41,7 @@ Flags:
--offline Offline mode (does not allow any online functionality)
-o, --output string Output format (text|json) (default "json")
--page-count-total
--page-key bytesBase64
--page-key binary
--page-limit uint
--page-offset uint
--page-reverse

View File

@ -9,7 +9,7 @@ Flags:
--an-address bech32 account address key name
--an-enum Enum (unspecified | one | two | five | neg-three) (default unspecified)
--bools bools (default [])
--bz bytesBase64
--bz binary
--deprecated-field string
--duration duration
--durations duration (repeated)
@ -24,7 +24,7 @@ Flags:
--node string <host>:<port> to CometBFT RPC interface for this chain (default "tcp://localhost:26657")
-o, --output string Output format (text|json) (default "text")
--page-count-total
--page-key bytesBase64
--page-key binary
--page-limit uint
--page-offset uint
--page-reverse

View File

@ -19,7 +19,7 @@ Flags:
--aux Generate aux signer data instead of sending a tx
--bools bools (default [])
-b, --broadcast-mode string Transaction broadcasting mode (sync|async) (default "sync")
--bz bytesBase64
--bz binary
--chain-id string The network chain ID
--deprecated-field string (DEPRECATED: don't use this)
--dry-run ignore the --gas flag and perform a simulation of a transaction, but don't broadcast it (when enabled, the local Keybase is not accessible)
@ -45,7 +45,7 @@ Flags:
--offline Offline mode (does not allow any online functionality)
-o, --output string Output format (text|json) (default "json")
--page-count-total
--page-key bytesBase64
--page-key binary
--page-limit uint
--page-offset uint
--page-reverse

View File

@ -16,7 +16,7 @@ Flags:
--an-address bech32 account address key name
--an-enum Enum (unspecified | one | two | five | neg-three) (default unspecified)
--bools bools (default [])
--bz bytesBase64
--bz binary some bytes
--deprecated-field string (DEPRECATED: don't use this)
--duration duration some random duration
--durations duration (repeated)
@ -30,7 +30,7 @@ Flags:
--node string <host>:<port> to CometBFT RPC interface for this chain (default "tcp://localhost:26657")
-o, --output string Output format (text|json) (default "text")
--page-count-total
--page-key bytesBase64
--page-key binary
--page-limit uint
--page-offset uint
--page-reverse