Skip to content

Commit ebe334d

Browse files
authored
Support ByteCount min/max constraints. Constrain on max_message_size in SQS sink. (#6158)
Jakarta constraints for ByteCount: ByteCountMin and ByteCountMax. Restrict the max_message_size in the SQS sink to 1 byte to 1 MB. Validate the threshold section of the configuration. Signed-off-by: David Venable <dlv@amazon.com>
1 parent 666c726 commit ebe334d

10 files changed

Lines changed: 469 additions & 0 deletions

File tree

data-prepper-api/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ dependencies {
1919
testImplementation project(':data-prepper-test:test-common')
2020
testImplementation 'org.skyscreamer:jsonassert:1.5.3'
2121
testImplementation libs.commons.io
22+
testImplementation 'org.hibernate.validator:hibernate-validator:8.0.1.Final'
2223
}
2324

2425
jacocoTestCoverageVerification {
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*
5+
* The OpenSearch Contributors require contributions made to
6+
* this file be licensed under the Apache-2.0 license or a
7+
* compatible open source license.
8+
*/
9+
10+
package org.opensearch.dataprepper.model.constraints;
11+
12+
import jakarta.validation.Constraint;
13+
import jakarta.validation.Payload;
14+
15+
import java.lang.annotation.ElementType;
16+
import java.lang.annotation.Retention;
17+
import java.lang.annotation.RetentionPolicy;
18+
import java.lang.annotation.Target;
19+
20+
/**
21+
* Use this annotation to ensure that the byte count does not exceed
22+
* a specified maximum value.
23+
*
24+
* @since 2.13
25+
*/
26+
@Constraint(validatedBy = {ByteCountMaxValidator.class})
27+
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE_USE})
28+
@Retention(RetentionPolicy.RUNTIME)
29+
public @interface ByteCountMax {
30+
/**
31+
* The maximum value defined as a {@link org.opensearch.dataprepper.model.types.ByteCount}
32+
* string.
33+
*
34+
* @return The byte count string
35+
* @since 2.13
36+
*/
37+
String value();
38+
39+
String message() default "The provided byte count exceeds the maximum allowed value.";
40+
41+
Class<?>[] groups() default {};
42+
43+
Class<? extends Payload>[] payload() default {};
44+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*
5+
* The OpenSearch Contributors require contributions made to
6+
* this file be licensed under the Apache-2.0 license or a
7+
* compatible open source license.
8+
*/
9+
10+
package org.opensearch.dataprepper.model.constraints;
11+
12+
import jakarta.validation.ConstraintValidator;
13+
import jakarta.validation.ConstraintValidatorContext;
14+
import org.opensearch.dataprepper.model.types.ByteCount;
15+
16+
public class ByteCountMaxValidator implements ConstraintValidator<ByteCountMax, ByteCount> {
17+
private ByteCount maxByteCount;
18+
19+
@Override
20+
public void initialize(final ByteCountMax constraint) {
21+
maxByteCount = ByteCount.parse(constraint.value());
22+
}
23+
24+
@Override
25+
public boolean isValid(final ByteCount byteCount, final ConstraintValidatorContext context) {
26+
if (byteCount == null) {
27+
return true;
28+
}
29+
30+
if (byteCount.compareTo(maxByteCount) > 0) {
31+
if (context != null) {
32+
context.disableDefaultConstraintViolation();
33+
context.buildConstraintViolationWithTemplate(
34+
String.format("The provided byte count %s exceeds maximum of %s", byteCount, maxByteCount))
35+
.addConstraintViolation();
36+
}
37+
38+
return false;
39+
}
40+
41+
return true;
42+
}
43+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*
5+
* The OpenSearch Contributors require contributions made to
6+
* this file be licensed under the Apache-2.0 license or a
7+
* compatible open source license.
8+
*/
9+
10+
package org.opensearch.dataprepper.model.constraints;
11+
12+
import jakarta.validation.Constraint;
13+
import jakarta.validation.Payload;
14+
15+
import java.lang.annotation.ElementType;
16+
import java.lang.annotation.Retention;
17+
import java.lang.annotation.RetentionPolicy;
18+
import java.lang.annotation.Target;
19+
20+
/**
21+
* Use this annotation to ensure that the byte count is not below
22+
* a specified maximum value.
23+
*
24+
* @since 2.13
25+
*/
26+
@Constraint(validatedBy = {ByteCountMinValidator.class})
27+
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE_USE})
28+
@Retention(RetentionPolicy.RUNTIME)
29+
public @interface ByteCountMin {
30+
/**
31+
* The minimum value defined as a {@link org.opensearch.dataprepper.model.types.ByteCount}
32+
* string.
33+
*
34+
* @return The byte count string
35+
* @since 2.13
36+
*/
37+
String value();
38+
39+
String message() default "The provided byte count is below the minimum allowed value.";
40+
41+
Class<?>[] groups() default {};
42+
43+
Class<? extends Payload>[] payload() default {};
44+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*
5+
* The OpenSearch Contributors require contributions made to
6+
* this file be licensed under the Apache-2.0 license or a
7+
* compatible open source license.
8+
*/
9+
10+
package org.opensearch.dataprepper.model.constraints;
11+
12+
import jakarta.validation.ConstraintValidator;
13+
import jakarta.validation.ConstraintValidatorContext;
14+
import org.opensearch.dataprepper.model.types.ByteCount;
15+
16+
public class ByteCountMinValidator implements ConstraintValidator<ByteCountMin, ByteCount> {
17+
private ByteCount minByteCount;
18+
19+
@Override
20+
public void initialize(final ByteCountMin constraint) {
21+
minByteCount = ByteCount.parse(constraint.value());
22+
}
23+
24+
@Override
25+
public boolean isValid(final ByteCount byteCount, final ConstraintValidatorContext context) {
26+
if (byteCount == null) {
27+
return true;
28+
}
29+
30+
if (byteCount.compareTo(minByteCount) < 0) {
31+
if (context != null) {
32+
context.disableDefaultConstraintViolation();
33+
context.buildConstraintViolationWithTemplate(
34+
String.format("The provided byte count %s is below minimum of %s", byteCount, minByteCount))
35+
.addConstraintViolation();
36+
}
37+
38+
return false;
39+
}
40+
41+
return true;
42+
}
43+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*
5+
* The OpenSearch Contributors require contributions made to
6+
* this file be licensed under the Apache-2.0 license or a
7+
* compatible open source license.
8+
*/
9+
10+
package org.opensearch.dataprepper.model.constraints;
11+
12+
import jakarta.validation.ConstraintValidatorContext;
13+
import org.junit.jupiter.api.Test;
14+
import org.junit.jupiter.api.extension.ExtendWith;
15+
import org.junit.jupiter.params.ParameterizedTest;
16+
import org.junit.jupiter.params.provider.CsvSource;
17+
import org.mockito.ArgumentCaptor;
18+
import org.mockito.Mock;
19+
import org.mockito.junit.jupiter.MockitoExtension;
20+
import org.opensearch.dataprepper.model.types.ByteCount;
21+
22+
import static org.hamcrest.CoreMatchers.containsString;
23+
import static org.hamcrest.CoreMatchers.equalTo;
24+
import static org.hamcrest.MatcherAssert.assertThat;
25+
import static org.mockito.ArgumentMatchers.anyString;
26+
import static org.mockito.Mockito.mock;
27+
import static org.mockito.Mockito.verify;
28+
import static org.mockito.Mockito.when;
29+
30+
@ExtendWith(MockitoExtension.class)
31+
class ByteCountMaxValidatorTest {
32+
@Mock
33+
private ByteCountMax byteCountMax;
34+
35+
@Mock
36+
private ConstraintValidatorContext context;
37+
38+
private ByteCountMaxValidator createObjectUnderTest() {
39+
final ByteCountMaxValidator objectUnderTest = new ByteCountMaxValidator();
40+
objectUnderTest.initialize(byteCountMax);
41+
return objectUnderTest;
42+
}
43+
44+
@Test
45+
void isValid_returns_true_for_null() {
46+
when(byteCountMax.value()).thenReturn("0b");
47+
assertThat(createObjectUnderTest().isValid(null, context), equalTo(true));
48+
}
49+
50+
@ParameterizedTest
51+
@CsvSource({
52+
"1b, 1b",
53+
"10kb, 1b",
54+
"10kb, 9kb",
55+
"10kb, 10kb",
56+
"10kb, 900b"
57+
})
58+
void isValid_returns_true_for_values_less_than_or_equal_to_the_maximum(final String maxBytes, final String givenBytes) {
59+
when(byteCountMax.value()).thenReturn(maxBytes);
60+
final ByteCount givenByteCount = ByteCount.parse(givenBytes);
61+
assertThat(createObjectUnderTest().isValid(givenByteCount, context), equalTo(true));
62+
}
63+
64+
@ParameterizedTest
65+
@CsvSource({
66+
"1b, 2b",
67+
"10kb, 11kb",
68+
"10kb, 1mb"
69+
})
70+
void isValid_returns_false_for_values_greater_than_the_maximum(final String maxBytes, final String givenBytes) {
71+
when(byteCountMax.value()).thenReturn(maxBytes);
72+
final ByteCount givenByteCount = ByteCount.parse(givenBytes);
73+
74+
final ConstraintValidatorContext.ConstraintViolationBuilder violationBuilder =
75+
mock(ConstraintValidatorContext.ConstraintViolationBuilder.class);
76+
when(context.buildConstraintViolationWithTemplate(anyString()))
77+
.thenReturn(violationBuilder);
78+
79+
assertThat(createObjectUnderTest().isValid(givenByteCount, context), equalTo(false));
80+
81+
verify(context).disableDefaultConstraintViolation();
82+
final ArgumentCaptor<String> errorMessageCaptor = ArgumentCaptor.forClass(String.class);
83+
verify(context).buildConstraintViolationWithTemplate(errorMessageCaptor.capture());
84+
85+
final String errorMessage = errorMessageCaptor.getValue();
86+
87+
assertThat(errorMessage, containsString(givenByteCount.toString()));
88+
assertThat(errorMessage, containsString(ByteCount.parse(maxBytes).toString()));
89+
}
90+
91+
@ParameterizedTest
92+
@CsvSource({
93+
"1b, 2b",
94+
"10kb, 11kb",
95+
"10kb, 1mb"
96+
})
97+
void isValid_returns_false_for_values_greater_than_the_maximum_when_context_is_null(final String maxBytes, final String givenBytes) {
98+
when(byteCountMax.value()).thenReturn(maxBytes);
99+
final ByteCount givenByteCount = ByteCount.parse(givenBytes);
100+
101+
assertThat(createObjectUnderTest().isValid(givenByteCount, null), equalTo(false));
102+
}
103+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*
5+
* The OpenSearch Contributors require contributions made to
6+
* this file be licensed under the Apache-2.0 license or a
7+
* compatible open source license.
8+
*/
9+
10+
package org.opensearch.dataprepper.model.constraints;
11+
12+
import jakarta.validation.ConstraintValidatorContext;
13+
import org.junit.jupiter.api.Test;
14+
import org.junit.jupiter.api.extension.ExtendWith;
15+
import org.junit.jupiter.params.ParameterizedTest;
16+
import org.junit.jupiter.params.provider.CsvSource;
17+
import org.mockito.ArgumentCaptor;
18+
import org.mockito.Mock;
19+
import org.mockito.junit.jupiter.MockitoExtension;
20+
import org.opensearch.dataprepper.model.types.ByteCount;
21+
22+
import static org.hamcrest.CoreMatchers.containsString;
23+
import static org.hamcrest.CoreMatchers.equalTo;
24+
import static org.hamcrest.MatcherAssert.assertThat;
25+
import static org.mockito.ArgumentMatchers.anyString;
26+
import static org.mockito.Mockito.mock;
27+
import static org.mockito.Mockito.verify;
28+
import static org.mockito.Mockito.when;
29+
30+
@ExtendWith(MockitoExtension.class)
31+
class ByteCountMinValidatorTest {
32+
@Mock
33+
private ByteCountMin byteCountMin;
34+
35+
@Mock
36+
private ConstraintValidatorContext context;
37+
38+
private ByteCountMinValidator createObjectUnderTest() {
39+
final ByteCountMinValidator objectUnderTest = new ByteCountMinValidator();
40+
objectUnderTest.initialize(byteCountMin);
41+
return objectUnderTest;
42+
}
43+
44+
@Test
45+
void isValid_returns_true_for_null() {
46+
when(byteCountMin.value()).thenReturn("0b");
47+
assertThat(createObjectUnderTest().isValid(null, context), equalTo(true));
48+
}
49+
50+
@ParameterizedTest
51+
@CsvSource({
52+
"1b, 1b",
53+
"1b, 10kb",
54+
"9kb, 10kb",
55+
"10kb, 10kb",
56+
"900b, 10kb"
57+
})
58+
void isValid_returns_true_for_values_greater_than_or_equal_to_the_minimum(final String minBytes, final String givenBytes) {
59+
when(byteCountMin.value()).thenReturn(minBytes);
60+
final ByteCount givenByteCount = ByteCount.parse(givenBytes);
61+
assertThat(createObjectUnderTest().isValid(givenByteCount, context), equalTo(true));
62+
}
63+
64+
@ParameterizedTest
65+
@CsvSource({
66+
"2b, 1b",
67+
"11kb, 10kb",
68+
"1mb, 10kb"
69+
})
70+
void isValid_returns_false_for_values_less_than_the_minimum(final String minBytes, final String givenBytes) {
71+
when(byteCountMin.value()).thenReturn(minBytes);
72+
final ByteCount givenByteCount = ByteCount.parse(givenBytes);
73+
74+
final ConstraintValidatorContext.ConstraintViolationBuilder violationBuilder =
75+
mock(ConstraintValidatorContext.ConstraintViolationBuilder.class);
76+
when(context.buildConstraintViolationWithTemplate(anyString()))
77+
.thenReturn(violationBuilder);
78+
79+
assertThat(createObjectUnderTest().isValid(givenByteCount, context), equalTo(false));
80+
81+
verify(context).disableDefaultConstraintViolation();
82+
final ArgumentCaptor<String> errorMessageCaptor = ArgumentCaptor.forClass(String.class);
83+
verify(context).buildConstraintViolationWithTemplate(errorMessageCaptor.capture());
84+
85+
final String errorMessage = errorMessageCaptor.getValue();
86+
87+
assertThat(errorMessage, containsString(givenByteCount.toString()));
88+
assertThat(errorMessage, containsString(ByteCount.parse(minBytes).toString()));
89+
}
90+
91+
@ParameterizedTest
92+
@CsvSource({
93+
"2b, 1b",
94+
"11kb, 10kb",
95+
"1mb, 10kb"
96+
})
97+
void isValid_returns_false_for_values_less_than_the_minimum_when_context_is_null(final String minBytes, final String givenBytes) {
98+
when(byteCountMin.value()).thenReturn(minBytes);
99+
final ByteCount givenByteCount = ByteCount.parse(givenBytes);
100+
101+
assertThat(createObjectUnderTest().isValid(givenByteCount, null), equalTo(false));
102+
}
103+
}

0 commit comments

Comments
 (0)