Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/

package org.opensearch.dataprepper.expression;

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

import java.util.List;
import java.util.function.Function;

abstract class AbstractSubstringExpressionFunction implements ExpressionFunction {
private static final int NUMBER_OF_ARGS = 2;

@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(getFunctionName() + "() takes exactly two arguments");
}

final String[] strArgs = new String[NUMBER_OF_ARGS];
for (int i = 0; i < NUMBER_OF_ARGS; i++) {
final Object arg = args.get(i);
if (arg instanceof EventKey) {
final Object obj = event.get((EventKey) arg, Object.class);
if (obj == null) {
strArgs[i] = null;
} else if (!(obj instanceof String)) {
throw new RuntimeException(String.format("%s() takes only string type arguments. \"%s\" is not of type string", getFunctionName(), obj));
} else {
strArgs[i] = (String) obj;
}
} else if (arg instanceof String) {
strArgs[i] = (String) arg;
} else {
throw new RuntimeException("Unexpected argument type: " + arg.getClass());
}
}

final String source = strArgs[0];
final String delimiter = strArgs[1];

if (source == null) {
return null;
}
if (delimiter == null || delimiter.isEmpty()) {
return source;
}
return extractSubstring(source, delimiter);
}

protected abstract String extractSubstring(final String source, final String delimiter);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/

package org.opensearch.dataprepper.expression;

import javax.inject.Named;

