request.go 4.07 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
			if str, ok := v.(string); ok {
				val, err := opt.Parse(str)
123
				if err != nil {
124
					value := fmt.Sprintf("value %q", v)
125 126 127
					if len(str) == 0 {
						value = "empty value"
					}
Hector Sanjuan's avatar
Hector Sanjuan committed
128
					return options, fmt.Errorf("could not convert %s to type %q (for option %q)",
129
						value, opt.Type().String(), "-"+k)
130
				}
131
				options[k] = val
132 133

			} else {
Hector Sanjuan's avatar
Hector Sanjuan committed
134
				return options, fmt.Errorf("option %q should be type %q, but got type %q",
135
					k, opt.Type().String(), kind.String())
136 137 138
			}
		}

139
		for _, name := range opt.Names() {
140
			if _, ok := options[name]; name != k && ok {
Hector Sanjuan's avatar
Hector Sanjuan committed
141
				return options, fmt.Errorf("duplicate command options were provided (%q and %q)",
142 143 144 145 146
					k, name)
			}
		}
	}

147
	return options, nil
148 149
}

150
// GetEncoding returns the EncodingType set in a request, falling back to JSON
Steven Allen's avatar
Steven Allen committed
151 152
func GetEncoding(req *Request, def EncodingType) EncodingType {
	switch enc := req.Options[EncLong].(type) {
153 154 155 156 157
	case string:
		return EncodingType(enc)
	case EncodingType:
		return enc
	default:
Steven Allen's avatar
Steven Allen committed
158 159 160 161
		if def == "" {
			return DefaultOutputEncoding
		}
		return def
Jeromy's avatar
Jeromy committed
162
	}
163 164
}

Hector Sanjuan's avatar
Hector Sanjuan committed
165
// FillDefaults fills in default values if option has not been set.
166 167
func (req *Request) FillDefaults() error {
	optDefMap, err := req.Root.GetOptions(req.Path)
168
	if err != nil {
169
		return err
170
	}
171

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

174 175
	for _, optDef := range optDefMap {
		optDefs[optDef] = struct{}{}
176 177
	}

178 179 180 181 182 183 184
Outer:
	for optDef := range optDefs {
		dflt := optDef.Default()
		if dflt == nil {
			// option has no dflt, continue
			continue
		}
185

186 187 188 189 190 191 192
		names := optDef.Names()
		for _, name := range names {
			if _, ok := req.Options[name]; ok {
				// option has been set, continue with next option
				continue Outer
			}
		}
193

194
		req.Options[optDef.Name()] = dflt
195 196
	}

197
	return nil
198
}