Skip to content

Commit a0ef6d1

Browse files
authored
fix: envelope is not taken into account with built-in types (#960)
* add precedence for the envelope over built-in types * cannot use same envelope for in and out * remove extra new line * handle event field names properly ex: SQS Records with big R * more explicit exception message
1 parent 8b4cc17 commit a0ef6d1

File tree

7 files changed

+122
-9
lines changed

7 files changed

+122
-9
lines changed

powertools-validation/pom.xml

+5-5
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,10 @@
7575
<artifactId>json-schema-validator</artifactId>
7676
<version>1.0.73</version>
7777
</dependency>
78+
<dependency>
79+
<groupId>com.amazonaws</groupId>
80+
<artifactId>aws-lambda-java-serialization</artifactId>
81+
</dependency>
7882

7983
<!-- Test dependencies -->
8084
<dependency>
@@ -87,11 +91,7 @@
8791
<artifactId>junit-jupiter-engine</artifactId>
8892
<scope>test</scope>
8993
</dependency>
90-
<dependency>
91-
<groupId>com.amazonaws</groupId>
92-
<artifactId>aws-lambda-java-serialization</artifactId>
93-
<scope>test</scope>
94-
</dependency>
94+
9595
<dependency>
9696
<groupId>org.apache.commons</groupId>
9797
<artifactId>commons-lang3</artifactId>

powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/ValidationUtils.java

+11-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
*/
1414
package software.amazon.lambda.powertools.validation;
1515

16+
import java.io.ByteArrayOutputStream;
1617
import java.io.IOException;
1718
import java.io.InputStream;
1819
import java.util.Collections;
@@ -21,9 +22,12 @@
2122
import java.util.concurrent.ConcurrentHashMap;
2223
import java.util.stream.Collectors;
2324

25+
import com.amazonaws.services.lambda.runtime.serialization.PojoSerializer;
26+
import com.amazonaws.services.lambda.runtime.serialization.events.LambdaEventSerializers;
2427
import com.fasterxml.jackson.core.JsonProcessingException;
2528
import com.fasterxml.jackson.databind.JsonNode;
2629
import com.fasterxml.jackson.databind.node.JsonNodeType;
30+
import com.fasterxml.jackson.databind.node.NullNode;
2731
import com.networknt.schema.JsonSchema;
2832
import com.networknt.schema.ValidationMessage;
2933
import io.burt.jmespath.Expression;
@@ -65,9 +69,15 @@ public static void validate(Object obj, JsonSchema jsonSchema, String envelope)
6569
}
6670
JsonNode subNode;
6771
try {
68-
JsonNode jsonNode = ValidationConfig.get().getObjectMapper().valueToTree(obj);
72+
PojoSerializer pojoSerializer = LambdaEventSerializers.serializerFor(obj.getClass(), ClassLoader.getSystemClassLoader());
73+
ByteArrayOutputStream out = new ByteArrayOutputStream();
74+
pojoSerializer.toJson(obj, out);
75+
JsonNode jsonNode = ValidationConfig.get().getObjectMapper().readTree(out.toString("UTF-8"));
6976
Expression<JsonNode> expression = ValidationConfig.get().getJmesPath().compile(envelope);
7077
subNode = expression.search(jsonNode);
78+
if (subNode == null || subNode instanceof NullNode) {
79+
throw new ValidationException("Envelope not found in the object");
80+
}
7181
} catch (Exception e) {
7282
throw new ValidationException("Cannot find envelope <"+envelope+"> in the object <"+obj+">", e);
7383
}

powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/internal/ValidationAspect.java

+9-3
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
import org.aspectj.lang.annotation.Around;
2020
import org.aspectj.lang.annotation.Aspect;
2121
import org.aspectj.lang.annotation.Pointcut;
22+
import org.slf4j.Logger;
23+
import org.slf4j.LoggerFactory;
2224
import software.amazon.lambda.powertools.validation.Validation;
2325
import software.amazon.lambda.powertools.validation.ValidationConfig;
2426

