Skip to content
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: ParseTreeCoercionService and tests #1153

Conversation

chenqi0805
Copy link
Collaborator

@chenqi0805 chenqi0805 commented Mar 8, 2022

Signed-off-by: Chen [email protected]

Description

As a breakdown of #1145 , this PR adds ParseTreeCoercionService that is responsible for type checking and casting on both terminal node in the ParseTree and evaluation result.

Issues Resolved

Contributes to #1003

Check List

  • New functionality includes testing.
  • New functionality has been documented.
    • New functionality has javadoc added
  • Commits are signed with a real name per the DCO

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.

@chenqi0805 chenqi0805 requested review from dlvenable and sbayer55 March 8, 2022 16:42
@chenqi0805 chenqi0805 requested a review from a team as a code owner March 8, 2022 16:42
Copy link
Member

@sbayer55 sbayer55 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @chenqi0805 for breaking up the larger PR. Overall everything is looking great. I added a few comments, the only requested change is enhancing the testCoerceTerminalNodeEscapedJsonPointerType() test case.


@Named
class ParseTreeCoercionService {
public Object coerceTerminalNode(final TerminalNode node, final Event event) throws ParseTreeCoercionException {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this method will only handle terminal nodes within a primary/literal context. If primary terminal nodes are the only use case, consider a more descriptive name. One suggestion I have is coercePrimaryNode.

Copy link
Contributor

@cmanning09 cmanning09 Mar 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sbayer55, would that new name suggest this method supports setInitializer and variableIdentifier as defined in the grammar language for primary? Are you also suggesting we are missing some coercion functionality or do we just need a better name?

primary
    : jsonPointer
    | variableIdentifier
    | setInitializer
    | literal
    ;

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm suggesting if a better name is possible. Considering operator tokens (==, in ...) are TerminalNodes but this method could not coerce them.

}

@Test
void testCoerceTerminalNodeEscapedJsonPointerType() throws ParseTreeCoercionException {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for explicitly testing JsonPointer and EscapedJsonPointer! Within testCoerceTerminalNodeEscapedJsonPointerType() consider using special characters in your json pointer path. Some of the trickier characters are ~, \, /, and .

* @since 1.3
* Exception thrown by {@link ParseTreeCoercionService} methods.
*/
public class ParseTreeCoercionException extends Exception {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From a caller's perspective, the parse tree doesn't mean much. I think we could use a better name here. Perhaps ExpressionCoercionException.


/**
* @since 1.3
* Exception thrown by {@link ParseTreeCoercionService} methods.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In line with my other comment on this exception, please update the Javadocs to explain what this means to the caller/client.

assertThrows(ParseTreeCoercionException.class, () -> objectUnderTest.coerce(testObj, String.class));
}

private Event createTestEvent(final Object data) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could decouple your tests from JacksonEvent and probably simplify these tests by using a mock. But, I can accept the current code.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Turns out I do have to mock Event in order to test tricky characters in escape jsonpointer which is not currently supported by JacksonEvent.

@Test
void testCoerceTerminalNodeIntegerType() throws ParseTreeCoercionException {
when(token.getType()).thenReturn(DataPrepperExpressionParser.Integer);
final Integer testInteger = 1;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I strongly recommend the use of random numbers here (within a decent range). It is quite easy to write against a test with a value like 1.

Alternatively, you can make this a @ParameterizedTest and verify against a few values.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good suggestiong @dlvenable!

As a best practice, to guarantee consistent test results, I recommend avoiding random values in test cases. Either method is acceptable in this use case and both are used throught Data Preppers test code.

@Test
void testCoerceTerminalNodeFloatType() throws ParseTreeCoercionException {
when(token.getType()).thenReturn(DataPrepperExpressionParser.Float);
final Float testFloat = 1.0f;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, please do not test against 1.0.

@Test
void testCoerceFailure() {
final Object testObj = false;
assertThrows(ParseTreeCoercionException.class, () -> objectUnderTest.coerce(testObj, String.class));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test might be more readable if you provided a mock(Object.class) instead of false. Presently, it seems that false means something specific, but you are just creating a different type.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately one can not mock getClass method since it is final. I will use TestObject here to indicate the difference.

@codecov-commenter
Copy link

Codecov Report

Merging #1153 (18cef17) into main (818d9cb) will not change coverage.
The diff coverage is n/a.

Impacted file tree graph

@@            Coverage Diff            @@
##               main    #1153   +/-   ##
=========================================
  Coverage     91.89%   91.89%           
  Complexity      703      703           
=========================================
  Files            87       87           
  Lines          2037     2037           
  Branches        171      171           
=========================================
  Hits           1872     1872           
  Misses          122      122           
  Partials         43       43           

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 818d9cb...18cef17. Read the comment docs.

sbayer55
sbayer55 previously approved these changes Mar 9, 2022
}

@ParameterizedTest
@MethodSource("provideKeys")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding this!

final String nodeStringValue = node.getText();
switch (nodeType) {
case DataPrepperExpressionParser.EscapedJsonPointer:
return event.get(nodeStringValue.substring(1, nodeStringValue.length() - 1), Object.class);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: reading the code here I understand what is happening but not why. It might be a good idea to include a variable or create a private function.

final String jsonPointerWithoutQuotes = nodeStringValue.substring(1, nodeStringValue.length() - 1);
// or
private String removeQuotesFrom(final String text) {
   return nodeStringValue.substring(1, nodeStringValue.length() - 1)
}

@chenqi0805 chenqi0805 merged commit 7cf318a into opensearch-project:main Mar 9, 2022
@chenqi0805 chenqi0805 deleted the feat/1003-parse-tree-coercion-service branch March 9, 2022 19:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants