diff --git a/README.md b/README.md index c02bf00b..301d8f2b 100644 --- a/README.md +++ b/README.md @@ -263,7 +263,18 @@ When CTRL-D 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 @@ -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 diff --git a/e2e/old_repl_test.go b/e2e/old_repl_test.go index 5d4066d7..bca55b95 100644 --- a/e2e/old_repl_test.go +++ b/e2e/old_repl_test.go @@ -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", diff --git a/e2e/repl_test.go b/e2e/repl_test.go index 79e6e135..7274b4c6 100644 --- a/e2e/repl_test.go +++ b/e2e/repl_test.go @@ -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", diff --git a/e2e/testdata/fixtures/teste2e_oldrepl-call_unarybytes_with_quoted_literals.golden b/e2e/testdata/fixtures/teste2e_oldrepl-call_unarybytes_with_quoted_literals.golden new file mode 100644 index 00000000..62e67d9e --- /dev/null +++ b/e2e/testdata/fixtures/teste2e_oldrepl-call_unarybytes_with_quoted_literals.golden @@ -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) やはり俺の青春ラブコメはまちがっている。" +} + diff --git a/e2e/testdata/fixtures/teste2e_repl-call_--help.golden b/e2e/testdata/fixtures/teste2e_repl-call_--help.golden index e9b83e1b..1a403911 100644 --- a/e2e/testdata/fixtures/teste2e_repl-call_--help.golden +++ b/e2e/testdata/fixtures/teste2e_repl-call_--help.golden @@ -1,11 +1,11 @@ usage: call 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) diff --git a/e2e/testdata/fixtures/teste2e_repl-call_unarybytes_with_quoted_literals.golden b/e2e/testdata/fixtures/teste2e_repl-call_unarybytes_with_quoted_literals.golden new file mode 100644 index 00000000..62e67d9e --- /dev/null +++ b/e2e/testdata/fixtures/teste2e_repl-call_unarybytes_with_quoted_literals.golden @@ -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) やはり俺の青春ラブコメはまちがっている。" +} + diff --git a/fill/filler.go b/fill/filler.go index e0a96b91..9238cf28 100644 --- a/fill/filler.go +++ b/fill/filler.go @@ -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 diff --git a/fill/proto/interactive_filler.go b/fill/proto/interactive_filler.go index 126bf4b4..e8908571 100644 --- a/fill/proto/interactive_filler.go +++ b/fill/proto/interactive_filler.go @@ -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: diff --git a/fill/proto/interactive_filler_test.go b/fill/proto/interactive_filler_test.go index c5072c77..f3367d82 100644 --- a/fill/proto/interactive_filler_test.go +++ b/fill/proto/interactive_filler_test.go @@ -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) @@ -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 @@ -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) diff --git a/repl/commands.go b/repl/commands.go index a203ab96..630fa64b 100644 --- a/repl/commands.go +++ b/repl/commands.go @@ -155,7 +155,7 @@ 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) { @@ -163,8 +163,8 @@ func (c *callCommand) FlagSet() (*pflag.FlagSet, bool) { 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") @@ -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") } diff --git a/usecase/call_rpc.go b/usecase/call_rpc.go index 48e795e0..73b8f246 100644 --- a/usecase/call_rpc.go +++ b/usecase/call_rpc.go @@ -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, }) }, })