Skip to content

Commit

Permalink
Accept bytes as base64-encoded string by default
Browse files Browse the repository at this point in the history
As per ktr0731#611 (comment),
this keeps thing symmetrical with the output, and how grpcurl handles it
as well.

The old quoted literal behaviour is still accessible via
`--bytes-as-quoted-literals`.
  • Loading branch information
flokli committed Feb 6, 2023
1 parent 5f3b4c5 commit b8d48fd
Show file tree
Hide file tree
Showing 11 changed files with 76 additions and 64 deletions.
21 changes: 13 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,18 @@ When <kbd>CTRL-D</kbd> is entered, inputting will be aborted.
```

### Bytes type fields
You can use byte literal and Unicode literal.
You can pass bytes as a base64-encoded string.

```
> call UnaryBytes
data (TYPE_BYTES) => SGVsbG8gV29ybGQh
{
"message": "received: (bytes) 48 65 6c 6c 6f 20 57 6f 72 6c 64 21, (string) Hello World!"
}
```

Or add the flag `--bytes-as-quoted-literals` to pass bytes as a (quoted) byte
literal or Unicode literal string:

```
> call UnaryBytes
Expand All @@ -279,13 +290,7 @@ data (TYPE_BYTES) => \u65e5\u672c\u8a9e
}
```

Or add the flag `--bytes-as-base64` to pass bytes as a base64-encoded string
```
> call UnaryBytes --bytes-as-base64
data (TYPE_BYTES) => SGVsbG8gV29ybGQh
```

Or add the flag `--bytes-from-file` to read bytes from the provided relative path
Or add the flag `--bytes-from-file` to read bytes from the provided relative path:
```
> call UnaryBytes --bytes-from-file
data (TYPE_BYTES) => ../relative/path/to/file
Expand Down
6 changes: 5 additions & 1 deletion e2e/old_repl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,11 @@ func TestE2E_OldREPL(t *testing.T) {
},
"call UnaryBytes": {
args: "testdata/test.proto",
input: []interface{}{"call UnaryBytes", "\\u3084\\u306f\\u308a\\u4ffa\\u306e\\u9752\\u6625\\u30e9\\u30d6\\u30b3\\u30e1\\u306f\\u307e\\u3061\\u304c\\u3063\\u3066\\u3044\\u308b\\u3002"},
input: []interface{}{"call UnaryBytes", "44KE44Gv44KK5L+644Gu6Z2S5pil44Op44OW44Kz44Oh44Gv44G+44Gh44GM44Gj44Gm44GE44KL44CC"},
},
"call UnaryBytes with quoted literals": {
args: "testdata/test.proto",
input: []interface{}{"call UnaryBytes --bytes-as-quoted-literals", "\\u3084\\u306f\\u308a\\u4ffa\\u306e\\u9752\\u6625\\u30e9\\u30d6\\u30b3\\u30e1\\u306f\\u307e\\u3061\\u304c\\u3063\\u3066\\u3044\\u308b\\u3002"},
},
"call UnaryRepeatedEnum": {
args: "testdata/test.proto",
Expand Down
6 changes: 5 additions & 1 deletion e2e/repl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,11 @@ func TestE2E_REPL(t *testing.T) {
},
"call UnaryBytes": {
commonFlags: "--proto testdata/test.proto",
input: []interface{}{"call UnaryBytes", "\\u3084\\u306f\\u308a\\u4ffa\\u306e\\u9752\\u6625\\u30e9\\u30d6\\u30b3\\u30e1\\u306f\\u307e\\u3061\\u304c\\u3063\\u3066\\u3044\\u308b\\u3002"},
input: []interface{}{"call UnaryBytes", "44KE44Gv44KK5L+644Gu6Z2S5pil44Op44OW44Kz44Oh44Gv44G+44Gh44GM44Gj44Gm44GE44KL44CC"},
},
"call UnaryBytes with quoted literals": {
commonFlags: "--proto testdata/test.proto",
input: []interface{}{"call UnaryBytes --bytes-as-quoted-literals", "\\u3084\\u306f\\u308a\\u4ffa\\u306e\\u9752\\u6625\\u30e9\\u30d6\\u30b3\\u30e1\\u306f\\u307e\\u3061\\u304c\\u3063\\u3066\\u3044\\u308b\\u3002"},
},
"call UnaryRepeatedEnum": {
commonFlags: "--proto testdata/test.proto",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"message": "received: (bytes) e3 82 84 e3 81 af e3 82 8a e4 bf ba e3 81 ae e9 9d 92 e6 98 a5 e3 83 a9 e3 83 96 e3 82 b3 e3 83 a1 e3 81 af e3 81 be e3 81 a1 e3 81 8c e3 81 a3 e3 81 a6 e3 81 84 e3 82 8b e3 80 82, (string) やはり俺の青春ラブコメはまちがっている。"
}

14 changes: 7 additions & 7 deletions e2e/testdata/fixtures/teste2e_repl-call_--help.golden
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
usage: call <method name>

Options:
--add-repeated-manually prompt asks whether to add a value if it encountered to a repeated field
--bytes-as-base64 interpret TYPE_BYTES input as base64-encoded string (mutually exclusive with --bytes-from-file)
--bytes-from-file interpret TYPE_BYTES input as a relative path to a file (mutually exclusive with --bytes-as-base64)
--dig-manually prompt asks whether to dig down if it encountered to a message field
--emit-defaults render fields with default values
--enrich enrich response output includes header, message, trailer and status
-r, --repeat repeat previous unary or server streaming request (if exists)
--add-repeated-manually prompt asks whether to add a value if it encountered to a repeated field
--bytes-as-quoted-literals interpret TYPE_BYTES input as a string of (quoted) byte literal or Unicode, instead of a base64-encoded string (mutually exclusive with --bytes-from-file)
--bytes-from-file interpret TYPE_BYTES input as a relative path to a file (mutually exclusive with --bytes-as-quoted-literals)
--dig-manually prompt asks whether to dig down if it encountered to a message field
--emit-defaults render fields with default values
--enrich enrich response output includes header, message, trailer and status
-r, --repeat repeat previous unary or server streaming request (if exists)

Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"message": "received: (bytes) e3 82 84 e3 81 af e3 82 8a e4 bf ba e3 81 ae e9 9d 92 e6 98 a5 e3 83 a9 e3 83 96 e3 82 b3 e3 83 a1 e3 81 af e3 81 be e3 81 a1 e3 81 8c e3 81 a3 e3 81 a6 e3 81 84 e3 82 8b e3 80 82, (string) やはり俺の青春ラブコメはまちがっている。"
}

