Skip to content

Commit ac56a5b

Browse files
committed
Fix DimensionSet validation to be inline with most recent specification.
1 parent ead827b commit ac56a5b

File tree

4 files changed

+147
-23
lines changed

4 files changed

+147
-23
lines changed

pom.xml

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,11 @@
279279
<artifactId>aws-embedded-metrics</artifactId>
280280
<version>${aws-embedded-metrics.version}</version>
281281
</dependency>
282+
<dependency>
283+
<groupId>org.apache.commons</groupId>
284+
<artifactId>commons-lang3</artifactId>
285+
<version>3.15.0</version>
286+
</dependency>
282287

283288
<!-- Test dependencies -->
284289
<dependency>
@@ -294,12 +299,6 @@
294299
<version>1.9.1</version>
295300
<scope>test</scope>
296301
</dependency>
297-
<dependency>
298-
<groupId>org.apache.commons</groupId>
299-
<artifactId>commons-lang3</artifactId>
300-
<version>3.15.0</version>
301-
<scope>test</scope>
302-
</dependency>
303302
<dependency>
304303
<groupId>org.aspectj</groupId>
305304
<artifactId>aspectjweaver</artifactId>

powertools-metrics/pom.xml

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@
6565
<groupId>com.fasterxml.jackson.core</groupId>
6666
<artifactId>jackson-databind</artifactId>
6767
</dependency>
68+
<dependency>
69+
<groupId>org.apache.commons</groupId>
70+
<artifactId>commons-lang3</artifactId>
71+
</dependency>
6872

6973
<!-- Test dependencies -->
7074
<dependency>
@@ -97,11 +101,6 @@
97101
<artifactId>junit-pioneer</artifactId>
98102
<scope>test</scope>
99103
</dependency>
100-
<dependency>
101-
<groupId>org.apache.commons</groupId>
102-
<artifactId>commons-lang3</artifactId>
103-
<scope>test</scope>
104-
</dependency>
105104
<dependency>
106105
<groupId>org.aspectj</groupId>
107106
<artifactId>aspectjweaver</artifactId>

powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/model/DimensionSet.java

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,15 @@
1818
import java.util.Map;
1919
import java.util.Set;
2020

