mirror of
https://github.com/MontFerret/ferret.git
synced 2025-01-08 03:13:15 +02:00
Added support of optional ignoring of errors to function calls (#652)
* Added support of optional ignoring of errors to function calls * Added support of handling of source failure to optional chaining * Updated unit tests
This commit is contained in:
parent
0cb7623a7f
commit
5f361e9e1b
@ -80,6 +80,21 @@ func TestFunctionNSCall(t *testing.T) {
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("T::FAIL()? should return NONE", t, func() {
|
||||
c := compiler.New()
|
||||
|
||||
p, err := c.Compile(`
|
||||
RETURN T::FAIL()?
|
||||
`)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
out, err := p.Run(context.Background())
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(string(out), ShouldEqual, `null`)
|
||||
})
|
||||
|
||||
Convey("Should use keywords", t, func() {
|
||||
c := compiler.New()
|
||||
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"github.com/MontFerret/ferret/pkg/compiler"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
"github.com/pkg/errors"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
"testing"
|
||||
)
|
||||
@ -83,6 +84,44 @@ func TestFunctionCall(t *testing.T) {
|
||||
|
||||
So(string(out), ShouldEqual, `[2,4,6,8]`)
|
||||
})
|
||||
|
||||
Convey("Should handle errors when ? is used", t, func() {
|
||||
c := compiler.New()
|
||||
c.RegisterFunction("ERROR", func(ctx context.Context, args ...core.Value) (core.Value, error) {
|
||||
return values.None, errors.New("test error")
|
||||
})
|
||||
|
||||
p, err := c.Compile(`
|
||||
RETURN ERROR()?
|
||||
`)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
out, err := p.Run(context.Background())
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(string(out), ShouldEqual, `null`)
|
||||
})
|
||||
|
||||
Convey("Should return NONE when error is handled", t, func() {
|
||||
c := compiler.New()
|
||||
c.RegisterFunction("ERROR", func(ctx context.Context, args ...core.Value) (core.Value, error) {
|
||||
return values.NewString("booo"), errors.New("test error")
|
||||
})
|
||||
|
||||
p, err := c.Compile(`
|
||||
RETURN ERROR()?
|
||||
`)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
out, err := p.Run(context.Background())
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(string(out), ShouldEqual, `null`)
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkFunctionCallArg1(b *testing.B) {
|
||||
|
@ -2,8 +2,10 @@ package compiler_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/MontFerret/ferret/pkg/compiler"
|
||||
"github.com/MontFerret/ferret/pkg/runtime"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
"testing"
|
||||
)
|
||||
@ -71,7 +73,43 @@ func TestLogicalOperators(t *testing.T) {
|
||||
So(string(out), ShouldEqual, `"foo"`)
|
||||
})
|
||||
|
||||
Convey("NONE && true should return null", t, func() {
|
||||
Convey("ERROR()? || 'boo' should return 'boo'", t, func() {
|
||||
c := compiler.New()
|
||||
c.RegisterFunction("ERROR", func(ctx context.Context, args ...core.Value) (core.Value, error) {
|
||||
return nil, errors.New("test")
|
||||
})
|
||||
|
||||
p, err := c.Compile(`
|
||||
RETURN ERROR()? || 'boo'
|
||||
`)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
out, err := p.Run(context.Background())
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(string(out), ShouldEqual, `"boo"`)
|
||||
})
|
||||
|
||||
Convey("!ERROR()? && TRUE should return false", t, func() {
|
||||
c := compiler.New()
|
||||
c.RegisterFunction("ERROR", func(ctx context.Context, args ...core.Value) (core.Value, error) {
|
||||
return nil, errors.New("test")
|
||||
})
|
||||
|
||||
p, err := c.Compile(`
|
||||
RETURN !ERROR()? && TRUE
|
||||
`)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
out, err := p.Run(context.Background())
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(string(out), ShouldEqual, `true`)
|
||||
})
|
||||
|
||||
Convey("NONE && true should return null", t, func() {
|
||||
c := compiler.New()
|
||||
|
||||
p, err := c.Compile(`
|
||||
|
@ -3,6 +3,7 @@ package compiler_test
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"testing"
|
||||
|
||||
"github.com/MontFerret/ferret/pkg/compiler"
|
||||
@ -495,6 +496,25 @@ RETURN o1.first["second"][o2.prop].fourth["fifth"]["bottom"]
|
||||
|
||||
So(string(out), ShouldEqual, `"bar"`)
|
||||
})
|
||||
|
||||
Convey("When function returns error", func() {
|
||||
c := compiler.New()
|
||||
c.RegisterFunction("ERROR", func(ctx context.Context, args ...core.Value) (core.Value, error) {
|
||||
return nil, core.ErrNotImplemented
|
||||
})
|
||||
|
||||
p, err := c.Compile(`
|
||||
RETURN ERROR()?.foo
|
||||
`)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
out, err := p.Run(context.Background())
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(string(out), ShouldEqual, `null`)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -928,8 +928,8 @@ func (v *visitor) doVisitMemberExpressionSource(ctx *fql.MemberExpressionSourceC
|
||||
return v.doVisitParamContext(param.(*fql.ParamContext), scope)
|
||||
}
|
||||
|
||||
if fnCall := ctx.FunctionCallExpression(); fnCall != nil {
|
||||
return v.doVisitFunctionCallExpression(fnCall.(*fql.FunctionCallExpressionContext), scope)
|
||||
if fnCall := ctx.FunctionCall(); fnCall != nil {
|
||||
return v.doVisitFunctionCall(fnCall.(*fql.FunctionCallContext), false, scope)
|
||||
}
|
||||
|
||||
if objectLiteral := ctx.ObjectLiteral(); objectLiteral != nil {
|
||||
@ -1134,7 +1134,7 @@ func (v *visitor) doVisitRangeOperator(ctx *fql.RangeOperatorContext, scope *sco
|
||||
)
|
||||
}
|
||||
|
||||
func (v *visitor) doVisitFunctionCallExpression(context *fql.FunctionCallExpressionContext, scope *scope) (core.Expression, error) {
|
||||
func (v *visitor) doVisitFunctionCall(context *fql.FunctionCallContext, ignoreErrors bool, scope *scope) (core.Expression, error) {
|
||||
args := make([]core.Expression, 0, 5)
|
||||
argsCtx := context.Arguments()
|
||||
|
||||
@ -1170,10 +1170,19 @@ func (v *visitor) doVisitFunctionCallExpression(context *fql.FunctionCallExpress
|
||||
return expressions.NewFunctionCallExpression(
|
||||
v.getSourceMap(context),
|
||||
fun,
|
||||
ignoreErrors,
|
||||
args...,
|
||||
)
|
||||
}
|
||||
|
||||
func (v *visitor) doVisitFunctionCallExpression(context *fql.FunctionCallExpressionContext, scope *scope) (core.Expression, error) {
|
||||
return v.doVisitFunctionCall(
|
||||
context.FunctionCall().(*fql.FunctionCallContext),
|
||||
context.QuestionMark() != nil,
|
||||
scope,
|
||||
)
|
||||
}
|
||||
|
||||
func (v *visitor) doVisitParamContext(context *fql.ParamContext, s *scope) (core.Expression, error) {
|
||||
name := context.Identifier().GetText()
|
||||
|
||||
|
@ -57,6 +57,7 @@ func NewBufferedTestEventStream(buffer int) *TestEventStream {
|
||||
es := new(TestEventStream)
|
||||
es.ready = make(chan struct{}, buffer)
|
||||
es.message = make(chan interface{}, buffer)
|
||||
|
||||
return es
|
||||
}
|
||||
|
||||
@ -394,7 +395,7 @@ func TestLoop(t *testing.T) {
|
||||
// Stop the loop
|
||||
cancel()
|
||||
|
||||
time.Sleep(time.Duration(100) * time.Millisecond)
|
||||
time.Sleep(time.Duration(500) * time.Millisecond)
|
||||
|
||||
onLoad.Emit(&page.LoadEventFiredReply{})
|
||||
|
||||
@ -404,6 +405,8 @@ func TestLoop(t *testing.T) {
|
||||
onLoad.Emit(&page.LoadEventFiredReply{})
|
||||
}
|
||||
|
||||
time.Sleep(time.Duration(500) * time.Millisecond)
|
||||
|
||||
So(counter.Value(), ShouldEqual, eventsToFire)
|
||||
})
|
||||
}
|
||||
|
@ -264,9 +264,17 @@ memberExpression
|
||||
memberExpressionSource
|
||||
: variable
|
||||
| param
|
||||
| functionCallExpression
|
||||
| arrayLiteral
|
||||
| objectLiteral
|
||||
| functionCall
|
||||
;
|
||||
|
||||
functionCall
|
||||
: namespace functionIdentifier arguments
|
||||
;
|
||||
|
||||
functionCallExpression
|
||||
: functionCall QuestionMark?
|
||||
;
|
||||
|
||||
memberExpressionPath
|
||||
@ -274,10 +282,6 @@ memberExpressionPath
|
||||
| (QuestionMark Dot)? computedPropertyName
|
||||
;
|
||||
|
||||
functionCallExpression
|
||||
: namespace functionIdentifier arguments
|
||||
;
|
||||
|
||||
functionIdentifier
|
||||
: Identifier
|
||||
| And
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@ -308,11 +308,11 @@ func (s *BaseFqlParserListener) EnterMemberExpressionSource(ctx *MemberExpressio
|
||||
// ExitMemberExpressionSource is called when production memberExpressionSource is exited.
|
||||
func (s *BaseFqlParserListener) ExitMemberExpressionSource(ctx *MemberExpressionSourceContext) {}
|
||||
|
||||
// EnterMemberExpressionPath is called when production memberExpressionPath is entered.
|
||||
func (s *BaseFqlParserListener) EnterMemberExpressionPath(ctx *MemberExpressionPathContext) {}
|
||||
// EnterFunctionCall is called when production functionCall is entered.
|
||||
func (s *BaseFqlParserListener) EnterFunctionCall(ctx *FunctionCallContext) {}
|
||||
|
||||
// ExitMemberExpressionPath is called when production memberExpressionPath is exited.
|
||||
func (s *BaseFqlParserListener) ExitMemberExpressionPath(ctx *MemberExpressionPathContext) {}
|
||||
// ExitFunctionCall is called when production functionCall is exited.
|
||||
func (s *BaseFqlParserListener) ExitFunctionCall(ctx *FunctionCallContext) {}
|
||||
|
||||
// EnterFunctionCallExpression is called when production functionCallExpression is entered.
|
||||
func (s *BaseFqlParserListener) EnterFunctionCallExpression(ctx *FunctionCallExpressionContext) {}
|
||||
@ -320,6 +320,12 @@ func (s *BaseFqlParserListener) EnterFunctionCallExpression(ctx *FunctionCallExp
|
||||
// ExitFunctionCallExpression is called when production functionCallExpression is exited.
|
||||
func (s *BaseFqlParserListener) ExitFunctionCallExpression(ctx *FunctionCallExpressionContext) {}
|
||||
|
||||
// EnterMemberExpressionPath is called when production memberExpressionPath is entered.
|
||||
func (s *BaseFqlParserListener) EnterMemberExpressionPath(ctx *MemberExpressionPathContext) {}
|
||||
|
||||
// ExitMemberExpressionPath is called when production memberExpressionPath is exited.
|
||||
func (s *BaseFqlParserListener) ExitMemberExpressionPath(ctx *MemberExpressionPathContext) {}
|
||||
|
||||
// EnterFunctionIdentifier is called when production functionIdentifier is entered.
|
||||
func (s *BaseFqlParserListener) EnterFunctionIdentifier(ctx *FunctionIdentifierContext) {}
|
||||
|
||||
|
@ -199,7 +199,7 @@ func (v *BaseFqlParserVisitor) VisitMemberExpressionSource(ctx *MemberExpression
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseFqlParserVisitor) VisitMemberExpressionPath(ctx *MemberExpressionPathContext) interface{} {
|
||||
func (v *BaseFqlParserVisitor) VisitFunctionCall(ctx *FunctionCallContext) interface{} {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
@ -207,6 +207,10 @@ func (v *BaseFqlParserVisitor) VisitFunctionCallExpression(ctx *FunctionCallExpr
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseFqlParserVisitor) VisitMemberExpressionPath(ctx *MemberExpressionPathContext) interface{} {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseFqlParserVisitor) VisitFunctionIdentifier(ctx *FunctionIdentifierContext) interface{} {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
@ -151,12 +151,15 @@ type FqlParserListener interface {
|
||||
// EnterMemberExpressionSource is called when entering the memberExpressionSource production.
|
||||
EnterMemberExpressionSource(c *MemberExpressionSourceContext)
|
||||
|
||||
// EnterMemberExpressionPath is called when entering the memberExpressionPath production.
|
||||
EnterMemberExpressionPath(c *MemberExpressionPathContext)
|
||||
// EnterFunctionCall is called when entering the functionCall production.
|
||||
EnterFunctionCall(c *FunctionCallContext)
|
||||
|
||||
// EnterFunctionCallExpression is called when entering the functionCallExpression production.
|
||||
EnterFunctionCallExpression(c *FunctionCallExpressionContext)
|
||||
|
||||
// EnterMemberExpressionPath is called when entering the memberExpressionPath production.
|
||||
EnterMemberExpressionPath(c *MemberExpressionPathContext)
|
||||
|
||||
// EnterFunctionIdentifier is called when entering the functionIdentifier production.
|
||||
EnterFunctionIdentifier(c *FunctionIdentifierContext)
|
||||
|
||||
@ -346,12 +349,15 @@ type FqlParserListener interface {
|
||||
// ExitMemberExpressionSource is called when exiting the memberExpressionSource production.
|
||||
ExitMemberExpressionSource(c *MemberExpressionSourceContext)
|
||||
|
||||
// ExitMemberExpressionPath is called when exiting the memberExpressionPath production.
|
||||
ExitMemberExpressionPath(c *MemberExpressionPathContext)
|
||||
// ExitFunctionCall is called when exiting the functionCall production.
|
||||
ExitFunctionCall(c *FunctionCallContext)
|
||||
|
||||
// ExitFunctionCallExpression is called when exiting the functionCallExpression production.
|
||||
ExitFunctionCallExpression(c *FunctionCallExpressionContext)
|
||||
|
||||
// ExitMemberExpressionPath is called when exiting the memberExpressionPath production.
|
||||
ExitMemberExpressionPath(c *MemberExpressionPathContext)
|
||||
|
||||
// ExitFunctionIdentifier is called when exiting the functionIdentifier production.
|
||||
ExitFunctionIdentifier(c *FunctionIdentifierContext)
|
||||
|
||||
|
@ -151,12 +151,15 @@ type FqlParserVisitor interface {
|
||||
// Visit a parse tree produced by FqlParser#memberExpressionSource.
|
||||
VisitMemberExpressionSource(ctx *MemberExpressionSourceContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by FqlParser#memberExpressionPath.
|
||||
VisitMemberExpressionPath(ctx *MemberExpressionPathContext) interface{}
|
||||
// Visit a parse tree produced by FqlParser#functionCall.
|
||||
VisitFunctionCall(ctx *FunctionCallContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by FqlParser#functionCallExpression.
|
||||
VisitFunctionCallExpression(ctx *FunctionCallExpressionContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by FqlParser#memberExpressionPath.
|
||||
VisitMemberExpressionPath(ctx *MemberExpressionPathContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by FqlParser#functionIdentifier.
|
||||
VisitFunctionIdentifier(ctx *FunctionIdentifierContext) interface{}
|
||||
|
||||
|
@ -7,21 +7,23 @@ import (
|
||||
)
|
||||
|
||||
type FunctionCallExpression struct {
|
||||
src core.SourceMap
|
||||
fun core.Function
|
||||
args []core.Expression
|
||||
src core.SourceMap
|
||||
fun core.Function
|
||||
ignoreErrors bool
|
||||
args []core.Expression
|
||||
}
|
||||
|
||||
func NewFunctionCallExpression(
|
||||
src core.SourceMap,
|
||||
fun core.Function,
|
||||
ignoreErrors bool,
|
||||
args ...core.Expression,
|
||||
) (*FunctionCallExpression, error) {
|
||||
if fun == nil {
|
||||
return nil, core.Error(core.ErrMissedArgument, "function")
|
||||
}
|
||||
|
||||
return &FunctionCallExpression{src, fun, args}, nil
|
||||
return &FunctionCallExpression{src, fun, ignoreErrors, args}, nil
|
||||
}
|
||||
|
||||
func (e *FunctionCallExpression) Arguments() []core.Expression {
|
||||
@ -29,6 +31,18 @@ func (e *FunctionCallExpression) Arguments() []core.Expression {
|
||||
}
|
||||
|
||||
func (e *FunctionCallExpression) Function() core.Function {
|
||||
if e.ignoreErrors {
|
||||
return func(ctx context.Context, args ...core.Value) (core.Value, error) {
|
||||
out, err := e.fun(ctx, args...)
|
||||
|
||||
if err != nil {
|
||||
return values.None, nil
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
}
|
||||
|
||||
return e.fun
|
||||
}
|
||||
|
||||
@ -49,7 +63,7 @@ func (e *FunctionCallExpression) Exec(ctx context.Context, scope *core.Scope) (c
|
||||
out, err := arg.Exec(ctx, scope)
|
||||
|
||||
if err != nil {
|
||||
return values.None, core.SourceError(e.src, err)
|
||||
return values.None, e.maybeError(core.SourceError(e.src, err))
|
||||
}
|
||||
|
||||
args[idx] = out
|
||||
@ -59,9 +73,17 @@ func (e *FunctionCallExpression) Exec(ctx context.Context, scope *core.Scope) (c
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return values.None, core.SourceError(e.src, err)
|
||||
return values.None, e.maybeError(core.SourceError(e.src, err))
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (e *FunctionCallExpression) maybeError(err error) error {
|
||||
if !e.ignoreErrors {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"github.com/MontFerret/ferret/pkg/runtime/expressions"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/expressions/literals"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values/types"
|
||||
"testing"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
@ -21,6 +22,7 @@ func TestFunctionCallExpression(t *testing.T) {
|
||||
|
||||
return values.True, nil
|
||||
},
|
||||
false,
|
||||
)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
@ -46,6 +48,7 @@ func TestFunctionCallExpression(t *testing.T) {
|
||||
|
||||
return values.True, nil
|
||||
},
|
||||
false,
|
||||
args...,
|
||||
)
|
||||
|
||||
@ -72,6 +75,7 @@ func TestFunctionCallExpression(t *testing.T) {
|
||||
|
||||
return values.True, nil
|
||||
},
|
||||
false,
|
||||
args...,
|
||||
)
|
||||
|
||||
@ -85,5 +89,21 @@ func TestFunctionCallExpression(t *testing.T) {
|
||||
|
||||
So(err, ShouldEqual, core.ErrTerminated)
|
||||
})
|
||||
|
||||
Convey("Should ignore errors and return NONE", func() {
|
||||
f, err := expressions.NewFunctionCallExpression(
|
||||
core.SourceMap{},
|
||||
func(ctx context.Context, args ...core.Value) (value core.Value, e error) {
|
||||
return values.NewString("booo"), core.ErrNotImplemented
|
||||
},
|
||||
true,
|
||||
)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
out, err := f.Exec(context.Background(), rootScope.Fork())
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(out.Type().String(), ShouldEqual, types.None.String())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -29,6 +29,10 @@ func (e *MemberExpression) Exec(ctx context.Context, scope *core.Scope) (core.Va
|
||||
val, err := e.source.Exec(ctx, scope)
|
||||
|
||||
if err != nil {
|
||||
if e.path[0].optional {
|
||||
return values.None, nil
|
||||
}
|
||||
|
||||
return values.None, core.SourceError(
|
||||
e.src,
|
||||
err,
|
||||
|
Loading…
Reference in New Issue
Block a user