-
-
Notifications
You must be signed in to change notification settings - Fork 475
Closed
Description
In our codebase we have a Patcher that let's us not specify the context as a function value if the function requires a context.
However when we compile scripts with the 1.15.4 version we get errors similar to: expected float64, but got interface {}
Embedded test:
import (
"context"
"fmt"
"reflect"
"strconv"
"testing"
"github.com/antonmedv/expr"
"github.com/antonmedv/expr/ast"
)
type TestStruct struct {
Data string
}
func (ts *TestStruct) GetFloat() (float64, error) {
return strconv.ParseFloat(ts.Data, 64)
}
func testMeFunc(arguments ...any) (any, error) {
if len(arguments) != 2 {
return nil, fmt.Errorf("expected 2 argument. got: %d", len(arguments))
}
ctx, ok := arguments[0].(context.Context)
if !ok {
return nil, fmt.Errorf("exprGetCurveTable() first argument should be context")
}
// imagine we need something from the context.
ctxValue := ctx.Value("test").(string)
if ctxValue != "value" {
return nil, fmt.Errorf("invalid context value")
}
data, ok := arguments[1].(string)
if !ok {
return nil, fmt.Errorf("expected argument 2 to be a string. was: %T", arguments[0])
}
return &TestStruct{
Data: data,
}, nil
}
type EvaluationData struct {
Ctx context.Context `expr:"ctx"`
}
type ContextPatcher struct{}
func (p *ContextPatcher) Visit(node *ast.Node) {
contextType := reflect.TypeOf((*context.Context)(nil)).Elem()
//nolint:nestif
if callNode, ok := (*node).(*ast.CallNode); ok {
// patch context calls so we don't have to pass the context in the code
if callNode.Func == nil {
return
}
funcType := callNode.Func.Types[0]
if funcType.NumIn() > 0 {
firstIn := funcType.In(0)
if firstIn.Implements(contextType) {
// prepend patch context calls so we don't need to specify the context in the function invocation
args := append([]ast.Node{
&ast.IdentifierNode{
Value: "ctx",
},
}, callNode.Arguments...)
newNode := &ast.CallNode{
Callee: callNode.Callee,
Arguments: args,
Typed: callNode.Typed,
Fast: callNode.Fast,
Func: callNode.Func,
}
ast.Patch(node, newNode)
}
}
}
}
func TestFunctionCallReturnsFloat(t *testing.T) {
opts := []expr.Option{
expr.Function("TestMe",
testMeFunc,
new(func(context.Context, string) *TestStruct),
),
expr.Env(&EvaluationData{}),
expr.AsFloat64(),
expr.Patch(&ContextPatcher{}),
}
program, err := expr.Compile(`TestMe("1.33").GetFloat()`, opts...)
if err != nil {
t.Fatalf("failed to compile script. %v", err)
}
ctx := context.WithValue(context.Background(), "test", "value")
evalData := &EvaluationData{
Ctx: ctx,
}
result, err := expr.Run(program, evalData)
if err != nil {
t.Fatalf("failed to run program. %v", err)
}
value, ok := result.(float64)
if !ok {
t.Errorf("expected result to be a float. was: %T", result)
}
if value != 1.33 {
t.Errorf("expected result to be 1.33, was: %v", value)
}
}
Metadata
Metadata
Assignees
Labels
No labels