Skip to content

Commit

Permalink
Merge pull request #705 from camunda/674-non-existing-variable
Browse files Browse the repository at this point in the history
feat: Handle non-existing variables
  • Loading branch information
saig0 authored Sep 8, 2023
2 parents d4a8cd3 + b9a449b commit ad09f93
Show file tree
Hide file tree
Showing 11 changed files with 528 additions and 619 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,9 @@ class FeelInterpreter {
case Ref(names) =>
val name = names.head
context.variable(name) match {
case _: ValError => error(EvaluationFailureType.NO_VARIABLE_FOUND, s"No variable found with name '$name'")
case _: ValError =>
error(EvaluationFailureType.NO_VARIABLE_FOUND, s"No variable found with name '$name'")
ValNull
case value => ref(value, names.tail)
}
case PathExpression(exp, key) => withVal(eval(exp), v => path(v, key))
Expand Down Expand Up @@ -426,7 +428,9 @@ class FeelInterpreter {

private def input(implicit context: EvalContext): Val =
context.variable(inputKey) match {
case _: ValError => error(EvaluationFailureType.NO_VARIABLE_FOUND, s"No input value found.")
case _: ValError =>
error(EvaluationFailureType.NO_VARIABLE_FOUND, s"No input value found.")
ValNull
case inputValue => inputValue
}

Expand Down
8 changes: 0 additions & 8 deletions src/test/scala/org/camunda/feel/api/FeelEngineTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -74,14 +74,6 @@ class FeelEngineTest extends AnyFlatSpec with Matchers with EitherValues {
)
}

it should "fail evaluation because of missing input" in {

engine.evalUnaryTests("< 3", variables = Map[String, Any]()) should be(
Left(Failure(
"failed to evaluate expression '< 3': No input value found."))
)
}

it should "fail while parsing '<'" in {
engine
.evalUnaryTests("<", variables = Map[String, Any]())
Expand Down
95 changes: 51 additions & 44 deletions src/test/scala/org/camunda/feel/api/context/CustomContextTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,65 +18,62 @@ package org.camunda.feel.api.context

import org.camunda.feel.FeelEngine
import org.camunda.feel.FeelEngine.{Failure, UnaryTests}
import org.camunda.feel.context.{
CustomContext,
FunctionProvider,
VariableProvider
}
import org.camunda.feel.context.{CustomContext, FunctionProvider, VariableProvider}
import org.camunda.feel.context.VariableProvider.StaticVariableProvider
import org.camunda.feel.impl.{EvaluationResultMatchers, FeelEngineTest}
import org.camunda.feel.syntaxtree._
import org.scalatest.matchers.should.Matchers
import org.scalatest.flatspec.AnyFlatSpec

class CustomContextTest extends AnyFlatSpec with Matchers {

val engine = new FeelEngine
class CustomContextTest extends AnyFlatSpec with Matchers with FeelEngineTest with EvaluationResultMatchers {

"A default context" should "provide its members" in {
engine.evalExpression("a", variables = Map("a" -> 2)) should be(Right(2))
engine.evalUnaryTests(
"2",
variables = Map(UnaryTests.defaultInputVariable -> 2)) should be(
Right(true))
evaluateExpression(
expression = "a",
variables = Map("a" -> 2)
) should returnResult(2)

evaluateUnaryTests(
expression = "2",
inputValue = 2
) should returnResult(true)
}

it should "fail on access to missing member" in {
engine.evalExpression("b", variables = Map("a" -> 2)) should be
Left(
Failure(
"failed to evaluate expression 'b': no variable found for name 'b'"))
it should "return null if the variable doesn't exist" in {
evaluateExpression(
expression = "b",
variables = Map("a" -> 2)
) should returnNull()
}

"A custom context" should "provide its members" in {
val myCustomContext = new CustomContext {

override def variableProvider: VariableProvider = new VariableProvider {
override def getVariable(name: String): Option[Any] = name match {
case "a" => Some(2)
case "a" => Some(2)
case UnaryTests.defaultInputVariable => Some(2)
case _ => None
case _ => None
}

override def keys: Iterable[String] =
List("a", UnaryTests.defaultInputVariable)
}

}
engine.evalExpression("a", myCustomContext) should be(Right(2))
engine.evalExpression("floor(3.8)", myCustomContext) should be(Right(3))
engine.evalUnaryTests("2", myCustomContext) should be(Right(true))
}

it should "fail on access to missing member" in {
val context = new CustomContext {
override def variableProvider: VariableProvider =
VariableProvider.StaticVariableProvider(Map.empty)
}
evaluateExpression(
expression = "a",
context = myCustomContext
) should returnResult(2)

engine.evalExpression("b", context) should be
Left(
Failure(
"failed to evaluate expression 'b': no variable found for name 'b'"))
evaluateExpression(
expression = "floor(3.8)",
context = myCustomContext) should returnResult(3)

evaluateUnaryTests(
expression = "2",
context = myCustomContext) should returnResult(true)
}

it should "provide its functions" in {
Expand Down Expand Up @@ -111,11 +108,13 @@ class CustomContextTest extends AnyFlatSpec with Matchers {
override val functionProvider = myFunctionProvider
}

engine.evalExpression("a + f(2) + a + f(8)", myCustomContext) should be(
Right(18))
evaluateExpression(
expression = "a + f(2) + a + f(8)",
context = myCustomContext
) should returnResult(18)

variableCallCount should be(2)
functionCallCount should be(2)

}

it should "evaluate expression" in {
Expand All @@ -125,18 +124,23 @@ class CustomContextTest extends AnyFlatSpec with Matchers {
override val variableProvider = SimpleTestContext(variables)
}

engine.evalExpression("foo", context) should be(Right(7))
evaluateExpression(
expression = "foo",
context = context
) should returnResult(7)
}

it should "fail on expression evaluation" in {
it should "return null if variable doesn't exist" in {
val variables: Map[String, _] = Map()

val context: CustomContext = new CustomContext {
override val variableProvider = SimpleTestContext(variables)
}

engine.evalExpression("bar", context) shouldBe Left(Failure(
"failed to evaluate expression 'bar': No variable found with name 'bar'"))
evaluateExpression(
expression = "bar",
context = context
) should returnNull()
}

val inputVariableContext = StaticVariableProvider(
Expand All @@ -153,10 +157,12 @@ class CustomContextTest extends AnyFlatSpec with Matchers {
List(inputVariableContext, SimpleTestContext(variables)))
}

engine.evalUnaryTests("foo", context) should be(Right(false))
evaluateUnaryTests(
expression = "foo",
context = context) should returnResult(false)
}

it should "fail on unary-test evaluation" in {
it should "return null if input value doesn't exist" in {
val variables: Map[String, _] = Map("foo" -> 7)

val context: CustomContext = new CustomContext {
Expand All @@ -165,8 +171,9 @@ class CustomContextTest extends AnyFlatSpec with Matchers {
List(inputVariableContext, SimpleTestContext(variables)))
}

engine.evalUnaryTests("foo", context) shouldBe Left(Failure(
"failed to evaluate expression 'foo': No input value found."))
evaluateUnaryTests(
expression = "foo",
context = context) should returnResult(false)
}

}
20 changes: 20 additions & 0 deletions src/test/scala/org/camunda/feel/impl/FeelEngineTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@ trait FeelEngineTest {
engine.evaluateExpression(expression, context)
}

def evaluateExpression(
expression: String,
context: Context
): EvaluationResult = {
engine.evaluateExpression(expression, context)
}

def evaluateUnaryTests(
expression: String,
inputValue: Any,
Expand All @@ -57,6 +64,19 @@ trait FeelEngineTest {
)
}

def evaluateUnaryTests(
expression: String,
context: Context
): EvaluationResult = {
// use parse + evaluate to avoid setting an input value
val parsedExpression = engine.parseUnaryTests(expression).parsedExpression

engine.evaluate(
expression = parsedExpression,
context = context
)
}

def evaluateFunction(function: String): ValFunction = {
engine.evaluateExpression(function) match {
case SuccessfulEvaluationResult(result: ValFunction, _) => result
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ class SuppressedFailuresTest extends AnyFlatSpec
),
EvaluationFailure(
failureType = EvaluationFailureType.INVALID_TYPE,
failureMessage = "Expected Number but found 'ValError(No variable found with name 'x')'"
failureMessage = "Expected Number but found 'ValNull'"
)
)
}
Expand Down
Loading

0 comments on commit ad09f93

Please sign in to comment.