Skip to content

Commit 504dc68

Browse files
Asynchronous function invocation support
1 parent 2309529 commit 504dc68

File tree

12 files changed

+885
-149
lines changed

12 files changed

+885
-149
lines changed

Diff for: cel/cel_test.go

+98
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,13 @@
1515
package cel
1616

1717
import (
18+
"context"
1819
"fmt"
1920
"io/ioutil"
2021
"log"
2122
"sync"
2223
"testing"
24+
"time"
2325

2426
"github.com/golang/protobuf/proto"
2527
"github.com/google/cel-go/checker/decls"
@@ -32,6 +34,7 @@ import (
3234
"github.com/google/cel-go/interpreter"
3335
"github.com/google/cel-go/interpreter/functions"
3436
"github.com/google/cel-go/parser"
37+
"github.com/google/cel-go/test"
3538

3639
descpb "github.com/golang/protobuf/protoc-gen-go/descriptor"
3740
proto2pb "github.com/google/cel-go/test/proto2pb"
@@ -876,3 +879,98 @@ func Test_CustomInterpreterDecorator(t *testing.T) {
876879
t.Errorf("got %v as the last observed constant, wanted 1", lastConst)
877880
}
878881
}
882+
883+
func Test_AsyncExtension(t *testing.T) {
884+
env, err := NewEnv(
885+
Declarations(
886+
decls.NewVar("x", decls.String),
887+
decls.NewFunction("asyncEcho",
888+
decls.NewOverload(
889+
"async_echo_string",
890+
[]*exprpb.Type{decls.String},
891+
decls.String)),
892+
),
893+
)
894+
if err != nil {
895+
t.Fatal(err)
896+
}
897+
funcs := Functions(
898+
&functions.Overload{
899+
Operator: "asyncEcho",
900+
Async: test.FakeRPC(25 * time.Millisecond),
901+
},
902+
&functions.Overload{
903+
Operator: "async_echo_string",
904+
Async: test.FakeRPC(25 * time.Millisecond),
905+
},
906+
)
907+
908+
tests := []struct {
909+
expr string
910+
parseOnly bool
911+
evalOpts EvalOption
912+
out ref.Val
913+
}{
914+
{
915+
expr: `asyncEcho(x)`,
916+
out: types.String("async echo success!"),
917+
},
918+
{
919+
expr: `asyncEcho(x)`,
920+
parseOnly: true,
921+
out: types.String("async echo success!"),
922+
},
923+
{
924+
expr: `asyncEcho(x)`,
925+
evalOpts: OptOptimize,
926+
out: types.String("async echo success!"),
927+
},
928+
{
929+
expr: `asyncEcho(x) == 'async echo success!'`,
930+
evalOpts: OptOptimize | OptTrackState,
931+
out: types.True,
932+
},
933+
{
934+
expr: `asyncEcho(x) == 'async echo success!' || true`,
935+
evalOpts: OptOptimize | OptTrackState,
936+
out: types.True,
937+
},
938+
}
939+
for i, tst := range tests {
940+
tc := tst
941+
t.Run(fmt.Sprintf("%d", i), func(tt *testing.T) {
942+
var ast *Ast
943+
var iss *Issues
944+
if tc.parseOnly {
945+
ast, iss = env.Parse(tc.expr)
946+
} else {
947+
ast, iss = env.Compile(tc.expr)
948+
}
949+
if iss.Err() != nil {
950+
tt.Fatal(iss.Err())
951+
}
952+
opts := []ProgramOption{funcs}
953+
if tc.evalOpts != 0 {
954+
opts = append(opts, EvalOptions(tc.evalOpts))
955+
}
956+
prg, err := env.AsyncProgram(ast, opts...)
957+
if err != nil {
958+
tt.Fatal(err)
959+
}
960+
ctx := context.TODO()
961+
out, det, err := prg.AsyncEval(ctx, map[string]interface{}{
962+
"x": "async echo",
963+
})
964+
if err != nil {
965+
tt.Fatal(err)
966+
}
967+
if out.Equal(tc.out) != types.True {
968+
tt.Errorf("got %v, wanted %v", out, tc.out)
969+
}
970+
if tc.evalOpts&OptTrackState == OptTrackState && det == nil {
971+
tt.Error("details was nil, expected non-nil")
972+
}
973+
})
974+
}
975+
976+
}

Diff for: cel/env.go

+9-10
Original file line numberDiff line numberDiff line change
@@ -307,14 +307,13 @@ func (e *Env) ParseSource(src common.Source) (*Ast, *Issues) {
307307

308308
// Program generates an evaluable instance of the Ast within the environment (Env).
309309
func (e *Env) Program(ast *Ast, opts ...ProgramOption) (Program, error) {
310-
optSet := e.progOpts
311-
if len(opts) != 0 {
312-
mergedOpts := []ProgramOption{}
313-
mergedOpts = append(mergedOpts, e.progOpts...)
314-
mergedOpts = append(mergedOpts, opts...)
315-
optSet = mergedOpts
316-
}
317-
return newProgram(e, ast, optSet)
310+
return e.newProgram(ast, opts /* async= */, false)
311+
}
312+
313+
// AsyncProgram generates an evaluable instance of the Ast with support for asynchronous extension
314+
// functions.
315+
func (e *Env) AsyncProgram(ast *Ast, opts ...ProgramOption) (AsyncProgram, error) {
316+
return e.newProgram(ast, opts /* async= */, true)
318317
}
319318

320319
// SetFeature sets the given feature flag, as enumerated in options.go.
@@ -427,8 +426,8 @@ func (i *Issues) Err() error {
427426
if i == nil {
428427
return nil
429428
}
430-
if len(i.errs.GetErrors()) > 0 {
431-
return errors.New(i.errs.ToDisplayString())
429+
if len(i.Errors()) > 0 {
430+
return errors.New(i.String())
432431
}
433432
return nil
434433
}

0 commit comments

Comments
 (0)