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

ENH: improve regex validaiton message #5447

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
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
Expand Up @@ -15,4 +15,8 @@ public class InvalidPluginConfigurationException extends RuntimeException {
public InvalidPluginConfigurationException(final String message) {
super(message);
}

public InvalidPluginConfigurationException(final String message, final Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,20 @@

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.util.UUID;

import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;

@ExtendWith(MockitoExtension.class)
class InvalidPluginConfigurationExceptionTest {
@Mock
private Throwable cause;

private String message;

@BeforeEach
Expand All @@ -30,4 +37,11 @@ void getMessage_returns_message() {
assertThat(createObjectUnderTest().getMessage(),
equalTo(message));
}

@Test
void getCause_returns_cause() {
final InvalidPluginConfigurationException objectUnderTest = new InvalidPluginConfigurationException(
message, cause);
assertThat(objectUnderTest.getCause(), equalTo(cause));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

package org.opensearch.dataprepper.plugins.regex;

import java.util.Objects;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

public class RegexValueValidator {
public static boolean validateRegex(final String pattern) {
if (pattern != null && !Objects.equals(pattern, "")) {
try {
Pattern.compile(pattern);
} catch (PatternSyntaxException e) {
return false;
}
}

return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

package org.opensearch.dataprepper.plugins.regex;

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import java.util.stream.Stream;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;

class RegexValueValidatorTest {
@ParameterizedTest
@MethodSource("provideRegexAndExpectedResult")
void testValidateRegex(final String delimiterRegex, final boolean isValid) {
assertThat(RegexValueValidator.validateRegex(delimiterRegex), is(isValid));
}

private static Stream<Arguments> provideRegexAndExpectedResult() {
return Stream.of(
Arguments.of(null, true),
Arguments.of("", true),
Arguments.of("abc", true),
Arguments.of("(abc", false)
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,14 @@ private void compileMatchPatterns() {
for (final Map.Entry<String, List<String>> entry : grokProcessorConfig.getMatch().entrySet()) {
fieldToGrok.put(entry.getKey(), entry.getValue()
.stream()
.map(item -> grokCompiler.compile(item, grokProcessorConfig.isNamedCapturesOnly()))
.map(item -> {
try {
return grokCompiler.compile(item, grokProcessorConfig.isNamedCapturesOnly());
} catch (IllegalArgumentException e) {
throw new InvalidPluginConfigurationException(
String.format("Invalid regex pattern in match.%s", entry.getKey()), e);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we want to stop the stream processing if we encounter IllegalArugmentException? or we want to collect the errors but continue with stream processing?

Also, in the exception we are throwing, we are attaching the original e. Depending on whoever is handling this exception, it could potentially print the entire stacktrace for each failure and create a lot of noise in the logs.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This will be a config validation error which means data prepper will crash at runtime. The change I made is to increase clarity on the failure message:

previous:

s3-log-pipeline.processor.grok: caused by: Exception thrown from plugin "grok". caused by: No definition for key 'ses_logs' found, aborting

now

2025-02-20T13:14:40,784 [main] ERROR org.opensearch.dataprepper.core.validation.LoggingPluginErrorsHandler - 1. waf-access-log-pipeline.processor.grok: caused by: Exception thrown from plugin "grok". caused by: Invalid regex pattern in match.message caused by: No definition for key 'CUSTOM_PATTERN_FROM_FILE' found, aborting

Copy link
Collaborator

Choose a reason for hiding this comment

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

Got it. It is not a data processing error 👍

Copy link
Collaborator

Choose a reason for hiding this comment

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

Just one thought, instead of RuntimeException, we could possibly throw InvalidPluginConfigurationException to be more explicit

}
})
.collect(Collectors.toList()));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import org.opensearch.dataprepper.metrics.PluginMetrics;
import org.opensearch.dataprepper.model.configuration.PluginSetting;
import org.opensearch.dataprepper.model.event.Event;
import org.opensearch.dataprepper.model.plugin.InvalidPluginConfigurationException;
import org.opensearch.dataprepper.model.record.Record;

import java.util.ArrayList;
Expand All @@ -30,6 +31,7 @@
import java.util.stream.Stream;

import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertTrue;
Expand Down Expand Up @@ -453,9 +455,11 @@ public void testPatternsDirWithCustomPatternsFilesGlob() throws JsonProcessingEx
pluginSetting.getSettings().put(GrokProcessorConfig.MATCH, matchConfigWithPatterns2Pattern);
grokProcessorConfig = OBJECT_MAPPER.convertValue(pluginSetting.getSettings(), GrokProcessorConfig.class);

Throwable throwable = assertThrows(IllegalArgumentException.class, () -> new GrokProcessor(
Throwable throwable = assertThrows(InvalidPluginConfigurationException.class, () -> new GrokProcessor(
pluginMetrics, grokProcessorConfig, expressionEvaluator));
assertThat("No definition for key 'CUSTOMBIRTHDAYPATTERN' found, aborting", equalTo(throwable.getMessage()));
assertThat(throwable.getCause(), instanceOf(IllegalArgumentException.class));
assertThat("No definition for key 'CUSTOMBIRTHDAYPATTERN' found, aborting", equalTo(throwable
Copy link
Member

Choose a reason for hiding this comment

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

Could we remove the "aborting" part of this message? Small but seems a little weird for users to get

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

.getCause().getMessage()));
}

@Test
Expand Down Expand Up @@ -522,7 +526,7 @@ public void testCompileNonRegisteredPatternThrowsIllegalArgumentException() {
pluginSetting.getSettings().put(GrokProcessorConfig.MATCH, matchConfig);
grokProcessorConfig = OBJECT_MAPPER.convertValue(pluginSetting.getSettings(), GrokProcessorConfig.class);

assertThrows(IllegalArgumentException.class, () -> new GrokProcessor(
assertThrows(InvalidPluginConfigurationException.class, () -> new GrokProcessor(
pluginMetrics, grokProcessorConfig, expressionEvaluator));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import org.opensearch.dataprepper.model.annotations.AlsoRequired;
import org.opensearch.dataprepper.model.annotations.ExampleValues;
import org.opensearch.dataprepper.model.annotations.ExampleValues.Example;
import org.opensearch.dataprepper.plugins.regex.RegexValueValidator;

import java.util.ArrayList;
import java.util.List;
Expand Down Expand Up @@ -278,6 +279,26 @@ boolean isValidStringLiteralConfig() {
return valueGrouping;
}

@AssertTrue(message = "The value of key_value_delimiter_regex is not a valid regex string")
boolean isKeyValueDelimiterRegexValid() {
return RegexValueValidator.validateRegex(keyValueDelimiterRegex);
}

@AssertTrue(message = "The value of delete_key_regex is not a valid regex string")
boolean isDeleteKeyRegexValid() {
return RegexValueValidator.validateRegex(deleteKeyRegex);
}

@AssertTrue(message = "The value of delete_value_regex is not a valid regex string")
boolean isDeleteValueRegexValid() {
return RegexValueValidator.validateRegex(deleteValueRegex);
}

@AssertTrue(message = "The value of field_delimiter_regex is not a valid regex string")
boolean isFieldDelimiterRegexValid() {
return RegexValueValidator.validateRegex(fieldDelimiterRegex);
}

public String getSource() {
return source;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

package org.opensearch.dataprepper.plugins.processor.keyvalue;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.opensearch.dataprepper.plugins.processor.keyvalue.KeyValueProcessorConfig.FIELD_DELIMITER_REGEX_KEY;
import static org.opensearch.dataprepper.plugins.processor.keyvalue.KeyValueProcessorConfig.KEY_VALUE_DELIMITER_REGEX_KEY;

class KeyValueProcessorConfigTest {
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

private KeyValueProcessorConfig createObjectUnderTest(final String keyValueDelimiterRegex,
final String deleteKeyRegex,
final String deleteValueRegex,
final String fieldDelimiterRegex) {
final Map<String, Object> map = new HashMap<>();
map.put(KEY_VALUE_DELIMITER_REGEX_KEY, keyValueDelimiterRegex);
map.put("delete_key_regex", deleteKeyRegex);
map.put("delete_value_regex", deleteValueRegex);
map.put(FIELD_DELIMITER_REGEX_KEY, fieldDelimiterRegex);
return OBJECT_MAPPER.convertValue(map, KeyValueProcessorConfig.class);
}

@ParameterizedTest
@MethodSource("provideRegexAndIsValid")
void testIsKeyValueDelimiterRegexValid(final String keyValueDelimiterRegex, final boolean isValid) {
final KeyValueProcessorConfig objectUnderTest = createObjectUnderTest(
keyValueDelimiterRegex, null, null, null);
assertThat(objectUnderTest.isKeyValueDelimiterRegexValid(), is(isValid));
}

@ParameterizedTest
@MethodSource("provideRegexAndIsValid")
void testIsDeleteKeyRegexValid(final String deleteKeyRegex, final boolean isValid) {
final KeyValueProcessorConfig objectUnderTest = createObjectUnderTest(
null, deleteKeyRegex, null, null);
assertThat(objectUnderTest.isDeleteKeyRegexValid(), is(isValid));
}

@ParameterizedTest
@MethodSource("provideRegexAndIsValid")
void testIsDeleteValueRegexValid(final String deleteValueRegex, final boolean isValid) {
final KeyValueProcessorConfig objectUnderTest = createObjectUnderTest(
null, null, deleteValueRegex, null);
assertThat(objectUnderTest.isDeleteValueRegexValid(), is(isValid));
}

@ParameterizedTest
@MethodSource("provideRegexAndIsValid")
void testIsFieldDelimiterRegexValid(final String fieldDelimiterRegex, final boolean isValid) {
final KeyValueProcessorConfig objectUnderTest = createObjectUnderTest(
null, null, null, fieldDelimiterRegex);
assertThat(objectUnderTest.isFieldDelimiterRegexValid(), is(isValid));
}

private static Stream<Arguments> provideRegexAndIsValid() {
return Stream.of(
Arguments.of(null, true),
Arguments.of("", true),
Arguments.of("abc", true),
Arguments.of("(abc", false)
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import jakarta.validation.Valid;
import jakarta.validation.constraints.AssertTrue;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import org.opensearch.dataprepper.model.annotations.AlsoRequired;
Expand All @@ -19,6 +20,7 @@
import org.opensearch.dataprepper.model.event.EventKey;
import org.opensearch.dataprepper.model.event.EventKeyConfiguration;
import org.opensearch.dataprepper.model.event.EventKeyFactory;
import org.opensearch.dataprepper.plugins.regex.RegexValueValidator;

import java.util.List;
import java.util.regex.Pattern;
Expand Down Expand Up @@ -101,6 +103,11 @@ public Pattern getFromKeyCompiledPattern() {
return fromKeyCompiledPattern;
}

@AssertTrue(message = "The value of from_key_regex is not a valid regex string")
boolean isFromKeyRegexValid() {
return RegexValueValidator.validateRegex(fromKeyRegex);
}

public Entry(final EventKey fromKey, final String fromKeyPattern, final EventKey toKey, final boolean overwriteIfKeyExists, final String renameWhen) {
this.fromKey = fromKey;
this.fromKeyRegex = fromKeyPattern;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

package org.opensearch.dataprepper.plugins.processor.mutateevent;

import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.opensearch.dataprepper.model.event.EventKey;

import java.util.stream.Stream;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;

@ExtendWith(MockitoExtension.class)
class RenameKeyProcessorConfigTest {
@Mock
private EventKey eventKey;

@ParameterizedTest
@MethodSource("provideFromKeyRegexAndIsValid")
void testIsFromKeyRegexValid(final String fromKeyRegex, final boolean isValid) {
final RenameKeyProcessorConfig.Entry objectUnderTest = new RenameKeyProcessorConfig.Entry(
null, fromKeyRegex, eventKey, false, null);
assertThat(objectUnderTest.isFromKeyRegexValid(), is(isValid));
}

private static Stream<Arguments> provideFromKeyRegexAndIsValid() {
return Stream.of(
Arguments.of(null, true),
Arguments.of("", true),
Copy link
Collaborator

Choose a reason for hiding this comment

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

You can add one null case too here.

Arguments.of("abc", true),
Arguments.of("(abc", false)
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import jakarta.validation.Valid;
import jakarta.validation.constraints.AssertTrue;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
Expand All @@ -21,6 +22,7 @@
import org.opensearch.dataprepper.model.annotations.ExampleValues;
import org.opensearch.dataprepper.model.annotations.ExampleValues.Example;
import org.opensearch.dataprepper.model.event.EventKey;
import org.opensearch.dataprepper.plugins.regex.RegexValueValidator;

import java.util.List;

Expand Down Expand Up @@ -91,6 +93,11 @@ public String getDelimiter() {

public String getSplitWhen() { return splitWhen; }

@AssertTrue(message = "The value of delimiter_regex is not a valid regex string")
boolean isDelimiterRegexValid() {
return RegexValueValidator.validateRegex(delimiterRegex);
}

public Entry(final EventKey source, final String delimiterRegex, final String delimiter, final String splitWhen) {
this.source = source;
this.delimiterRegex = delimiterRegex;
Expand Down
Loading
Loading