diff --git a/cmd/ipfs2/init.go b/cmd/ipfs2/init.go index 009412613..d06525aec 100644 --- a/cmd/ipfs2/init.go +++ b/cmd/ipfs2/init.go @@ -20,30 +20,28 @@ var initCmd = &cmds.Command{ `, Options: []cmds.Option{ - cmds.IntOption("bits", "b", "Number of bits to use in the generated RSA private key (defaults to 4096)"), + cmds.UintOption("bits", "b", "Number of bits to use in the generated RSA private key (defaults to 4096)"), cmds.StringOption("passphrase", "p", "Passphrase for encrypting the private key"), cmds.BoolOption("force", "f", "Overwrite existing config (if it exists)"), cmds.StringOption("datastore", "d", "Location for the IPFS data store"), }, Run: func(req cmds.Request) (interface{}, error) { - arg, found := req.Option("d") - dspath, ok := arg.(string) - if found && !ok { - return nil, errors.New("failed to parse datastore flag") + dspath, err := req.Option("d").String() + if err != nil { + return nil, err } - arg, found = req.Option("f") - force, ok := arg.(bool) // TODO param - if found && !ok { - return nil, errors.New("failed to parse force flag") + force, err := req.Option("f").Bool() + if err != nil { + return nil, err } - arg, found = req.Option("b") - nBitsForKeypair, ok := arg.(int) // TODO param - if found && !ok { - return nil, errors.New("failed to get bits flag") - } else if !found { + nBitsForKeypair, err := req.Option("b").Int() + if err != nil { + return nil, err + } + if !req.Option("b").Found() { nBitsForKeypair = 4096 } diff --git a/cmd/ipfs2/main.go b/cmd/ipfs2/main.go index 40d25c09a..e5cdc3465 100644 --- a/cmd/ipfs2/main.go +++ b/cmd/ipfs2/main.go @@ -66,10 +66,7 @@ func createRequest(args []string) (cmds.Request, *cmds.Command) { // or if a path was returned (user specified a valid subcommand), show the error message // (this means there was an option or argument error) if path != nil && len(path) > 0 { - help := false - opt, _ := req.Option("help") - help, _ = opt.(bool) - + help, _ := req.Option("help").Bool() if !help { fmt.Printf(errorFormat, err) } @@ -108,7 +105,7 @@ func createRequest(args []string) (cmds.Request, *cmds.Command) { ctx.ConfigRoot = configPath ctx.Config = conf - if _, found := req.Option("encoding"); !found { + if !req.Option("encoding").Found() { if req.Command().Marshallers != nil && req.Command().Marshallers[cmds.Text] != nil { req.SetOption("encoding", cmds.Text) } else { @@ -120,30 +117,25 @@ func createRequest(args []string) (cmds.Request, *cmds.Command) { } func handleOptions(req cmds.Request, root *cmds.Command) { - if help, found := req.Option("help"); found { - if helpBool, ok := help.(bool); helpBool && ok { - helpText, err := cmdsCli.HelpText("ipfs", root, req.Path()) - if err != nil { - fmt.Println(err.Error()) - } else { - fmt.Println(helpText) - } - exit(0) - } else if !ok { - fmt.Println("error: expected 'help' option to be a bool") - exit(1) + if help, err := req.Option("help").Bool(); help && err == nil { + helpText, err := cmdsCli.HelpText("ipfs", root, req.Path()) + if err != nil { + fmt.Println(err.Error()) + } else { + fmt.Println(helpText) } + exit(0) + } else if err != nil { + fmt.Println(err) + exit(1) } - if debug, found := req.Option("debug"); found { - if debugBool, ok := debug.(bool); debugBool && ok { - u.Debug = true - - u.SetAllLoggers(logging.DEBUG) - } else if !ok { - fmt.Println("error: expected 'debug' option to be a bool") - exit(1) - } + if debug, err := req.Option("debug").Bool(); debug && err == nil { + u.Debug = true + u.SetAllLoggers(logging.DEBUG) + } else if err != nil { + fmt.Println(err) + exit(1) } } @@ -154,19 +146,13 @@ func callCommand(req cmds.Request, root *cmds.Command) cmds.Response { res = root.Call(req) } else { - var found bool - var local interface{} - localBool := false - if local, found = req.Option("local"); found { - var ok bool - localBool, ok = local.(bool) - if !ok { - fmt.Println("error: expected 'local' option to be a bool") - exit(1) - } + local, err := req.Option("local").Bool() + if err != nil { + fmt.Println(err) + exit(1) } - if (!found || !localBool) && daemon.Locked(req.Context().ConfigRoot) { + if (!req.Option("local").Found() || !local) && daemon.Locked(req.Context().ConfigRoot) { addr, err := ma.NewMultiaddr(req.Context().Config.Addresses.API) if err != nil { fmt.Println(err) @@ -229,12 +215,12 @@ func outputResponse(res cmds.Response, root *cmds.Command) { } func getConfigRoot(req cmds.Request) (string, error) { - if opt, found := req.Option("config"); found { - if optStr, ok := opt.(string); ok { - return optStr, nil - } else { - return "", fmt.Errorf("Expected 'config' option to be a string") - } + configOpt, err := req.Option("config").String() + if err != nil { + return "", err + } + if configOpt != "" { + return configOpt, nil } configPath, err := config.PathRoot() diff --git a/commands/command.go b/commands/command.go index 1f531be85..fc8092d7b 100644 --- a/commands/command.go +++ b/commands/command.go @@ -66,13 +66,7 @@ func (c *Command) Call(req Request) Response { return res } - options, err := c.GetOptions(req.Path()) - if err != nil { - res.SetError(err, ErrClient) - return res - } - - err = req.ConvertOptions(options) + err = req.ConvertOptions() if err != nil { res.SetError(err, ErrClient) return res diff --git a/commands/command_test.go b/commands/command_test.go index 2186678b7..44a39d0c1 100644 --- a/commands/command_test.go +++ b/commands/command_test.go @@ -15,29 +15,23 @@ func TestOptionValidation(t *testing.T) { Run: noop, } - req := NewEmptyRequest() - req.SetOption("beep", 5) - req.SetOption("b", 10) - res := cmd.Call(req) - if res.Error() == nil { - t.Error("Should have failed (duplicate options)") - } + opts, _ := cmd.GetOptions(nil) - req = NewEmptyRequest() - req.SetOption("beep", "foo") - res = cmd.Call(req) + req := NewRequest(nil, nil, nil, nil, opts) + req.SetOption("beep", true) + res := cmd.Call(req) if res.Error() == nil { t.Error("Should have failed (incorrect type)") } - req = NewEmptyRequest() + req = NewRequest(nil, nil, nil, nil, opts) req.SetOption("beep", 5) res = cmd.Call(req) if res.Error() != nil { t.Error(res.Error(), "Should have passed") } - req = NewEmptyRequest() + req = NewRequest(nil, nil, nil, nil, opts) req.SetOption("beep", 5) req.SetOption("boop", "test") res = cmd.Call(req) @@ -45,7 +39,7 @@ func TestOptionValidation(t *testing.T) { t.Error("Should have passed") } - req = NewEmptyRequest() + req = NewRequest(nil, nil, nil, nil, opts) req.SetOption("b", 5) req.SetOption("B", "test") res = cmd.Call(req) @@ -53,32 +47,32 @@ func TestOptionValidation(t *testing.T) { t.Error("Should have passed") } - req = NewEmptyRequest() + req = NewRequest(nil, nil, nil, nil, opts) req.SetOption("foo", 5) res = cmd.Call(req) if res.Error() != nil { t.Error("Should have passed") } - req = NewEmptyRequest() + req = NewRequest(nil, nil, nil, nil, opts) req.SetOption(EncShort, "json") res = cmd.Call(req) if res.Error() != nil { t.Error("Should have passed") } - req = NewEmptyRequest() + req = NewRequest(nil, nil, nil, nil, opts) req.SetOption("b", "100") res = cmd.Call(req) if res.Error() != nil { t.Error("Should have passed") } - req = NewEmptyRequest() + req = NewRequest(nil, nil, nil, nil, opts) req.SetOption("b", ":)") res = cmd.Call(req) if res.Error() == nil { - t.Error(res.Error(), "Should have failed (string value not convertible to int)") + t.Error("Should have failed (string value not convertible to int)") } } @@ -107,13 +101,14 @@ func TestRegistration(t *testing.T) { Run: noop, } - res := cmdB.Call(NewRequest([]string{"a"}, nil, nil, nil, nil)) - if res.Error() == nil { + path := []string{"a"} + _, err := cmdB.GetOptions(path) + if err == nil { t.Error("Should have failed (option name collision)") } - res = cmdC.Call(NewEmptyRequest()) - if res.Error() == nil { + _, err = cmdC.GetOptions(nil) + if err == nil { t.Error("Should have failed (option name collision with global options)") } } diff --git a/commands/http/client.go b/commands/http/client.go index 9e8c1d3a7..55d6f11a7 100644 --- a/commands/http/client.go +++ b/commands/http/client.go @@ -34,23 +34,8 @@ func NewClient(address string) Client { } func (c *client) Send(req cmds.Request) (cmds.Response, error) { - var userEncoding string - if enc, found := req.Option(cmds.EncShort); found { - var ok bool - userEncoding, ok = enc.(string) - if !ok { - return nil, castError - } - req.SetOption(cmds.EncShort, cmds.JSON) - } else { - var ok bool - enc, _ := req.Option(cmds.EncLong) - userEncoding, ok = enc.(string) - if !ok { - return nil, castError - } - req.SetOption(cmds.EncLong, cmds.JSON) - } + userEncoding, _ := req.Option(cmds.EncShort).String() + req.SetOption(cmds.EncShort, cmds.JSON) query, inputStream, err := getQuery(req) if err != nil { @@ -72,7 +57,6 @@ func (c *client) Send(req cmds.Request) (cmds.Response, error) { if len(userEncoding) > 0 { req.SetOption(cmds.EncShort, userEncoding) - req.SetOption(cmds.EncLong, userEncoding) } return res, nil diff --git a/commands/http/handler.go b/commands/http/handler.go index a8fec766e..79d862fad 100644 --- a/commands/http/handler.go +++ b/commands/http/handler.go @@ -57,13 +57,12 @@ func (i Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { w.Header().Set(streamHeader, "1") } else { - enc, _ := req.Option(cmds.EncShort) - encStr, ok := enc.(string) - if !ok { + enc, err := req.Option(cmds.EncShort).String() + if err != nil || len(enc) == 0 { w.WriteHeader(http.StatusInternalServerError) return } - mime := mimeTypes[encStr] + mime := mimeTypes[enc] w.Header().Set("Content-Type", mime) } diff --git a/commands/option.go b/commands/option.go index 8f11ace49..f8b3c3b3c 100644 --- a/commands/option.go +++ b/commands/option.go @@ -1,6 +1,9 @@ package commands -import "reflect" +import ( + "errors" + "reflect" +) // Types of Command options const ( @@ -30,7 +33,7 @@ func NewOption(kind reflect.Kind, names ...string) Option { } desc := names[len(names)-1] - names = names[:len(names)-2] + names = names[:len(names)-1] return Option{ Names: names, @@ -55,6 +58,73 @@ func StringOption(names ...string) Option { return NewOption(String, names...) } +type OptionValue struct { + value interface{} + found bool +} + +// Found returns true if the option value was provided by the user (not a default value) +func (ov OptionValue) Found() bool { + return ov.found +} + +// value accessor methods, gets the value as a certain type +func (ov OptionValue) Bool() (bool, error) { + val, ok := ov.value.(bool) + if !ok { + var err error + if ov.value != nil { + err = errors.New("error casting to bool") + } + return false, err + } + return val, nil +} +func (ov OptionValue) Int() (int, error) { + val, ok := ov.value.(int) + if !ok { + var err error + if ov.value != nil { + err = errors.New("error casting to int") + } + return 0, err + } + return val, nil +} +func (ov OptionValue) Uint() (uint, error) { + val, ok := ov.value.(uint) + if !ok { + var err error + if ov.value != nil { + err = errors.New("error casting to uint") + } + return 0, err + } + return val, nil +} +func (ov OptionValue) Float() (float64, error) { + val, ok := ov.value.(float64) + if !ok { + var err error + if ov.value != nil { + err = errors.New("error casting to float64") + } + return 0.0, err + } + return val, nil +} +func (ov OptionValue) String() (string, error) { + val, ok := ov.value.(string) + if !ok { + var err error + if ov.value != nil { + err = errors.New("error casting to string") + } + return "", err + } + return val, nil +} + // Flag names const ( EncShort = "enc" diff --git a/commands/request.go b/commands/request.go index c08c0b22a..25f59ad8c 100644 --- a/commands/request.go +++ b/commands/request.go @@ -21,15 +21,15 @@ type Context struct { // Request represents a call to a command from a consumer type Request interface { Path() []string - Option(name string) (interface{}, bool) - Options() map[string]interface{} + Option(name string) *OptionValue + Options() optMap SetOption(name string, val interface{}) Arguments() []interface{} // TODO: make argument value type instead of using interface{} Context() *Context SetContext(Context) Command() *Command - ConvertOptions(options map[string]Option) error + ConvertOptions() error } type request struct { @@ -47,31 +47,34 @@ func (r *request) Path() []string { } // Option returns the value of the option for given name. -func (r *request) Option(name string) (interface{}, bool) { +func (r *request) Option(name string) *OptionValue { val, found := r.options[name] if found { - return val, found + return &OptionValue{val, found} } // if a value isn't defined for that name, we will try to look it up by its aliases // find the option with the specified name option, found := r.optionDefs[name] - if found { - // try all the possible names, break if we find a value - for _, n := range option.Names { - val, found := r.options[n] - if found { - return val, found - } + if !found { + return nil + } + + // try all the possible names, break if we find a value + for _, n := range option.Names { + val, found = r.options[n] + if found { + return &OptionValue{val, found} } } - return nil, false + // MAYBE_TODO: use default value instead of nil + return &OptionValue{nil, false} } // Options returns a copy of the option map -func (r *request) Options() map[string]interface{} { +func (r *request) Options() optMap { output := make(optMap) for k, v := range r.options { output[k] = v @@ -136,11 +139,11 @@ var converters = map[reflect.Kind]converter{ }, } -func (r *request) ConvertOptions(options map[string]Option) error { +func (r *request) ConvertOptions() error { converted := make(map[string]interface{}) for k, v := range r.options { - opt, ok := options[k] + opt, ok := r.optionDefs[k] if !ok { continue } @@ -203,5 +206,9 @@ func NewRequest(path []string, opts optMap, args []interface{}, cmd *Command, op if optDefs == nil { optDefs = make(map[string]Option) } - return &request{path, opts, args, cmd, Context{}, optDefs} + + req := &request{path, opts, args, cmd, Context{}, optDefs} + req.ConvertOptions() + + return req } diff --git a/commands/response.go b/commands/response.go index 912eddb0a..ac5b6b293 100644 --- a/commands/response.go +++ b/commands/response.go @@ -108,18 +108,22 @@ func (r *response) Marshal() ([]byte, error) { return []byte{}, nil } - enc, found := r.req.Option(EncShort) - encStr, ok := enc.(string) - if !found || !ok || encStr == "" { + fmt.Println(r.req, r.req.Option(EncShort)) + if !r.req.Option(EncShort).Found() { return nil, fmt.Errorf("No encoding type was specified") } - encType := EncodingType(strings.ToLower(encStr)) + enc, err := r.req.Option(EncShort).String() + if err != nil { + return nil, err + } + encType := EncodingType(strings.ToLower(enc)) var marshaller Marshaller if r.req.Command() != nil && r.req.Command().Marshallers != nil { marshaller = r.req.Command().Marshallers[encType] } if marshaller == nil { + var ok bool marshaller, ok = marshallers[encType] if !ok { return nil, fmt.Errorf("No marshaller found for encoding type '%s'", enc) diff --git a/commands/response_test.go b/commands/response_test.go index c8033f2f9..6bc7417ea 100644 --- a/commands/response_test.go +++ b/commands/response_test.go @@ -12,25 +12,20 @@ type TestOutput struct { } func TestMarshalling(t *testing.T) { - req := NewEmptyRequest() + cmd := &Command{} + opts, _ := cmd.GetOptions(nil) + + req := NewRequest(nil, nil, nil, nil, opts) res := NewResponse(req) res.SetOutput(TestOutput{"beep", "boop", 1337}) - // get command global options so we can set the encoding option - cmd := Command{} - options, err := cmd.GetOptions(nil) - if err != nil { - t.Error(err) - } - - _, err = res.Marshal() + _, err := res.Marshal() if err == nil { t.Error("Should have failed (no encoding type specified in request)") } req.SetOption(EncShort, JSON) - req.ConvertOptions(options) bytes, err := res.Marshal() if err != nil { diff --git a/core/commands2/mount_unix.go b/core/commands2/mount_unix.go index 7bd7128f9..792f8dec4 100644 --- a/core/commands2/mount_unix.go +++ b/core/commands2/mount_unix.go @@ -44,17 +44,15 @@ not be listable, as it is virtual. Accessing known paths directly. // update fsdir with flag. fsdir := ctx.Config.Mounts.IPFS - opt, _ := req.Option("f") - if val, ok := opt.(string); ok && val != "" { - fsdir = val + if req.Option("f").Found() { + fsdir, _ = req.Option("f").String() } fsdone := mountIpfs(ctx.Node, fsdir) // get default mount points nsdir := ctx.Config.Mounts.IPNS - opt, _ = req.Option("f") - if val, ok := opt.(string); ok && val != "" { - nsdir = val + if req.Option("n").Found() { + nsdir, _ = req.Option("n").String() } nsdone := mountIpns(ctx.Node, nsdir, fsdir) diff --git a/core/commands2/pin.go b/core/commands2/pin.go index 77d382c9a..1d7930823 100644 --- a/core/commands2/pin.go +++ b/core/commands2/pin.go @@ -35,8 +35,7 @@ on disk. n := req.Context().Node // set recursive flag - opt, _ := req.Option("recursive") - recursive, _ := opt.(bool) // false if cast fails. + recursive, _ := req.Option("recursive").Bool() // false if cast fails. paths, err := internal.CastToStrings(req.Arguments()) if err != nil { @@ -70,8 +69,7 @@ collected if needed. n := req.Context().Node // set recursive flag - opt, _ := req.Option("recursive") - recursive, _ := opt.(bool) // false if cast fails. + recursive, _ := req.Option("recursive").Bool() // false if cast fails. paths, err := internal.CastToStrings(req.Arguments()) if err != nil { diff --git a/core/commands2/refs.go b/core/commands2/refs.go index f4eb4d2e2..a72324fd4 100644 --- a/core/commands2/refs.go +++ b/core/commands2/refs.go @@ -35,17 +35,8 @@ Note: list all refs recursively with -r.`, Run: func(req cmds.Request) (interface{}, error) { n := req.Context().Node - opt, found := req.Option("unique") - unique, ok := opt.(bool) - if !ok && found { - unique = false - } - - opt, found = req.Option("recursive") - recursive, ok := opt.(bool) - if !ok && found { - recursive = false - } + unique, _ := req.Option("unique").Bool() + recursive, _ := req.Option("recursive").Bool() paths, err := internal.CastToStrings(req.Arguments()) if err != nil { diff --git a/core/commands2/version.go b/core/commands2/version.go index ca80f538f..1e7adee8c 100644 --- a/core/commands2/version.go +++ b/core/commands2/version.go @@ -1,8 +1,6 @@ package commands import ( - "errors" - cmds "github.com/jbenet/go-ipfs/commands" config "github.com/jbenet/go-ipfs/config" ) @@ -29,11 +27,7 @@ var versionCmd = &cmds.Command{ v := res.Output().(*VersionOutput) s := "" - opt, found := res.Request().Option("number") - number, ok := opt.(bool) - if found && !ok { - return nil, errors.New("cast error") - } + number, _ := res.Request().Option("number").Bool() if !number { s += "ipfs version "