From f3e26c276256cb053de94725c10fecf243f11a68 Mon Sep 17 00:00:00 2001 From: Florian Klink Date: Sat, 26 Nov 2022 12:16:53 +0000 Subject: [PATCH] Add support for --bytes-as-base64 This allows entering bytes as a base64-encoded string, using the standard encoding. --- README.md | 6 ++++++ .../fixtures/teste2e_repl-call_--help.golden | 3 ++- fill/filler.go | 2 ++ fill/proto/interactive_filler.go | 8 +++++++- repl/commands.go | 13 ++++++++++--- usecase/call_rpc.go | 7 ++++--- 6 files changed, 31 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 0f5932d8..c02bf00b 100644 --- a/README.md +++ b/README.md @@ -279,6 +279,12 @@ 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 ``` > call UnaryBytes --bytes-from-file diff --git a/e2e/testdata/fixtures/teste2e_repl-call_--help.golden b/e2e/testdata/fixtures/teste2e_repl-call_--help.golden index 3fb24b3f..e9b83e1b 100644 --- a/e2e/testdata/fixtures/teste2e_repl-call_--help.golden +++ b/e2e/testdata/fixtures/teste2e_repl-call_--help.golden @@ -2,7 +2,8 @@ usage: call Options: --add-repeated-manually prompt asks whether to add a value if it encountered to a repeated field - --bytes-from-file interpret TYPE_BYTES input as a relative path to a file + --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 diff --git a/fill/filler.go b/fill/filler.go index c53a62da..e0a96b91 100644 --- a/fill/filler.go +++ b/fill/filler.go @@ -22,6 +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, // 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 fda0f8d4..9b0d18c7 100644 --- a/fill/proto/interactive_filler.go +++ b/fill/proto/interactive_filler.go @@ -1,6 +1,7 @@ package proto import ( + "encoding/base64" "fmt" "io" "io/ioutil" @@ -201,7 +202,12 @@ 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.BytesFromFile { + if r.opts.BytesAsBase64 { + b, err := base64.StdEncoding.DecodeString(v) + if err == nil { + return b, nil + } + } else if r.opts.BytesFromFile { b, err := ioutil.ReadFile(v) if err == nil { return b, nil diff --git a/repl/commands.go b/repl/commands.go index 6ba677ee..a203ab96 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, bytesFromFile, emitDefaults, repeatCall, addRepeatedManually bool + enrich, digManually, bytesAsBase64, bytesFromFile, emitDefaults, repeatCall, addRepeatedManually bool } func (c *callCommand) FlagSet() (*pflag.FlagSet, bool) { @@ -163,7 +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.bytesFromFile, "bytes-from-file", false, "interpret TYPE_BYTES input as a relative path to a file") + 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.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") @@ -199,9 +200,15 @@ func (c *callCommand) Run(w io.Writer, args []string) error { }, ) + // Ensure bytesAsBase64 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") + } + // 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.bytesFromFile, c.repeatCall, c.addRepeatedManually) + err := usecase.CallRPCInteractively(context.Background(), w, args[0], c.digManually, c.bytesAsBase64, 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 e5f9415e..48e795e0 100644 --- a/usecase/call_rpc.go +++ b/usecase/call_rpc.go @@ -478,15 +478,16 @@ func (f *interactiveFiller) Fill(v interface{}) error { return f.fillFunc(v) } -func CallRPCInteractively(ctx context.Context, w io.Writer, rpcName string, digManually, bytesFromFile, rerunPrevious, addRepeatedManually bool) error { - return dm.CallRPCInteractively(ctx, w, rpcName, digManually, bytesFromFile, rerunPrevious, addRepeatedManually) +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 (m *dependencyManager) CallRPCInteractively(ctx context.Context, w io.Writer, rpcName string, digManually, bytesFromFile, rerunPrevious, addRepeatedManually bool) error { +func (m *dependencyManager) CallRPCInteractively(ctx context.Context, w io.Writer, rpcName string, digManually, bytesAsBase64, 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, })