@@ -36,6 +38,8 @@
3638
*/
3739
@Aspect
3840
public class ValidationAspect {
41+
private static final Logger LOG = LoggerFactory.getLogger(ValidationAspect.class);
42+
3943
@SuppressWarnings({"EmptyMethod"})
4044
@Pointcut("@annotation(validation)")
4145
public void callAt(Validation validation) {
@@ -59,7 +63,9 @@ && placedOnRequestHandler(pjp)) {
5963
JsonSchema inboundJsonSchema = getJsonSchema(validation.inboundSchema(), true);
6064

6165
Object obj = pjp.getArgs()[0];
62-
if (obj instanceof APIGatewayProxyRequestEvent) {
66+
if (validation.envelope() != null && !validation.envelope().isEmpty()) {
67+
validate(obj, inboundJsonSchema, validation.envelope());
68+
} else if (obj instanceof APIGatewayProxyRequestEvent) {
6369
APIGatewayProxyRequestEvent event = (APIGatewayProxyRequestEvent) obj;
6470
validate(event.getBody(), inboundJsonSchema);
6571
} else if (obj instanceof APIGatewayV2HTTPEvent) {
@@ -105,7 +111,7 @@ && placedOnRequestHandler(pjp)) {
105111
KinesisAnalyticsStreamsInputPreprocessingEvent event = (KinesisAnalyticsStreamsInputPreprocessingEvent) obj;
106112
event.getRecords().forEach(record -> validate(decode(record.getData()), inboundJsonSchema));
107113
} else {
108-
validate(obj, inboundJsonSchema, validation.envelope());
114+
LOG.warn("Unhandled event type {}, please use the 'envelope' parameter to specify what to validate", obj.getClass().getName());
109115
}
110116
}
111117
}
@@ -131,7 +137,7 @@ && placedOnRequestHandler(pjp)) {
131137
KinesisAnalyticsInputPreprocessingResponse response = (KinesisAnalyticsInputPreprocessingResponse) result;
132138
response.getRecords().forEach(record -> validate(decode(record.getData()), outboundJsonSchema));
133139
} else {
134-
validate(result, outboundJsonSchema, validation.envelope());
140+
LOG.warn("Unhandled response type {}, please use the 'envelope' parameter to specify what to validate", result.getClass().getName());
135141
}
136142
}
137143

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright 2020 Amazon.com, Inc. or its affiliates.
3+
* Licensed under the Apache License, Version 2.0 (the
4+
* "License"); you may not use this file except in compliance
5+
* with the License. You may obtain a copy of the License at
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
* Unless required by applicable law or agreed to in writing, software
8+
* distributed under the License is distributed on an "AS IS" BASIS,
9+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
* See the License for the specific language governing permissions and
11+
* limitations under the License.
12+
*
13+
*/
14+
package software.amazon.lambda.powertools.validation.handlers;
15+
16+
import com.amazonaws.services.lambda.runtime.Context;
17+
import com.amazonaws.services.lambda.runtime.RequestHandler;
18+
import com.amazonaws.services.lambda.runtime.events.SQSEvent;
19+
import software.amazon.lambda.powertools.validation.Validation;
20+
21+
public class SQSWithCustomEnvelopeHandler implements RequestHandler<SQSEvent, String> {
22+
23+
@Override
24+
@Validation(inboundSchema = "classpath:/schema_v7.json", envelope = "Records[*].powertools_json(body).powertools_json(Message)")
25+
public String handleRequest(SQSEvent input, Context context) {
26+
return "OK";
27+
}
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Copyright 2020 Amazon.com, Inc. or its affiliates.
3+
* Licensed under the Apache License, Version 2.0 (the
4+
* "License"); you may not use this file except in compliance
5+
* with the License. You may obtain a copy of the License at
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
* Unless required by applicable law or agreed to in writing, software
8+
* distributed under the License is distributed on an "AS IS" BASIS,
9+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
* See the License for the specific language governing permissions and
11+
* limitations under the License.
12+
*
13+
*/
14+
package software.amazon.lambda.powertools.validation.handlers;
15+
16+
import com.amazonaws.services.lambda.runtime.Context;
17+
import com.amazonaws.services.lambda.runtime.RequestHandler;
18+
import com.amazonaws.services.lambda.runtime.events.SQSEvent;
19+
import software.amazon.lambda.powertools.validation.Validation;
20+
21+
public class SQSWithWrongEnvelopeHandler implements RequestHandler<SQSEvent, String> {
22+
23+
@Override
24+
// real event contains Records with big R (https://docs.aws.amazon.com/lambda/latest/dg/with-sqs.html)
25+
@Validation(inboundSchema = "classpath:/schema_v7.json", envelope = "records[*].powertools_json(body).powertools_json(Message)")
26+
public String handleRequest(SQSEvent input, Context context) {
27+
return "OK";
28+
}
29+
}

powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/internal/ValidationAspectTest.java

+18
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,24 @@ public void validate_SQS() {
101101
assertThat(handler.handleRequest(event, context)).isEqualTo("OK");
102102
}
103103

104+
@Test
105+
public void validate_SQS_CustomEnvelopeTakePrecedence() {
106+
PojoSerializer<SQSEvent> pojoSerializer = LambdaEventSerializers.serializerFor(SQSEvent.class, ClassLoader.getSystemClassLoader());
107+
SQSEvent event = pojoSerializer.fromJson(this.getClass().getResourceAsStream("/sqs_message.json"));
108+
109+
SQSWithCustomEnvelopeHandler handler = new SQSWithCustomEnvelopeHandler();
110+
assertThat(handler.handleRequest(event, context)).isEqualTo("OK");
111+
}
112+
113+
@Test
114+
public void validate_SQS_WrongEnvelope_shouldThrowValidationException() {
115+
PojoSerializer<SQSEvent> pojoSerializer = LambdaEventSerializers.serializerFor(SQSEvent.class, ClassLoader.getSystemClassLoader());
116+
SQSEvent event = pojoSerializer.fromJson(this.getClass().getResourceAsStream("/sqs_message.json"));
117+
118+
SQSWithWrongEnvelopeHandler handler = new SQSWithWrongEnvelopeHandler();
119+
assertThatExceptionOfType(ValidationException.class).isThrownBy(() -> handler.handleRequest(event, context));
120+
}
121+
104122
@Test
105123
public void validate_Kinesis() {
106124
PojoSerializer<KinesisEvent> pojoSerializer = LambdaEventSerializers.serializerFor(KinesisEvent.class, ClassLoader.getSystemClassLoader());
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"Records": [
3+
{
4+
"messageId": "d9144555-9a4f-4ec3-99a0-fc4e625a8db2",
5+
"receiptHandle": "7kam5bfzbDsjtcjElvhSbxeLJbeey3A==",
6+
"body": "{\n \"Message\": \"{\\n \\\"id\\\": 43242,\\n \\\"name\\\": \\\"FooBar XY\\\",\\n \\\"price\\\": 258\\n}\"}",
7+
"attributes": {
8+
"ApproximateReceiveCount": "1",
9+
"SentTimestamp": "1601975709495",
10+
"SenderId": "AROAIFU457DVZ5L2J53F2",
11+
"ApproximateFirstReceiveTimestamp": "1601975709499"
12+
},
13+
"messageAttributes": {
14+
15+
},
16+
"md5OfBody": "0f96e88a291edb4429f2f7b9fdc3df96",
17+
"eventSource": "aws:sqs",
18+
"eventSourceARN": "arn:aws:sqs:eu-central-1:123456789012:TestLambda",
19+
"awsRegion": "eu-central-1"
20+
}
21+
]
22+
}

0 commit comments

Comments
 (0)