From d6b503220a07fe43dfdb79bc18cc63cb192447b9 Mon Sep 17 00:00:00 2001 From: "benjamin.slabbert" Date: Tue, 12 May 2020 11:50:09 +0200 Subject: [PATCH 1/3] Enhancement: in REPL mode allow user to specify path to file for bytes field --- README.md | 6 ++ .../fixtures/teste2e_repl-call_--help.golden | 5 +- fill/filler.go | 10 ++- fill/proto/interactive_filler.go | 50 +++++++++-- fill/proto/interactive_filler_test.go | 82 +++++++++++++++++-- repl/commands.go | 7 +- usecase/call_rpc.go | 8 +- 7 files changed, 146 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index fbead09a..2ac8e5ec 100644 --- a/README.md +++ b/README.md @@ -261,6 +261,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..25ffdf5e 100644 --- a/fill/proto/interactive_filler.go +++ b/fill/proto/interactive_filler.go @@ -3,6 +3,8 @@ package proto import ( "fmt" "io" + "io/ioutil" + "path/filepath" "strings" "github.com/golang/protobuf/protoc-gen-go/descriptor" @@ -20,7 +22,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 +38,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 +217,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(descriptor.FieldDescriptorProto_Type(descriptor.FieldDescriptorProto_Type_value[field.GetType().String()])) if err != nil { return err } @@ -301,7 +305,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 +314,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 +463,16 @@ 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 + } + + absPath, err := filepath.Abs(path) + if err != nil { + return nil, err + } + + return ioutil.ReadFile(absPath) +} diff --git a/fill/proto/interactive_filler_test.go b/fill/proto/interactive_filler_test.go index 7f4f546b..7da5ec52 100644 --- a/fill/proto/interactive_filler_test.go +++ b/fill/proto/interactive_filler_test.go @@ -1,16 +1,86 @@ -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" + "testing" ) +type testPrompt struct { + input string +} + +func (t *testPrompt) Input() (string, error) { + return t.input, nil +} + +func (t *testPrompt) Select(message string, options []string) (selected string, _ error) { + panic("implement me") +} + +func (t *testPrompt) SetPrefix(prefix string) { +} + +func (t *testPrompt) SetPrefixColor(color prompt.Color) { +} + +func (t *testPrompt) SetCompleter(c prompt.Completer) { +} + +func (t *testPrompt) GetCommandHistory() []string { + return []string{} +} + 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}) }, }) } From c122cc5afd353cc7fef570a60049633dd7d292a5 Mon Sep 17 00:00:00 2001 From: "benjamin.slabbert" Date: Tue, 12 May 2020 12:09:56 +0200 Subject: [PATCH 2/3] fixing imports --- fill/proto/interactive_filler_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fill/proto/interactive_filler_test.go b/fill/proto/interactive_filler_test.go index 7da5ec52..74089d42 100644 --- a/fill/proto/interactive_filler_test.go +++ b/fill/proto/interactive_filler_test.go @@ -1,10 +1,11 @@ package proto import ( + "testing" + "github.com/golang/protobuf/protoc-gen-go/descriptor" "github.com/ktr0731/evans/fill" "github.com/ktr0731/evans/prompt" - "testing" ) type testPrompt struct { From 34ca7950f68e582ed7c6ceeb070e057ff8245592 Mon Sep 17 00:00:00 2001 From: "benjamin.slabbert" Date: Fri, 22 May 2020 11:06:40 +0200 Subject: [PATCH 3/3] applying changes from review --- fill/proto/interactive_filler.go | 11 ++--------- fill/proto/interactive_filler_test.go | 19 ++----------------- 2 files changed, 4 insertions(+), 26 deletions(-) diff --git a/fill/proto/interactive_filler.go b/fill/proto/interactive_filler.go index 25ffdf5e..298aa69d 100644 --- a/fill/proto/interactive_filler.go +++ b/fill/proto/interactive_filler.go @@ -4,7 +4,6 @@ import ( "fmt" "io" "io/ioutil" - "path/filepath" "strings" "github.com/golang/protobuf/protoc-gen-go/descriptor" @@ -217,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(descriptor.FieldDescriptorProto_Type(descriptor.FieldDescriptorProto_Type_value[field.GetType().String()])) + v, err := f.inputPrimitiveField(field.GetType()) if err != nil { return err } @@ -468,11 +467,5 @@ func readFileFromRelativePath(path string) ([]byte, error) { if path == "" { return nil, nil } - - absPath, err := filepath.Abs(path) - if err != nil { - return nil, err - } - - return ioutil.ReadFile(absPath) + return ioutil.ReadFile(path) } diff --git a/fill/proto/interactive_filler_test.go b/fill/proto/interactive_filler_test.go index 74089d42..b466c870 100644 --- a/fill/proto/interactive_filler_test.go +++ b/fill/proto/interactive_filler_test.go @@ -9,6 +9,8 @@ import ( ) type testPrompt struct { + prompt.Prompt + input string } @@ -16,23 +18,6 @@ func (t *testPrompt) Input() (string, error) { return t.input, nil } -func (t *testPrompt) Select(message string, options []string) (selected string, _ error) { - panic("implement me") -} - -func (t *testPrompt) SetPrefix(prefix string) { -} - -func (t *testPrompt) SetPrefixColor(color prompt.Color) { -} - -func (t *testPrompt) SetCompleter(c prompt.Completer) { -} - -func (t *testPrompt) GetCommandHistory() []string { - return []string{} -} - func TestInteractiveProtoFiller(t *testing.T) { f := NewInteractiveFiller(nil, "") err := f.Fill("invalid type", fill.InteractiveFillerOpts{})