Skip to content

Commit

Permalink
Enhancement: in REPL mode allow user to specify path to file for byte…
Browse files Browse the repository at this point in the history
…s field (#280)

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

* fixing imports

* applying changes from review

Co-authored-by: ktr <ktr@syfm.me>
  • Loading branch information
BenSlabbert and ktr0731 committed May 24, 2020
1 parent ad23f4b commit b5f289f
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 20 deletions.
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) {
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

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

0 comments on commit b5f289f

Please sign in to comment.