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

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

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

14 15
type optMap map[string]interface{}

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

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

34
	ConvertOptions() error
35 36 37
}

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

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

51
// Option returns the value of the option for given name.
52
func (r *request) Option(name string) *OptionValue {
53 54
	val, found := r.options[name]
	if found {
55
		return &OptionValue{val, found}
56 57 58 59 60 61
	}

	// 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]
62 63 64 65 66 67 68 69 70
	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}
71 72 73
		}
	}

74 75
	// MAYBE_TODO: use default value instead of nil
	return &OptionValue{nil, false}
Matt Bell's avatar
Matt Bell committed
76 77
}

78
// Options returns a copy of the option map
79
func (r *request) Options() optMap {
80 81 82 83 84 85 86
	output := make(optMap)
	for k, v := range r.options {
		output[k] = v
	}
	return output
}

87 88
// SetOption sets the value of the option for given name.
func (r *request) SetOption(name string, val interface{}) {
89 90 91 92 93 94 95 96
	// find the option with the specified name
	option, found := r.optionDefs[name]
	if !found {
		return
	}

	// try all the possible names, if we already have a value then set over it
	for _, n := range option.Names {
97
		_, found := r.options[n]
98 99 100 101 102 103
		if found {
			r.options[n] = val
			return
		}
	}

104
	r.options[name] = val
105 106
}

107
// Arguments returns the arguments slice
108
func (r *request) Arguments() []interface{} {
Matt Bell's avatar
Matt Bell committed
109
	return r.arguments
110
}
Matt Bell's avatar
Matt Bell committed
111

112 113 114 115
func (r *request) Context() *Context {
	return &r.ctx
}

116 117 118 119
func (r *request) SetContext(ctx Context) {
	r.ctx = ctx
}

120 121 122 123
func (r *request) Command() *Command {
	return r.cmd
}

124 125 126 127 128 129 130 131 132 133 134 135 136 137
func (r *request) Cleanup() error {
	for _, arg := range r.arguments {
		closer, ok := arg.(io.Closer)
		if ok {
			err := closer.Close()
			if err != nil {
				return err
			}
		}
	}

	return nil
}

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

140
var converters = map[reflect.Kind]converter{
Matt Bell's avatar
Matt Bell committed
141
	Bool: func(v string) (interface{}, error) {
142 143 144 145 146
		if v == "" {
			return true, nil
		}
		return strconv.ParseBool(v)
	},
Matt Bell's avatar
Matt Bell committed
147
	Int: func(v string) (interface{}, error) {
148 149 150 151 152
		val, err := strconv.ParseInt(v, 0, 32)
		if err != nil {
			return nil, err
		}
		return int(val), err
153
	},
Matt Bell's avatar
Matt Bell committed
154
	Uint: func(v string) (interface{}, error) {
155 156 157 158 159
		val, err := strconv.ParseUint(v, 0, 32)
		if err != nil {
			return nil, err
		}
		return int(val), err
160
	},
Matt Bell's avatar
Matt Bell committed
161
	Float: func(v string) (interface{}, error) {
162 163 164 165
		return strconv.ParseFloat(v, 64)
	},
}

166
func (r *request) ConvertOptions() error {
167
	for k, v := range r.options {
168
		opt, ok := r.optionDefs[k]
169
		if !ok {
170
			continue
171 172 173 174 175 176
		}

		kind := reflect.TypeOf(v).Kind()
		if kind != opt.Type {
			if kind == String {
				convert := converters[opt.Type]
177 178 179 180 181
				str, ok := v.(string)
				if !ok {
					return errors.New("cast error")
				}
				val, err := convert(str)
182 183 184 185
				if err != nil {
					return fmt.Errorf("Could not convert string value '%s' to type '%s'",
						v, opt.Type.String())
				}
186
				r.options[k] = val
187 188 189 190 191 192

			} else {
				return fmt.Errorf("Option '%s' should be type '%s', but got type '%s'",
					k, opt.Type.String(), kind.String())
			}
		} else {
193
			r.options[k] = v
194 195 196 197 198 199 200 201 202 203 204 205 206
		}

		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)
			}
		}
	}

	return nil
}

207 208
// NewEmptyRequest initializes an empty request
func NewEmptyRequest() Request {
209
	return NewRequest(nil, nil, nil, nil, nil)
Matt Bell's avatar
Matt Bell committed
210 211
}

212
// NewRequest returns a request initialized with given arguments
213
func NewRequest(path []string, opts optMap, args []interface{}, cmd *Command, optDefs map[string]Option) Request {
214
	if path == nil {
Matt Bell's avatar
Matt Bell committed
215
		path = make([]string, 0)
Matt Bell's avatar
Matt Bell committed
216
	}
217 218 219 220
	if opts == nil {
		opts = make(map[string]interface{})
	}
	if args == nil {
221
		args = make([]interface{}, 0)
222
	}
223 224 225
	if optDefs == nil {
		optDefs = make(map[string]Option)
	}
226 227 228 229 230

	req := &request{path, opts, args, cmd, Context{}, optDefs}
	req.ConvertOptions()

	return req
Matt Bell's avatar
Matt Bell committed
231
}