diff --git a/README.md b/README.md
index ab80d37f..822cb934 100644
--- a/README.md
+++ b/README.md
@@ -262,6 +262,12 @@ data (TYPE_BYTES) => \u65e5\u672c\u8a9e
}
```
+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
+```
+
### Client streaming RPC
Client streaming RPC accepts some requests and then returns only one response.
Finish request inputting with CTRL-D
diff --git a/e2e/testdata/fixtures/teste2e_repl-call_--help.golden b/e2e/testdata/fixtures/teste2e_repl-call_--help.golden
index 99c8cd71..3de07264 100644
--- a/e2e/testdata/fixtures/teste2e_repl-call_--help.golden
+++ b/e2e/testdata/fixtures/teste2e_repl-call_--help.golden
@@ -1,6 +1,7 @@
usage: call
Options:
- --dig-manually prompt asks whether to dig down if it encountered to a message field
- --enrich enrich response output includes header, message, trailer and status
+ --bytes-from-file interpret TYPE_BYTES input as a relative path to a file
+ --dig-manually prompt asks whether to dig down if it encountered to a message field
+ --enrich enrich response output includes header, message, trailer and status
diff --git a/fill/filler.go b/fill/filler.go
index e44a840c..01f521fe 100644
--- a/fill/filler.go
+++ b/fill/filler.go
@@ -18,14 +18,20 @@ type Filler interface {
Fill(v interface{}) error
}
+// InteractiveFillerOpts
+// If DigManually is true, Fill asks whether to dig down if it encountered to a message field.
+// If BytesFromFile is true, Fill will read the contents of the file from the provided relative path
+type InteractiveFillerOpts struct {
+ DigManually, BytesFromFile bool
+}
+
// Filler tries to correspond input text to a struct interactively.
type InteractiveFiller interface {
// Fill receives a struct v and corresponds input that is have internally to the struct.
- // If digManually is true, Fill asks whether to dig down if it encountered to a message field.
// Fill may return these errors:
//
// - io.EOF: At the end of input.
// - ErrCodecMismatch: If v isn't a supported type.
//
- Fill(v interface{}, digManually bool) error
+ Fill(v interface{}, opts InteractiveFillerOpts) error
}
diff --git a/fill/proto/interactive_filler.go b/fill/proto/interactive_filler.go
index f624e5fc..298aa69d 100644
--- a/fill/proto/interactive_filler.go
+++ b/fill/proto/interactive_filler.go
@@ -3,6 +3,7 @@ package proto
import (
"fmt"
"io"
+ "io/ioutil"
"strings"
"github.com/golang/protobuf/protoc-gen-go/descriptor"
@@ -20,7 +21,8 @@ type InteractiveFiller struct {
prefixFormat string
state promptInputterState
- digManually bool
+ digManually bool
+ bytesFromFile bool
}
// NewInteractiveFiller instantiates a new filler that fills each field interactively.
@@ -35,8 +37,9 @@ func NewInteractiveFiller(prompt prompt.Prompt, prefixFormat string) *Interactiv
// Fill let you input each field interactively by using a prompt. v will be set field values inputted by a prompt.
//
// Note that Fill resets the previous state when it is called again.
-func (f *InteractiveFiller) Fill(v interface{}, digManually bool) error {
- f.digManually = digManually
+func (f *InteractiveFiller) Fill(v interface{}, opts fill.InteractiveFillerOpts) error {
+ f.digManually = opts.DigManually
+ f.bytesFromFile = opts.BytesFromFile
msg, ok := v.(*dynamic.Message)
if !ok {
@@ -213,7 +216,7 @@ func (f *InteractiveFiller) inputField(dmsg *dynamic.Message, field *desc.FieldD
f.state.color.Next()
default: // Normal fields.
f.prompt.SetPrefix(f.makePrefix(field))
- v, err := f.inputPrimitiveField(field)
+ v, err := f.inputPrimitiveField(field.GetType())
if err != nil {
return err
}
@@ -301,7 +304,7 @@ func (f *InteractiveFiller) inputRepeatedField(dmsg *dynamic.Message, field *des
// inputPrimitiveField reads an input and converts it to a Go type.
// If CTRL+d is entered, inputPrimitiveField returns io.EOF.
-func (f *InteractiveFiller) inputPrimitiveField(field *desc.FieldDescriptor) (interface{}, error) {
+func (f *InteractiveFiller) inputPrimitiveField(fieldType descriptor.FieldDescriptorProto_Type) (interface{}, error) {
in, err := f.prompt.Input()
if errors.Is(err, io.EOF) {
return "", io.EOF
@@ -310,7 +313,28 @@ func (f *InteractiveFiller) inputPrimitiveField(field *desc.FieldDescriptor) (in
return "", errors.Wrap(err, "failed to read user input")
}
- return convertValue(in, descriptor.FieldDescriptorProto_Type(descriptor.FieldDescriptorProto_Type_value[field.GetType().String()]))
+ v, err := convertValue(in, fieldType)
+ if err != nil {
+ return nil, err
+ }
+
+ if fieldType == descriptor.FieldDescriptorProto_TYPE_BYTES {
+ return f.processBytesInput(v)
+ }
+
+ return v, err
+}
+
+func (f *InteractiveFiller) processBytesInput(v interface{}) (interface{}, error) {
+ if _, ok := v.([]byte); !ok {
+ return nil, errors.New("value is not of type bytes")
+ }
+
+ if f.bytesFromFile {
+ return readFileFromRelativePath(string(v.([]byte)))
+ }
+
+ return v, nil
}
func (f *InteractiveFiller) isSelectedOneOf(field *desc.FieldDescriptor) bool {
@@ -438,3 +462,10 @@ func makePrefix(s string, field *desc.FieldDescriptor, ancestor []string, ancest
func isOneOfField(field *desc.FieldDescriptor) bool {
return field.GetOneOf() != nil
}
+
+func readFileFromRelativePath(path string) ([]byte, error) {
+ if path == "" {
+ return nil, nil
+ }
+ return ioutil.ReadFile(path)
+}
diff --git a/fill/proto/interactive_filler_test.go b/fill/proto/interactive_filler_test.go
index 7f4f546b..b466c870 100644
--- a/fill/proto/interactive_filler_test.go
+++ b/fill/proto/interactive_filler_test.go
@@ -1,16 +1,72 @@
-package proto_test
+package proto
import (
"testing"
+ "github.com/golang/protobuf/protoc-gen-go/descriptor"
"github.com/ktr0731/evans/fill"
- "github.com/ktr0731/evans/fill/proto"
+ "github.com/ktr0731/evans/prompt"
)
+type testPrompt struct {
+ prompt.Prompt
+
+ input string
+}
+
+func (t *testPrompt) Input() (string, error) {
+ return t.input, nil
+}
+
func TestInteractiveProtoFiller(t *testing.T) {
- f := proto.NewInteractiveFiller(nil, "")
- err := f.Fill("invalid type", false)
+ f := NewInteractiveFiller(nil, "")
+ err := f.Fill("invalid type", fill.InteractiveFillerOpts{})
if err != fill.ErrCodecMismatch {
t.Errorf("must return fill.ErrCodecMismatch because the arg is invalid type, but got: %s", err)
}
+
+ tp := &testPrompt{
+ input: "../../go.mod",
+ }
+
+ f = NewInteractiveFiller(tp, "")
+ f.bytesFromFile = true
+
+ var v interface{}
+ v, err = f.inputPrimitiveField(descriptor.FieldDescriptorProto_TYPE_BYTES)
+ if err != nil {
+ t.Error(err)
+ }
+
+ if _, ok := v.([]byte); !ok {
+ t.Errorf("value should be of type []byte")
+ }
+
+ fileContent, err := readFileFromRelativePath(tp.input)
+ if err != nil {
+ t.Error(err)
+ }
+
+ if len(v.([]byte)) != len(fileContent) {
+ t.Error("contents should have the same length")
+ }
+
+ tp = &testPrompt{
+ input: "\\x6f\\x67\\x69\\x73\\x6f",
+ }
+
+ f = NewInteractiveFiller(tp, "")
+
+ v, err = f.inputPrimitiveField(descriptor.FieldDescriptorProto_TYPE_BYTES)
+ if err != nil {
+ t.Error(err)
+ }
+
+ if _, ok := v.([]byte); !ok {
+ t.Errorf("value should be of type []byte")
+ }
+
+ if string(v.([]byte)) != "ogiso" {
+ t.Error("unequal content")
+ }
}
diff --git a/repl/commands.go b/repl/commands.go
index 2d7d2797..1ab1f341 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 bool
+ enrich, digManually, bytesFromFile bool
}
func (c *callCommand) FlagSet() (*pflag.FlagSet, bool) {
@@ -163,6 +163,7 @@ 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")
return fs, true
}
@@ -195,7 +196,9 @@ func (c *callCommand) Run(w io.Writer, args []string) error {
},
)
- err := usecase.CallRPCInteractively(context.Background(), w, args[0], c.digManually)
+ // 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)
if errors.Is(err, io.EOF) {
return errors.New("inputting canceled")
}
diff --git a/usecase/call_rpc.go b/usecase/call_rpc.go
index e95b74c5..4d725869 100644
--- a/usecase/call_rpc.go
+++ b/usecase/call_rpc.go
@@ -383,14 +383,14 @@ func (f *interactiveFiller) Fill(v interface{}) error {
return f.fillFunc(v)
}
-func CallRPCInteractively(ctx context.Context, w io.Writer, rpcName string, digManually bool) error {
- return dm.CallRPCInteractively(ctx, w, rpcName, digManually)
+func CallRPCInteractively(ctx context.Context, w io.Writer, rpcName string, digManually, bytesFromFile bool) error {
+ return dm.CallRPCInteractively(ctx, w, rpcName, digManually, bytesFromFile)
}
-func (m *dependencyManager) CallRPCInteractively(ctx context.Context, w io.Writer, rpcName string, digManually bool) error {
+func (m *dependencyManager) CallRPCInteractively(ctx context.Context, w io.Writer, rpcName string, digManually, bytesFromFile bool) error {
return m.CallRPC(ctx, w, rpcName, &interactiveFiller{
fillFunc: func(v interface{}) error {
- return m.interactiveFiller.Fill(v, digManually)
+ return m.interactiveFiller.Fill(v, fill.InteractiveFillerOpts{DigManually: digManually, BytesFromFile: bytesFromFile})
},
})
}