feat(autocli): add support for binary file hex encoding (#15504)
Co-authored-by: Marko <marbar3778@yahoo.com>
This commit is contained in:
parent
a5093dc7cb
commit
401d0d72c9
63
client/v2/autocli/flag/binary.go
Normal file
63
client/v2/autocli/flag/binary.go
Normal 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"
|
||||
}
|
||||
@ -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:
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
1
client/v2/autocli/testdata/file.test
vendored
Normal file
@ -0,0 +1 @@
|
||||
this is just a test 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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
4
client/v2/autocli/testdata/help-echo.golden
vendored
4
client/v2/autocli/testdata/help-echo.golden
vendored
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user