@Named
public class SubstringAfterExpressionFunction extends AbstractSubstringExpressionFunction {
static final String FUNCTION_NAME = "substringAfter";

@Override
public String getFunctionName() {
return FUNCTION_NAME;
}

@Override
protected String extractSubstring(final String source, final String delimiter) {
final int index = source.indexOf(delimiter);
if (index == -1) {
return source;
}
return source.substring(index + delimiter.length());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/

package org.opensearch.dataprepper.expression;

import javax.inject.Named;

@Named
public class SubstringAfterLastExpressionFunction extends AbstractSubstringExpressionFunction {
static final String FUNCTION_NAME = "substringAfterLast";

@Override
public String getFunctionName() {
return FUNCTION_NAME;
}

@Override
protected String extractSubstring(final String source, final String delimiter) {
final int index = source.lastIndexOf(delimiter);
if (index == -1) {
return source;
}
return source.substring(index + delimiter.length());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/

package org.opensearch.dataprepper.expression;

import javax.inject.Named;

@Named
public class SubstringBeforeExpressionFunction extends AbstractSubstringExpressionFunction {
static final String FUNCTION_NAME = "substringBefore";

@Override
public String getFunctionName() {
return FUNCTION_NAME;
}

@Override
protected String extractSubstring(final String source, final String delimiter) {
final int index = source.indexOf(delimiter);
if (index == -1) {
return source;
}
return source.substring(0, index);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/

package org.opensearch.dataprepper.expression;

import javax.inject.Named;

@Named
public class SubstringBeforeLastExpressionFunction extends AbstractSubstringExpressionFunction {
static final String FUNCTION_NAME = "substringBeforeLast";

@Override
public String getFunctionName() {
return FUNCTION_NAME;
}

@Override
protected String extractSubstring(final String source, final String delimiter) {
final int index = source.lastIndexOf(delimiter);
if (index == -1) {
return source;
}
return source.substring(0, index);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,12 @@ private static Stream<Arguments> validExpressionArguments() {
arguments("startsWith(\""+ UUID.randomUUID() +strValue+ "\",/status)", event("{\"status\":\""+strValue+"\"}"), false),
arguments("getEventType() == \"event\"", longEvent, true),
arguments("getEventType() == \"LOG\"", longEvent, false),
arguments("formatDateTime(/time, \"'year='yyyy'/month='MM'/day='dd\", \"UTC-8\") == \"year=2025/month=04/day=01\"", event("{\"time\": " + LocalDateTime.of(2025, 4, 1, 23, 59).toInstant(ZoneOffset.UTC).toEpochMilli() + "}"), true)
arguments("formatDateTime(/time, \"'year='yyyy'/month='MM'/day='dd\", \"UTC-8\") == \"year=2025/month=04/day=01\"", event("{\"time\": " + LocalDateTime.of(2025, 4, 1, 23, 59).toInstant(ZoneOffset.UTC).toEpochMilli() + "}"), true),
arguments("substringAfter(\"file.txt\", \".\") == \"txt\"", event("{}"), true),
arguments("substringAfter(/path, \"/\") == \"app/src/main.py\"", event("{\"path\": \"/app/src/main.py\"}"), true),
arguments("substringBefore(\"key=a=b\", \"=\") == \"key\"", event("{}"), true),
arguments("substringAfterLast(\"/app/src/main.py\", \"/\") == \"main.py\"", event("{}"), true),
arguments("substringBeforeLast(\"app.src.main\", \".\") == \"app.src\"", event("{}"), true)
);
}

Expand Down Expand Up @@ -297,7 +302,11 @@ private static Stream<Arguments> invalidExpressionArguments() {
arguments("contains(1234, /strField)", event("{\"intField\":1234,\"strField\":\"string\"}")),
arguments("contains(/strField, 1234)", event("{\"intField\":1234,\"strField\":\"string\"}")),
arguments("getMetadata(10)", tagEvent),
arguments("cidrContains(/sourceIp,123)", event("{\"sourceIp\": \"192.0.2.3\"}"))
arguments("cidrContains(/sourceIp,123)", event("{\"sourceIp\": \"192.0.2.3\"}")),
arguments("substringAfter()", event("{}")),
arguments("substringBefore()", event("{}")),
arguments("substringAfterLast()", event("{}")),
arguments("substringBeforeLast()", event("{}"))
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,19 @@ private static Stream<Arguments> validStringExpressionArguments() {
Arguments.of("getMetadata(\"strAttr\")+\""+testString2+"\"+/key", testEvent, testString+testString2+"value", String.class),
Arguments.of("join(/list)", testEvent, "string,1,true", String.class),
Arguments.of("join(\"\\\\, \", /list)", testEvent, "string, 1, true", String.class),
Arguments.of("join(\" \", /list)", testEvent, "string 1 true", String.class)
Arguments.of("join(\" \", /list)", testEvent, "string 1 true", String.class),
Arguments.of("substringAfter(\"hello-world\", \"-\")", event("{}"), "world", String.class),
Arguments.of("substringAfter(/field, \"-\")", event("{\"field\": \"hello-world\"}"), "world", String.class),
Arguments.of("substringAfter(\"no-match\", \"xyz\")", event("{}"), "no-match", String.class),
Arguments.of("substringBefore(\"hello-world\", \"-\")", event("{}"), "hello", String.class),
Arguments.of("substringBefore(/field, \"-\")", event("{\"field\": \"hello-world\"}"), "hello", String.class),
Arguments.of("substringBefore(\"no-match\", \"xyz\")", event("{}"), "no-match", String.class),
Arguments.of("substringAfterLast(\"/app/src/main.py\", \"/\")", event("{}"), "main.py", String.class),
Arguments.of("substringAfterLast(/field, \"/\")", event("{\"field\": \"/app/src/main.py\"}"), "main.py", String.class),
Arguments.of("substringAfterLast(\"no-match\", \"xyz\")", event("{}"), "no-match", String.class),
Arguments.of("substringBeforeLast(\"/app/src/main.py\", \"/\")", event("{}"), "/app/src", String.class),
Arguments.of("substringBeforeLast(/field, \"/\")", event("{\"field\": \"/app/src/main.py\"}"), "/app/src", String.class),
Arguments.of("substringBeforeLast(\"no-match\", \"xyz\")", event("{}"), "no-match", String.class)
);
}

Expand All @@ -155,7 +167,11 @@ private static Stream<Arguments> exceptionExpressionArguments() {
Arguments.of("join(/list, \" \", \"third_arg\")", event("{\"list\":[\"string\", 1, true]}")),
Arguments.of("join()", event("{\"list\":[\"string\", 1, true]}")),
Arguments.of("contains()", event("{\"list\":[\"string\", 1, true]}")),
Arguments.of("startsWith()", event("{\"list\":[\"string\", 1, true]}"))
Arguments.of("startsWith()", event("{\"list\":[\"string\", 1, true]}")),
Arguments.of("substringAfter()", event("{\"list\":[\"string\", 1, true]}")),
Arguments.of("substringBefore()", event("{\"list\":[\"string\", 1, true]}")),
Arguments.of("substringAfterLast()", event("{\"list\":[\"string\", 1, true]}")),
Arguments.of("substringBeforeLast()", event("{\"list\":[\"string\", 1, true]}"))
);
}

Expand Down
Loading
Loading