21+
import org.apache.commons.lang3.StringUtils;
22+
2123
/**
2224
* Represents a set of dimensions for CloudWatch metrics
2325
*/
2426
public class DimensionSet {
25-
private static final int MAX_DIMENSION_SET_SIZE = 9;
27+
private static final int MAX_DIMENSION_SET_SIZE = 30;
28+
private static final int MAX_DIMENSION_NAME_LENGTH = 250;
29+
private static final int MAX_DIMENSION_VALUE_LENGTH = 1024;
2630

2731
private final Map<String, String> dimensions = new LinkedHashMap<>();
2832

@@ -186,12 +190,42 @@ public Map<String, String> getDimensions() {
186190
}
187191

188192
private void validateDimension(String key, String value) {
189-
if (key == null || key.isEmpty()) {
193+
if (key == null || key.trim().isEmpty()) {
190194
throw new IllegalArgumentException("Dimension key cannot be null or empty");
191195
}
192196

193-
if (value == null) {
194-
throw new IllegalArgumentException("Dimension value cannot be null");
197+
if (value == null || value.trim().isEmpty()) {
198+
throw new IllegalArgumentException("Dimension value cannot be null or empty");
199+
}
200+
201+
if (StringUtils.containsWhitespace(key)) {
202+
throw new IllegalArgumentException("Dimension key cannot contain whitespaces: " + key);
203+
}
204+
205+
if (StringUtils.containsWhitespace(value)) {
206+
throw new IllegalArgumentException("Dimension value cannot contain whitespaces: " + value);
207+
}
208+
209+
if (key.startsWith(":")) {
210+
throw new IllegalArgumentException("Dimension key cannot start with colon: " + key);
211+
}
212+
213+
if (key.length() > MAX_DIMENSION_NAME_LENGTH) {
214+
throw new IllegalArgumentException(
215+
"Dimension name exceeds maximum length of " + MAX_DIMENSION_NAME_LENGTH + ": " + key);
216+
}
217+
218+
if (value.length() > MAX_DIMENSION_VALUE_LENGTH) {
219+
throw new IllegalArgumentException(
220+
"Dimension value exceeds maximum length of " + MAX_DIMENSION_VALUE_LENGTH + ": " + value);
221+
}
222+
223+
if (!StringUtils.isAsciiPrintable(key)) {
224+
throw new IllegalArgumentException("Dimension name has invalid characters: " + key);
225+
}
226+
227+
if (!StringUtils.isAsciiPrintable(value)) {
228+
throw new IllegalArgumentException("Dimension value has invalid characters: " + value);
195229
}
196230
}
197231
}

powertools-metrics/src/test/java/software/amazon/lambda/powertools/metrics/model/DimensionSetTest.java

Lines changed: 100 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -152,16 +152,16 @@ void shouldReturnNullForNonExistentDimension() {
152152
@Test
153153
void shouldThrowExceptionWhenExceedingMaxDimensions() {
154154
// Given
155-
// Create a map with 9 dimensions (9 is maximum)
156-
Map<String, String> dimensions = Map.of(
157-
"Key1", "Value1", "Key2", "Value2", "Key3", "Value3", "Key4", "Value4", "Key5", "Value5",
158-
"Key6", "Value6", "Key7", "Value7", "Key8", "Value8", "Key9", "Value9");
159-
DimensionSet dimensionSet = DimensionSet.of(dimensions);
155+
// Create a dimension set with 30 dimensions (30 is maximum)
156+
DimensionSet dimensionSet = new DimensionSet();
157+
for (int i = 1; i <= 30; i++) {
158+
dimensionSet.addDimension("Key" + i, "Value" + i);
159+
}
160160

161161
// When/Then
162-
assertThatThrownBy(() -> dimensionSet.addDimension("Key10", "Value10"))
162+
assertThatThrownBy(() -> dimensionSet.addDimension("Key31", "Value31"))
163163
.isInstanceOf(IllegalStateException.class)
164-
.hasMessageContaining("Cannot exceed 9 dimensions per dimension set");
164+
.hasMessageContaining("Cannot exceed 30 dimensions per dimension set");
165165
}
166166

167167
@Test
@@ -194,6 +194,98 @@ void shouldThrowExceptionWhenValueIsNull() {
194194
// When/Then
195195
assertThatThrownBy(() -> dimensionSet.addDimension("Key", null))
196196
.isInstanceOf(IllegalArgumentException.class)
197-
.hasMessage("Dimension value cannot be null");
197+
.hasMessage("Dimension value cannot be null or empty");
198+
}
199+
200+
@Test
201+
void shouldThrowExceptionWhenValueIsEmpty() {
202+
// Given
203+
DimensionSet dimensionSet = DimensionSet.of(Collections.emptyMap());
204+
205+
// When/Then
206+
assertThatThrownBy(() -> dimensionSet.addDimension("Key", ""))
207+
.isInstanceOf(IllegalArgumentException.class)
208+
.hasMessage("Dimension value cannot be null or empty");
209+
}
210+
211+
@Test
212+
void shouldThrowExceptionWhenKeyContainsWhitespace() {
213+
// Given
214+
DimensionSet dimensionSet = DimensionSet.of(Collections.emptyMap());
215+
216+
// When/Then
217+
assertThatThrownBy(() -> dimensionSet.addDimension("Key With Space", "Value"))
218+
.isInstanceOf(IllegalArgumentException.class)
219+
.hasMessage("Dimension key cannot contain whitespaces: Key With Space");
220+
}
221+
222+
@Test
223+
void shouldThrowExceptionWhenValueContainsWhitespace() {
224+
// Given
225+
DimensionSet dimensionSet = DimensionSet.of(Collections.emptyMap());
226+
227+
// When/Then
228+
assertThatThrownBy(() -> dimensionSet.addDimension("Key", "Value With Space"))
229+
.isInstanceOf(IllegalArgumentException.class)
230+
.hasMessage("Dimension value cannot contain whitespaces: Value With Space");
231+
}
232+
233+
@Test
234+
void shouldThrowExceptionWhenKeyStartsWithColon() {
235+
// Given
236+
DimensionSet dimensionSet = DimensionSet.of(Collections.emptyMap());
237+
238+
// When/Then
239+
assertThatThrownBy(() -> dimensionSet.addDimension(":Key", "Value"))
240+
.isInstanceOf(IllegalArgumentException.class)
241+
.hasMessage("Dimension key cannot start with colon: :Key");
242+
}
243+
244+
@Test
245+
void shouldThrowExceptionWhenKeyExceedsMaxLength() {
246+
// Given
247+
DimensionSet dimensionSet = DimensionSet.of(Collections.emptyMap());
248+
String longKey = "a".repeat(251); // MAX_DIMENSION_NAME_LENGTH + 1
249+
250+
// When/Then
251+
assertThatThrownBy(() -> dimensionSet.addDimension(longKey, "Value"))
252+
.isInstanceOf(IllegalArgumentException.class)
253+
.hasMessage("Dimension name exceeds maximum length of 250: " + longKey);
254+
}
255+
256+
@Test
257+
void shouldThrowExceptionWhenValueExceedsMaxLength() {
258+
// Given
259+
DimensionSet dimensionSet = DimensionSet.of(Collections.emptyMap());
260+
String longValue = "a".repeat(1025); // MAX_DIMENSION_VALUE_LENGTH + 1
261+
262+
// When/Then
263+
assertThatThrownBy(() -> dimensionSet.addDimension("Key", longValue))
264+
.isInstanceOf(IllegalArgumentException.class)
265+
.hasMessage("Dimension value exceeds maximum length of 1024: " + longValue);
266+
}
267+
268+
@Test
269+
void shouldThrowExceptionWhenKeyContainsNonAsciiCharacters() {
270+
// Given
271+
DimensionSet dimensionSet = DimensionSet.of(Collections.emptyMap());
272+
String keyWithNonAscii = "Key\u0080"; // Non-ASCII character
273+
274+
// When/Then
275+
assertThatThrownBy(() -> dimensionSet.addDimension(keyWithNonAscii, "Value"))
276+
.isInstanceOf(IllegalArgumentException.class)
277+
.hasMessage("Dimension name has invalid characters: " + keyWithNonAscii);
278+
}
279+
280+
@Test
281+
void shouldThrowExceptionWhenValueContainsNonAsciiCharacters() {
282+
// Given
283+
DimensionSet dimensionSet = DimensionSet.of(Collections.emptyMap());
284+
String valueWithNonAscii = "Value\u0080"; // Non-ASCII character
285+
286+
// When/Then
287+
assertThatThrownBy(() -> dimensionSet.addDimension("Key", valueWithNonAscii))
288+
.isInstanceOf(IllegalArgumentException.class)
289+
.hasMessage("Dimension value has invalid characters: " + valueWithNonAscii);
198290
}
199291
}

0 commit comments

Comments
 (0)