-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathfunc.go
336 lines (297 loc) · 10.1 KB
/
func.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
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
136
137
138
139
140
141
142
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
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package argmapper
import (
"fmt"
"reflect"
"runtime"
"github.com/hashicorp/go-argmapper/internal/graph"
)
// Func represents both a target function you want to execute as well as
// a function that can be used to provide values, convert types, etc. for
// calling another Func.
//
// A Func can take any number of arguments and return any number of values.
// Direct function arguments are matched via type. You may use a struct
// that embeds the Struct type (see Struct) for named value matching.
// Go reflection doesn't enable accessing direct function parameter names,
// so a struct is required for named matching.
//
// Structs that do not embed the Struct type are matched as typed.
//
// Converter Basics
//
// A Func also can act as a converter for another function call when used
// with the Converter Arg option.
//
// Converters are used if a direct match argument isn't found for a Func call.
// If a converter exists (or a chain of converts) to go from the input arguments
// to the desired argument, then the chain will be called and the result used.
//
// Like any typical Func, converters can take as input zero or more values of
// any kind. Converters can return any number of values as a result. Note that
// while no return values are acceptable, such a converter would never be
// called since it provides no value to the target function call.
//
// Converters can output both typed and named values. Similar to inputs,
// outputting a name value requires using a struct with the Struct type
// embedded.
//
// Converter Errors
//
// A final return type of "error" can be used with converters to signal
// that conversion failed. If this occurs, the full function call attempt
// fails and the error is reported to the user.
//
// If there is only one return value and it is of type "error", then this
// is still considered the error result. A function can't return a non-erroneous
// error value without returning more than one result value.
//
// Converter Priorities
//
// When multiple converters are available to reach some desired type,
// Func will determine which converter to call using an implicit "cost"
// associated with the converter. The cost is calculated across multiple
// dimensions:
//
// * When converting from one named value to another, such as "Input int"
// to "Input string", conversion will favor any converters that explicitly
// use the equivalent name (but different type). So if there are two
// converters, one `func(int) string` and another `func(Input int) string`,
// then the latter will be preferred.
//
// * Building on the above, if there is only one converter `func(int) string`
// but there are multiple `int` inputs available, an input with a matching
// name is preferred. Therefore, if an input named `Input` is available,
// that will be used for the conversion.
//
// * Converters that have less input values are preferred. This isn't
// a direct parameter count on the function, but a count on the input
// values which includes struct members and so on.
//
type Func struct {
fn reflect.Value
input *ValueSet
output *ValueSet
callOpts []Arg
name string
once bool
onceResult *Result
}
// MustFunc can be called around NewFunc in order to force success and
// panic if there is any error.
func MustFunc(f *Func, err error) *Func {
if err != nil {
panic(err)
}
return f
}
// NewFunc creates a new Func from the given input function f.
//
// For more details on the format of the function f, see the package docs.
//
// Additional opts can be provided. These will always be set when calling
// Call. Any conflicting arguments given on Call will override these args.
// This can be used to provide some initial values, converters, etc.
func NewFunc(f interface{}, opts ...Arg) (*Func, error) {
args, err := newArgBuilder(opts...)
if err != nil {
return nil, err
}
fv := reflect.ValueOf(f)
ft := fv.Type()
if k := ft.Kind(); k != reflect.Func {
return nil, fmt.Errorf("fn should be a function, got %s", k)
}
inTyp, err := newValueSet(ft.NumIn(), ft.In)
if err != nil {
return nil, err
}
// Get our output parameters. If the last parameter is an error type
// then we don't parse that as the struct information.
numOut := ft.NumOut()
if numOut >= 1 && ft.Out(numOut-1) == errType {
numOut -= 1
}
outTyp, err := newValueSet(numOut, ft.Out)
if err != nil {
return nil, err
}
return &Func{
fn: fv,
input: inTyp,
output: outTyp,
callOpts: opts,
name: args.funcName,
once: args.funcOnce,
}, nil
}
// NewFuncList initializes multiple Funcs at once. This is the same as
// calling NewFunc for each f.
func NewFuncList(fs []interface{}, opts ...Arg) ([]*Func, error) {
result := make([]*Func, len(fs))
for i, f := range fs {
var err error
result[i], err = NewFunc(f)
if err != nil {
return nil, err
}
}
return result, nil
}
// BuildFunc builds a function based on the specified input and output
// value sets. When called, this will call the cb with a valueset matching
// input and output with the argument values set. The cb should return
// a populated ValueSet.
func BuildFunc(input, output *ValueSet, cb func(in, out *ValueSet) error, opts ...Arg) (*Func, error) {
// If our input or output is nil, we set it to an allocated empty value set.
// This lets the caller always have a non-nil value but it may contain
// no values.
if input == nil {
input = &ValueSet{}
}
if output == nil {
output = &ValueSet{}
}
// Make our function type.
funcType := reflect.FuncOf(
input.Signature(),
append(output.Signature(), errType), // append error so we can return errors
false,
)
// Build our function
return NewFunc(reflect.MakeFunc(funcType, func(vs []reflect.Value) []reflect.Value {
// Set our input
if err := input.FromSignature(vs); err != nil {
// FromSignature can not currently return an error
panic(err)
}
// Call
if err := cb(input, output); err != nil {
return append(output.SignatureValues(), reflect.ValueOf(err))
}
return append(output.SignatureValues(), reflect.Zero(errType))
}).Interface(), opts...)
}
// Input returns the input ValueSet for this function, representing the values
// that this function requires as input.
func (f *Func) Input() *ValueSet { return f.input }
// Output returns the output ValueSet for this function, representing the values
// that this function produces as an output.
func (f *Func) Output() *ValueSet { return f.output }
// Func returns the function pointer that this Func is built around.
func (f *Func) Func() interface{} {
return f.fn.Interface()
}
// Name returns the name of the function.
//
// This will return the configured name if one was given on NewFunc. If not,
// this will attempt to look up the function name using the pointer. If
// no friendly name can be found, then this will default to the function
// type signature.
func (f *Func) Name() string {
// Use our set name first, if we have one
name := f.name
// Fall back to inspecting the program counter
if name == "" {
if rfunc := runtime.FuncForPC(f.fn.Pointer()); rfunc != nil {
name = rfunc.Name()
}
// Final fallback is our type signature
if name == "" {
name = f.fn.String()
}
}
return name
}
// String returns the name for this function. See Name.
func (f *Func) String() string {
return f.Name()
}
// argBuilder returns the instantiated argBuilder based on the opts provided
// as well as the default opts attached to the func.
func (f *Func) argBuilder(opts ...Arg) (*argBuilder, error) {
if len(f.callOpts) > 0 {
optsCopy := make([]Arg, len(opts)+len(f.callOpts))
copy(optsCopy, f.callOpts)
copy(optsCopy[len(f.callOpts):], opts)
opts = optsCopy
}
return newArgBuilder(opts...)
}
// graph adds this function to the graph. The given root should be a single
// shared root to the graph, typically a rootVertex. This returns the
// funcVertex created.
//
// includeOutput controls whether to include the output values in the graph.
// This should be true for all intermediary functions but false for the
// target function.
func (f *Func) graph(g *graph.Graph, root graph.Vertex, includeOutput bool) graph.Vertex {
vertex := g.Add(&funcVertex{
Func: f,
})
// If we take no arguments, we add this function to the root
// so that it isn't pruned.
if f.input.empty() {
g.AddEdge(vertex, root)
}
// Add all our inputs and add an edge from the func to the input
for _, val := range f.input.values {
switch val.Kind() {
case ValueNamed:
g.AddEdge(vertex, g.Add(&valueVertex{
Name: val.Name,
Type: val.Type,
Subtype: val.Subtype,
}))
case ValueTyped:
g.AddEdgeWeighted(vertex, g.Add(&typedArgVertex{
Type: val.Type,
Subtype: val.Subtype,
}), weightTyped)
default:
panic(fmt.Sprintf("unknown value kind: %s", val.Kind()))
}
}
if includeOutput {
// Add all our outputs
for k, f := range f.output.namedValues {
g.AddEdge(g.Add(&valueVertex{
Name: k,
Type: f.Type,
Subtype: f.Subtype,
}), vertex)
}
for _, f := range f.output.typedValues {
g.AddEdgeWeighted(g.Add(&typedOutputVertex{
Type: f.Type,
Subtype: f.Subtype,
}), vertex, weightTyped)
}
}
return vertex
}
// outputValues extracts the output from the given Result. The Result must
// be a result of calling Call on this exact Func. Specifying any other
// Result is undefined and will likely result in panics.
func (f *Func) outputValues(r Result, vs []graph.Vertex, state *callState) {
// Get our struct
structVal := f.output.result(r).out[0]
// Go through our out edges to find all our results so we can update
// the graph nodes with our values. Along the way, we also update our
// total call state.
for _, v := range vs {
switch v := v.(type) {
case *valueVertex:
// Set the value on the vertex. During the graph walk, we'll
// set the Named value.
v.Value = structVal.Field(f.output.namedValues[v.Name].index)
case *typedOutputVertex:
// Get our field with the same name
field := f.output.typedValues[v.Type]
v.Value = structVal.Field(field.index)
}
}
}
// errType is used for comparison in Spec
var errType = reflect.TypeOf((*error)(nil)).Elem()