Repository: ktr0731/evans
Pull request #611 (2022-11-26 15:38:02)

Add support for --bytes-as-base64
This allows entering bytes as a base64-encoded string, using the standard encoding.

Suggested in https://github.com/ktr0731/evans/discussions/610

flokli: I tried extending `fill/proto/interactive_filler_test.go` with an additional field, but I don't understand the test harness enough to understand how to test the filler with other filler opts just for one field. It must work somehow, because we fill bytes passed in there, and read from `./proto.go` file.

@ktr0731, @BenSlabbert, any thoughts?
flokli: Hmmh, we output bytes as base64, like grpcurl does. It would feel much more natural if this was the default TBH.
flokli: poke @ktr0731 . WDYT?
codecov[bot]: # [Codecov](https://codecov.io/gh/ktr0731/evans/pull/611?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ktr) Report
> Merging [#611](https://codecov.io/gh/ktr0731/evans/pull/611?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ktr) (c84378e) into [master](https://codecov.io/gh/ktr0731/evans/commit/f75862c2922bdb5824e159222ab806f8783550fd?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ktr) (f75862c) will **decrease** coverage by `0.16%`.
> The diff coverage is `55.55%`.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master     #611      +/-   ##
==========================================
- Coverage   80.17%   80.01%   -0.16%     
==========================================
  Files          57       57              
  Lines        3848     3858      +10     
==========================================
+ Hits         3085     3087       +2     
- Misses        536      542       +6     
- Partials      227      229       +2     
```

</details>

Patch:
diff --git a/README.md b/README.md
index 0f5932d..c02bf00 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/fill/filler.go b/fill/filler.go
index c53a62d..e0a96b9 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 fda0f8d..9b0d18c 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 6ba677e..a203ab9 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 e5f9415..48e795e 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,
 			})


Test patch:
diff --git a/e2e/testdata/fixtures/teste2e_repl-call_--help.golden b/e2e/testdata/fixtures/teste2e_repl-call_--help.golden
index 3fb24b3..e9b83e1 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 <method name>
 
 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