Skip to content

Commit 02abf9d

Browse files
authored
feat(function): update function library to have its own arguments struct (#5123)
The function package now uses its own arguments struct. This is to eventually remove the `interpreter.Arguments` interface. An interface is a poor choice for that type because interfaces with that many methods cannot be changed or adapted easily. At some point, the function package will be used for all situations instead of the raw register methods.
1 parent ba550f6 commit 02abf9d

File tree

8 files changed

+382
-51
lines changed

8 files changed

+382
-51
lines changed

internal/function/arguments.go

Lines changed: 309 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,309 @@
1+
package function
2+
3+
import (
4+
"github.com/influxdata/flux"
5+
"github.com/influxdata/flux/codes"
6+
"github.com/influxdata/flux/internal/errors"
7+
"github.com/influxdata/flux/semantic"
8+
"github.com/influxdata/flux/values"
9+
)
10+
11+
// Arguments provides access to the arguments of a function call.
12+
// This struct can only be created by using one of the Register functions
13+
// to register a function or by directly calling Invoke.
14+
type Arguments struct {
15+
obj values.Object
16+
used map[string]bool
17+
}
18+
19+
func newArguments(obj values.Object) *Arguments {
20+
if obj == nil {
21+
return new(Arguments)
22+
}
23+
return &Arguments{
24+
obj: obj,
25+
used: make(map[string]bool, obj.Len()),
26+
}
27+
}
28+
29+
func (a *Arguments) Get(name string) (values.Value, bool) {
30+
a.used[name] = true
31+
v, ok := a.obj.Get(name)
32+
return v, ok
33+
}
34+
35+
func (a *Arguments) GetRequired(name string) (values.Value, error) {
36+
a.used[name] = true
37+
v, ok := a.obj.Get(name)
38+
if !ok {
39+
return nil, errors.Newf(codes.Invalid, "missing required keyword argument %q", name)
40+
}
41+
return v, nil
42+
}
43+
44+
func (a *Arguments) GetString(name string) (string, bool, error) {
45+
v, ok, err := a.get(name, semantic.String, false)
46+
if err != nil || !ok {
47+
return "", ok, err
48+
}
49+
return v.Str(), ok, nil
50+
}
51+
52+
func (a *Arguments) GetRequiredString(name string) (string, error) {
53+
v, _, err := a.get(name, semantic.String, true)
54+
if err != nil {
55+
return "", err
56+
}
57+
return v.Str(), nil
58+
}
59+
60+
func (a *Arguments) GetInt(name string) (int64, bool, error) {
61+
v, ok, err := a.get(name, semantic.Int, false)
62+
if err != nil || !ok {
63+
return 0, ok, err
64+
}
65+
return v.Int(), ok, nil
66+
}
67+
68+
func (a *Arguments) GetRequiredInt(name string) (int64, error) {
69+
v, _, err := a.get(name, semantic.Int, true)
70+
if err != nil {
71+
return 0, err
72+
}
73+
return v.Int(), nil
74+
}
75+
76+
func (a *Arguments) GetUInt(name string) (uint64, bool, error) {
77+
v, ok, err := a.get(name, semantic.UInt, false)
78+
if err != nil || !ok {
79+
return 0, ok, err
80+
}
81+
return v.UInt(), ok, nil
82+
}
83+
84+
func (a *Arguments) GetRequiredUInt(name string) (uint64, error) {
85+
v, _, err := a.get(name, semantic.UInt, true)
86+
if err != nil {
87+
return 0, err
88+
}
89+
return v.UInt(), nil
90+
}
91+
92+
func (a *Arguments) GetFloat(name string) (float64, bool, error) {
93+
v, ok, err := a.get(name, semantic.Float, false)
94+
if err != nil || !ok {
95+
return 0, ok, err
96+
}
97+
return v.Float(), ok, nil
98+
}
99+
100+
func (a *Arguments) GetRequiredFloat(name string) (float64, error) {
101+
v, _, err := a.get(name, semantic.Float, true)
102+
if err != nil {
103+
return 0, err
104+
}
105+
return v.Float(), nil
106+
}
107+
108+
func (a *Arguments) GetBool(name string) (bool, bool, error) {
109+
v, ok, err := a.get(name, semantic.Bool, false)
110+
if err != nil || !ok {
111+
return false, ok, err
112+
}
113+
return v.Bool(), ok, nil
114+
}
115+
116+
func (a *Arguments) GetRequiredBool(name string) (bool, error) {
117+
v, _, err := a.get(name, semantic.Bool, true)
118+
if err != nil {
119+
return false, err
120+
}
121+
return v.Bool(), nil
122+
}
123+
124+
func (a *Arguments) GetArray(name string, t semantic.Nature) (values.Array, bool, error) {
125+
v, ok, err := a.get(name, semantic.Array, false)
126+
if err != nil || !ok {
127+
return nil, ok, err
128+
}
129+
arr := v.Array()
130+
et, err := arr.Type().ElemType()
131+
if err != nil {
132+
return nil, false, err
133+
}
134+
if et.Nature() != t {
135+
return nil, true, errors.Newf(codes.Invalid, "keyword argument %q should be of an array of type %v, but got an array of type %v", name, t, arr.Type())
136+
}
137+
return v.Array(), ok, nil
138+
}
139+
140+
func (a *Arguments) GetArrayAllowEmpty(name string, t semantic.Nature) (values.Array, bool, error) {
141+
v, ok, err := a.get(name, semantic.Array, false)
142+
if err != nil || !ok {
143+
return nil, ok, err
144+
}
145+
arr := v.Array()
146+
if arr.Len() > 0 {
147+
et, err := arr.Type().ElemType()
148+
if err != nil {
149+
return nil, false, err
150+
}
151+
if et.Nature() != t {
152+
return nil, true, errors.Newf(codes.Invalid, "keyword argument %q should be of an array of type %v, but got an array of type %v", name, t, arr.Type())
153+
}
154+
}
155+
return arr, ok, nil
156+
}
157+
158+
func (a *Arguments) GetRequiredArray(name string, t semantic.Nature) (values.Array, error) {
159+
v, _, err := a.get(name, semantic.Array, true)
160+
if err != nil {
161+
return nil, err
162+
}
163+
arr := v.Array()
164+
et, err := arr.Type().ElemType()
165+
if err != nil {
166+
return nil, err
167+
}
168+
if et.Nature() != t {
169+
return nil, errors.Newf(codes.Invalid, "keyword argument %q should be of an array of type %v, but got an array of type %v", name, t, arr.Type())
170+
}
171+
return arr, nil
172+
}
173+
174+
// GetRequiredArrayAllowEmpty ensures a required array (with element type) is present,
175+
// but unlike GetRequiredArray, does not fail if the array is empty.
176+
func (a *Arguments) GetRequiredArrayAllowEmpty(name string, t semantic.Nature) (values.Array, error) {
177+
v, _, err := a.get(name, semantic.Array, true)
178+
if err != nil {
179+
return nil, err
180+
}
181+
arr := v.Array()
182+
if arr.Array().Len() > 0 {
183+
et, err := arr.Type().ElemType()
184+
if err != nil {
185+
return nil, err
186+
}
187+
if et.Nature() != t {
188+
return nil, errors.Newf(codes.Invalid, "keyword argument %q should be of an array of type %v, but got an array of type %v", name, t, arr.Type())
189+
}
190+
}
191+
return arr, nil
192+
}
193+
194+
func (a *Arguments) GetFunction(name string) (values.Function, bool, error) {
195+
v, ok, err := a.get(name, semantic.Function, false)
196+
if err != nil || !ok {
197+
return nil, ok, err
198+
}
199+
return v.Function(), ok, nil
200+
}
201+
202+
func (a *Arguments) GetRequiredFunction(name string) (values.Function, error) {
203+
v, _, err := a.get(name, semantic.Function, true)
204+
if err != nil {
205+
return nil, err
206+
}
207+
return v.Function(), nil
208+
}
209+
210+
func (a *Arguments) GetObject(name string) (values.Object, bool, error) {
211+
v, ok, err := a.get(name, semantic.Object, false)
212+
if err != nil || !ok {
213+
return nil, ok, err
214+
}
215+
return v.Object(), ok, nil
216+
}
217+
218+
func (a *Arguments) GetRequiredObject(name string) (values.Object, error) {
219+
v, _, err := a.get(name, semantic.Object, true)
220+
if err != nil {
221+
return nil, err
222+
}
223+
return v.Object(), nil
224+
}
225+
226+
func (a *Arguments) GetDictionary(name string) (values.Dictionary, bool, error) {
227+
v, ok, err := a.get(name, semantic.Dictionary, false)
228+
if err != nil || !ok {
229+
return nil, ok, err
230+
}
231+
return v.Dict(), ok, nil
232+
}
233+
234+
func (a *Arguments) GetRequiredDictionary(name string) (values.Dictionary, error) {
235+
v, _, err := a.get(name, semantic.Dictionary, true)
236+
if err != nil {
237+
return nil, err
238+
}
239+
return v.Dict(), nil
240+
}
241+
242+
func (a *Arguments) GetTime(name string) (flux.Time, bool, error) {
243+
v, ok := a.Get(name)
244+
if !ok {
245+
return flux.Time{}, false, nil
246+
}
247+
qt, err := flux.ToQueryTime(v)
248+
if err != nil {
249+
return flux.Time{}, ok, err
250+
}
251+
return qt, ok, nil
252+
}
253+
254+
func (a *Arguments) GetRequiredTime(name string) (flux.Time, error) {
255+
qt, ok, err := a.GetTime(name)
256+
if err != nil {
257+
return flux.Time{}, err
258+
}
259+
if !ok {
260+
return flux.Time{}, errors.Newf(codes.Invalid, "missing required keyword argument %q", name)
261+
}
262+
return qt, nil
263+
}
264+
265+
func (a *Arguments) GetDuration(name string) (flux.Duration, bool, error) {
266+
v, ok := a.Get(name)
267+
if !ok {
268+
return flux.ConvertDuration(0), false, nil
269+
}
270+
return v.Duration(), true, nil
271+
}
272+
273+
func (a *Arguments) GetRequiredDuration(name string) (flux.Duration, error) {
274+
d, ok, err := a.GetDuration(name)
275+
if err != nil {
276+
return flux.ConvertDuration(0), err
277+
}
278+
if !ok {
279+
return flux.ConvertDuration(0), errors.Newf(codes.Invalid, "missing required keyword argument %q", name)
280+
}
281+
return d, nil
282+
}
283+
284+
func (a *Arguments) get(name string, kind semantic.Nature, required bool) (values.Value, bool, error) {
285+
a.used[name] = true
286+
v, ok := a.obj.Get(name)
287+
if !ok {
288+
if required {
289+
return nil, false, errors.Newf(codes.Invalid, "missing required keyword argument %q", name)
290+
}
291+
return nil, false, nil
292+
}
293+
if v.Type().Nature() != kind {
294+
return nil, true, errors.Newf(codes.Invalid, "keyword argument %q should be of kind %v, but got %v", name, kind, v.Type().Nature())
295+
}
296+
return v, true, nil
297+
}
298+
299+
func (a *Arguments) listUnused() []string {
300+
var unused []string
301+
if a.obj != nil {
302+
a.obj.Range(func(k string, v values.Value) {
303+
if !a.used[k] {
304+
unused = append(unused, k)
305+
}
306+
})
307+
}
308+
return unused
309+
}

internal/function/function.go

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ package function
33
import (
44
"context"
55

6-
"github.com/influxdata/flux/interpreter"
6+
"github.com/influxdata/flux/codes"
7+
"github.com/influxdata/flux/internal/errors"
78
"github.com/influxdata/flux/runtime"
89
"github.com/influxdata/flux/values"
910
)
@@ -19,24 +20,50 @@ func ForPackage(name string) Builder {
1920
}
2021

2122
type (
22-
Definition func(args interpreter.Arguments) (values.Value, error)
23-
DefinitionContext func(ctx context.Context, args interpreter.Arguments) (values.Value, error)
23+
Definition func(args *Arguments) (values.Value, error)
24+
DefinitionContext func(ctx context.Context, args *Arguments) (values.Value, error)
2425
)
2526

2627
func (b Builder) Register(name string, fn Definition) {
27-
mt := runtime.MustLookupBuiltinType(b.PackagePath, name)
28-
runtime.RegisterPackageValue(b.PackagePath, name,
29-
values.NewFunction(name, mt, func(ctx context.Context, args values.Object) (values.Value, error) {
30-
return interpreter.DoFunctionCall(fn, args)
31-
}, false),
32-
)
28+
b.RegisterContext(name, func(ctx context.Context, args *Arguments) (values.Value, error) {
29+
return fn(args)
30+
})
3331
}
3432

3533
func (b Builder) RegisterContext(name string, fn DefinitionContext) {
3634
mt := runtime.MustLookupBuiltinType(b.PackagePath, name)
3735
runtime.RegisterPackageValue(b.PackagePath, name,
3836
values.NewFunction(name, mt, func(ctx context.Context, args values.Object) (values.Value, error) {
39-
return interpreter.DoFunctionCallContext(fn, ctx, args)
37+
return InvokeContext(fn, ctx, args)
4038
}, false),
4139
)
4240
}
41+
42+
// InvokeContext calls a function and returns the result.
43+
//
44+
// It passes the object as the arguments to the function and returns an error
45+
// if any supplied arguments are not used.
46+
func InvokeContext[T any](f func(ctx context.Context, args *Arguments) (T, error), ctx context.Context, argsObj values.Object) (T, error) {
47+
args := newArguments(argsObj)
48+
v, err := f(ctx, args)
49+
if err == nil {
50+
if unused := args.listUnused(); len(unused) > 0 {
51+
err = errors.Newf(codes.Invalid, "unused arguments %v", unused)
52+
}
53+
}
54+
return v, err
55+
}
56+
57+
// Invoke calls a function and returns the result.
58+
//
59+
// This is the same as InvokeContext but with a background context.
60+
func Invoke[T any](f func(args *Arguments) (T, error), argsObj values.Object) (T, error) {
61+
args := newArguments(argsObj)
62+
v, err := f(args)
63+
if err == nil {
64+
if unused := args.listUnused(); len(unused) > 0 {
65+
err = errors.Newf(codes.Invalid, "unused arguments %v", unused)
66+
}
67+
}
68+
return v, err
69+
}

0 commit comments

Comments
 (0)