-
Notifications
You must be signed in to change notification settings - Fork 188
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for basic arithmetic and string returning expressions to DataPrepper Expression #2697
Conversation
Codecov Report
@@ Coverage Diff @@
## main #2697 +/- ##
=========================================
Coverage 93.61% 93.61%
- Complexity 2267 2269 +2
=========================================
Files 263 264 +1
Lines 6330 6331 +1
Branches 526 526
=========================================
+ Hits 5926 5927 +1
Misses 266 266
Partials 138 138
|
…l types of expressions Signed-off-by: Krishna Kondaka <krishkdk@amazon.com>
Signed-off-by: Krishna Kondaka <krishkdk@amazon.com>
@Test | ||
public void testDefaultEvaluateConditionalThrows() { | ||
expressionEvaluator = new TestExpressionEvaluator(); | ||
assertThrows(ClassCastException.class, () -> expressionEvaluator.evaluateConditional("/status", event("{\"status\":200}"))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not related to this PR, but I don't love how a conditional expression of /status
throws this Exception. I would prefer /status
to check if the key exists, and /status == true
to actually evaluate if the value is true.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This raises an interesting point which I didn't consider.
By combining the expression evaluator, there really is no way to evaluate one expression with some conditional goal in mind versus evaluating with an object in mind.
I don't think that should hinder this PR because I think it could be resolved with some internal refactoring.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We already have a way to check for existence /status == null
would check for key exists or not. As David said, that functionality is not new.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes that does work, I'm just saying I think /status
as an alternative to /status != null
is cleaner.
import static org.hamcrest.CoreMatchers.not; | ||
import org.apache.commons.lang3.RandomStringUtils; | ||
|
||
class ArithmeticExpressionEvaluatorIT { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since there is no ArithmeticExpressionEvaluator
class, is this really an IT for GenericExpressionEvaluator
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another approach that is fairly common in the Java world is to split tests using an underscore. This is testing the GenericExpressionEvaluator
for arithmetic and you have another testing GenericExpressionEvaluator
for strings.
Thus:
GenericExpressionEvaluator_ArithmeticIT
GenericExpressionEvaluator_StringIT
This has the benefit of putting them next to each other in file tree UIs.
I'm fine keeping what you have or using this pattern.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will rename to GenericExpressionEvaluator_ArithmeticIT and GenericExpressionEvaluator_StringIT
|
||
public class ExpressionEvaluatorTest { | ||
private ExpressionEvaluator expressionEvaluator; | ||
class TestExpressionEvaluator implements ExpressionEvaluator { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could use a mock and tell it to use real methods for the default method. But, it's ok this way too. I'm just pointing out that possibility.
@Test | ||
public void testDefaultEvaluateConditionalThrows() { | ||
expressionEvaluator = new TestExpressionEvaluator(); | ||
assertThrows(ClassCastException.class, () -> expressionEvaluator.evaluateConditional("/status", event("{\"status\":200}"))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This raises an interesting point which I didn't consider.
By combining the expression evaluator, there really is no way to evaluate one expression with some conditional goal in mind versus evaluating with an object in mind.
I don't think that should hinder this PR because I think it could be resolved with some internal refactoring.
try { | ||
final ParseTreeEvaluatorListener listener = new ParseTreeEvaluatorListener(operatorProvider, coercionService, event); | ||
walker.walk(listener, parseTree); | ||
return coercionService.coerce(listener.getResult(), Boolean.class); | ||
return listener.getResult(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are there any possible limitations here? Could this break some existing scenarios?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't think of cases where it can break the existing scenarios. All the existing scenarios are changed to use Boolean and they all check for return Object type to be Boolean and if not throw an error.
import static org.hamcrest.CoreMatchers.not; | ||
import org.apache.commons.lang3.RandomStringUtils; | ||
|
||
class ArithmeticExpressionEvaluatorIT { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another approach that is fairly common in the Java world is to split tests using an underscore. This is testing the GenericExpressionEvaluator
for arithmetic and you have another testing GenericExpressionEvaluator
for strings.
Thus:
GenericExpressionEvaluator_ArithmeticIT
GenericExpressionEvaluator_StringIT
This has the benefit of putting them next to each other in file tree UIs.
I'm fine keeping what you have or using this pattern.
Signed-off-by: Krishna Kondaka <krishkdk@amazon.com>
if (result instanceof Boolean) { | ||
return (Boolean) result; | ||
} else { | ||
throw new ClassCastException("Unexpected expression return type of " + result.getClass()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I took a look at the ParseTreeCoercionService
class. It appears that it will throw the following when it fails to get the correct value.
throw new ExpressionCoercionException("Unable to cast " + obj.getClass().getName() + " into " + clazz.getName());
Perhaps, we should move this evaluateConditional
implementation into the GenericExpressionEvaluator
and use that ParseTreeCoercionService
class instead.
So this would be an unimplemented method here in the interface.
evaluateConditional(String statement, Event context);
Then the GenericExpressionEvaluator
looks somewhat like:
@Override
public Boolean evaluateConditional(final String statement, final Event context) {
final Object result = evaluate(statement, context);
return coersionService.coerce(result, Boolean.class);
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dlvenable Yes, I could change ClassCastException
to ExpressionCoercionException
if that helps. Ultimately there is not much difference between the two when the type is Boolean
because Boolean cannot be Coerced to a different type anyways. I don't see the need to move the evaluateConditional
to GenericExpressionEvaluator
just for that purpose because Boolean is not really coercion friendly anyways. Let me know if you strongly prefer calling coersionService here.
Description
Add support for basic arithmetic and string returning expressions to DataPrepper Expression.
Adds support for expressions returning arithmetic (integer/float) values from function/JsonPointers/literals
Also, adds support for expressions returning string values from function/JsonPointers/literals
Partially addresses the issues #2686 and #2685
Resolves #2703
Issues Resolved
#2703
Check List
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
For more information on following Developer Certificate of Origin and signing off your commits, please check here.