Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhancement: in REPL mode allow user to specify path to file for bytes field #280

Merged
merged 5 commits into from
May 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <kbd>CTRL-D</kbd>
Expand Down
5 changes: 3 additions & 2 deletions e2e/testdata/fixtures/teste2e_repl-call_--help.golden
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
usage: call <method name>

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

10 changes: 8 additions & 2 deletions fill/filler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
43 changes: 37 additions & 6 deletions fill/proto/interactive_filler.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package proto
import (
"fmt"
"io"
"io/ioutil"
"strings"

"github.com/golang/protobuf/protoc-gen-go/descriptor"
Expand All @@ -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.
Expand All @@ -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 {
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

in, err := f.prompt.Input()
if errors.Is(err, io.EOF) {
return "", io.EOF
Expand All @@ -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 {
Expand Down Expand Up @@ -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)
}
64 changes: 60 additions & 4 deletions fill/proto/interactive_filler_test.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,72 @@
package proto_test
package proto
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dropped the _test because I needed access to the internal function I wanted to test.


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")
}
}
7 changes: 5 additions & 2 deletions repl/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,14 +155,15 @@ 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) {
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.bytesFromFile, "bytes-from-file", false, "interpret TYPE_BYTES input as a relative path to a file")
return fs, true
}

Expand Down Expand Up @@ -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")
}
Expand Down
8 changes: 4 additions & 4 deletions usecase/call_rpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -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})
},
})
}
Expand Down