request.go 4.32 KB
Newer Older
Matt Bell's avatar
Matt Bell committed
1 2
package commands

3 4
import (
	"fmt"
5
	"io"
6 7
	"reflect"
	"strconv"
8 9 10

	"github.com/jbenet/go-ipfs/config"
	"github.com/jbenet/go-ipfs/core"
11 12
)

13 14
type optMap map[string]interface{}

15 16 17 18 19 20
type Context struct {
	ConfigRoot string
	Config     *config.Config
	Node       *core.IpfsNode
}

Matt Bell's avatar
Matt Bell committed
21
// Request represents a call to a command from a consumer
22 23 24
type Request interface {
	Path() []string
	Option(name string) (interface{}, bool)
25
	Options() map[string]interface{}
26
	SetOption(name string, val interface{})
27
	Arguments() []interface{} // TODO: make argument value type instead of using interface{}
28
	Context() *Context
29
	SetContext(Context)
30
	Command() *Command
31

32
	CheckArguments(args []Argument) error
33 34 35 36
	ConvertOptions(options map[string]Option) error
}

type request struct {
Matt Bell's avatar
Matt Bell committed
37
	path      []string
38
	options   optMap
39
	arguments []interface{}
40
	cmd       *Command
41
	ctx       Context
Matt Bell's avatar
Matt Bell committed
42 43
}

44 45
// Path returns the command path of this request
func (r *request) Path() []string {
46 47 48
	return r.path
}

49 50 51 52
// 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
Matt Bell's avatar
Matt Bell committed
53 54
}

55 56 57 58 59 60 61 62 63
// Options returns a copy of the option map
func (r *request) Options() map[string]interface{} {
	output := make(optMap)
	for k, v := range r.options {
		output[k] = v
	}
	return output
}

64 65 66
// SetOption sets the value of the option for given name.
func (r *request) SetOption(name string, val interface{}) {
	r.options[name] = val
67 68
}

69
// Arguments returns the arguments slice
70
func (r *request) Arguments() []interface{} {
Matt Bell's avatar
Matt Bell committed
71
	return r.arguments
72
}
Matt Bell's avatar
Matt Bell committed
73

74 75 76 77
func (r *request) Context() *Context {
	return &r.ctx
}

78 79 80 81
func (r *request) SetContext(ctx Context) {
	r.ctx = ctx
}

82 83 84 85
func (r *request) Command() *Command {
	return r.cmd
}

Matt Bell's avatar
Matt Bell committed
86 87
type converter func(string) (interface{}, error)

88
var converters = map[reflect.Kind]converter{
Matt Bell's avatar
Matt Bell committed
89
	Bool: func(v string) (interface{}, error) {
90 91 92 93 94
		if v == "" {
			return true, nil
		}
		return strconv.ParseBool(v)
	},
Matt Bell's avatar
Matt Bell committed
95
	Int: func(v string) (interface{}, error) {
96 97
		return strconv.ParseInt(v, 0, 32)
	},
Matt Bell's avatar
Matt Bell committed
98
	Uint: func(v string) (interface{}, error) {
99 100
		return strconv.ParseInt(v, 0, 32)
	},
Matt Bell's avatar
Matt Bell committed
101
	Float: func(v string) (interface{}, error) {
102 103 104 105
		return strconv.ParseFloat(v, 64)
	},
}

106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
// MAYBE_TODO: maybe this should be a Command method? (taking a Request as a param)
func (r *request) CheckArguments(args []Argument) error {
	var argDef Argument

	for i, arg := range r.arguments {
		if i < len(args) {
			argDef = args[i]
		} else if !argDef.Variadic {
			return fmt.Errorf("Expected %v arguments, got %v", len(args), len(r.arguments))
		}

		if argDef.Required && arg == nil {
			return fmt.Errorf("Argument '%s' is required", argDef.Name)
		}
		if argDef.Type == ArgFile {
			_, ok := arg.(io.Reader)
			if !ok {
				return fmt.Errorf("Argument '%s' isn't valid", argDef.Name)
			}
		} else if argDef.Type == ArgString {
			_, ok := arg.(string)
			if !ok {
				return fmt.Errorf("Argument '%s' must be a string", argDef.Name)
			}
		}
	}

	return nil
}

136
func (r *request) ConvertOptions(options map[string]Option) error {
137 138 139 140 141
	converted := make(map[string]interface{})

	for k, v := range r.options {
		opt, ok := options[k]
		if !ok {
142
			continue
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
		}

		kind := reflect.TypeOf(v).Kind()
		var value interface{}

		if kind != opt.Type {
			if kind == String {
				convert := converters[opt.Type]
				val, err := convert(v.(string))
				if err != nil {
					return fmt.Errorf("Could not convert string value '%s' to type '%s'",
						v, opt.Type.String())
				}
				value = val

			} else {
				return fmt.Errorf("Option '%s' should be type '%s', but got type '%s'",
					k, opt.Type.String(), kind.String())
			}
		} else {
			value = v
		}

		for _, name := range opt.Names {
			if _, ok := r.options[name]; name != k && ok {
				return fmt.Errorf("Duplicate command options were provided ('%s' and '%s')",
					k, name)
			}

Matt Bell's avatar
Matt Bell committed
172
			converted[name] = value
173 174 175 176 177 178 179
		}
	}

	r.options = converted
	return nil
}

180 181
// NewEmptyRequest initializes an empty request
func NewEmptyRequest() Request {
182
	return NewRequest(nil, nil, nil, nil)
Matt Bell's avatar
Matt Bell committed
183 184
}

185
// NewRequest returns a request initialized with given arguments
186
func NewRequest(path []string, opts optMap, args []interface{}, cmd *Command) Request {
187
	if path == nil {
Matt Bell's avatar
Matt Bell committed
188
		path = make([]string, 0)
Matt Bell's avatar
Matt Bell committed
189
	}
190 191 192 193
	if opts == nil {
		opts = make(map[string]interface{})
	}
	if args == nil {
194
		args = make([]interface{}, 0)
195
	}
196
	return &request{path, opts, args, cmd, Context{}}
Matt Bell's avatar
Matt Bell committed
197
}