Skip to content

Commit

Permalink
Add startsWith expression function
Browse files Browse the repository at this point in the history
Signed-off-by: Taylor Gray <[email protected]>
  • Loading branch information
graytaylor0 committed Aug 16, 2024
1 parent 91b6666 commit a507bdd
Show file tree
Hide file tree
Showing 4 changed files with 171 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package org.opensearch.dataprepper.expression;

import org.opensearch.dataprepper.model.event.Event;

import javax.inject.Named;
import java.util.List;
import java.util.function.Function;

@Named
public class StartsWithExpressionFunction implements ExpressionFunction {
private static final int NUMBER_OF_ARGS = 2;

static final String STARTS_WITH_FUNCTION_NAME = "startsWith";
@Override
public String getFunctionName() {
return STARTS_WITH_FUNCTION_NAME;
}

@Override
public Object evaluate(
final List<Object> args,
final Event event,
final Function<Object, Object> convertLiteralType) {

if (args.size() != NUMBER_OF_ARGS) {
throw new RuntimeException("startsWith() takes exactly two arguments");
}

String[] strArgs = new String[NUMBER_OF_ARGS];
for (int i = 0; i < NUMBER_OF_ARGS; i++) {
Object arg = args.get(i);
if (!(arg instanceof String)) {
throw new RuntimeException(String.format("startsWith() takes only string type arguments. \"%s\" is not of type string", arg));
}
String stringOrKey = (String) arg;
if (stringOrKey.charAt(0) == '"') {
strArgs[i] = stringOrKey.substring(1, stringOrKey.length()-1);
} else if (stringOrKey.charAt(0) == '/') {
Object obj = event.get(stringOrKey, Object.class);
if (obj == null) {
return false;
}
if (!(obj instanceof String)) {
throw new RuntimeException(String.format("startsWith() only operates on string types. The value at \"%s\" is \"%s\" which is not a string type.", stringOrKey, obj));
}
strArgs[i] = (String)obj;
} else {
throw new RuntimeException(String.format("Arguments to startsWith() must be a literal string or a Json Pointer. \"%s\" is not string literal or json pointer", stringOrKey));
}
}
return strArgs[0].startsWith(strArgs[1]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,7 @@ public ContainsExpressionFunction createObjectUnderTest() {
}

@Test
void testContainsBasic() {
containsExpressionFunction = createObjectUnderTest();
void testContainsBasic() {containsExpressionFunction = createObjectUnderTest();
assertThat(containsExpressionFunction.evaluate(List.of("\"abcde\"", "\"abcd\""), testEvent, testFunction), equalTo(true));
assertThat(containsExpressionFunction.evaluate(List.of("/"+testKey, "/"+testKey2), testEvent, testFunction), equalTo(true));
assertThat(containsExpressionFunction.evaluate(List.of("\""+testValue+"\"", "/"+testKey2), testEvent, testFunction), equalTo(true));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
Expand Down Expand Up @@ -234,8 +235,10 @@ private static Stream<Arguments> validExpressionArguments() {
arguments("/name =~ \".*dataprepper-[0-9]+\"", event("{\"name\": \"dataprepper-0\"}"), true),
arguments("/name =~ \".*dataprepper-[0-9]+\"", event("{\"name\": \"dataprepper-212\"}"), true),
arguments("/name =~ \".*dataprepper-[0-9]+\"", event("{\"name\": \"dataprepper-abc\"}"), false),
arguments("/name =~ \".*dataprepper-[0-9]+\"", event("{\"other\": \"dataprepper-abc\"}"), false)
);
arguments("/name =~ \".*dataprepper-[0-9]+\"", event("{\"other\": \"dataprepper-abc\"}"), false),
arguments("startsWith(\""+strValue+ UUID.randomUUID() + "\",/status)", event("{\"status\":\""+strValue+"\"}"), true),
arguments("startsWith(\""+ UUID.randomUUID() +strValue+ "\",/status)", event("{\"status\":\""+strValue+"\"}"), false)
);
}

private static Stream<Arguments> invalidExpressionArguments() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package org.opensearch.dataprepper.expression;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.opensearch.dataprepper.model.event.Event;
import org.opensearch.dataprepper.model.event.JacksonEvent;

import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.function.Function;
import java.util.stream.Stream;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;
import static org.opensearch.dataprepper.expression.StartsWithExpressionFunction.STARTS_WITH_FUNCTION_NAME;

public class StartsWithExpressionFunctionTest {

private Event testEvent;

private Event createTestEvent(final Object data) {
return JacksonEvent.builder().withEventType("event").withData(data).build();
}

private ExpressionFunction createObjectUnderTest() {
return new StartsWithExpressionFunction();
}

@ParameterizedTest
@MethodSource("validStartsWithProvider")
void startsWith_returns_expected_result_when_evaluated(
final String value, final String prefix, final boolean expectedResult) {
final String key = "test_key";
testEvent = createTestEvent(Map.of(key, value));

final ExpressionFunction objectUnderTest = createObjectUnderTest();
assertThat(objectUnderTest.getFunctionName(), equalTo(STARTS_WITH_FUNCTION_NAME));

final Object result = objectUnderTest.evaluate(List.of("/" + key, "\"" + prefix + "\""), testEvent, mock(Function.class));

assertThat(result, equalTo(expectedResult));
}

@Test
void startsWith_with_a_key_as_the_prefix_returns_expected_result() {

final String prefixKey = "prefix";
final String prefixValue = "te";

final String key = "test_key";
final String value = "test";
testEvent = createTestEvent(Map.of(key, value, prefixKey, prefixValue));

final ExpressionFunction objectUnderTest = createObjectUnderTest();
assertThat(objectUnderTest.getFunctionName(), equalTo(STARTS_WITH_FUNCTION_NAME));

final Object result = objectUnderTest.evaluate(List.of("/" + key, "/" + prefixKey), testEvent, mock(Function.class));

assertThat(result, equalTo(true));
}

@Test
void startsWith_returns_false_when_key_does_not_exist_in_Event() {
final String key = "test_key";
testEvent = createTestEvent(Map.of(UUID.randomUUID().toString(), UUID.randomUUID().toString()));

final ExpressionFunction startsWithExpressionFunction = createObjectUnderTest();
final Object result = startsWithExpressionFunction.evaluate(List.of("/" + key, "\"abcd\""), testEvent, mock(Function.class));

assertThat(result, equalTo(false));
}

@Test
void startsWith_without_2_arguments_throws_RuntimeException() {
final ExpressionFunction startsWithExpressionFunction = createObjectUnderTest();
assertThrows(RuntimeException.class, () -> startsWithExpressionFunction.evaluate(List.of("abcd"), testEvent, mock(Function.class)));
}

@ParameterizedTest
@MethodSource("invalidStartsWithProvider")
void invalid_startsWith_arguments_throws_RuntimeException(final String firstArg, final Object secondArg, final Object value) {
final ExpressionFunction startsWithExpressionFunction = createObjectUnderTest();
final String testKey = "test_key";

assertThrows(RuntimeException.class, () -> startsWithExpressionFunction.evaluate(List.of(firstArg, secondArg), createTestEvent(Map.of(testKey, value)), mock(Function.class)));
}

private static Stream<Arguments> validStartsWithProvider() {
return Stream.of(
Arguments.of("{test", "{te", true),
Arguments.of("{test", "{", true),
Arguments.of("test", "{", false),
Arguments.of("MyPrefix", "My", true),
Arguments.of("MyPrefix", "Prefix", false)
);
}

private static Stream<Arguments> invalidStartsWithProvider() {
return Stream.of(
Arguments.of("\"abc\"", "/test_key", 1234),
Arguments.of("abcd", "/test_key", "value"),
Arguments.of("\"abcd\"", "/test_key", 1234),
Arguments.of("\"/test_key\"", 1234, "value")
);
}
}

0 comments on commit a507bdd

Please sign in to comment.