4 changes: 2 additions & 2 deletions fill/filler.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ type Filler interface {
type InteractiveFillerOpts struct {
// DigManually is true, Fill asks whether to dig down if it encountered to a message field.
DigManually,
// BytesAsBase64 is true, Fill will interpret input as base64-encoded string
BytesAsBase64,
// BytesAsQuotedLiterals is true, Fill will interpret input as a quoted byte or unicode literal string.
BytesAsQuotedLiterals,
// BytesFromFile is true, Fill will read the contents of the file from the provided relative path.
BytesFromFile,
// AddRepeatedManually is true, Fill asks whether to add a repeated field value
Expand Down
17 changes: 6 additions & 11 deletions fill/proto/interactive_filler.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,20 +202,15 @@ func (r *resolver) resolveField(f *desc.FieldDescriptor) error {
// So, we need to call strconv.Unquote to interpret backslashes as an escape sequence.
case descriptorpb.FieldDescriptorProto_TYPE_BYTES:
converter = func(v string) (interface{}, error) {
if r.opts.BytesAsBase64 {
b, err := base64.StdEncoding.DecodeString(v)
if err == nil {
return b, nil
}
if r.opts.BytesAsQuotedLiterals {
v, err := strconv.Unquote(`"` + v + `"`)
return []byte(v), err
} else if r.opts.BytesFromFile {
b, err := os.ReadFile(v)
if err == nil {
return b, nil
}
return b, err
}

v, err := strconv.Unquote(`"` + v + `"`)
return []byte(v), err
b, err := base64.StdEncoding.DecodeString(v)
return b, err
}

default:
Expand Down
36 changes: 16 additions & 20 deletions fill/proto/interactive_filler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,6 @@ func TestInteractiveFiller(t *testing.T) {
b.AddField(builder.NewField("o", builder.FieldTypeBool()))
b.AddField(builder.NewField("p", builder.FieldTypeString()))
b.AddField(builder.NewField("q", builder.FieldTypeBytes()))
b.AddField(builder.NewField("r", builder.FieldTypeBytes()))
b.AddField(builder.NewField("s", builder.FieldTypeBytes()))
m, err := b.Build()
if err != nil {
t.Fatalf("Build should not return an error, but got '%s'", err)
Expand All @@ -87,23 +85,21 @@ func TestInteractiveFiller(t *testing.T) {
p := &stubPrompt{
t: t,
input: []string{
"1.1", // c
"1.2", // d
"1", // e
"2", // f
"3", // g
"4", // h
"5", // i
"6", // j
"7", // k
"8", // l
"9", // m
"10", // n
"true", // o
"foo", // p
"bar", // q
"\x62\x61\x7a", // r
"./proto.go", // s
"1.1", // c
"1.2", // d
"1", // e
"2", // f
"3", // g
"4", // h
"5", // i
"6", // j
"7", // k
"8", // l
"9", // m
"10", // n
"true", // o
"foo", // p
"./proto.go", // q
},
selection: []int{
0, // a - yes
Expand All @@ -121,7 +117,7 @@ func TestInteractiveFiller(t *testing.T) {
return
}

const want = `{"a":[{}],"b":"enum2","c":1.1,"d":1.2,"e":"1","f":"2","g":"3","h":"4","i":"5","j":6,"k":7,"l":8,"m":9,"n":10,"o":true,"p":"foo","q":"YmFy","r":"YmF6","s":"Ly8gUGFja2FnZSBwcm90byBwcm92aWRlcyBhIGZpbGxlciBpbXBsZW1lbnRhdGlvbiBmb3IgUHJvdG9jb2wgQnVmZmVycy4KcGFja2FnZSBwcm90bwo="}`
const want = `{"a":[{}],"b":"enum2","c":1.1,"d":1.2,"e":"1","f":"2","g":"3","h":"4","i":"5","j":6,"k":7,"l":8,"m":9,"n":10,"o":true,"p":"foo","q":"Ly8gUGFja2FnZSBwcm90byBwcm92aWRlcyBhIGZpbGxlciBpbXBsZW1lbnRhdGlvbiBmb3IgUHJvdG9jb2wgQnVmZmVycy4KcGFja2FnZSBwcm90bwo="}`

marshaler := jsonpb.Marshaler{EmitDefaults: true}
got, err := marshaler.MarshalToString(msg)
Expand Down
14 changes: 7 additions & 7 deletions repl/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,16 +155,16 @@ func (c *showCommand) Run(w io.Writer, args []string) error {
}

type callCommand struct {
enrich, digManually, bytesAsBase64, bytesFromFile, emitDefaults, repeatCall, addRepeatedManually bool
enrich, digManually, bytesAsQuotedLiterals, bytesFromFile, emitDefaults, repeatCall, addRepeatedManually bool
}

func (c *callCommand) FlagSet() (*pflag.FlagSet, bool) {
fs := pflag.NewFlagSet("call", pflag.ContinueOnError)
fs.Usage = func() {} // Disable help output when an error occurred.
fs.BoolVar(&c.enrich, "enrich", false, "enrich response output includes header, message, trailer and status")
fs.BoolVar(&c.digManually, "dig-manually", false, "prompt asks whether to dig down if it encountered to a message field")
fs.BoolVar(&c.bytesAsBase64, "bytes-as-base64", false, "interpret TYPE_BYTES input as base64-encoded string (mutually exclusive with --bytes-from-file)")
fs.BoolVar(&c.bytesFromFile, "bytes-from-file", false, "interpret TYPE_BYTES input as a relative path to a file (mutually exclusive with --bytes-as-base64)")
fs.BoolVar(&c.bytesAsQuotedLiterals, "bytes-as-quoted-literals", false, "interpret TYPE_BYTES input as a string of (quoted) byte literal or Unicode, instead of a base64-encoded string (mutually exclusive with --bytes-from-file)")
fs.BoolVar(&c.bytesFromFile, "bytes-from-file", false, "interpret TYPE_BYTES input as a relative path to a file (mutually exclusive with --bytes-as-quoted-literals)")
fs.BoolVar(&c.emitDefaults, "emit-defaults", false, "render fields with default values")
fs.BoolVarP(&c.repeatCall, "repeat", "r", false, "repeat previous unary or server streaming request (if exists)")
fs.BoolVar(&c.addRepeatedManually, "add-repeated-manually", false, "prompt asks whether to add a value if it encountered to a repeated field")
Expand Down Expand Up @@ -200,15 +200,15 @@ func (c *callCommand) Run(w io.Writer, args []string) error {
},
)

// Ensure bytesAsBase64 and bytesFromFile are not both set
// Ensure bytesAsLiteral and bytesFromFile are not both set
// pflag doesn't suppport mutually exclusive flags (https://github.com/spf13/pflag/issues/270)
if c.bytesAsBase64 && c.bytesFromFile {
return errors.New("only one of --bytes-as-base64 or --bytes-from-file can be specified")
if c.bytesAsQuotedLiterals && c.bytesFromFile {
return errors.New("only one of --bytes-as-quoted-literals or --bytes-from-file can be specified")
}

// here we create the request context
// we also add the call command flags here
err := usecase.CallRPCInteractively(context.Background(), w, args[0], c.digManually, c.bytesAsBase64, c.bytesFromFile, c.repeatCall, c.addRepeatedManually)
err := usecase.CallRPCInteractively(context.Background(), w, args[0], c.digManually, c.bytesAsQuotedLiterals, c.bytesFromFile, c.repeatCall, c.addRepeatedManually)
if errors.Is(err, io.EOF) {
return errors.New("inputting canceled")
}
Expand Down
14 changes: 7 additions & 7 deletions usecase/call_rpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -478,18 +478,18 @@ func (f *interactiveFiller) Fill(v interface{}) error {
return f.fillFunc(v)
}

func CallRPCInteractively(ctx context.Context, w io.Writer, rpcName string, digManually, bytesAsBase64, bytesFromFile, rerunPrevious, addRepeatedManually bool) error {
return dm.CallRPCInteractively(ctx, w, rpcName, digManually, bytesAsBase64, bytesFromFile, rerunPrevious, addRepeatedManually)
func CallRPCInteractively(ctx context.Context, w io.Writer, rpcName string, digManually, bytesAsQuotedLiterals, bytesFromFile, rerunPrevious, addRepeatedManually bool) error {
return dm.CallRPCInteractively(ctx, w, rpcName, digManually, bytesAsQuotedLiterals, bytesFromFile, rerunPrevious, addRepeatedManually)
}

func (m *dependencyManager) CallRPCInteractively(ctx context.Context, w io.Writer, rpcName string, digManually, bytesAsBase64, bytesFromFile, rerunPrevious, addRepeatedManually bool) error {
func (m *dependencyManager) CallRPCInteractively(ctx context.Context, w io.Writer, rpcName string, digManually, bytesAsQuotedLiterals, bytesFromFile, rerunPrevious, addRepeatedManually bool) error {
return m.CallRPC(ctx, w, rpcName, rerunPrevious, &interactiveFiller{
fillFunc: func(v interface{}) error {
return m.interactiveFiller.Fill(v, fill.InteractiveFillerOpts{
DigManually: digManually,
BytesAsBase64: bytesAsBase64,
BytesFromFile: bytesFromFile,
AddRepeatedManually: addRepeatedManually,
DigManually: digManually,
BytesAsQuotedLiterals: bytesAsQuotedLiterals,
BytesFromFile: bytesFromFile,
AddRepeatedManually: addRepeatedManually,
})
},
})
Expand Down

0 comments on commit b8d48fd

Please sign in to comment.