Skip to content

Commit a08cd7b

Browse files
authored
Add getEventType() expression function (#5686)
* add geteventType expresion Signed-off-by: Shenoy Pratik <sgguruda@amazon.com> * update antlr grammar, parser and add expression tests Signed-off-by: Shenoy Pratik <sgguruda@amazon.com> * update expression syntax documentation Signed-off-by: Shenoy Pratik <sgguruda@amazon.com> * move expression parse exception check to evaluation exceptions Signed-off-by: Shenoy Pratik <sgguruda@amazon.com> * resolve comments, update getEventTypet test to become parameterized Signed-off-by: Shenoy Pratik <sgguruda@amazon.com> --------- Signed-off-by: Shenoy Pratik <sgguruda@amazon.com>
1 parent 49ef09d commit a08cd7b

7 files changed

Lines changed: 142 additions & 43 deletions

File tree

data-prepper-expression/src/main/antlr/DataPrepperExpression.g4

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ Function
160160

161161
fragment
162162
FunctionArgs
163-
: (FunctionArg SPACE* COMMA SPACE*)* SPACE* FunctionArg
163+
: ((FunctionArg SPACE* COMMA SPACE*)* SPACE* FunctionArg)?
164164
;
165165

166166
fragment
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package org.opensearch.dataprepper.expression;
7+
8+
import org.opensearch.dataprepper.model.event.Event;
9+
import javax.inject.Named;
10+
import java.util.function.Function;
11+
import java.util.List;
12+
13+
@Named
14+
public class GetEventTypeExpressionFunction implements ExpressionFunction {
15+
16+
@Override
17+
public String getFunctionName() {
18+
return "getEventType";
19+
}
20+
21+
@Override
22+
public Object evaluate(final List<Object> args, final Event event, final Function<Object, Object> convertLiteralType) {
23+
if (!args.isEmpty()) {
24+
throw new RuntimeException("getEventType() does not take any arguments");
25+
}
26+
return event.getMetadata().getEventType();
27+
}
28+
}

data-prepper-expression/src/main/java/org/opensearch/dataprepper/expression/ParseTreeCoercionService.java

Lines changed: 35 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,17 @@ class ParseTreeCoercionService {
2424
private Function<Object, Object> convertLiteralType;
2525

2626
@Inject
27-
public ParseTreeCoercionService(final Map<Class<? extends Serializable>, Function<Object, Object>> literalTypeConversions, ExpressionFunctionProvider expressionFunctionProvider) {
27+
public ParseTreeCoercionService(
28+
final Map<Class<? extends Serializable>, Function<Object, Object>> literalTypeConversions,
29+
final ExpressionFunctionProvider expressionFunctionProvider) {
2830
this.literalTypeConversions = literalTypeConversions;
2931
convertLiteralType = (value) -> {
30-
if (literalTypeConversions.containsKey(value.getClass())) {
31-
return literalTypeConversions.get(value.getClass()).apply(value);
32-
} else {
33-
throw new ExpressionCoercionException("Unsupported type for value " + value);
34-
}
35-
};
32+
if (literalTypeConversions.containsKey(value.getClass())) {
33+
return literalTypeConversions.get(value.getClass()).apply(value);
34+
} else {
35+
throw new ExpressionCoercionException("Unsupported type for value " + value);
36+
}
37+
};
3638
this.expressionFunctionProvider = expressionFunctionProvider;
3739
}
3840

@@ -44,31 +46,40 @@ public Object coercePrimaryTerminalNode(final TerminalNode node, final Event eve
4446
final int funcNameIndex = nodeStringValue.indexOf("(");
4547
final String functionName = nodeStringValue.substring(0, funcNameIndex);
4648
final int argsEndIndex = nodeStringValue.indexOf(")", funcNameIndex);
47-
final String argsStr = nodeStringValue.substring(funcNameIndex+1, argsEndIndex);
48-
// Split at commas if there's no backslash before the commas, because commas can be part of a function parameter
49-
final String[] args = argsStr.split("(?<!\\\\),");
5049
List<Object> argList = new ArrayList<>();
51-
for (final String arg: args) {
52-
String trimmedArg = arg.trim();
53-
if (trimmedArg.charAt(0) == '/') {
54-
argList.add(trimmedArg);
55-
} else if (trimmedArg.charAt(0) == '"') {
56-
if (trimmedArg.length() < 2 || trimmedArg.charAt(trimmedArg.length()-1) != '"') {
57-
throw new RuntimeException("Invalid string argument: check if any argument is missing a closing double quote or contains comma that's not escaped with `\\`.");
50+
51+
// Check if the function has at least one argument
52+
if(argsEndIndex > funcNameIndex + 1) {
53+
final String argsStr = nodeStringValue.substring(funcNameIndex + 1, argsEndIndex);
54+
// Split at commas if there's no backslash before the commas, because commas can
55+
// be part of a function parameter
56+
final String[] args = argsStr.split("(?<!\\\\),");
57+
58+
for (final String arg : args) {
59+
String trimmedArg = arg.trim();
60+
if (trimmedArg.charAt(0) == '/') {
61+
argList.add(trimmedArg);
62+
} else if (trimmedArg.charAt(0) == '"') {
63+
if (trimmedArg.length() < 2 || trimmedArg.charAt(trimmedArg.length() - 1) != '"') {
64+
throw new RuntimeException(
65+
"Invalid string argument: check if any argument is missing a closing double quote or contains comma that's not escaped with `\\`.");
66+
}
67+
argList.add(trimmedArg);
68+
} else {
69+
throw new RuntimeException("Unsupported type passed as function argument");
5870
}
59-
argList.add(trimmedArg);
60-
} else {
61-
throw new RuntimeException("Unsupported type passed as function argument");
6271
}
6372
}
73+
6474
return expressionFunctionProvider.provideFunction(functionName, argList, event, convertLiteralType);
6575
case DataPrepperExpressionParser.EscapedJsonPointer:
6676
final String jsonPointerWithoutQuotes = nodeStringValue.substring(1, nodeStringValue.length() - 1);
6777
return resolveJsonPointerValue(jsonPointerWithoutQuotes, event);
6878
case DataPrepperExpressionParser.JsonPointer:
6979
return resolveJsonPointerValue(nodeStringValue, event);
7080
case DataPrepperExpressionParser.String:
71-
final String nodeStringValueWithQuotesStripped = nodeStringValue.substring(1, nodeStringValue.length() - 1);
81+
final String nodeStringValueWithQuotesStripped = nodeStringValue.substring(1,
82+
nodeStringValue.length() - 1);
7283
return nodeStringValueWithQuotesStripped;
7384
case DataPrepperExpressionParser.Integer:
7485
Long longValue = Long.valueOf(nodeStringValue);
@@ -98,14 +109,15 @@ public <T> T coerce(final Object obj, Class<T> clazz) throws ExpressionCoercionE
98109
if (obj.getClass().isAssignableFrom(clazz)) {
99110
return (T) obj;
100111
}
101-
throw new ExpressionCoercionException("Unable to cast " + obj.getClass().getName() + " into " + clazz.getName());
112+
throw new ExpressionCoercionException(
113+
"Unable to cast " + obj.getClass().getName() + " into " + clazz.getName());
102114
}
103115

104116
private Object resolveJsonPointerValue(final String jsonPointer, final Event event) {
105117
final Object value = event.get(jsonPointer, Object.class);
106118
if (value == null) {
107119
return null;
108-
}
120+
}
109121
return convertLiteralType.apply(value);
110122
}
111123
}

data-prepper-expression/src/test/java/org/opensearch/dataprepper/expression/GenericExpressionEvaluator_ConditionalIT.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -248,8 +248,10 @@ private static Stream<Arguments> validExpressionArguments() {
248248
arguments("/name =~ \".*dataprepper-[0-9]+\"", event("{\"name\": \"dataprepper-abc\"}"), false),
249249
arguments("/name =~ \".*dataprepper-[0-9]+\"", event("{\"other\": \"dataprepper-abc\"}"), false),
250250
arguments("startsWith(\""+strValue+ UUID.randomUUID() + "\",/status)", event("{\"status\":\""+strValue+"\"}"), true),
251-
arguments("startsWith(\""+ UUID.randomUUID() +strValue+ "\",/status)", event("{\"status\":\""+strValue+"\"}"), false)
252-
);
251+
arguments("startsWith(\""+ UUID.randomUUID() +strValue+ "\",/status)", event("{\"status\":\""+strValue+"\"}"), false),
252+
arguments("getEventType() == \"event\"", longEvent, true),
253+
arguments("getEventType() == \"LOG\"", longEvent, false)
254+
);
253255
}
254256

255257
private static Stream<Arguments> invalidExpressionArguments() {
@@ -344,7 +346,10 @@ private static Stream<Arguments> invalidExpressionSyntaxArguments() {
344346
arguments("getMetadata(10)", tagEvent),
345347
arguments("getMetadata("+ testMetadataKey+ ")", tagEvent),
346348
arguments("getMetadata(\""+ testMetadataKey+")", tagEvent),
347-
arguments("cidrContains(/sourceIp,123)", event("{\"sourceIp\": \"192.0.2.3\"}"))
349+
arguments("cidrContains(/sourceIp,123)", event("{\"sourceIp\": \"192.0.2.3\"}")),
350+
arguments("getEventType() == \"test_event", tagEvent),
351+
arguments("getEventType() == test_event\"", tagEvent)
352+
348353
);
349354
}
350355

data-prepper-expression/src/test/java/org/opensearch/dataprepper/expression/GenericExpressionEvaluator_MultiTypeIT.java

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -111,13 +111,6 @@ void testMapExpressionEvaluatorWithMultipleThreads(final String expression, fina
111111
}
112112
}
113113

