diff --git a/commands/cli/parse.go b/commands/cli/parse.go index 23a7e1976..a953d0c5e 100644 --- a/commands/cli/parse.go +++ b/commands/cli/parse.go @@ -9,7 +9,7 @@ import ( // Parse parses the input commandline string (cmd, flags, and args). // returns the corresponding command Request object. -func Parse(input []string, root *commands.Command) (*commands.Request, error) { +func Parse(input []string, root *commands.Command) (commands.Request, error) { path, input := parsePath(input, root) opts, args, err := parseOptions(input) if err != nil { diff --git a/commands/command.go b/commands/command.go index 64b63903b..cf9472dc2 100644 --- a/commands/command.go +++ b/commands/command.go @@ -4,11 +4,15 @@ import ( "errors" "fmt" "strings" + + u "github.com/jbenet/go-ipfs/util" ) +var log = u.Logger("command") + // Function is the type of function that Commands use. // It reads from the Request, and writes results to the Response. -type Function func(*Request, *Response) +type Function func(Request, Response) // Command is a runnable command, with input arguments and options (flags). // It can also have subcommands, to group units of work into sets. @@ -43,10 +47,10 @@ func (c *Command) Register(id string, sub *Command) error { } // Call invokes the command at the given subcommand path -func (c *Command) Call(req *Request) *Response { - res := &Response{req: req} +func (c *Command) Call(req Request) Response { + res := NewResponse(req) - cmds, err := c.Resolve(req.path) + cmds, err := c.Resolve(req.Path()) if err != nil { res.SetError(err, ErrClient) return res @@ -58,13 +62,13 @@ func (c *Command) Call(req *Request) *Response { return res } - options, err := c.GetOptions(req.path) + options, err := c.GetOptions(req.Path()) if err != nil { res.SetError(err, ErrClient) return res } - err = req.convertOptions(options) + err = req.ConvertOptions(options) if err != nil { res.SetError(err, ErrClient) return res diff --git a/commands/command_test.go b/commands/command_test.go index 2f1601f4f..6850fa401 100644 --- a/commands/command_test.go +++ b/commands/command_test.go @@ -8,70 +8,70 @@ func TestOptionValidation(t *testing.T) { Option{[]string{"b", "beep"}, Int}, Option{[]string{"B", "boop"}, String}, }, - run: func(req *Request, res *Response) {}, + run: func(req Request, res Response) {}, } req := NewEmptyRequest() - req.options["foo"] = 5 + req.SetOption("foo", 5) res := cmd.Call(req) - if res.Error == nil { + if res.Error() == nil { t.Error("Should have failed (unrecognized option)") } req = NewEmptyRequest() - req.options["beep"] = 5 - req.options["b"] = 10 + req.SetOption("beep", 5) + req.SetOption("b", 10) res = cmd.Call(req) - if res.Error == nil { + if res.Error() == nil { t.Error("Should have failed (duplicate options)") } req = NewEmptyRequest() - req.options["beep"] = "foo" + req.SetOption("beep", "foo") res = cmd.Call(req) - if res.Error == nil { + if res.Error() == nil { t.Error("Should have failed (incorrect type)") } req = NewEmptyRequest() - req.options["beep"] = 5 + req.SetOption("beep", 5) res = cmd.Call(req) - if res.Error != nil { - t.Error(res.Error, "Should have passed") + if res.Error() != nil { + t.Error(res.Error(), "Should have passed") } req = NewEmptyRequest() - req.options["beep"] = 5 - req.options["boop"] = "test" + req.SetOption("beep", 5) + req.SetOption("boop", "test") res = cmd.Call(req) - if res.Error != nil { + if res.Error() != nil { t.Error("Should have passed") } req = NewEmptyRequest() - req.options["b"] = 5 - req.options["B"] = "test" + req.SetOption("b", 5) + req.SetOption("B", "test") res = cmd.Call(req) - if res.Error != nil { + if res.Error() != nil { t.Error("Should have passed") } req = NewEmptyRequest() - req.options[EncShort] = "json" + req.SetOption(EncShort, "json") res = cmd.Call(req) - if res.Error != nil { + if res.Error() != nil { t.Error("Should have passed") } req = NewEmptyRequest() - req.options["b"] = "100" + req.SetOption("b", "100") res = cmd.Call(req) - if res.Error != nil { + if res.Error() != nil { t.Error("Should have passed") } req = NewEmptyRequest() - req.options["b"] = ":)" + req.SetOption("b", ":)") res = cmd.Call(req) if res.Error == nil { t.Error(res.Error, "Should have failed (string value not convertible to int)") @@ -79,40 +79,41 @@ func TestOptionValidation(t *testing.T) { } func TestRegistration(t *testing.T) { + noop := func(req Request, res Response) {} cmds := []*Command{ &Command{ Options: []Option{ Option{[]string{"beep"}, Int}, }, - run: func(req *Request, res *Response) {}, + run: noop, }, &Command{ Options: []Option{ Option{[]string{"boop"}, Int}, }, - run: func(req *Request, res *Response) {}, + run: noop, }, &Command{ Options: []Option{ Option{[]string{"boop"}, String}, }, - run: func(req *Request, res *Response) {}, + run: noop, }, &Command{ Options: []Option{ Option{[]string{"bop"}, String}, }, - run: func(req *Request, res *Response) {}, + run: noop, }, &Command{ Options: []Option{ Option{[]string{EncShort}, String}, }, - run: func(req *Request, res *Response) {}, + run: noop, }, } diff --git a/commands/request.go b/commands/request.go index b8025a6b3..1571a1b93 100644 --- a/commands/request.go +++ b/commands/request.go @@ -6,37 +6,48 @@ import ( "strconv" ) +type optMap map[string]interface{} + // Request represents a call to a command from a consumer -type Request struct { +type Request interface { + Path() []string + Option(name string) (interface{}, bool) + SetOption(name string, val interface{}) + Arguments() []string + + ConvertOptions(options map[string]Option) error +} + +type request struct { path []string - options map[string]interface{} + options optMap arguments []string } -func (r *Request) Path() []string { +// Path returns the command path of this request +func (r *request) Path() []string { return r.path } -func (r *Request) SetPath(path []string) { - r.path = path +// Option returns the value of the option for given name. +func (r *request) Option(name string) (interface{}, bool) { + val, err := r.options[name] + return val, err } -func (r *Request) Option(name string) (interface{}, bool) { - val, ok := r.options[name] - return val, ok +// SetOption sets the value of the option for given name. +func (r *request) SetOption(name string, val interface{}) { + r.options[name] = val } -func (r *Request) SetOption(name string, value interface{}) { - r.options[name] = value -} - -func (r *Request) Arguments() []string { +// Arguments returns the arguments slice +func (r *request) Arguments() []string { return r.arguments } type converter func(string) (interface{}, error) -var converters map[reflect.Kind]converter = map[reflect.Kind]converter{ +var converters = map[reflect.Kind]converter{ Bool: func(v string) (interface{}, error) { if v == "" { return true, nil @@ -54,7 +65,7 @@ var converters map[reflect.Kind]converter = map[reflect.Kind]converter{ }, } -func (r *Request) convertOptions(options map[string]Option) error { +func (r *request) ConvertOptions(options map[string]Option) error { converted := make(map[string]interface{}) for k, v := range r.options { @@ -98,11 +109,13 @@ func (r *Request) convertOptions(options map[string]Option) error { return nil } -func NewEmptyRequest() *Request { +// NewEmptyRequest initializes an empty request +func NewEmptyRequest() Request { return NewRequest(nil, nil, nil) } -func NewRequest(path []string, opts map[string]interface{}, args []string) *Request { +// NewRequest returns a request initialized with given arguments +func NewRequest(path []string, opts optMap, args []string) Request { if path == nil { path = make([]string, 0) } @@ -112,5 +125,5 @@ func NewRequest(path []string, opts map[string]interface{}, args []string) *Requ if args == nil { args = make([]string, 0) } - return &Request{path, opts, args} + return &request{path, opts, args} } diff --git a/commands/response.go b/commands/response.go index c9d1c5b93..a8b01b8db 100644 --- a/commands/response.go +++ b/commands/response.go @@ -48,21 +48,53 @@ var marshallers = map[EncodingType]Marshaller{ // Response is the result of a command request. Handlers write to the response, // setting Error or Value. Response is returned to the client. -type Response struct { - req *Request - Error *Error - Value interface{} +type Response interface { + Request() Request + + // Set/Return the response Error + SetError(err error, code ErrorType) + Error() error + + // Sets/Returns the response value + SetValue(interface{}) + Value() interface{} + + // Marshal marshals out the response into a buffer. It uses the EncodingType + // on the Request to chose a Marshaller (Codec). + Marshal() ([]byte, error) } -// SetError updates the response Error. -func (r *Response) SetError(err error, code ErrorType) { - r.Error = &Error{Message: err.Error(), Code: code} +type response struct { + req Request + err *Error + value interface{} } -// Marshal marshals out the response into a buffer. It uses the EncodingType -// on the Request to chose a Marshaller (Codec). -func (r *Response) Marshal() ([]byte, error) { - if r.Error == nil && r.Value == nil { +func (r *response) Request() Request { + return r.req +} + +func (r *response) Value() interface{} { + return r.value +} + +func (r *response) SetValue(v interface{}) { + r.value = v +} + +func (r *response) Error() error { + if r.err == nil { + return nil + } + return r.err +} + +func (r *response) SetError(err error, code ErrorType) { + r.err = &Error{Message: err.Error(), Code: code} +} + +func (r *response) Marshal() ([]byte, error) { + if r.err == nil && r.value == nil { return nil, fmt.Errorf("No error or value set, there is nothing to marshal") } @@ -77,8 +109,13 @@ func (r *Response) Marshal() ([]byte, error) { return nil, fmt.Errorf("No marshaller found for encoding type '%s'", enc) } - if r.Error != nil { - return marshaller(r.Error) + if r.err != nil { + return marshaller(r.err) } - return marshaller(r.Value) + return marshaller(r.value) +} + +// NewResponse returns a response to match given Request +func NewResponse(req Request) Response { + return &response{req: req} } diff --git a/commands/response_test.go b/commands/response_test.go index 432d78d69..058cead07 100644 --- a/commands/response_test.go +++ b/commands/response_test.go @@ -13,10 +13,8 @@ type TestOutput struct { func TestMarshalling(t *testing.T) { req := NewEmptyRequest() - res := Response{ - req: req, - Value: TestOutput{"beep", "boop", 1337}, - } + res := NewResponse(req) + res.SetValue(TestOutput{"beep", "boop", 1337}) // get command global options so we can set the encoding option cmd := Command{} @@ -31,7 +29,7 @@ func TestMarshalling(t *testing.T) { } req.SetOption(EncShort, JSON) - req.convertOptions(options) + req.ConvertOptions(options) bytes, err := res.Marshal() if err != nil {