diff --git a/docs/utilities/serialization.md b/docs/utilities/serialization.md
index 19ff00d37..d9e392ca4 100644
--- a/docs/utilities/serialization.md
+++ b/docs/utilities/serialization.md
@@ -3,7 +3,249 @@ title: Serialization Utilities
description: Utility
---
-This module contains a set of utilities you may use in your Lambda functions, mainly associated with other modules like [validation](validation.md) and [idempotency](idempotency.md), to manipulate JSON.
+This module contains a set of utilities you may use in your Lambda functions, to manipulate JSON.
+
+## Easy deserialization
+
+### Key features
+
+* Easily deserialize the main content of an event (for example, the body of an API Gateway event)
+* 15+ built-in events (see the [list below](#built-in-events))
+
+### Getting started
+
+=== "Maven"
+
+ ```xml hl_lines="5"
+
+ ...
+
+ software.amazon.lambda
+ powertools-serialization
+ {{ powertools.version }}
+
+ ...
+
+ ```
+
+=== "Gradle"
+
+ ```
+ implementation 'software.amazon.lambda:powertools-serialization:{{ powertools.version }}'
+ ```
+
+### EventDeserializer
+
+The `EventDeserializer` can be used to extract the main part of an event (body, message, records) and deserialize it from JSON to your desired type.
+
+It can handle single elements like the body of an API Gateway event:
+
+=== "APIGWHandler.java"
+
+ ```java hl_lines="1 6 9"
+ import static software.amazon.lambda.powertools.utilities.EventDeserializer.extractDataFrom;
+
+ public class APIGWHandler implements RequestHandler {
+
+ public APIGatewayProxyResponseEvent handleRequest(
+ final APIGatewayProxyRequestEvent event,
+ final Context context) {
+
+ Product product = extractDataFrom(event).as(Product.class);
+
+ }
+ ```
+
+=== "Product.java"
+
+ ```java
+ public class Product {
+ private long id;
+ private String name;
+ private double price;
+
+ public Product() {
+ }
+
+ public Product(long id, String name, double price) {
+ this.id = id;
+ this.name = name;
+ this.price = price;
+ }
+
+ public long getId() {
+ return id;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public double getPrice() {
+ return price;
+ }
+
+ public void setPrice(double price) {
+ this.price = price;
+ }
+ }
+ ```
+
+=== "event"
+
+ ```json hl_lines="2"
+ {
+ "body": "{\"id\":1234, \"name\":\"product\", \"price\":42}",
+ "resource": "/{proxy+}",
+ "path": "/path/to/resource",
+ "httpMethod": "POST",
+ "isBase64Encoded": false,
+ "queryStringParameters": {
+ "foo": "bar"
+ },
+ "pathParameters": {
+ "proxy": "/path/to/resource"
+ },
+ "stageVariables": {
+ "baz": "qux"
+ },
+ "headers": {
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
+ "Accept-Encoding": "gzip, deflate, sdch",
+ "Accept-Language": "en-US,en;q=0.8",
+ "Cache-Control": "max-age=0",
+ "Host": "1234567890.execute-api.us-east-1.amazonaws.com",
+ "Upgrade-Insecure-Requests": "1",
+ "User-Agent": "Custom User Agent String",
+ "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)",
+ "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==",
+ "X-Forwarded-For": "127.0.0.1, 127.0.0.2",
+ "X-Forwarded-Port": "443",
+ "X-Forwarded-Proto": "https"
+ },
+ "requestContext": {
+ "accountId": "123456789012",
+ "resourceId": "123456",
+ "stage": "prod",
+ "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef",
+ "requestTime": "09/Apr/2015:12:34:56 +0000",
+ "requestTimeEpoch": 1428582896000,
+ "identity": {
+ "cognitoIdentityPoolId": null,
+ "accountId": null,
+ "cognitoIdentityId": null,
+ "caller": null,
+ "accessKey": null,
+ "sourceIp": "127.0.0.1",
+ "cognitoAuthenticationType": null,
+ "cognitoAuthenticationProvider": null,
+ "userArn": null,
+ "userAgent": "Custom User Agent String",
+ "user": null
+ },
+ "path": "/prod/path/to/resource",
+ "resourcePath": "/{proxy+}",
+ "httpMethod": "POST",
+ "apiId": "1234567890",
+ "protocol": "HTTP/1.1"
+ }
+ }
+ ```
+
+It can also handle a collection of elements like the records of an SQS event:
+
+=== "SQSHandler.java"
+
+ ```java hl_lines="1 6 9"
+ import static software.amazon.lambda.powertools.utilities.EventDeserializer.extractDataFrom;
+
+ public class SQSHandler implements RequestHandler {
+
+ public String handleRequest(
+ final SQSEvent event,
+ final Context context) {
+
+ List products = extractDataFrom(event).asListOf(Product.class);
+
+ }
+ ```
+
+=== "event"
+
+ ```json hl_lines="6 23"
+ {
+ "Records": [
+ {
+ "messageId": "d9144555-9a4f-4ec3-99a0-34ce359b4b54",
+ "receiptHandle": "13e7f7851d2eaa5c01f208ebadbf1e72==",
+ "body": "{ \"id\": 1234, \"name\": \"product\", \"price\": 42}",
+ "attributes": {
+ "ApproximateReceiveCount": "1",
+ "SentTimestamp": "1601975706495",
+ "SenderId": "AROAIFU437PVZ5L2J53F5",
+ "ApproximateFirstReceiveTimestamp": "1601975706499"
+ },
+ "messageAttributes": {
+ },
+ "md5OfBody": "13e7f7851d2eaa5c01f208ebadbf1e72",
+ "eventSource": "aws:sqs",
+ "eventSourceARN": "arn:aws:sqs:eu-central-1:123456789012:TestLambda",
+ "awsRegion": "eu-central-1"
+ },
+ {
+ "messageId": "d9144555-9a4f-4ec3-99a0-34ce359b4b54",
+ "receiptHandle": "13e7f7851d2eaa5c01f208ebadbf1e72==",
+ "body": "{ \"id\": 12345, \"name\": \"product5\", \"price\": 45}",
+ "attributes": {
+ "ApproximateReceiveCount": "1",
+ "SentTimestamp": "1601975706495",
+ "SenderId": "AROAIFU437PVZ5L2J53F5",
+ "ApproximateFirstReceiveTimestamp": "1601975706499"
+ },
+ "messageAttributes": {
+
+ },
+ "md5OfBody": "13e7f7851d2eaa5c01f208ebadbf1e72",
+ "eventSource": "aws:sqs",
+ "eventSourceARN": "arn:aws:sqs:eu-central-1:123456789012:TestLambda",
+ "awsRegion": "eu-central-1"
+ }
+ ]
+ }
+ ```
+
+!!! Tip
+ In the background, `EventDeserializer` is using Jackson. The `ObjectMapper` is configured in `JsonConfig`. You can customize the configuration of the mapper if needed:
+ `JsonConfig.get().getObjectMapper()`. Using this feature, you don't need to add Jackson to your project and create another instance of `ObjectMapper`.
+
+### Built-in events
+
+| Event Type | Path to the content | List |
+|---------------------------------------------------|-----------------------------------------------------------|------|
+| `APIGatewayProxyRequestEvent` | `body` | |
+| `APIGatewayV2HTTPEvent` | `body` | |
+| `SNSEvent` | `Records[0].Sns.Message` | |
+| `SQSEvent` | `Records[*].body` | x |
+ | `ScheduledEvent` | `detail` | |
+ | `ApplicationLoadBalancerRequestEvent` | `body` | |
+ | `CloudWatchLogsEvent` | `powertools_base64_gzip(data)` | |
+ | `CloudFormationCustomResourceEvent` | `resourceProperties` | |
+ | `KinesisEvent` | `Records[*].kinesis.powertools_base64(data)` | x |
+ | `KinesisFirehoseEvent` | `Records[*].powertools_base64(data)` | x |
+ | `KafkaEvent` | `records[*].values[*].powertools_base64(value)` | x |
+ | `ActiveMQEvent` | `messages[*].powertools_base64(data)` | x |
+| `RabbitMQEvent` | `rmqMessagesByQueue[*].values[*].powertools_base64(data)` | x |
+| `KinesisAnalyticsFirehoseInputPreprocessingEvent` | `Records[*].kinesis.powertools_base64(data)` | x |
+| `KinesisAnalyticsStreamsInputPreprocessingEvent` | `Records[*].kinesis.powertools_base64(data)` | x |
+
## JMESPath functions
diff --git a/pom.xml b/pom.xml
index 50f935840..28e1f941d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -64,6 +64,7 @@
UTF-8
1.2.1
3.11.0
+ 1.0.0
3.10.0
1.14.0
2.22.2
@@ -122,6 +123,11 @@
aws-lambda-java-events
${lambda.events.version}
+
+ com.amazonaws
+ aws-lambda-java-serialization
+ ${lambda.serial.version}
+
software.amazon.awssdk
bom
diff --git a/powertools-serialization/pom.xml b/powertools-serialization/pom.xml
index 5cff32313..e460b7022 100644
--- a/powertools-serialization/pom.xml
+++ b/powertools-serialization/pom.xml
@@ -45,6 +45,14 @@
io.burt
jmespath-jackson
+
+ com.amazonaws
+ aws-lambda-java-events
+
+
+ org.apache.logging.log4j
+ log4j-slf4j-impl
+
@@ -57,6 +65,11 @@
assertj-core
test
+
+ com.amazonaws
+ aws-lambda-java-tests
+ test
+
diff --git a/powertools-serialization/src/main/java/software/amazon/lambda/powertools/utilities/EventDeserializationException.java b/powertools-serialization/src/main/java/software/amazon/lambda/powertools/utilities/EventDeserializationException.java
new file mode 100644
index 000000000..2b06c9256
--- /dev/null
+++ b/powertools-serialization/src/main/java/software/amazon/lambda/powertools/utilities/EventDeserializationException.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2022 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.utilities;
+
+public class EventDeserializationException extends RuntimeException {
+ private static final long serialVersionUID = -5003158148870110442L;
+
+ public EventDeserializationException(String msg, Exception e) {
+ super(msg, e);
+ }
+
+ public EventDeserializationException(String msg) {
+ super(msg);
+ }
+}
diff --git a/powertools-serialization/src/main/java/software/amazon/lambda/powertools/utilities/EventDeserializer.java b/powertools-serialization/src/main/java/software/amazon/lambda/powertools/utilities/EventDeserializer.java
new file mode 100644
index 000000000..9742299ee
--- /dev/null
+++ b/powertools-serialization/src/main/java/software/amazon/lambda/powertools/utilities/EventDeserializer.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright 2022 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.utilities;
+
+import com.amazonaws.services.lambda.runtime.events.*;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectReader;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static software.amazon.lambda.powertools.utilities.jmespath.Base64Function.decode;
+import static software.amazon.lambda.powertools.utilities.jmespath.Base64GZipFunction.decompress;
+
+/**
+ * Class that can be used to extract the meaningful part of an event and deserialize it into a Java object.
+ * For example, extract the body of an API Gateway event, or messages from an SQS event.
+ */
+public class EventDeserializer {
+
+ private static final Logger LOG = LoggerFactory.getLogger(EventDeserializer.class);
+
+ /**
+ * Extract the meaningful part of a Lambda Event object. Main events are built-in:
+ *
+ * - {@link APIGatewayProxyRequestEvent} -> body
+ * - {@link APIGatewayV2HTTPEvent} -> body
+ * - {@link SNSEvent} -> Records[0].Sns.Message
+ * - {@link SQSEvent} -> Records[*].body (list)
+ * - {@link ScheduledEvent} -> detail
+ * - {@link ApplicationLoadBalancerRequestEvent} -> body
+ * - {@link CloudWatchLogsEvent} -> powertools_base64_gzip(data)
+ * - {@link CloudFormationCustomResourceEvent} -> resourceProperties
+ * - {@link KinesisEvent} -> Records[*].kinesis.powertools_base64(data) (list)
+ * - {@link KinesisFirehoseEvent} -> Records[*].powertools_base64(data) (list)
+ * - {@link KafkaEvent} -> records[*].values[*].powertools_base64(value) (list)
+ * - {@link ActiveMQEvent} -> messages[*].powertools_base64(data) (list)
+ * - {@link RabbitMQEvent} -> rmqMessagesByQueue[*].values[*].powertools_base64(data) (list)
+ * - {@link KinesisAnalyticsFirehoseInputPreprocessingEvent} -> Records[*].kinesis.powertools_base64(data) (list)
+ * - {@link KinesisAnalyticsStreamsInputPreprocessingEvent} > Records[*].kinesis.powertools_base64(data) (list)
+ * - {@link String}
+ * - {@link Map}
+ *
+ * To be used in conjunction with {@link EventPart#as(Class)} or {@link EventPart#asListOf(Class)}
+ * for the deserialization.
+ *
+ * @param object the event of your Lambda function handler method
+ * @return the part of the event which is meaningful (ex: body of the API Gateway).
+ */
+ public static EventPart extractDataFrom(Object object) {
+ if (object instanceof String) {
+ return new EventPart((String) object);
+ } else if (object instanceof Map) {
+ return new EventPart((Map) object);
+ } else if (object instanceof APIGatewayProxyRequestEvent) {
+ APIGatewayProxyRequestEvent event = (APIGatewayProxyRequestEvent) object;
+ return new EventPart(event.getBody());
+ } else if (object instanceof APIGatewayV2HTTPEvent) {
+ APIGatewayV2HTTPEvent event = (APIGatewayV2HTTPEvent) object;
+ return new EventPart(event.getBody());
+ } else if (object instanceof SNSEvent) {
+ SNSEvent event = (SNSEvent) object;
+ return new EventPart(event.getRecords().get(0).getSNS().getMessage());
+ } else if (object instanceof SQSEvent) {
+ SQSEvent event = (SQSEvent) object;
+ return new EventPart(event.getRecords().stream()
+ .map(SQSEvent.SQSMessage::getBody)
+ .collect(Collectors.toList()));
+ } else if (object instanceof ScheduledEvent) {
+ ScheduledEvent event = (ScheduledEvent) object;
+ return new EventPart(event.getDetail());
+ } else if (object instanceof ApplicationLoadBalancerRequestEvent) {
+ ApplicationLoadBalancerRequestEvent event = (ApplicationLoadBalancerRequestEvent) object;
+ return new EventPart(event.getBody());
+ } else if (object instanceof CloudWatchLogsEvent) {
+ CloudWatchLogsEvent event = (CloudWatchLogsEvent) object;
+ return new EventPart(decompress(decode(event.getAwsLogs().getData().getBytes(UTF_8))));
+ } else if (object instanceof CloudFormationCustomResourceEvent) {
+ CloudFormationCustomResourceEvent event = (CloudFormationCustomResourceEvent) object;
+ return new EventPart(event.getResourceProperties());
+ } else if (object instanceof KinesisEvent) {
+ KinesisEvent event = (KinesisEvent) object;
+ return new EventPart(event.getRecords().stream()
+ .map(r -> decode(r.getKinesis().getData()))
+ .collect(Collectors.toList()));
+ } else if (object instanceof KinesisFirehoseEvent) {
+ KinesisFirehoseEvent event = (KinesisFirehoseEvent) object;
+ return new EventPart(event.getRecords().stream()
+ .map(r -> decode(r.getData()))
+ .collect(Collectors.toList()));
+ } else if (object instanceof KafkaEvent) {
+ KafkaEvent event = (KafkaEvent) object;
+ return new EventPart(event.getRecords().values().stream()
+ .flatMap(List::stream)
+ .map(r -> decode(r.getValue()))
+ .collect(Collectors.toList()));
+ } else if (object instanceof ActiveMQEvent) {
+ ActiveMQEvent event = (ActiveMQEvent) object;
+ return new EventPart(event.getMessages().stream()
+ .map(m -> decode(m.getData()))
+ .collect(Collectors.toList()));
+ } else if (object instanceof RabbitMQEvent) {
+ RabbitMQEvent event = (RabbitMQEvent) object;
+ return new EventPart(event.getRmqMessagesByQueue().values().stream()
+ .flatMap(List::stream)
+ .map(r -> decode(r.getData()))
+ .collect(Collectors.toList()));
+ } else if (object instanceof KinesisAnalyticsFirehoseInputPreprocessingEvent) {
+ KinesisAnalyticsFirehoseInputPreprocessingEvent event = (KinesisAnalyticsFirehoseInputPreprocessingEvent) object;
+ return new EventPart(event.getRecords().stream()
+ .map(r -> decode(r.getData()))
+ .collect(Collectors.toList()));
+ } else if (object instanceof KinesisAnalyticsStreamsInputPreprocessingEvent) {
+ KinesisAnalyticsStreamsInputPreprocessingEvent event = (KinesisAnalyticsStreamsInputPreprocessingEvent) object;
+ return new EventPart(event.getRecords().stream()
+ .map(r -> decode(r.getData()))
+ .collect(Collectors.toList()));
+ } else {
+ // does not really make sense to use this EventDeserializer when you already have a typed object
+ // just not to throw an exception
+ LOG.warn("Consider using your object directly instead of using EventDeserializer");
+ return new EventPart(object);
+ }
+ }
+
+ /**
+ * Meaningful part of a Lambda event.
+ * Use {@link #extractDataFrom(Object)} to retrieve an instance of this class.
+ */
+ public static class EventPart {
+ private Map contentMap;
+ private String content;
+ private List contentList;
+ private Object contentObject;
+
+ private EventPart(List contentList) {
+ this.contentList = contentList;
+ }
+
+ private EventPart(String content) {
+ this.content = content;
+ }
+
+ private EventPart(Map contentMap) {
+ this.contentMap = contentMap;
+ }
+
+ private EventPart(Object content) {
+ this.contentObject = content;
+ }
+
+ /**
+ * Deserialize this part of event from JSON to an object of type T
+ * @param clazz the target type for deserialization
+ * @param type of object to return
+ * @return an Object of type T (deserialized from the content)
+ */
+ public T as(Class clazz) {
+ try {
+ if (content != null) {
+ if (content.getClass().equals(clazz)) {
+ // do not read json when returning String, just return the String
+ return (T) content;
+ }
+ return JsonConfig.get().getObjectMapper().reader().readValue(content, clazz);
+ }
+ if (contentMap != null) {
+ return JsonConfig.get().getObjectMapper().convertValue(contentMap, clazz);
+ }
+ if (contentObject != null) {
+ return (T) contentObject;
+ }
+ if (contentList != null) {
+ throw new EventDeserializationException("The content of this event is a list, consider using 'asListOf' instead");
+ }
+ // should not occur, except if the event is malformed (missing fields)
+ throw new IllegalStateException("Event content is null: the event may be malformed (missing fields)");
+ } catch (IOException e) {
+ throw new EventDeserializationException("Cannot load the event as " + clazz.getSimpleName(), e);
+ }
+ }
+
+ /**
+ * Deserialize this part of event from JSON to a list of objects of type T
+ * @param clazz the target type for deserialization
+ * @param type of object to return
+ * @return a list of objects of type T (deserialized from the content)
+ */
+ public List asListOf(Class clazz) {
+ if (contentList == null && content == null) {
+ if (contentMap != null || contentObject != null) {
+ throw new EventDeserializationException("The content of this event is not a list, consider using 'as' instead");
+ }
+ // should not occur, except if the event is really malformed
+ throw new IllegalStateException("Event content is null: the event may be malformed (missing fields)");
+ }
+ if (content != null) {
+ ObjectReader reader = JsonConfig.get().getObjectMapper().readerForListOf(clazz);
+ try {
+ return reader.readValue(content);
+ } catch (JsonProcessingException e) {
+ throw new EventDeserializationException("Cannot load the event as a list of " + clazz.getSimpleName() + ", consider using 'as' instead", e);
+ }
+ } else {
+ return contentList.stream().map(s -> {
+ try {
+ return s == null ? null : JsonConfig.get().getObjectMapper().reader().readValue(s, clazz);
+ } catch (IOException e) {
+ throw new EventDeserializationException("Cannot load the event as a list of " + clazz.getSimpleName(), e);
+ }
+ }).collect(Collectors.toList());
+ }
+ }
+ }
+}
diff --git a/powertools-serialization/src/main/java/software/amazon/lambda/powertools/utilities/JsonConfig.java b/powertools-serialization/src/main/java/software/amazon/lambda/powertools/utilities/JsonConfig.java
index c3a5fc865..d8af1c0cb 100644
--- a/powertools-serialization/src/main/java/software/amazon/lambda/powertools/utilities/JsonConfig.java
+++ b/powertools-serialization/src/main/java/software/amazon/lambda/powertools/utilities/JsonConfig.java
@@ -24,8 +24,6 @@
import software.amazon.lambda.powertools.utilities.jmespath.Base64GZipFunction;
import software.amazon.lambda.powertools.utilities.jmespath.JsonFunction;
-import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES;
-
public class JsonConfig {
private JsonConfig() {
}
@@ -38,11 +36,7 @@ public static JsonConfig get() {
return ConfigHolder.instance;
}
- private static final ThreadLocal om = ThreadLocal.withInitial(() -> {
- ObjectMapper objectMapper = new ObjectMapper();
- objectMapper.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);
- return objectMapper;
- });
+ private static final ThreadLocal om = ThreadLocal.withInitial(ObjectMapper::new);
private final FunctionRegistry defaultFunctions = FunctionRegistry.defaultRegistry();
private final FunctionRegistry customFunctions = defaultFunctions.extend(
diff --git a/powertools-serialization/src/test/java/software/amazon/lambda/powertools/utilities/EventDeserializerTest.java b/powertools-serialization/src/test/java/software/amazon/lambda/powertools/utilities/EventDeserializerTest.java
new file mode 100644
index 000000000..90143b2a0
--- /dev/null
+++ b/powertools-serialization/src/test/java/software/amazon/lambda/powertools/utilities/EventDeserializerTest.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright 2022 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.utilities;
+
+import com.amazonaws.services.lambda.runtime.events.*;
+import com.amazonaws.services.lambda.runtime.tests.annotations.Event;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import software.amazon.lambda.powertools.utilities.model.Basket;
+import software.amazon.lambda.powertools.utilities.model.Product;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static software.amazon.lambda.powertools.utilities.EventDeserializer.extractDataFrom;
+
+public class EventDeserializerTest {
+
+ @Test
+ public void testDeserializeStringAsString_shouldReturnString() {
+ String stringEvent = "Hello World";
+ String result = extractDataFrom(stringEvent).as(String.class);
+ assertThat(result).isEqualTo(stringEvent);
+ }
+
+ @Test
+ public void testDeserializeStringAsObject_shouldReturnObject() {
+ String productStr = "{\"id\":1234, \"name\":\"product\", \"price\":42}";
+ Product product = extractDataFrom(productStr).as(Product.class);
+ assertProduct(product);
+ }
+
+ @Test
+ public void testDeserializeStringArrayAsList_shouldReturnList() {
+ String productStr = "[{\"id\":1234, \"name\":\"product\", \"price\":42}, {\"id\":2345, \"name\":\"product2\", \"price\":43}]";
+ List products = extractDataFrom(productStr).asListOf(Product.class);
+ assertThat(products).hasSize(2);
+ assertProduct(products.get(0));
+ }
+
+ @Test
+ public void testDeserializeStringAsList_shouldThrowException() {
+ String productStr = "{\"id\":1234, \"name\":\"product\", \"price\":42}";
+ assertThatThrownBy(() -> extractDataFrom(productStr).asListOf(Product.class))
+ .isInstanceOf(EventDeserializationException.class)
+ .hasMessage("Cannot load the event as a list of Product, consider using 'as' instead");
+ }
+
+ @Test
+ public void testDeserializeMapAsObject_shouldReturnObject() {
+ Map map = new HashMap<>();
+ map.put("id", 1234);
+ map.put("name", "product");
+ map.put("price", 42);
+ Product product = extractDataFrom(map).as(Product.class);
+ assertProduct(product);
+ }
+
+ @ParameterizedTest
+ @Event(value = "apigw_event.json", type = APIGatewayProxyRequestEvent.class)
+ public void testDeserializeAPIGWEventBodyAsObject_shouldReturnObject(APIGatewayProxyRequestEvent event) {
+ Product product = extractDataFrom(event).as(Product.class);
+ assertProduct(product);
+ }
+
+ @ParameterizedTest
+ @Event(value = "apigw_event.json", type = APIGatewayProxyRequestEvent.class)
+ public void testDeserializeAPIGWEventBodyAsWrongObjectType_shouldThrowException(APIGatewayProxyRequestEvent event) {
+ assertThatThrownBy(() -> extractDataFrom(event).as(Basket.class))
+ .isInstanceOf(EventDeserializationException.class)
+ .hasMessage("Cannot load the event as Basket");
+ }
+
+ @ParameterizedTest
+ @Event(value = "sns_event.json", type = SNSEvent.class)
+ public void testDeserializeSNSEventMessageAsObject_shouldReturnObject(SNSEvent event) {
+ Product product = extractDataFrom(event).as(Product.class);
+ assertProduct(product);
+ }
+
+ @ParameterizedTest
+ @Event(value = "sqs_event.json", type = SQSEvent.class)
+ public void testDeserializeSQSEventMessageAsList_shouldReturnList(SQSEvent event) {
+ List products = extractDataFrom(event).asListOf(Product.class);
+ assertThat(products).hasSize(2);
+ assertProduct(products.get(0));
+ }
+
+ @ParameterizedTest
+ @Event(value = "kinesis_event.json", type = KinesisEvent.class)
+ public void testDeserializeKinesisEventMessageAsList_shouldReturnList(KinesisEvent event) {
+ List products = extractDataFrom(event).asListOf(Product.class);
+ assertThat(products).hasSize(2);
+ assertProduct(products.get(0));
+ }
+
+ @ParameterizedTest
+ @Event(value = "kafka_event.json", type = KafkaEvent.class)
+ public void testDeserializeKafkaEventMessageAsList_shouldReturnList(KafkaEvent event) {
+ List products = extractDataFrom(event).asListOf(Product.class);
+ assertThat(products).hasSize(2);
+ assertProduct(products.get(0));
+ }
+
+ @ParameterizedTest
+ @Event(value = "sqs_event.json", type = SQSEvent.class)
+ public void testDeserializeSQSEventMessageAsObject_shouldThrowException(SQSEvent event) {
+ assertThatThrownBy(() -> extractDataFrom(event).as(Product.class))
+ .isInstanceOf(EventDeserializationException.class)
+ .hasMessageContaining("consider using 'asListOf' instead");
+ }
+
+ @ParameterizedTest
+ @Event(value = "apigw_event.json", type = APIGatewayProxyRequestEvent.class)
+ public void testDeserializeAPIGatewayEventAsList_shouldThrowException(APIGatewayProxyRequestEvent event) {
+ assertThatThrownBy(() -> extractDataFrom(event).asListOf(Product.class))
+ .isInstanceOf(EventDeserializationException.class)
+ .hasMessageContaining("consider using 'as' instead");
+ }
+
+ @ParameterizedTest
+ @Event(value = "sqs_event.json", type = SQSEvent.class)
+ public void testDeserializeSQSEventBodyAsWrongObjectType_shouldThrowException(SQSEvent event) {
+ assertThatThrownBy(() -> extractDataFrom(event).asListOf(Basket.class))
+ .isInstanceOf(EventDeserializationException.class)
+ .hasMessage("Cannot load the event as a list of Basket");
+ }
+
+ @ParameterizedTest
+ @Event(value = "apigw_event_no_body.json", type = APIGatewayProxyRequestEvent.class)
+ public void testDeserializeAPIGatewayNoBody_shouldThrowException(APIGatewayProxyRequestEvent event) {
+ assertThatThrownBy(() -> extractDataFrom(event).as(Product.class))
+ .isInstanceOf(IllegalStateException.class)
+ .hasMessageContaining("Event content is null");
+ }
+
+ @ParameterizedTest
+ @Event(value = "sqs_event_no_body.json", type = SQSEvent.class)
+ public void testDeserializeSQSEventNoBody_shouldThrowException(SQSEvent event) {
+ List products = extractDataFrom(event).asListOf(Product.class);
+ assertThat(products.get(0)).isNull();
+ }
+
+ @Test
+ public void testDeserializeProductAsProduct_shouldReturnProduct() {
+ Product myProduct = new Product(1234, "product", 42);
+ Product product = extractDataFrom(myProduct).as(Product.class);
+ assertProduct(product);
+ }
+
+
+ private void assertProduct(Product product) {
+assertThat(product)
+ .isEqualTo(new Product(1234, "product", 42))
+ .usingRecursiveComparison();
+ }
+
+}
diff --git a/powertools-serialization/src/test/java/software/amazon/lambda/powertools/utilities/model/Basket.java b/powertools-serialization/src/test/java/software/amazon/lambda/powertools/utilities/model/Basket.java
new file mode 100644
index 000000000..228089c52
--- /dev/null
+++ b/powertools-serialization/src/test/java/software/amazon/lambda/powertools/utilities/model/Basket.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2022 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.utilities.model;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+public class Basket {
+ private List products = new ArrayList<>();
+
+ public List getProducts() {
+ return products;
+ }
+
+ public void setProducts(List products) {
+ this.products = products;
+ }
+
+ public Basket() {
+ }
+
+ public Basket( Product ...p){
+ products.addAll(Arrays.asList(p));
+ }
+
+ public void add(Product product) {
+ products.add(product);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Basket basket = (Basket) o;
+ return products.equals(basket.products);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(products);
+ }
+}
diff --git a/powertools-serialization/src/test/java/software/amazon/lambda/powertools/utilities/model/Product.java b/powertools-serialization/src/test/java/software/amazon/lambda/powertools/utilities/model/Product.java
new file mode 100644
index 000000000..f03f6d426
--- /dev/null
+++ b/powertools-serialization/src/test/java/software/amazon/lambda/powertools/utilities/model/Product.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2022 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.utilities.model;
+
+import java.util.Objects;
+
+public class Product {
+ private long id;
+
+ private String name;
+
+ private double price;
+
+ public Product() {
+ }
+
+ public Product(long id, String name, double price) {
+ this.id = id;
+ this.name = name;
+ this.price = price;
+ }
+
+ public long getId() {
+ return id;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public double getPrice() {
+ return price;
+ }
+
+ public void setPrice(double price) {
+ this.price = price;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Product product = (Product) o;
+ return id == product.id && Double.compare(product.price, price) == 0 && Objects.equals(name, product.name);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id, name, price);
+ }
+}
diff --git a/powertools-serialization/src/test/resources/apigw_event.json b/powertools-serialization/src/test/resources/apigw_event.json
new file mode 100644
index 000000000..7758cb0bb
--- /dev/null
+++ b/powertools-serialization/src/test/resources/apigw_event.json
@@ -0,0 +1,62 @@
+{
+ "body": "{\"id\":1234, \"name\":\"product\", \"price\":42}",
+ "resource": "/{proxy+}",
+ "path": "/path/to/resource",
+ "httpMethod": "POST",
+ "isBase64Encoded": false,
+ "queryStringParameters": {
+ "foo": "bar"
+ },
+ "pathParameters": {
+ "proxy": "/path/to/resource"
+ },
+ "stageVariables": {
+ "baz": "qux"
+ },
+ "headers": {
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
+ "Accept-Encoding": "gzip, deflate, sdch",
+ "Accept-Language": "en-US,en;q=0.8",
+ "Cache-Control": "max-age=0",
+ "CloudFront-Forwarded-Proto": "https",
+ "CloudFront-Is-Desktop-Viewer": "true",
+ "CloudFront-Is-Mobile-Viewer": "false",
+ "CloudFront-Is-SmartTV-Viewer": "false",
+ "CloudFront-Is-Tablet-Viewer": "false",
+ "CloudFront-Viewer-Country": "US",
+ "Host": "1234567890.execute-api.us-east-1.amazonaws.com",
+ "Upgrade-Insecure-Requests": "1",
+ "User-Agent": "Custom User Agent String",
+ "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)",
+ "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==",
+ "X-Forwarded-For": "127.0.0.1, 127.0.0.2",
+ "X-Forwarded-Port": "443",
+ "X-Forwarded-Proto": "https"
+ },
+ "requestContext": {
+ "accountId": "123456789012",
+ "resourceId": "123456",
+ "stage": "prod",
+ "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef",
+ "requestTime": "09/Apr/2015:12:34:56 +0000",
+ "requestTimeEpoch": 1428582896000,
+ "identity": {
+ "cognitoIdentityPoolId": null,
+ "accountId": null,
+ "cognitoIdentityId": null,
+ "caller": null,
+ "accessKey": null,
+ "sourceIp": "127.0.0.1",
+ "cognitoAuthenticationType": null,
+ "cognitoAuthenticationProvider": null,
+ "userArn": null,
+ "userAgent": "Custom User Agent String",
+ "user": null
+ },
+ "path": "/prod/path/to/resource",
+ "resourcePath": "/{proxy+}",
+ "httpMethod": "POST",
+ "apiId": "1234567890",
+ "protocol": "HTTP/1.1"
+ }
+}
diff --git a/powertools-serialization/src/test/resources/apigw_event_no_body.json b/powertools-serialization/src/test/resources/apigw_event_no_body.json
new file mode 100644
index 000000000..f534c91a3
--- /dev/null
+++ b/powertools-serialization/src/test/resources/apigw_event_no_body.json
@@ -0,0 +1,61 @@
+{
+ "resource": "/{proxy+}",
+ "path": "/path/to/resource",
+ "httpMethod": "POST",
+ "isBase64Encoded": false,
+ "queryStringParameters": {
+ "foo": "bar"
+ },
+ "pathParameters": {
+ "proxy": "/path/to/resource"
+ },
+ "stageVariables": {
+ "baz": "qux"
+ },
+ "headers": {
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
+ "Accept-Encoding": "gzip, deflate, sdch",
+ "Accept-Language": "en-US,en;q=0.8",
+ "Cache-Control": "max-age=0",
+ "CloudFront-Forwarded-Proto": "https",
+ "CloudFront-Is-Desktop-Viewer": "true",
+ "CloudFront-Is-Mobile-Viewer": "false",
+ "CloudFront-Is-SmartTV-Viewer": "false",
+ "CloudFront-Is-Tablet-Viewer": "false",
+ "CloudFront-Viewer-Country": "US",
+ "Host": "1234567890.execute-api.us-east-1.amazonaws.com",
+ "Upgrade-Insecure-Requests": "1",
+ "User-Agent": "Custom User Agent String",
+ "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)",
+ "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==",
+ "X-Forwarded-For": "127.0.0.1, 127.0.0.2",
+ "X-Forwarded-Port": "443",
+ "X-Forwarded-Proto": "https"
+ },
+ "requestContext": {
+ "accountId": "123456789012",
+ "resourceId": "123456",
+ "stage": "prod",
+ "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef",
+ "requestTime": "09/Apr/2015:12:34:56 +0000",
+ "requestTimeEpoch": 1428582896000,
+ "identity": {
+ "cognitoIdentityPoolId": null,
+ "accountId": null,
+ "cognitoIdentityId": null,
+ "caller": null,
+ "accessKey": null,
+ "sourceIp": "127.0.0.1",
+ "cognitoAuthenticationType": null,
+ "cognitoAuthenticationProvider": null,
+ "userArn": null,
+ "userAgent": "Custom User Agent String",
+ "user": null
+ },
+ "path": "/prod/path/to/resource",
+ "resourcePath": "/{proxy+}",
+ "httpMethod": "POST",
+ "apiId": "1234567890",
+ "protocol": "HTTP/1.1"
+ }
+}
diff --git a/powertools-serialization/src/test/resources/kafka_event.json b/powertools-serialization/src/test/resources/kafka_event.json
new file mode 100644
index 000000000..cf1bad615
--- /dev/null
+++ b/powertools-serialization/src/test/resources/kafka_event.json
@@ -0,0 +1,27 @@
+{
+ "eventSource": "aws:kafka",
+ "eventSourceArn": "arn:aws:kafka:us-east-1:123456789012:cluster/vpc-3432434/4834-3547-3455-9872-7929",
+ "bootstrapServers": "b-2.demo-cluster-1.a1bcde.c1.kafka.us-east-1.amazonaws.com:9092,b-1.demo-cluster-1.a1bcde.c1.kafka.us-east-1.amazonaws.com:9092",
+ "records": {
+ "mytopic-01": [
+ {
+ "topic": "mytopic1",
+ "partition": 0,
+ "offset": 15,
+ "timestamp": 1596480920837,
+ "timestampType": "CREATE_TIME",
+ "value": "eyJpZCI6MTIzNCwgIm5hbWUiOiJwcm9kdWN0IiwgInByaWNlIjo0Mn0="
+ }
+ ],
+ "mytopic-02": [
+ {
+ "topic": "mytopic2",
+ "partition": 0,
+ "offset": 15,
+ "timestamp": 1596480920838,
+ "timestampType": "CREATE_TIME",
+ "value": "eyJpZCI6MTIzNDUsICJuYW1lIjoicHJvZHVjdDUiLCAicHJpY2UiOjQ1fQ=="
+ }
+ ]
+ }
+}
diff --git a/powertools-serialization/src/test/resources/kinesis_event.json b/powertools-serialization/src/test/resources/kinesis_event.json
new file mode 100644
index 000000000..5b95ddaf4
--- /dev/null
+++ b/powertools-serialization/src/test/resources/kinesis_event.json
@@ -0,0 +1,38 @@
+{
+ "Records": [
+ {
+ "kinesis": {
+ "partitionKey": "partitionKey-03",
+ "kinesisSchemaVersion": "1.0",
+ "data": "eyJpZCI6MTIzNCwgIm5hbWUiOiJwcm9kdWN0IiwgInByaWNlIjo0Mn0=",
+ "sequenceNumber": "49545115243490985018280067714973144582180062593244200961",
+ "approximateArrivalTimestamp": 1428537600,
+ "encryptionType": "NONE"
+ },
+ "eventSource": "aws:kinesis",
+ "eventID": "shardId-000000000000:49545115243490985018280067714973144582180062593244200961",
+ "invokeIdentityArn": "arn:aws:iam::EXAMPLE",
+ "eventVersion": "1.0",
+ "eventName": "aws:kinesis:record",
+ "eventSourceARN": "arn:aws:kinesis:EXAMPLE",
+ "awsRegion": "eu-central-1"
+ },
+ {
+ "kinesis": {
+ "partitionKey": "partitionKey-03",
+ "kinesisSchemaVersion": "1.0",
+ "data": "eyJpZCI6MTIzNDUsICJuYW1lIjoicHJvZHVjdDUiLCAicHJpY2UiOjQ1fQ==",
+ "sequenceNumber": "49545115243490985018280067714973144582180062593244200961",
+ "approximateArrivalTimestamp": 1428537600,
+ "encryptionType": "NONE"
+ },
+ "eventSource": "aws:kinesis",
+ "eventID": "shardId-000000000000:49545115243490985018280067714973144582180062593244200961",
+ "invokeIdentityArn": "arn:aws:iam::EXAMPLE",
+ "eventVersion": "1.0",
+ "eventName": "aws:kinesis:record",
+ "eventSourceARN": "arn:aws:kinesis:EXAMPLE",
+ "awsRegion": "eu-central-1"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/powertools-serialization/src/test/resources/sns_event.json b/powertools-serialization/src/test/resources/sns_event.json
new file mode 100644
index 000000000..317a657d9
--- /dev/null
+++ b/powertools-serialization/src/test/resources/sns_event.json
@@ -0,0 +1,27 @@
+{
+ "Records": [
+ {
+ "EventSource": "aws:sns",
+ "EventVersion": "1.0",
+ "EventSubscriptionArn": "arn:aws:sns:eu-central-1:123456789012:TopicSendToMe:e3ddc7d5-2f86-40b8-a13d-3362f94fd8dd",
+ "Sns": {
+ "Type": "Notification",
+ "MessageId": "dc918f50-80c6-56a2-ba33-d8a9bbf013ab",
+ "TopicArn": "arn:aws:sns:eu-central-1:123456789012:TopicSendToMe",
+ "Subject": "Test sns message",
+ "Message": "{\"id\":1234, \"name\":\"product\", \"price\":42}",
+ "Timestamp": "2020-10-08T16:06:14.656Z",
+ "SignatureVersion": "1",
+ "Signature": "UWnPpkqPAphyr+6PXzUF9++4zJcw==",
+ "SigningCertUrl": "https://sns.eu-central-1.amazonaws.com/SimpleNotificationService-a86cb10b4e1f29c941702d737128f7b6.pem",
+ "UnsubscribeUrl": "https://sns.eu-central-1.amazonaws.com/?Action=Unsubscribe",
+ "MessageAttributes": {
+ "name": {
+ "Type": "String",
+ "Value": "Bob"
+ }
+ }
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/powertools-serialization/src/test/resources/sqs_event.json b/powertools-serialization/src/test/resources/sqs_event.json
new file mode 100644
index 000000000..d33db4b53
--- /dev/null
+++ b/powertools-serialization/src/test/resources/sqs_event.json
@@ -0,0 +1,40 @@
+{
+ "Records": [
+ {
+ "messageId": "d9144555-9a4f-4ec3-99a0-34ce359b4b54",
+ "receiptHandle": "13e7f7851d2eaa5c01f208ebadbf1e72==",
+ "body": "{\n \"id\": 1234,\n \"name\": \"product\",\n \"price\": 42\n}",
+ "attributes": {
+ "ApproximateReceiveCount": "1",
+ "SentTimestamp": "1601975706495",
+ "SenderId": "AROAIFU437PVZ5L2J53F5",
+ "ApproximateFirstReceiveTimestamp": "1601975706499"
+ },
+ "messageAttributes": {
+
+ },
+ "md5OfBody": "13e7f7851d2eaa5c01f208ebadbf1e72",
+ "eventSource": "aws:sqs",
+ "eventSourceARN": "arn:aws:sqs:eu-central-1:123456789012:TestLambda",
+ "awsRegion": "eu-central-1"
+ },
+ {
+ "messageId": "d9144555-9a4f-4ec3-99a0-34ce359b4b54",
+ "receiptHandle": "13e7f7851d2eaa5c01f208ebadbf1e72==",
+ "body": "{\n \"id\": 12345,\n \"name\": \"product5\",\n \"price\": 45\n}",
+ "attributes": {
+ "ApproximateReceiveCount": "1",
+ "SentTimestamp": "1601975706495",
+ "SenderId": "AROAIFU437PVZ5L2J53F5",
+ "ApproximateFirstReceiveTimestamp": "1601975706499"
+ },
+ "messageAttributes": {
+
+ },
+ "md5OfBody": "13e7f7851d2eaa5c01f208ebadbf1e72",
+ "eventSource": "aws:sqs",
+ "eventSourceARN": "arn:aws:sqs:eu-central-1:123456789012:TestLambda",
+ "awsRegion": "eu-central-1"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/powertools-serialization/src/test/resources/sqs_event_no_body.json b/powertools-serialization/src/test/resources/sqs_event_no_body.json
new file mode 100644
index 000000000..3a313dd6b
--- /dev/null
+++ b/powertools-serialization/src/test/resources/sqs_event_no_body.json
@@ -0,0 +1,21 @@
+{
+ "Records": [
+ {
+ "messageId": "d9144555-9a4f-4ec3-99a0-34ce359b4b54",
+ "receiptHandle": "13e7f7851d2eaa5c01f208ebadbf1e72==",
+ "attributes": {
+ "ApproximateReceiveCount": "1",
+ "SentTimestamp": "1601975706495",
+ "SenderId": "AROAIFU437PVZ5L2J53F5",
+ "ApproximateFirstReceiveTimestamp": "1601975706499"
+ },
+ "messageAttributes": {
+
+ },
+ "md5OfBody": "13e7f7851d2eaa5c01f208ebadbf1e72",
+ "eventSource": "aws:sqs",
+ "eventSourceARN": "arn:aws:sqs:eu-central-1:123456789012:TestLambda",
+ "awsRegion": "eu-central-1"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/powertools-validation/pom.xml b/powertools-validation/pom.xml
index db73fb12d..721f30a28 100644
--- a/powertools-validation/pom.xml
+++ b/powertools-validation/pom.xml
@@ -87,6 +87,11 @@
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/internal/ValidationAspect.java b/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/internal/ValidationAspect.java
index b42ce71ab..0a1f00599 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
@@ -91,8 +91,14 @@ && placedOnRequestHandler(pjp)) {
event.getRecords().forEach(record -> validate(decode(record.getData()), inboundJsonSchema));
} else if (obj instanceof KafkaEvent) {
KafkaEvent event = (KafkaEvent) obj;
- event.getRecords().forEach((s, records) -> records.forEach(record -> validate(record.getValue(), inboundJsonSchema)));
- }else if (obj instanceof KinesisAnalyticsFirehoseInputPreprocessingEvent) {
+ event.getRecords().forEach((s, records) -> records.forEach(record -> validate(decode(record.getValue()), inboundJsonSchema)));
+ } else if (obj instanceof ActiveMQEvent) {
+ ActiveMQEvent event = (ActiveMQEvent) obj;
+ event.getMessages().forEach(record -> validate(decode(record.getData()), inboundJsonSchema));
+ } else if (obj instanceof RabbitMQEvent) {
+ RabbitMQEvent event = (RabbitMQEvent) obj;
+ event.getRmqMessagesByQueue().forEach((s, records) -> records.forEach(record -> validate(decode(record.getData()), inboundJsonSchema)));
+ } else if (obj instanceof KinesisAnalyticsFirehoseInputPreprocessingEvent) {
KinesisAnalyticsFirehoseInputPreprocessingEvent event = (KinesisAnalyticsFirehoseInputPreprocessingEvent) obj;
event.getRecords().forEach(record -> validate(decode(record.getData()), inboundJsonSchema));
} else if (obj instanceof KinesisAnalyticsStreamsInputPreprocessingEvent) {
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 2bbe7cdaa..c8741f1e3 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
@@ -18,12 +18,14 @@
import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent;
import com.amazonaws.services.lambda.runtime.events.KinesisEvent;
import com.amazonaws.services.lambda.runtime.events.SQSEvent;
+import com.amazonaws.services.lambda.runtime.serialization.PojoSerializer;
+import com.amazonaws.services.lambda.runtime.serialization.events.LambdaEventSerializers;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import software.amazon.lambda.powertools.validation.ValidationException;
import software.amazon.lambda.powertools.validation.ValidationConfig;
+import software.amazon.lambda.powertools.validation.ValidationException;
import software.amazon.lambda.powertools.validation.handlers.*;
import software.amazon.lambda.powertools.validation.model.MyCustomEvent;
@@ -91,16 +93,18 @@ public void validate_inputKO_schemaInString_shouldThrowValidationException() {
}
@Test
- public void validate_SQS() throws IOException {
- SQSEvent event = ValidationConfig.get().getObjectMapper().readValue(this.getClass().getResourceAsStream("/sqs.json"), SQSEvent.class);
+ public void validate_SQS() {
+ PojoSerializer pojoSerializer = LambdaEventSerializers.serializerFor(SQSEvent.class, ClassLoader.getSystemClassLoader());
+ SQSEvent event = pojoSerializer.fromJson(this.getClass().getResourceAsStream("/sqs.json"));
SQSHandler handler = new SQSHandler();
assertThat(handler.handleRequest(event, context)).isEqualTo("OK");
}
@Test
- public void validate_Kinesis() throws IOException {
- KinesisEvent event = ValidationConfig.get().getObjectMapper().readValue(this.getClass().getResourceAsStream("/kinesis.json"), KinesisEvent.class);
+ public void validate_Kinesis() {
+ PojoSerializer pojoSerializer = LambdaEventSerializers.serializerFor(KinesisEvent.class, ClassLoader.getSystemClassLoader());
+ KinesisEvent event = pojoSerializer.fromJson(this.getClass().getResourceAsStream("/kinesis.json"));
KinesisHandler handler = new KinesisHandler();
assertThat(handler.handleRequest(event, context)).isEqualTo("OK");
diff --git a/powertools-validation/src/test/resources/kinesis.json b/powertools-validation/src/test/resources/kinesis.json
index 6d99be7e5..ad33ae456 100644
--- a/powertools-validation/src/test/resources/kinesis.json
+++ b/powertools-validation/src/test/resources/kinesis.json
@@ -1,5 +1,5 @@
{
- "records": [
+ "Records": [
{
"kinesis": {
"partitionKey": "partitionKey-03",
diff --git a/powertools-validation/src/test/resources/sqs.json b/powertools-validation/src/test/resources/sqs.json
index 9180c5839..129e79bb2 100644
--- a/powertools-validation/src/test/resources/sqs.json
+++ b/powertools-validation/src/test/resources/sqs.json
@@ -1,5 +1,5 @@
{
- "records": [
+ "Records": [
{
"messageId": "d9144555-9a4f-4ec3-99a0-fc4e625a8db2",
"receiptHandle": "7kam5bfzbDsjtcjElvhSbxeLJbeey3A==",
diff --git a/spotbugs-exclude.xml b/spotbugs-exclude.xml
index 30e627a56..0c8d1d8f8 100644
--- a/spotbugs-exclude.xml
+++ b/spotbugs-exclude.xml
@@ -77,6 +77,14 @@
+
+
+
+
+
+
+
+