114-
@ParameterizedTest
115-
@MethodSource("exceptionExpressionSyntaxArguments")
116-
void testExpressionSyntaxEvaluatorCausesException(final String expression, final Event event) {
117-
final GenericExpressionEvaluator evaluator = applicationContext.getBean(GenericExpressionEvaluator.class);
118-
assertThrows(ExpressionParsingException.class, () -> evaluator.evaluate(expression, event));
119-
}
120-
121114
@ParameterizedTest
122115
@MethodSource("exceptionExpressionArguments")
123116
void testExpressionEvaluatorCausesException(final String expression, final Event event) {
@@ -165,20 +158,15 @@ private static Stream<Arguments> validMapExpressionArguments() {
165158
);
166159
}
167160

168-
private static Stream<Arguments> exceptionExpressionSyntaxArguments() {
169-
return Stream.of(
170-
Arguments.of("join()", event("{\"list\":[\"string\", 1, true]}")),
171-
Arguments.of("contains()", event("{\"list\":[\"string\", 1, true]}")),
172-
Arguments.of("startsWith()", event("{\"list\":[\"string\", 1, true]}"))
173-
);
174-
}
175-
176161
private static Stream<Arguments> exceptionExpressionArguments() {
177162
return Stream.of(
178163
// Can't mix Numbers and Strings when using operators
179164
Arguments.of("/status + /message", event("{\"status\": 200, \"message\":\"msg\"}")),
180165
// Wrong number of arguments
181-
Arguments.of("join(/list, \" \", \"third_arg\")", event("{\"list\":[\"string\", 1, true]}"))
166+
Arguments.of("join(/list, \" \", \"third_arg\")", event("{\"list\":[\"string\", 1, true]}")),
167+
Arguments.of("join()", event("{\"list\":[\"string\", 1, true]}")),
168+
Arguments.of("contains()", event("{\"list\":[\"string\", 1, true]}")),
169+
Arguments.of("startsWith()", event("{\"list\":[\"string\", 1, true]}"))
182170
);
183171
}
184172

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package org.opensearch.dataprepper.expression;
2+
3+
import org.opensearch.dataprepper.model.event.Event;
4+
import org.opensearch.dataprepper.model.event.JacksonEvent;
5+
import org.junit.jupiter.api.Test;
6+
import org.junit.jupiter.params.ParameterizedTest;
7+
import org.junit.jupiter.params.provider.ValueSource;
8+
9+
import static org.hamcrest.CoreMatchers.equalTo;
10+
import static org.hamcrest.MatcherAssert.assertThat;
11+
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
12+
import static org.junit.jupiter.api.Assertions.assertThrows;
13+
import static org.mockito.Mockito.mock;
14+
import static org.mockito.Mockito.when;
15+
16+
import java.util.List;
17+
import java.util.Map;
18+
import java.util.function.Function;
19+
20+
class GetEventTypeExpressionFunctionTest {
21+
22+
private GetEventTypeExpressionFunction createObjectUnderTest() {
23+
return new GetEventTypeExpressionFunction();
24+
}
25+
26+
private Event createTestEvent(final String eventType) {
27+
return JacksonEvent.builder()
28+
.withEventType(eventType)
29+
.withData(Map.of())
30+
.build();
31+
}
32+
33+
@ParameterizedTest
34+
@ValueSource(strings = {"LOG", "TRACE", "METRIC"})
35+
void testGetEventTypeReturnsCorrectType(String eventType) {
36+
GetEventTypeExpressionFunction function = createObjectUnderTest();
37+
Event testEvent = createTestEvent(eventType);
38+
39+
Object result = function.evaluate(List.of(), testEvent, Function.identity());
40+
41+
assertThat(result, equalTo(eventType));
42+
}
43+
44+
@Test
45+
void testGetEventTypeThrowsExceptionWhenArgumentsProvided() {
46+
GetEventTypeExpressionFunction function = createObjectUnderTest();
47+
Event testEvent = createTestEvent("LOG");
48+
49+
assertThrows(RuntimeException.class, () -> function.evaluate(List.of("arg1"), testEvent, Function.identity()));
50+
}
51+
52+
@Test
53+
void testGetEventTypeFunctionRegistered() {
54+
Event testEvent = createTestEvent("event");
55+
final GenericExpressionEvaluator evaluator = mock(GenericExpressionEvaluator.class);
56+
when(evaluator.evaluateConditional("getEventType() == \"event\"", testEvent)).thenReturn(true);
57+
assertDoesNotThrow(() -> evaluator.evaluateConditional("getEventType() == \"event\"", testEvent));
58+
}
59+
}

docs/expression_syntax.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,13 @@ Currently, the following functions are supported
184184
- takes one String literal as argument. This is the key to lookup in the event's metadata. If the key contains "/", then recursive lookup into the metadata attributes is done.
185185
- returns the value corresponding to the argument (key) passed. Value can be of any type.
186186
For example, if metadata contains {"key1": "value2", "key2": 10}, then `getMetadata("key1")` returns "value2", and `getMetadata("key2")` return 10.
187+
* `getEventType()`
188+
- Takes no arguments.
189+
- Returns the event type of the given event as a String.
190+
- Throws an error if any arguments are provided.
191+
- Event types is useful from routing requests based on type examples: `LOG`, `TRACE` and `METRIC`.
192+
For example, if the event has an event type `"LOG"`, `getEventType()` will return `"LOG"`.
193+
Example usage in an expression: `getEventType() == "LOG"`.
187194
* `contains()`
188195
- takes two String arguments. Both should be either string literals or Json Pointers with String values.
189196
- returns true if the second argument is a substring of the first argument. Otherwise, return false.

0 commit comments

Comments
 (0)