diff --git a/powertools-validation/pom.xml b/powertools-validation/pom.xml
index 61beb34ba..c5a0a767c 100644
--- a/powertools-validation/pom.xml
+++ b/powertools-validation/pom.xml
@@ -75,6 +75,10 @@
json-schema-validator
1.0.73
+
+ com.amazonaws
+ aws-lambda-java-serialization
+
@@ -87,11 +91,7 @@
junit-jupiter-engine
test
-
- com.amazonaws
- aws-lambda-java-serialization
- test
-
+
org.apache.commons
commons-lang3
diff --git a/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/ValidationUtils.java b/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/ValidationUtils.java
index 83f34ebfd..12c51c632 100644
--- a/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/ValidationUtils.java
+++ b/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/ValidationUtils.java
@@ -13,6 +13,7 @@
*/
package software.amazon.lambda.powertools.validation;
+import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
@@ -21,9 +22,12 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
+import com.amazonaws.services.lambda.runtime.serialization.PojoSerializer;
+import com.amazonaws.services.lambda.runtime.serialization.events.LambdaEventSerializers;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.JsonNodeType;
+import com.fasterxml.jackson.databind.node.NullNode;
import com.networknt.schema.JsonSchema;
import com.networknt.schema.ValidationMessage;
import io.burt.jmespath.Expression;
@@ -65,9 +69,15 @@ public static void validate(Object obj, JsonSchema jsonSchema, String envelope)
}
JsonNode subNode;
try {
- JsonNode jsonNode = ValidationConfig.get().getObjectMapper().valueToTree(obj);
+ PojoSerializer pojoSerializer = LambdaEventSerializers.serializerFor(obj.getClass(), ClassLoader.getSystemClassLoader());
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ pojoSerializer.toJson(obj, out);
+ JsonNode jsonNode = ValidationConfig.get().getObjectMapper().readTree(out.toString("UTF-8"));
Expression expression = ValidationConfig.get().getJmesPath().compile(envelope);
subNode = expression.search(jsonNode);
+ if (subNode == null || subNode instanceof NullNode) {
+ throw new ValidationException("Envelope not found in the object");
+ }
} catch (Exception e) {
throw new ValidationException("Cannot find envelope <"+envelope+"> in the object <"+obj+">", e);
}
diff --git a/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/internal/ValidationAspect.java b/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/internal/ValidationAspect.java
index 0a1f00599..a9d43271b 100644
--- a/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/internal/ValidationAspect.java
+++ b/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/internal/ValidationAspect.java
@@ -19,6 +19,8 @@
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import software.amazon.lambda.powertools.validation.Validation;
import software.amazon.lambda.powertools.validation.ValidationConfig;
@@ -36,6 +38,8 @@
*/
@Aspect
public class ValidationAspect {
+ private static final Logger LOG = LoggerFactory.getLogger(ValidationAspect.class);
+
@SuppressWarnings({"EmptyMethod"})
@Pointcut("@annotation(validation)")
public void callAt(Validation validation) {
@@ -59,7 +63,9 @@ && placedOnRequestHandler(pjp)) {
JsonSchema inboundJsonSchema = getJsonSchema(validation.inboundSchema(), true);
Object obj = pjp.getArgs()[0];
- if (obj instanceof APIGatewayProxyRequestEvent) {
+ if (validation.envelope() != null && !validation.envelope().isEmpty()) {
+ validate(obj, inboundJsonSchema, validation.envelope());
+ } else if (obj instanceof APIGatewayProxyRequestEvent) {
APIGatewayProxyRequestEvent event = (APIGatewayProxyRequestEvent) obj;
validate(event.getBody(), inboundJsonSchema);
} else if (obj instanceof APIGatewayV2HTTPEvent) {
@@ -105,7 +111,7 @@ && placedOnRequestHandler(pjp)) {
KinesisAnalyticsStreamsInputPreprocessingEvent event = (KinesisAnalyticsStreamsInputPreprocessingEvent) obj;
event.getRecords().forEach(record -> validate(decode(record.getData()), inboundJsonSchema));
} else {
- validate(obj, inboundJsonSchema, validation.envelope());
+ LOG.warn("Unhandled event type {}, please use the 'envelope' parameter to specify what to validate", obj.getClass().getName());
}
}
}
@@ -131,7 +137,7 @@ && placedOnRequestHandler(pjp)) {
KinesisAnalyticsInputPreprocessingResponse response = (KinesisAnalyticsInputPreprocessingResponse) result;
response.getRecords().forEach(record -> validate(decode(record.getData()), outboundJsonSchema));
} else {
- validate(result, outboundJsonSchema, validation.envelope());
+ LOG.warn("Unhandled response type {}, please use the 'envelope' parameter to specify what to validate", result.getClass().getName());
}
}
diff --git a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/SQSWithCustomEnvelopeHandler.java b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/SQSWithCustomEnvelopeHandler.java
new file mode 100644
index 000000000..9eb96c0e8
--- /dev/null
+++ b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/SQSWithCustomEnvelopeHandler.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2020 Amazon.com, Inc. or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package software.amazon.lambda.powertools.validation.handlers;
+
+import com.amazonaws.services.lambda.runtime.Context;
+import com.amazonaws.services.lambda.runtime.RequestHandler;
+import com.amazonaws.services.lambda.runtime.events.SQSEvent;
+import software.amazon.lambda.powertools.validation.Validation;
+
+public class SQSWithCustomEnvelopeHandler implements RequestHandler {
+
+ @Override
+ @Validation(inboundSchema = "classpath:/schema_v7.json", envelope = "Records[*].powertools_json(body).powertools_json(Message)")
+ public String handleRequest(SQSEvent input, Context context) {
+ return "OK";
+ }
+}
diff --git a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/SQSWithWrongEnvelopeHandler.java b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/SQSWithWrongEnvelopeHandler.java
new file mode 100644
index 000000000..c1859f29a
--- /dev/null
+++ b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/SQSWithWrongEnvelopeHandler.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2020 Amazon.com, Inc. or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package software.amazon.lambda.powertools.validation.handlers;
+
+import com.amazonaws.services.lambda.runtime.Context;
+import com.amazonaws.services.lambda.runtime.RequestHandler;
+import com.amazonaws.services.lambda.runtime.events.SQSEvent;
+import software.amazon.lambda.powertools.validation.Validation;
+
+public class SQSWithWrongEnvelopeHandler implements RequestHandler {
+
+ @Override
+ // real event contains Records with big R (https://docs.aws.amazon.com/lambda/latest/dg/with-sqs.html)
+ @Validation(inboundSchema = "classpath:/schema_v7.json", envelope = "records[*].powertools_json(body).powertools_json(Message)")
+ public String handleRequest(SQSEvent input, Context context) {
+ return "OK";
+ }
+}
diff --git a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/internal/ValidationAspectTest.java b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/internal/ValidationAspectTest.java
index c8741f1e3..2c66885d6 100644
--- a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/internal/ValidationAspectTest.java
+++ b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/internal/ValidationAspectTest.java
@@ -101,6 +101,24 @@ public void validate_SQS() {
assertThat(handler.handleRequest(event, context)).isEqualTo("OK");
}
+ @Test
+ public void validate_SQS_CustomEnvelopeTakePrecedence() {
+ PojoSerializer pojoSerializer = LambdaEventSerializers.serializerFor(SQSEvent.class, ClassLoader.getSystemClassLoader());
+ SQSEvent event = pojoSerializer.fromJson(this.getClass().getResourceAsStream("/sqs_message.json"));
+
+ SQSWithCustomEnvelopeHandler handler = new SQSWithCustomEnvelopeHandler();
+ assertThat(handler.handleRequest(event, context)).isEqualTo("OK");
+ }
+
+ @Test
+ public void validate_SQS_WrongEnvelope_shouldThrowValidationException() {
+ PojoSerializer pojoSerializer = LambdaEventSerializers.serializerFor(SQSEvent.class, ClassLoader.getSystemClassLoader());
+ SQSEvent event = pojoSerializer.fromJson(this.getClass().getResourceAsStream("/sqs_message.json"));
+
+ SQSWithWrongEnvelopeHandler handler = new SQSWithWrongEnvelopeHandler();
+ assertThatExceptionOfType(ValidationException.class).isThrownBy(() -> handler.handleRequest(event, context));
+ }
+
@Test
public void validate_Kinesis() {
PojoSerializer pojoSerializer = LambdaEventSerializers.serializerFor(KinesisEvent.class, ClassLoader.getSystemClassLoader());
diff --git a/powertools-validation/src/test/resources/sqs_message.json b/powertools-validation/src/test/resources/sqs_message.json
new file mode 100644
index 000000000..068279b74
--- /dev/null
+++ b/powertools-validation/src/test/resources/sqs_message.json
@@ -0,0 +1,22 @@
+{
+ "Records": [
+ {
+ "messageId": "d9144555-9a4f-4ec3-99a0-fc4e625a8db2",
+ "receiptHandle": "7kam5bfzbDsjtcjElvhSbxeLJbeey3A==",
+ "body": "{\n \"Message\": \"{\\n \\\"id\\\": 43242,\\n \\\"name\\\": \\\"FooBar XY\\\",\\n \\\"price\\\": 258\\n}\"}",
+ "attributes": {
+ "ApproximateReceiveCount": "1",
+ "SentTimestamp": "1601975709495",
+ "SenderId": "AROAIFU457DVZ5L2J53F2",
+ "ApproximateFirstReceiveTimestamp": "1601975709499"
+ },
+ "messageAttributes": {
+
+ },
+ "md5OfBody": "0f96e88a291edb4429f2f7b9fdc3df96",
+ "eventSource": "aws:sqs",
+ "eventSourceARN": "arn:aws:sqs:eu-central-1:123456789012:TestLambda",
+ "awsRegion": "eu-central-1"
+ }
+ ]
+}
\ No newline at end of file