request.go 4.28 KB
Newer Older
1
package cmds
Matt Bell's avatar
Matt Bell committed
2

3
import (
4
	"context"
5 6
	"fmt"
	"reflect"
7

8
	files "github.com/ipfs/go-ipfs-files"
9 10
)

Matt Bell's avatar
Matt Bell committed
11
// Request represents a call to a command from a consumer
12 13 14
type Request struct {
	Context       context.Context
	Root, Command *Command
15

16 17
	Path      []string
	Arguments []string
Steven Allen's avatar
Steven Allen committed
18
	Options   OptMap
Matt Bell's avatar
Matt Bell committed
19

Łukasz Magiera's avatar
Łukasz Magiera committed
20
	Files files.Directory
21

22
	bodyArgs *arguments
23 24
}

25 26
// NewRequest returns a request initialized with given arguments
// An non-nil error will be returned if the provided option values are invalid
27
func NewRequest(ctx context.Context,
28 29
	path []string,
	opts OptMap,
30 31 32 33
	args []string,
	file files.Directory,
	root *Command,
) (*Request, error) {
34

35 36 37
	cmd, err := root.Get(path)
	if err != nil {
		return nil, err
38 39
	}

40 41
	options, err := checkAndConvertOptions(root, opts, path)

42 43
	req := &Request{
		Path:      path,
44
		Options:   options,
45 46 47 48 49
		Arguments: args,
		Files:     file,
		Root:      root,
		Command:   cmd,
		Context:   ctx,
Jeromy's avatar
Jeromy committed
50 51
	}

52
	return req, err
Jeromy's avatar
Jeromy committed
53 54
}

55
// BodyArgs returns a scanner that returns arguments passed in the body as tokens.
56 57 58 59 60 61 62 63 64 65 66
//
// Returns nil if there are no arguments to be consumed via stdin.
func (req *Request) BodyArgs() StdinArguments {
	// dance to make sure we return an *untyped* nil.
	// DO NOT just return `req.bodyArgs`.
	// If you'd like to complain, go to
	// https://github.com/golang/go/issues/.
	if req.bodyArgs != nil {
		return req.bodyArgs
	}
	return nil
Jeromy's avatar
Jeromy committed
67 68
}

Hector Sanjuan's avatar
Hector Sanjuan committed
69
// ParseBodyArgs parses arguments in the request body.
70
func (req *Request) ParseBodyArgs() error {
71 72 73
	s := req.BodyArgs()
	if s == nil {
		return nil
Jeromy's avatar
cleanup  
Jeromy committed
74 75
	}

76 77
	for s.Scan() {
		req.Arguments = append(req.Arguments, s.Argument())
78
	}
79
	return s.Err()
Jeromy's avatar
Jeromy committed
80 81
}

Hector Sanjuan's avatar
Hector Sanjuan committed
82
// SetOption sets a request option.
83 84 85
func (req *Request) SetOption(name string, value interface{}) {
	optDefs, err := req.Root.GetOptions(req.Path)
	optDef, found := optDefs[name]
Jeromy's avatar
Jeromy committed
86

87 88
	if req.Options == nil {
		req.Options = map[string]interface{}{}
Jeromy's avatar
Jeromy committed
89 90
	}

91 92 93 94 95 96
	// unknown option, simply set the value and return
	// TODO we might error out here instead
	if err != nil || !found {
		req.Options[name] = value
		return
	}
97

98 99
	name = optDef.Name()
	req.Options[name] = value
100 101
}

102
func checkAndConvertOptions(root *Command, opts OptMap, path []string) (OptMap, error) {
103
	optDefs, err := root.GetOptions(path)
104 105
	options := make(OptMap)

106
	if err != nil {
107 108 109 110
		return options, err
	}
	for k, v := range opts {
		options[k] = v
111
	}
112

113
	for k, v := range opts {
114
		opt, ok := optDefs[k]
115
		if !ok {
116
			continue
117 118 119
		}

		kind := reflect.TypeOf(v).Kind()
120
		if kind != opt.Type() {
121 122 123 124
			if opt.Type() == Strings {
				if _, ok := v.([]string); !ok {
					return options, fmt.Errorf("option %q should be type %q, but got type %q",
						k, opt.Type().String(), kind.String())
125 126
				}
			} else {
Adin Schmahmann's avatar
Adin Schmahmann committed
127 128
				str, ok := v.(string)
				if !ok {
129 130 131
					return options, fmt.Errorf("option %q should be type %q, but got type %q",
						k, opt.Type().String(), kind.String())
				}
Adin Schmahmann's avatar
Adin Schmahmann committed
132 133 134 135 136 137 138 139 140 141 142

				val, err := opt.Parse(str)
				if err != nil {
					value := fmt.Sprintf("value %q", v)
					if len(str) == 0 {
						value = "empty value"
					}
					return options, fmt.Errorf("could not convert %s to type %q (for option %q)",
						value, opt.Type().String(), "-"+k)
				}
				options[k] = val
143 144 145
			}
		}

146
		for _, name := range opt.Names() {
147
			if _, ok := options[name]; name != k && ok {
Hector Sanjuan's avatar
Hector Sanjuan committed
148
				return options, fmt.Errorf("duplicate command options were provided (%q and %q)",
149 150 151 152 153
					k, name)
			}
		}
	}

154
	return options, nil
155 156
}

157
// GetEncoding returns the EncodingType set in a request, falling back to JSON
Steven Allen's avatar
Steven Allen committed
158 159
func GetEncoding(req *Request, def EncodingType) EncodingType {
	switch enc := req.Options[EncLong].(type) {
160 161 162 163 164
	case string:
		return EncodingType(enc)
	case EncodingType:
		return enc
	default:
Steven Allen's avatar
Steven Allen committed
165 166 167 168
		if def == "" {
			return DefaultOutputEncoding
		}
		return def
Jeromy's avatar
Jeromy committed
169
	}
170 171
}

Hector Sanjuan's avatar
Hector Sanjuan committed
172
// FillDefaults fills in default values if option has not been set.
173 174
func (req *Request) FillDefaults() error {
	optDefMap, err := req.Root.GetOptions(req.Path)
175
	if err != nil {
176
		return err
177
	}
178

Steven Allen's avatar
Steven Allen committed
179
	optDefs := map[Option]struct{}{}
180

181 182
	for _, optDef := range optDefMap {
		optDefs[optDef] = struct{}{}
183 184
	}

185 186 187 188 189 190 191
Outer:
	for optDef := range optDefs {
		dflt := optDef.Default()
		if dflt == nil {
			// option has no dflt, continue
			continue
		}
192

193 194 195 196 197 198 199
		names := optDef.Names()
		for _, name := range names {
			if _, ok := req.Options[name]; ok {
				// option has been set, continue with next option
				continue Outer
			}
		}
200

201
		req.Options[optDef.Name()] = dflt
202 203
	}

204
	return nil
205
}