From 72d8eac36797f4d030fb62fd2f1dec0476de4698 Mon Sep 17 00:00:00 2001
From: Jerome Van Der Linden
Date: Tue, 20 Oct 2020 00:28:48 +0200
Subject: [PATCH 1/7] module validation
---
powertools-validation/pom.xml | 108 ++++++++
.../powertools/validation/Validation.java | 109 ++++++++
.../validation/ValidationException.java | 27 ++
.../powertools/validation/Validator.java | 198 ++++++++++++++
.../validation/ValidatorConfig.java | 105 ++++++++
.../validation/internal/ValidationAspect.java | 134 ++++++++++
.../validation/jmespath/Base64Function.java | 45 ++++
.../jmespath/Base64GZipFunction.java | 65 +++++
.../main/resources/schemas/meta/applicator | 56 ++++
.../src/main/resources/schemas/meta/content | 17 ++
.../src/main/resources/schemas/meta/core | 57 ++++
.../src/main/resources/schemas/meta/format | 14 +
.../src/main/resources/schemas/meta/meta-data | 37 +++
.../main/resources/schemas/meta/validation | 98 +++++++
.../resources/schemas/meta_schema_V201909 | 42 +++
.../src/main/resources/schemas/meta_schema_V4 | 149 +++++++++++
.../src/main/resources/schemas/meta_schema_V6 | 155 +++++++++++
.../src/main/resources/schemas/meta_schema_V7 | 172 ++++++++++++
.../validation/Base64FunctionTest.java | 26 ++
.../validation/Base64GZipFunctionTest.java | 22 ++
.../powertools/validation/ValidatorTest.java | 249 ++++++++++++++++++
.../validation/handlers/KinesisHandler.java | 15 ++
.../handlers/MyCustomEventHandler.java | 15 ++
.../validation/handlers/SQSHandler.java | 15 ++
.../ValidationInboundClasspathHandler.java | 29 ++
.../ValidationInboundStringHandler.java | 85 ++++++
.../internal/ValidationAspectTest.java | 116 ++++++++
.../powertools/validation/model/Basket.java | 33 +++
.../validation/model/MyCustomEvent.java | 20 ++
.../powertools/validation/model/Product.java | 42 +++
.../src/test/resources/custom_event.json | 12 +
.../src/test/resources/custom_event_gzip.json | 12 +
.../src/test/resources/json_ko.json | 5 +
.../src/test/resources/json_ok.json | 5 +
.../src/test/resources/kinesis.json | 38 +++
.../src/test/resources/schema_v4.json | 22 ++
.../src/test/resources/schema_v7.json | 55 ++++
.../src/test/resources/schema_v7_ko.json | 54 ++++
.../src/test/resources/sqs.json | 22 ++
39 files changed, 2480 insertions(+)
create mode 100644 powertools-validation/pom.xml
create mode 100644 powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/Validation.java
create mode 100644 powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/ValidationException.java
create mode 100644 powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/Validator.java
create mode 100644 powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/ValidatorConfig.java
create mode 100644 powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/internal/ValidationAspect.java
create mode 100644 powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/jmespath/Base64Function.java
create mode 100644 powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/jmespath/Base64GZipFunction.java
create mode 100644 powertools-validation/src/main/resources/schemas/meta/applicator
create mode 100644 powertools-validation/src/main/resources/schemas/meta/content
create mode 100644 powertools-validation/src/main/resources/schemas/meta/core
create mode 100644 powertools-validation/src/main/resources/schemas/meta/format
create mode 100644 powertools-validation/src/main/resources/schemas/meta/meta-data
create mode 100644 powertools-validation/src/main/resources/schemas/meta/validation
create mode 100644 powertools-validation/src/main/resources/schemas/meta_schema_V201909
create mode 100644 powertools-validation/src/main/resources/schemas/meta_schema_V4
create mode 100644 powertools-validation/src/main/resources/schemas/meta_schema_V6
create mode 100644 powertools-validation/src/main/resources/schemas/meta_schema_V7
create mode 100644 powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/Base64FunctionTest.java
create mode 100644 powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/Base64GZipFunctionTest.java
create mode 100644 powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/ValidatorTest.java
create mode 100644 powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/KinesisHandler.java
create mode 100644 powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/MyCustomEventHandler.java
create mode 100644 powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/SQSHandler.java
create mode 100644 powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/ValidationInboundClasspathHandler.java
create mode 100644 powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/ValidationInboundStringHandler.java
create mode 100644 powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/internal/ValidationAspectTest.java
create mode 100644 powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/model/Basket.java
create mode 100644 powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/model/MyCustomEvent.java
create mode 100644 powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/model/Product.java
create mode 100644 powertools-validation/src/test/resources/custom_event.json
create mode 100644 powertools-validation/src/test/resources/custom_event_gzip.json
create mode 100644 powertools-validation/src/test/resources/json_ko.json
create mode 100644 powertools-validation/src/test/resources/json_ok.json
create mode 100644 powertools-validation/src/test/resources/kinesis.json
create mode 100644 powertools-validation/src/test/resources/schema_v4.json
create mode 100644 powertools-validation/src/test/resources/schema_v7.json
create mode 100644 powertools-validation/src/test/resources/schema_v7_ko.json
create mode 100644 powertools-validation/src/test/resources/sqs.json
diff --git a/powertools-validation/pom.xml b/powertools-validation/pom.xml
new file mode 100644
index 000000000..f730908e5
--- /dev/null
+++ b/powertools-validation/pom.xml
@@ -0,0 +1,108 @@
+
+
+ 4.0.0
+
+ powertools-validation
+ jar
+
+
+ powertools-parent
+ software.amazon.lambda
+ 0.5.0-beta
+
+
+ AWS Lambda Powertools Java validation library
+
+ Json schema validation for Lambda events and responses
+
+ https://aws.amazon.com/lambda/
+
+ GitHub Issues
+ https://github.com/awslabs/aws-lambda-powertools-java/issues
+
+
+
+ https://github.com/awslabs/aws-lambda-powertools-java.git
+
+
+
+ AWS Lambda Powertools team
+ Amazon Web Services
+ https://aws.amazon.com/
+
+
+
+
+
+ ossrh
+ https://aws.oss.sonatype.org/content/repositories/snapshots
+
+
+
+
+
+ software.amazon.lambda
+ powertools-core
+
+
+ com.amazonaws
+ aws-lambda-java-events
+
+
+ com.amazonaws
+ aws-lambda-java-core
+
+
+ io.burt
+ jmespath-jackson
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+
+
+ org.aspectj
+ aspectjrt
+
+
+ com.networknt
+ json-schema-validator
+ 1.0.43
+
+
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ test
+
+
+ org.apache.commons
+ commons-lang3
+ test
+
+
+ org.mockito
+ mockito-core
+ test
+
+
+ org.aspectj
+ aspectjweaver
+ test
+
+
+ org.assertj
+ assertj-core
+ test
+
+
+
+
\ No newline at end of file
diff --git a/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/Validation.java b/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/Validation.java
new file mode 100644
index 000000000..26faea9b8
--- /dev/null
+++ b/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/Validation.java
@@ -0,0 +1,109 @@
+/*
+ * 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;
+
+import com.amazonaws.services.lambda.runtime.Context;
+import com.networknt.schema.SpecVersion;
+import com.networknt.schema.SpecVersion.VersionFlag;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import static com.networknt.schema.SpecVersion.VersionFlag.*;
+
+/**
+ * {@link Validation} is used to specify that the annotated method input and/or output needs to be valid.
+ *
+ *
{@link Validation} should be used on the {@link com.amazonaws.services.lambda.runtime.RequestHandler#handleRequest(Object, Context)}
+ * or {@link com.amazonaws.services.lambda.runtime.RequestStreamHandler#handleRequest(InputStream, OutputStream, Context)} methods.
+ *
+ *
Using the Java language, {@link com.amazonaws.services.lambda.runtime.RequestHandler} input and output are already
+ * strongly typed, and if a json event cannot be deserialize to the specified object,
+ * invocation will either fail or retrieve a partial event.
+ * More information in the documentation (java-handler).
+ *
+ *
But when using built-in types from the
+ * aws-lambda-java-events library,
+ * such as {@link com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent}
+ * or {@link com.amazonaws.services.lambda.runtime.events.SQSEvent},
+ * using the {@link Validation} annotation will permit to validate the underlying content,
+ * for example the body of an API Gateway request, or the records body of an SQS event.
+ *
+ *
{@link Validation} has built-in validation for the following input types:
+ *
+ * You can specify either inboundSchema or outboundSchema or both, depending on what you want to validate.
+ * The schema must be passed as a json string (constant), or using the syntax {@code "classpath:/some/path/to/schema.json" },
+ * provided that the schema.json file is available in the classpath at the specified path.
+ *
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface Validation {
+ /**
+ * schema used to validate the lambda function input
+ */
+ String inboundSchema() default "";
+
+ /**
+ * schema used to validate the lambda function output
+ */
+ String outboundSchema() default "";
+
+ /**
+ * path to the subelement
+ */
+ String envelope() default "";
+
+ /**
+ * json schema specification version (default is 2019-09)
+ */
+ VersionFlag schemaVersion() default V7;
+}
diff --git a/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/ValidationException.java b/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/ValidationException.java
new file mode 100644
index 000000000..901d78a44
--- /dev/null
+++ b/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/ValidationException.java
@@ -0,0 +1,27 @@
+/*
+ * 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;
+
+public class ValidationException extends RuntimeException {
+
+ private static final long serialVersionUID = 1133341411263381508L;
+
+ public ValidationException(String message) {
+ super(message);
+ }
+
+ public ValidationException(Exception e) {
+ super(e);
+ }
+}
diff --git a/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/Validator.java b/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/Validator.java
new file mode 100644
index 000000000..6ea5ec10c
--- /dev/null
+++ b/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/Validator.java
@@ -0,0 +1,198 @@
+package software.amazon.lambda.powertools.validation;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.JsonNodeType;
+import com.networknt.schema.JsonSchema;
+import com.networknt.schema.ValidationMessage;
+import io.burt.jmespath.Expression;
+import software.amazon.lambda.powertools.validation.internal.ValidationAspect;
+
+import java.io.InputStream;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+
+public class Validator {
+ private static final String CLASSPATH = "classpath:";
+
+ private static final ConcurrentHashMap schemas = new ConcurrentHashMap<>();
+
+ /**
+ * @param obj
+ * @param jsonSchema
+ * @param envelope
+ */
+ public static void validate(Object obj, JsonSchema jsonSchema, String envelope) {
+ if (envelope == null || envelope.isEmpty()) {
+ validate(obj, jsonSchema);
+ return;
+ }
+ JsonNode subNode;
+ try {
+ JsonNode jsonNode = ValidatorConfig.get().getObjectMapper().valueToTree(obj);
+ Expression expression = ValidatorConfig.get().getJmesPath().compile(envelope);
+ subNode = expression.search(jsonNode);
+ } catch (Exception e) {
+ throw new ValidationException(e);
+ }
+ if (subNode.getNodeType() == JsonNodeType.ARRAY) {
+ subNode.forEach(jsonNode -> validate(jsonNode, jsonSchema));
+ } else if (subNode.getNodeType() == JsonNodeType.OBJECT) {
+ validate(subNode, jsonSchema);
+ } else if (subNode.getNodeType() == JsonNodeType.STRING) {
+ // try to validate as json string
+ try {
+ validate(subNode.asText(), jsonSchema);
+ } catch (ValidationException e) {
+ throw new ValidationException("Invalid format for '" + envelope + "': 'STRING' and no JSON found in it.");
+ }
+ } else {
+ throw new ValidationException("Invalid format for '" + envelope + "': '" + subNode.getNodeType() + "'");
+ }
+ }
+
+ /**
+ * Validate a json object against a json schema
+ *
+ * @param obj object to validate
+ * @param jsonSchema the schema used to validate
+ * @throws ValidationException if validation fails
+ */
+ public static void validate(Object obj, JsonSchema jsonSchema) {
+ JsonNode jsonNode;
+ try {
+ jsonNode = ValidatorConfig.get().getObjectMapper().valueToTree(obj);
+ } catch (Exception e) {
+ throw new ValidationException(e);
+ }
+
+ validate(jsonNode, jsonSchema);
+ }
+
+ /**
+ * Validate a json object (in string format) against a json schema
+ *
+ * @param json json in string format
+ * @param jsonSchema the schema used to validate json string
+ * @throws ValidationException if validation fails
+ */
+ public static void validate(String json, JsonSchema jsonSchema) throws ValidationException {
+ JsonNode jsonNode;
+ try {
+ jsonNode = ValidatorConfig.get().getObjectMapper().readTree(json);
+ } catch (JsonProcessingException e) {
+ throw new ValidationException(e);
+ }
+
+ validate(jsonNode, jsonSchema);
+ }
+
+ /**
+ * Validate a json object (in map format) against a json schema
+ *
+ * @param map map to be transformed in json and validated against the schema
+ * @param jsonSchema the schema used to validate json map
+ * @throws ValidationException if validation fails
+ */
+ public static void validate(Map map, JsonSchema jsonSchema) throws ValidationException {
+ JsonNode jsonNode;
+ try {
+ jsonNode = ValidatorConfig.get().getObjectMapper().valueToTree(map);
+ } catch (Exception e) {
+ throw new ValidationException(e);
+ }
+
+ validate(jsonNode, jsonSchema);
+ }
+
+ /**
+ * Validate a json object (in JsonNode format) against a json schema.
+ * Perform the actual validation.
+ *
+ * @param jsonNode json to be validated against the schema
+ * @param jsonSchema the schema to validate json node
+ * @throws ValidationException if validation fails
+ */
+ public static void validate(JsonNode jsonNode, JsonSchema jsonSchema) {
+ Set validationMessages = jsonSchema.validate(jsonNode);
+ if (!validationMessages.isEmpty()) {
+ String message;
+ try {
+ message = ValidatorConfig.get().getObjectMapper().writeValueAsString(new ValidationErrors(validationMessages));
+ } catch (JsonProcessingException e) {
+ message = validationMessages.stream().map(ValidationMessage::getMessage).collect(Collectors.joining(", "));
+ }
+ throw new ValidationException(message);
+ }
+ }
+
+ /**
+ * Retrieve {@link JsonSchema} from string (either the schema itself, either from the classpath).
+ * No validation of the schema will be performed (equivalent to
getJsonSchema(schema, false)
+ * Store it in memory to avoid reloading it.
+ *
+ * @param schema either the schema itself of a "classpath:/path/to/schema.json"
+ * @return the loaded json schema
+ */
+ public static JsonSchema getJsonSchema(String schema) {
+ return getJsonSchema(schema, false);
+ }
+
+ /**
+ * Retrieve {@link JsonSchema} from string (either the schema itself, either from the classpath).
+ * Optional: validate the schema against the version specifications.
+ * Store it in memory to avoid reloading it.
+ *
+ * @param schema either the schema itself of a "classpath:/path/to/schema.json"
+ * @param validateSchema specify if the schema itself must be validated against specifications
+ * @return the loaded json schema
+ */
+ public static JsonSchema getJsonSchema(String schema, boolean validateSchema) {
+ JsonSchema jsonSchema = schemas.get(schema);
+
+ if (jsonSchema == null) {
+ if (schema.startsWith(CLASSPATH)) {
+ String filePath = schema.substring(CLASSPATH.length());
+ InputStream schemaStream = ValidationAspect.class.getResourceAsStream(filePath);
+ if (schemaStream == null) {
+ throw new IllegalArgumentException("'" + schema + "' is invalid, verify '" + filePath + "' is in your classpath");
+ }
+ jsonSchema = ValidatorConfig.get().getFactory().getSchema(schemaStream);
+ } else {
+ jsonSchema = ValidatorConfig.get().getFactory().getSchema(schema);
+ }
+
+ if (validateSchema) {
+ String version = ValidatorConfig.get().getSchemaVersion().toString();
+ try {
+ validate(jsonSchema.getSchemaNode(),
+ getJsonSchema("classpath:/schemas/meta_schema_" + version));
+ } catch (ValidationException ve) {
+ throw new IllegalArgumentException("The schema " + schema + " is not valid, it does not respect the specification " + version, ve);
+ }
+ }
+
+ schemas.put(schema, jsonSchema);
+ }
+
+ return jsonSchema;
+ }
+
+ /**
+ *
+ */
+ public static class ValidationErrors {
+
+ private final Set validationErrors;
+
+ public ValidationErrors(Set validationErrors) {
+ this.validationErrors = validationErrors;
+ }
+
+ public Set getValidationErrors() {
+ return validationErrors;
+ }
+ }
+}
diff --git a/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/ValidatorConfig.java b/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/ValidatorConfig.java
new file mode 100644
index 000000000..2bc477c69
--- /dev/null
+++ b/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/ValidatorConfig.java
@@ -0,0 +1,105 @@
+package software.amazon.lambda.powertools.validation;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.networknt.schema.JsonSchemaFactory;
+import com.networknt.schema.SpecVersion;
+import io.burt.jmespath.JmesPath;
+import io.burt.jmespath.RuntimeConfiguration;
+import io.burt.jmespath.function.BaseFunction;
+import io.burt.jmespath.function.FunctionRegistry;
+import io.burt.jmespath.jackson.JacksonRuntime;
+import software.amazon.lambda.powertools.validation.jmespath.Base64Function;
+import software.amazon.lambda.powertools.validation.jmespath.Base64GZipFunction;
+
+import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES;
+
+/**
+ * Use this if you need to customize some part of the JSON Schema validation
+ * (eg. specification version, Jackson ObjectMapper, or adding functions to JMESPath)
+ */
+public class ValidatorConfig {
+ private ValidatorConfig() {
+ }
+
+ private static class ConfigHolder {
+ private final static ValidatorConfig instance = new ValidatorConfig();
+ }
+
+ public static ValidatorConfig 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 SpecVersion.VersionFlag jsonSchemaVersion = SpecVersion.VersionFlag.V7;
+ private JsonSchemaFactory factory = JsonSchemaFactory.getInstance(jsonSchemaVersion);
+
+ private final FunctionRegistry defaultFunctions = FunctionRegistry.defaultRegistry();
+ private final FunctionRegistry customFunctions = defaultFunctions.extend(
+ new Base64Function(),
+ new Base64GZipFunction());
+ private final RuntimeConfiguration configuration = new RuntimeConfiguration.Builder()
+ .withFunctionRegistry(customFunctions)
+ .build();
+ private JmesPath jmesPath = new JacksonRuntime(configuration, getObjectMapper());
+
+ /**
+ * Set the version of the json schema specifications (default is V7)
+ *
+ * @param version May be V4, V6, V7 or V201909
+ */
+ public void setSchemaVersion(SpecVersion.VersionFlag version) {
+ if (version != jsonSchemaVersion) {
+ jsonSchemaVersion = version;
+ factory = JsonSchemaFactory.getInstance(version);
+ }
+ }
+
+ public SpecVersion.VersionFlag getSchemaVersion() {
+ return jsonSchemaVersion;
+ }
+
+ /**
+ * Add a custom {@link io.burt.jmespath.function.Function} to JMESPath
+ * {@link Base64Function} and {@link Base64GZipFunction} are already built-in.
+ *
+ * @param function the function to add
+ * @param Must extends {@link BaseFunction}
+ */
+ public void addFunction(T function) {
+ configuration.functionRegistry().extend(function);
+ jmesPath = new JacksonRuntime(configuration, getObjectMapper());
+ }
+
+ /**
+ * Return the Json Schema Factory, used to load schemas
+ *
+ * @return the Json Schema Factory
+ */
+ public JsonSchemaFactory getFactory() {
+ return factory;
+ }
+
+ /**
+ * Return the JmesPath used to select sub node of Json
+ *
+ * @return the {@link JmesPath}
+ */
+ public JmesPath getJmesPath() {
+ return jmesPath;
+ }
+
+ /**
+ * Return an Object Mapper. Use this to customize (de)serialization config.
+ *
+ * @return the {@link ObjectMapper} to serialize / deserialize JSON
+ */
+ public ObjectMapper getObjectMapper() {
+ return om.get();
+ }
+}
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
new file mode 100644
index 000000000..38c4a14c7
--- /dev/null
+++ b/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/internal/ValidationAspect.java
@@ -0,0 +1,134 @@
+/*
+ * 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.internal;
+
+import com.amazonaws.services.lambda.runtime.events.*;
+import com.networknt.schema.JsonSchema;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Pointcut;
+import software.amazon.lambda.powertools.validation.Validation;
+import software.amazon.lambda.powertools.validation.ValidatorConfig;
+
+import static com.networknt.schema.SpecVersion.VersionFlag.V201909;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor.isHandlerMethod;
+import static software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor.placedOnRequestHandler;
+import static software.amazon.lambda.powertools.validation.Validator.getJsonSchema;
+import static software.amazon.lambda.powertools.validation.Validator.validate;
+import static software.amazon.lambda.powertools.validation.jmespath.Base64Function.decode;
+import static software.amazon.lambda.powertools.validation.jmespath.Base64GZipFunction.decompress;
+
+/**
+ * Aspect for {@link Validation} annotation
+ */
+@Aspect
+public class ValidationAspect {
+ @SuppressWarnings({"EmptyMethod"})
+ @Pointcut("@annotation(validation)")
+ public void callAt(Validation validation) {
+ }
+
+ @Around(value = "callAt(validation) && execution(@Validation * *.*(..))", argNames = "pjp,validation")
+ public Object around(ProceedingJoinPoint pjp,
+ Validation validation) throws Throwable {
+ Object[] proceedArgs = pjp.getArgs();
+ boolean validationNeeded = false;
+
+ if (validation.schemaVersion() != V201909) {
+ ValidatorConfig.get().setSchemaVersion(validation.schemaVersion());
+ }
+
+ if (isHandlerMethod(pjp)
+ && placedOnRequestHandler(pjp)) {
+ validationNeeded = true;
+
+ if (!validation.inboundSchema().isEmpty()) {
+ JsonSchema inboundJsonSchema = getJsonSchema(validation.inboundSchema(), true);
+
+ Object obj = pjp.getArgs()[0];
+ if (obj instanceof APIGatewayProxyRequestEvent) {
+ APIGatewayProxyRequestEvent event = (APIGatewayProxyRequestEvent) obj;
+ validate(event.getBody(), inboundJsonSchema);
+ } else if (obj instanceof APIGatewayV2HTTPEvent) {
+ APIGatewayV2HTTPEvent event = (APIGatewayV2HTTPEvent) obj;
+ validate(event.getBody(), inboundJsonSchema);
+ } else if (obj instanceof SNSEvent) {
+ SNSEvent event = (SNSEvent) obj;
+ event.getRecords().forEach(record -> validate(record.getSNS().getMessage(), inboundJsonSchema));
+ } else if (obj instanceof SQSEvent) {
+ SQSEvent event = (SQSEvent) obj;
+ event.getRecords().forEach(record -> validate(record.getBody(), inboundJsonSchema));
+ } else if (obj instanceof ScheduledEvent) {
+ ScheduledEvent event = (ScheduledEvent) obj;
+ validate(event.getDetail(), inboundJsonSchema);
+ } else if (obj instanceof ApplicationLoadBalancerRequestEvent) {
+ ApplicationLoadBalancerRequestEvent event = (ApplicationLoadBalancerRequestEvent) obj;
+ validate(event.getBody(), inboundJsonSchema);
+ } else if (obj instanceof CloudWatchLogsEvent) {
+ CloudWatchLogsEvent event = (CloudWatchLogsEvent) obj;
+ validate(decompress(decode(event.getAwsLogs().getData().getBytes(UTF_8))), inboundJsonSchema);
+ } else if (obj instanceof CloudFormationCustomResourceEvent) {
+ CloudFormationCustomResourceEvent event = (CloudFormationCustomResourceEvent) obj;
+ validate(event.getResourceProperties(), inboundJsonSchema);
+ } else if (obj instanceof KinesisEvent) {
+ KinesisEvent event = (KinesisEvent) obj;
+ event.getRecords().forEach(record -> validate(decode(record.getKinesis().getData()), inboundJsonSchema));
+ } else if (obj instanceof KinesisFirehoseEvent) {
+ KinesisFirehoseEvent event = (KinesisFirehoseEvent) obj;
+ 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) {
+ KinesisAnalyticsFirehoseInputPreprocessingEvent event = (KinesisAnalyticsFirehoseInputPreprocessingEvent) obj;
+ event.getRecords().forEach(record -> validate(decode(record.getData()), inboundJsonSchema));
+ } else if (obj instanceof KinesisAnalyticsStreamsInputPreprocessingEvent) {
+ KinesisAnalyticsStreamsInputPreprocessingEvent event = (KinesisAnalyticsStreamsInputPreprocessingEvent) obj;
+ event.getRecords().forEach(record -> validate(decode(record.getData()), inboundJsonSchema));
+ } else {
+ validate(obj, inboundJsonSchema, validation.envelope());
+ }
+ }
+ }
+
+ Object result = pjp.proceed(proceedArgs);
+
+ if (validationNeeded && !validation.outboundSchema().isEmpty()) {
+ JsonSchema outboundJsonSchema = getJsonSchema(validation.outboundSchema(), true);
+
+ if (result instanceof APIGatewayProxyResponseEvent) {
+ APIGatewayProxyResponseEvent response = (APIGatewayProxyResponseEvent) result;
+ validate(response.getBody(), outboundJsonSchema);
+ } else if (result instanceof APIGatewayV2HTTPResponse) {
+ APIGatewayV2HTTPResponse response = (APIGatewayV2HTTPResponse) result;
+ validate(response.getBody(), outboundJsonSchema);
+ } else if (result instanceof APIGatewayV2WebSocketResponse) {
+ APIGatewayV2WebSocketResponse response = (APIGatewayV2WebSocketResponse) result;
+ validate(response.getBody(), outboundJsonSchema);
+ } else if (result instanceof ApplicationLoadBalancerResponseEvent) {
+ ApplicationLoadBalancerResponseEvent response = (ApplicationLoadBalancerResponseEvent) result;
+ validate(response.getBody(), outboundJsonSchema);
+ } else if (result instanceof KinesisAnalyticsInputPreprocessingResponse) {
+ KinesisAnalyticsInputPreprocessingResponse response = (KinesisAnalyticsInputPreprocessingResponse) result;
+ response.getRecords().forEach(record -> validate(decode(record.getData()), outboundJsonSchema));
+ } else {
+ validate(result, outboundJsonSchema, validation.envelope());
+ }
+ }
+
+ return result;
+ }
+}
diff --git a/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/jmespath/Base64Function.java b/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/jmespath/Base64Function.java
new file mode 100644
index 000000000..47112722f
--- /dev/null
+++ b/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/jmespath/Base64Function.java
@@ -0,0 +1,45 @@
+package software.amazon.lambda.powertools.validation.jmespath;
+
+import io.burt.jmespath.Adapter;
+import io.burt.jmespath.JmesPathType;
+import io.burt.jmespath.function.ArgumentConstraints;
+import io.burt.jmespath.function.BaseFunction;
+import io.burt.jmespath.function.FunctionArgument;
+
+import java.nio.ByteBuffer;
+import java.util.Base64;
+import java.util.List;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+/**
+ * Function used by JMESPath to decode a Base64 encoded String into a decoded String
+ */
+public class Base64Function extends BaseFunction {
+
+ public Base64Function() {
+ super("powertools_base64", ArgumentConstraints.typeOf(JmesPathType.STRING));
+ }
+
+ @Override
+ protected T callFunction(Adapter runtime, List> arguments) {
+ T value = arguments.get(0).value();
+ String encodedString = runtime.toString(value);
+
+ String decodedString = decode(encodedString);
+
+ return runtime.createString(decodedString);
+ }
+
+ public static String decode(String encodedString) {
+ return new String(decode(encodedString.getBytes(UTF_8)));
+ }
+
+ public static String decode(ByteBuffer byteBuffer) {
+ return UTF_8.decode(byteBuffer).toString();
+ }
+
+ public static byte[] decode(byte[] encoded) {
+ return Base64.getDecoder().decode(encoded);
+ }
+}
diff --git a/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/jmespath/Base64GZipFunction.java b/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/jmespath/Base64GZipFunction.java
new file mode 100644
index 000000000..4336f1e50
--- /dev/null
+++ b/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/jmespath/Base64GZipFunction.java
@@ -0,0 +1,65 @@
+package software.amazon.lambda.powertools.validation.jmespath;
+
+import io.burt.jmespath.Adapter;
+import io.burt.jmespath.JmesPathType;
+import io.burt.jmespath.function.ArgumentConstraints;
+import io.burt.jmespath.function.BaseFunction;
+import io.burt.jmespath.function.FunctionArgument;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.Arrays;
+import java.util.List;
+import java.util.zip.GZIPInputStream;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static software.amazon.lambda.powertools.validation.jmespath.Base64Function.decode;
+
+/**
+ * Function used by JMESPath to decode a Base64 encoded GZipped String into a decoded String
+ */
+public class Base64GZipFunction extends BaseFunction {
+
+ public Base64GZipFunction() {
+ super("powertools_base64_gzip", ArgumentConstraints.typeOf(JmesPathType.STRING));
+ }
+
+ @Override
+ protected T callFunction(Adapter runtime, List> arguments) {
+ T value = arguments.get(0).value();
+ String encodedString = runtime.toString(value);
+
+ String decompressString = decompress(decode(encodedString.getBytes(UTF_8)));
+
+ return runtime.createString(decompressString);
+ }
+
+ public static String decompress(byte[] compressed) {
+ if ((compressed == null) || (compressed.length == 0)) {
+ return "";
+ }
+ try {
+ StringBuilder out = new StringBuilder();
+ if (isCompressed(compressed)) {
+ GZIPInputStream gzipStream = new GZIPInputStream(new ByteArrayInputStream(compressed));
+ BufferedReader bf = new BufferedReader(new InputStreamReader(gzipStream, UTF_8));
+
+ String line;
+ while ((line = bf.readLine()) != null) {
+ out.append(line);
+ }
+ } else {
+ out.append(Arrays.toString(compressed));
+ }
+ return out.toString();
+ } catch (IOException e) {
+ return new String(compressed);
+ }
+ }
+
+ public static boolean isCompressed(final byte[] compressed) {
+ return (compressed[0] == (byte) (GZIPInputStream.GZIP_MAGIC)) && (compressed[1] == (byte) (GZIPInputStream.GZIP_MAGIC >> 8));
+ }
+}
diff --git a/powertools-validation/src/main/resources/schemas/meta/applicator b/powertools-validation/src/main/resources/schemas/meta/applicator
new file mode 100644
index 000000000..24a1cc4f4
--- /dev/null
+++ b/powertools-validation/src/main/resources/schemas/meta/applicator
@@ -0,0 +1,56 @@
+{
+ "$schema": "https://json-schema.org/draft/2019-09/schema",
+ "$id": "https://json-schema.org/draft/2019-09/meta/applicator",
+ "$vocabulary": {
+ "https://json-schema.org/draft/2019-09/vocab/applicator": true
+ },
+ "$recursiveAnchor": true,
+
+ "title": "Applicator vocabulary meta-schema",
+ "type": ["object", "boolean"],
+ "properties": {
+ "additionalItems": { "$recursiveRef": "#" },
+ "unevaluatedItems": { "$recursiveRef": "#" },
+ "items": {
+ "anyOf": [
+ { "$recursiveRef": "#" },
+ { "$ref": "#/$defs/schemaArray" }
+ ]
+ },
+ "contains": { "$recursiveRef": "#" },
+ "additionalProperties": { "$recursiveRef": "#" },
+ "unevaluatedProperties": { "$recursiveRef": "#" },
+ "properties": {
+ "type": "object",
+ "additionalProperties": { "$recursiveRef": "#" },
+ "default": {}
+ },
+ "patternProperties": {
+ "type": "object",
+ "additionalProperties": { "$recursiveRef": "#" },
+ "propertyNames": { "format": "regex" },
+ "default": {}
+ },
+ "dependentSchemas": {
+ "type": "object",
+ "additionalProperties": {
+ "$recursiveRef": "#"
+ }
+ },
+ "propertyNames": { "$recursiveRef": "#" },
+ "if": { "$recursiveRef": "#" },
+ "then": { "$recursiveRef": "#" },
+ "else": { "$recursiveRef": "#" },
+ "allOf": { "$ref": "#/$defs/schemaArray" },
+ "anyOf": { "$ref": "#/$defs/schemaArray" },
+ "oneOf": { "$ref": "#/$defs/schemaArray" },
+ "not": { "$recursiveRef": "#" }
+ },
+ "$defs": {
+ "schemaArray": {
+ "type": "array",
+ "minItems": 1,
+ "items": { "$recursiveRef": "#" }
+ }
+ }
+}
diff --git a/powertools-validation/src/main/resources/schemas/meta/content b/powertools-validation/src/main/resources/schemas/meta/content
new file mode 100644
index 000000000..f6752a8ef
--- /dev/null
+++ b/powertools-validation/src/main/resources/schemas/meta/content
@@ -0,0 +1,17 @@
+{
+ "$schema": "https://json-schema.org/draft/2019-09/schema",
+ "$id": "https://json-schema.org/draft/2019-09/meta/content",
+ "$vocabulary": {
+ "https://json-schema.org/draft/2019-09/vocab/content": true
+ },
+ "$recursiveAnchor": true,
+
+ "title": "Content vocabulary meta-schema",
+
+ "type": ["object", "boolean"],
+ "properties": {
+ "contentMediaType": { "type": "string" },
+ "contentEncoding": { "type": "string" },
+ "contentSchema": { "$recursiveRef": "#" }
+ }
+}
diff --git a/powertools-validation/src/main/resources/schemas/meta/core b/powertools-validation/src/main/resources/schemas/meta/core
new file mode 100644
index 000000000..eb708a560
--- /dev/null
+++ b/powertools-validation/src/main/resources/schemas/meta/core
@@ -0,0 +1,57 @@
+{
+ "$schema": "https://json-schema.org/draft/2019-09/schema",
+ "$id": "https://json-schema.org/draft/2019-09/meta/core",
+ "$vocabulary": {
+ "https://json-schema.org/draft/2019-09/vocab/core": true
+ },
+ "$recursiveAnchor": true,
+
+ "title": "Core vocabulary meta-schema",
+ "type": ["object", "boolean"],
+ "properties": {
+ "$id": {
+ "type": "string",
+ "format": "uri-reference",
+ "$comment": "Non-empty fragments not allowed.",
+ "pattern": "^[^#]*#?$"
+ },
+ "$schema": {
+ "type": "string",
+ "format": "uri"
+ },
+ "$anchor": {
+ "type": "string",
+ "pattern": "^[A-Za-z][-A-Za-z0-9.:_]*$"
+ },
+ "$ref": {
+ "type": "string",
+ "format": "uri-reference"
+ },
+ "$recursiveRef": {
+ "type": "string",
+ "format": "uri-reference"
+ },
+ "$recursiveAnchor": {
+ "type": "boolean",
+ "default": false
+ },
+ "$vocabulary": {
+ "type": "object",
+ "propertyNames": {
+ "type": "string",
+ "format": "uri"
+ },
+ "additionalProperties": {
+ "type": "boolean"
+ }
+ },
+ "$comment": {
+ "type": "string"
+ },
+ "$defs": {
+ "type": "object",
+ "additionalProperties": { "$recursiveRef": "#" },
+ "default": {}
+ }
+ }
+}
diff --git a/powertools-validation/src/main/resources/schemas/meta/format b/powertools-validation/src/main/resources/schemas/meta/format
new file mode 100644
index 000000000..09bbfdda9
--- /dev/null
+++ b/powertools-validation/src/main/resources/schemas/meta/format
@@ -0,0 +1,14 @@
+{
+ "$schema": "https://json-schema.org/draft/2019-09/schema",
+ "$id": "https://json-schema.org/draft/2019-09/meta/format",
+ "$vocabulary": {
+ "https://json-schema.org/draft/2019-09/vocab/format": true
+ },
+ "$recursiveAnchor": true,
+
+ "title": "Format vocabulary meta-schema",
+ "type": ["object", "boolean"],
+ "properties": {
+ "format": { "type": "string" }
+ }
+}
diff --git a/powertools-validation/src/main/resources/schemas/meta/meta-data b/powertools-validation/src/main/resources/schemas/meta/meta-data
new file mode 100644
index 000000000..da04cff6d
--- /dev/null
+++ b/powertools-validation/src/main/resources/schemas/meta/meta-data
@@ -0,0 +1,37 @@
+{
+ "$schema": "https://json-schema.org/draft/2019-09/schema",
+ "$id": "https://json-schema.org/draft/2019-09/meta/meta-data",
+ "$vocabulary": {
+ "https://json-schema.org/draft/2019-09/vocab/meta-data": true
+ },
+ "$recursiveAnchor": true,
+
+ "title": "Meta-data vocabulary meta-schema",
+
+ "type": ["object", "boolean"],
+ "properties": {
+ "title": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "default": true,
+ "deprecated": {
+ "type": "boolean",
+ "default": false
+ },
+ "readOnly": {
+ "type": "boolean",
+ "default": false
+ },
+ "writeOnly": {
+ "type": "boolean",
+ "default": false
+ },
+ "examples": {
+ "type": "array",
+ "items": true
+ }
+ }
+}
diff --git a/powertools-validation/src/main/resources/schemas/meta/validation b/powertools-validation/src/main/resources/schemas/meta/validation
new file mode 100644
index 000000000..9f59677b3
--- /dev/null
+++ b/powertools-validation/src/main/resources/schemas/meta/validation
@@ -0,0 +1,98 @@
+{
+ "$schema": "https://json-schema.org/draft/2019-09/schema",
+ "$id": "https://json-schema.org/draft/2019-09/meta/validation",
+ "$vocabulary": {
+ "https://json-schema.org/draft/2019-09/vocab/validation": true
+ },
+ "$recursiveAnchor": true,
+
+ "title": "Validation vocabulary meta-schema",
+ "type": ["object", "boolean"],
+ "properties": {
+ "multipleOf": {
+ "type": "number",
+ "exclusiveMinimum": 0
+ },
+ "maximum": {
+ "type": "number"
+ },
+ "exclusiveMaximum": {
+ "type": "number"
+ },
+ "minimum": {
+ "type": "number"
+ },
+ "exclusiveMinimum": {
+ "type": "number"
+ },
+ "maxLength": { "$ref": "#/$defs/nonNegativeInteger" },
+ "minLength": { "$ref": "#/$defs/nonNegativeIntegerDefault0" },
+ "pattern": {
+ "type": "string",
+ "format": "regex"
+ },
+ "maxItems": { "$ref": "#/$defs/nonNegativeInteger" },
+ "minItems": { "$ref": "#/$defs/nonNegativeIntegerDefault0" },
+ "uniqueItems": {
+ "type": "boolean",
+ "default": false
+ },
+ "maxContains": { "$ref": "#/$defs/nonNegativeInteger" },
+ "minContains": {
+ "$ref": "#/$defs/nonNegativeInteger",
+ "default": 1
+ },
+ "maxProperties": { "$ref": "#/$defs/nonNegativeInteger" },
+ "minProperties": { "$ref": "#/$defs/nonNegativeIntegerDefault0" },
+ "required": { "$ref": "#/$defs/stringArray" },
+ "dependentRequired": {
+ "type": "object",
+ "additionalProperties": {
+ "$ref": "#/$defs/stringArray"
+ }
+ },
+ "const": true,
+ "enum": {
+ "type": "array",
+ "items": true
+ },
+ "type": {
+ "anyOf": [
+ { "$ref": "#/$defs/simpleTypes" },
+ {
+ "type": "array",
+ "items": { "$ref": "#/$defs/simpleTypes" },
+ "minItems": 1,
+ "uniqueItems": true
+ }
+ ]
+ }
+ },
+ "$defs": {
+ "nonNegativeInteger": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "nonNegativeIntegerDefault0": {
+ "$ref": "#/$defs/nonNegativeInteger",
+ "default": 0
+ },
+ "simpleTypes": {
+ "enum": [
+ "array",
+ "boolean",
+ "integer",
+ "null",
+ "number",
+ "object",
+ "string"
+ ]
+ },
+ "stringArray": {
+ "type": "array",
+ "items": { "type": "string" },
+ "uniqueItems": true,
+ "default": []
+ }
+ }
+}
diff --git a/powertools-validation/src/main/resources/schemas/meta_schema_V201909 b/powertools-validation/src/main/resources/schemas/meta_schema_V201909
new file mode 100644
index 000000000..2248a0c80
--- /dev/null
+++ b/powertools-validation/src/main/resources/schemas/meta_schema_V201909
@@ -0,0 +1,42 @@
+{
+ "$schema": "https://json-schema.org/draft/2019-09/schema",
+ "$id": "https://json-schema.org/draft/2019-09/schema",
+ "$vocabulary": {
+ "https://json-schema.org/draft/2019-09/vocab/core": true,
+ "https://json-schema.org/draft/2019-09/vocab/applicator": true,
+ "https://json-schema.org/draft/2019-09/vocab/validation": true,
+ "https://json-schema.org/draft/2019-09/vocab/meta-data": true,
+ "https://json-schema.org/draft/2019-09/vocab/format": false,
+ "https://json-schema.org/draft/2019-09/vocab/content": true
+ },
+ "$recursiveAnchor": true,
+
+ "title": "Core and Validation specifications meta-schema",
+ "allOf": [
+ {"$ref": "meta/core"},
+ {"$ref": "meta/applicator"},
+ {"$ref": "meta/validation"},
+ {"$ref": "meta/meta-data"},
+ {"$ref": "meta/format"},
+ {"$ref": "meta/content"}
+ ],
+ "type": ["object", "boolean"],
+ "properties": {
+ "definitions": {
+ "$comment": "While no longer an official keyword as it is replaced by $defs, this keyword is retained in the meta-schema to prevent incompatible extensions as it remains in common use.",
+ "type": "object",
+ "additionalProperties": { "$recursiveRef": "#" },
+ "default": {}
+ },
+ "dependencies": {
+ "$comment": "\"dependencies\" is no longer a keyword, but schema authors should avoid redefining it to facilitate a smooth transition to \"dependentSchemas\" and \"dependentRequired\"",
+ "type": "object",
+ "additionalProperties": {
+ "anyOf": [
+ { "$recursiveRef": "#" },
+ { "$ref": "meta/validation#/$defs/stringArray" }
+ ]
+ }
+ }
+ }
+}
diff --git a/powertools-validation/src/main/resources/schemas/meta_schema_V4 b/powertools-validation/src/main/resources/schemas/meta_schema_V4
new file mode 100644
index 000000000..bcbb84743
--- /dev/null
+++ b/powertools-validation/src/main/resources/schemas/meta_schema_V4
@@ -0,0 +1,149 @@
+{
+ "id": "http://json-schema.org/draft-04/schema#",
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "description": "Core schema meta-schema",
+ "definitions": {
+ "schemaArray": {
+ "type": "array",
+ "minItems": 1,
+ "items": { "$ref": "#" }
+ },
+ "positiveInteger": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "positiveIntegerDefault0": {
+ "allOf": [ { "$ref": "#/definitions/positiveInteger" }, { "default": 0 } ]
+ },
+ "simpleTypes": {
+ "enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ]
+ },
+ "stringArray": {
+ "type": "array",
+ "items": { "type": "string" },
+ "minItems": 1,
+ "uniqueItems": true
+ }
+ },
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "$schema": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "default": {},
+ "multipleOf": {
+ "type": "number",
+ "minimum": 0,
+ "exclusiveMinimum": true
+ },
+ "maximum": {
+ "type": "number"
+ },
+ "exclusiveMaximum": {
+ "type": "boolean",
+ "default": false
+ },
+ "minimum": {
+ "type": "number"
+ },
+ "exclusiveMinimum": {
+ "type": "boolean",
+ "default": false
+ },
+ "maxLength": { "$ref": "#/definitions/positiveInteger" },
+ "minLength": { "$ref": "#/definitions/positiveIntegerDefault0" },
+ "pattern": {
+ "type": "string",
+ "format": "regex"
+ },
+ "additionalItems": {
+ "anyOf": [
+ { "type": "boolean" },
+ { "$ref": "#" }
+ ],
+ "default": {}
+ },
+ "items": {
+ "anyOf": [
+ { "$ref": "#" },
+ { "$ref": "#/definitions/schemaArray" }
+ ],
+ "default": {}
+ },
+ "maxItems": { "$ref": "#/definitions/positiveInteger" },
+ "minItems": { "$ref": "#/definitions/positiveIntegerDefault0" },
+ "uniqueItems": {
+ "type": "boolean",
+ "default": false
+ },
+ "maxProperties": { "$ref": "#/definitions/positiveInteger" },
+ "minProperties": { "$ref": "#/definitions/positiveIntegerDefault0" },
+ "required": { "$ref": "#/definitions/stringArray" },
+ "additionalProperties": {
+ "anyOf": [
+ { "type": "boolean" },
+ { "$ref": "#" }
+ ],
+ "default": {}
+ },
+ "definitions": {
+ "type": "object",
+ "additionalProperties": { "$ref": "#" },
+ "default": {}
+ },
+ "properties": {
+ "type": "object",
+ "additionalProperties": { "$ref": "#" },
+ "default": {}
+ },
+ "patternProperties": {
+ "type": "object",
+ "additionalProperties": { "$ref": "#" },
+ "default": {}
+ },
+ "dependencies": {
+ "type": "object",
+ "additionalProperties": {
+ "anyOf": [
+ { "$ref": "#" },
+ { "$ref": "#/definitions/stringArray" }
+ ]
+ }
+ },
+ "enum": {
+ "type": "array",
+ "minItems": 1,
+ "uniqueItems": true
+ },
+ "type": {
+ "anyOf": [
+ { "$ref": "#/definitions/simpleTypes" },
+ {
+ "type": "array",
+ "items": { "$ref": "#/definitions/simpleTypes" },
+ "minItems": 1,
+ "uniqueItems": true
+ }
+ ]
+ },
+ "format": { "type": "string" },
+ "allOf": { "$ref": "#/definitions/schemaArray" },
+ "anyOf": { "$ref": "#/definitions/schemaArray" },
+ "oneOf": { "$ref": "#/definitions/schemaArray" },
+ "not": { "$ref": "#" }
+ },
+ "dependencies": {
+ "exclusiveMaximum": [ "maximum" ],
+ "exclusiveMinimum": [ "minimum" ]
+ },
+ "default": {}
+}
diff --git a/powertools-validation/src/main/resources/schemas/meta_schema_V6 b/powertools-validation/src/main/resources/schemas/meta_schema_V6
new file mode 100644
index 000000000..bd3e763bc
--- /dev/null
+++ b/powertools-validation/src/main/resources/schemas/meta_schema_V6
@@ -0,0 +1,155 @@
+{
+ "$schema": "http://json-schema.org/draft-06/schema#",
+ "$id": "http://json-schema.org/draft-06/schema#",
+ "title": "Core schema meta-schema",
+ "definitions": {
+ "schemaArray": {
+ "type": "array",
+ "minItems": 1,
+ "items": { "$ref": "#" }
+ },
+ "nonNegativeInteger": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "nonNegativeIntegerDefault0": {
+ "allOf": [
+ { "$ref": "#/definitions/nonNegativeInteger" },
+ { "default": 0 }
+ ]
+ },
+ "simpleTypes": {
+ "enum": [
+ "array",
+ "boolean",
+ "integer",
+ "null",
+ "number",
+ "object",
+ "string"
+ ]
+ },
+ "stringArray": {
+ "type": "array",
+ "items": { "type": "string" },
+ "uniqueItems": true,
+ "default": []
+ }
+ },
+ "type": ["object", "boolean"],
+ "properties": {
+ "$id": {
+ "type": "string",
+ "format": "uri-reference"
+ },
+ "$schema": {
+ "type": "string",
+ "format": "uri"
+ },
+ "$ref": {
+ "type": "string",
+ "format": "uri-reference"
+ },
+ "title": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "default": {},
+ "examples": {
+ "type": "array",
+ "items": {}
+ },
+ "multipleOf": {
+ "type": "number",
+ "exclusiveMinimum": 0
+ },
+ "maximum": {
+ "type": "number"
+ },
+ "exclusiveMaximum": {
+ "type": "number"
+ },
+ "minimum": {
+ "type": "number"
+ },
+ "exclusiveMinimum": {
+ "type": "number"
+ },
+ "maxLength": { "$ref": "#/definitions/nonNegativeInteger" },
+ "minLength": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
+ "pattern": {
+ "type": "string",
+ "format": "regex"
+ },
+ "additionalItems": { "$ref": "#" },
+ "items": {
+ "anyOf": [
+ { "$ref": "#" },
+ { "$ref": "#/definitions/schemaArray" }
+ ],
+ "default": {}
+ },
+ "maxItems": { "$ref": "#/definitions/nonNegativeInteger" },
+ "minItems": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
+ "uniqueItems": {
+ "type": "boolean",
+ "default": false
+ },
+ "contains": { "$ref": "#" },
+ "maxProperties": { "$ref": "#/definitions/nonNegativeInteger" },
+ "minProperties": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
+ "required": { "$ref": "#/definitions/stringArray" },
+ "additionalProperties": { "$ref": "#" },
+ "definitions": {
+ "type": "object",
+ "additionalProperties": { "$ref": "#" },
+ "default": {}
+ },
+ "properties": {
+ "type": "object",
+ "additionalProperties": { "$ref": "#" },
+ "default": {}
+ },
+ "patternProperties": {
+ "type": "object",
+ "additionalProperties": { "$ref": "#" },
+ "propertyNames": { "format": "regex" },
+ "default": {}
+ },
+ "dependencies": {
+ "type": "object",
+ "additionalProperties": {
+ "anyOf": [
+ { "$ref": "#" },
+ { "$ref": "#/definitions/stringArray" }
+ ]
+ }
+ },
+ "propertyNames": { "$ref": "#" },
+ "const": {},
+ "enum": {
+ "type": "array",
+ "minItems": 1,
+ "uniqueItems": true
+ },
+ "type": {
+ "anyOf": [
+ { "$ref": "#/definitions/simpleTypes" },
+ {
+ "type": "array",
+ "items": { "$ref": "#/definitions/simpleTypes" },
+ "minItems": 1,
+ "uniqueItems": true
+ }
+ ]
+ },
+ "format": { "type": "string" },
+ "allOf": { "$ref": "#/definitions/schemaArray" },
+ "anyOf": { "$ref": "#/definitions/schemaArray" },
+ "oneOf": { "$ref": "#/definitions/schemaArray" },
+ "not": { "$ref": "#" }
+ },
+ "default": {}
+}
diff --git a/powertools-validation/src/main/resources/schemas/meta_schema_V7 b/powertools-validation/src/main/resources/schemas/meta_schema_V7
new file mode 100644
index 000000000..fb92c7f75
--- /dev/null
+++ b/powertools-validation/src/main/resources/schemas/meta_schema_V7
@@ -0,0 +1,172 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "$id": "http://json-schema.org/draft-07/schema#",
+ "title": "Core schema meta-schema",
+ "definitions": {
+ "schemaArray": {
+ "type": "array",
+ "minItems": 1,
+ "items": { "$ref": "#" }
+ },
+ "nonNegativeInteger": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "nonNegativeIntegerDefault0": {
+ "allOf": [
+ { "$ref": "#/definitions/nonNegativeInteger" },
+ { "default": 0 }
+ ]
+ },
+ "simpleTypes": {
+ "enum": [
+ "array",
+ "boolean",
+ "integer",
+ "null",
+ "number",
+ "object",
+ "string"
+ ]
+ },
+ "stringArray": {
+ "type": "array",
+ "items": { "type": "string" },
+ "uniqueItems": true,
+ "default": []
+ }
+ },
+ "type": ["object", "boolean"],
+ "properties": {
+ "$id": {
+ "type": "string",
+ "format": "uri-reference"
+ },
+ "$schema": {
+ "type": "string",
+ "format": "uri"
+ },
+ "$ref": {
+ "type": "string",
+ "format": "uri-reference"
+ },
+ "$comment": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "default": true,
+ "readOnly": {
+ "type": "boolean",
+ "default": false
+ },
+ "writeOnly": {
+ "type": "boolean",
+ "default": false
+ },
+ "examples": {
+ "type": "array",
+ "items": true
+ },
+ "multipleOf": {
+ "type": "number",
+ "exclusiveMinimum": 0
+ },
+ "maximum": {
+ "type": "number"
+ },
+ "exclusiveMaximum": {
+ "type": "number"
+ },
+ "minimum": {
+ "type": "number"
+ },
+ "exclusiveMinimum": {
+ "type": "number"
+ },
+ "maxLength": { "$ref": "#/definitions/nonNegativeInteger" },
+ "minLength": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
+ "pattern": {
+ "type": "string",
+ "format": "regex"
+ },
+ "additionalItems": { "$ref": "#" },
+ "items": {
+ "anyOf": [
+ { "$ref": "#" },
+ { "$ref": "#/definitions/schemaArray" }
+ ],
+ "default": true
+ },
+ "maxItems": { "$ref": "#/definitions/nonNegativeInteger" },
+ "minItems": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
+ "uniqueItems": {
+ "type": "boolean",
+ "default": false
+ },
+ "contains": { "$ref": "#" },
+ "maxProperties": { "$ref": "#/definitions/nonNegativeInteger" },
+ "minProperties": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
+ "required": { "$ref": "#/definitions/stringArray" },
+ "additionalProperties": { "$ref": "#" },
+ "definitions": {
+ "type": "object",
+ "additionalProperties": { "$ref": "#" },
+ "default": {}
+ },
+ "properties": {
+ "type": "object",
+ "additionalProperties": { "$ref": "#" },
+ "default": {}
+ },
+ "patternProperties": {
+ "type": "object",
+ "additionalProperties": { "$ref": "#" },
+ "propertyNames": { "format": "regex" },
+ "default": {}
+ },
+ "dependencies": {
+ "type": "object",
+ "additionalProperties": {
+ "anyOf": [
+ { "$ref": "#" },
+ { "$ref": "#/definitions/stringArray" }
+ ]
+ }
+ },
+ "propertyNames": { "$ref": "#" },
+ "const": true,
+ "enum": {
+ "type": "array",
+ "items": true,
+ "minItems": 1,
+ "uniqueItems": true
+ },
+ "type": {
+ "anyOf": [
+ { "$ref": "#/definitions/simpleTypes" },
+ {
+ "type": "array",
+ "items": { "$ref": "#/definitions/simpleTypes" },
+ "minItems": 1,
+ "uniqueItems": true
+ }
+ ]
+ },
+ "format": { "type": "string" },
+ "contentMediaType": { "type": "string" },
+ "contentEncoding": { "type": "string" },
+ "if": { "$ref": "#" },
+ "then": { "$ref": "#" },
+ "else": { "$ref": "#" },
+ "allOf": { "$ref": "#/definitions/schemaArray" },
+ "anyOf": { "$ref": "#/definitions/schemaArray" },
+ "oneOf": { "$ref": "#/definitions/schemaArray" },
+ "not": { "$ref": "#" }
+ },
+ "default": true
+}
diff --git a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/Base64FunctionTest.java b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/Base64FunctionTest.java
new file mode 100644
index 000000000..97837d9b2
--- /dev/null
+++ b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/Base64FunctionTest.java
@@ -0,0 +1,26 @@
+package software.amazon.lambda.powertools.validation;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.JsonNodeType;
+import io.burt.jmespath.Expression;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class Base64FunctionTest {
+
+ @Test
+ public void testPowertoolsBase64() throws IOException {
+ JsonNode event = ValidatorConfig.get().getObjectMapper().readTree(this.getClass().getResourceAsStream("/custom_event.json"));
+ Expression expression = ValidatorConfig.get().getJmesPath().compile("basket.powertools_base64(hiddenProduct)");
+ JsonNode result = expression.search(event);
+ assertThat(result.getNodeType()).isEqualTo(JsonNodeType.STRING);
+ assertThat(result.asText()).isEqualTo("{\n" +
+ " \"id\": 43242,\n" +
+ " \"name\": \"FooBar XY\",\n" +
+ " \"price\": 258\n" +
+ "}");
+ }
+}
diff --git a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/Base64GZipFunctionTest.java b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/Base64GZipFunctionTest.java
new file mode 100644
index 000000000..2f413d6d6
--- /dev/null
+++ b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/Base64GZipFunctionTest.java
@@ -0,0 +1,22 @@
+package software.amazon.lambda.powertools.validation;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.JsonNodeType;
+import io.burt.jmespath.Expression;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class Base64GZipFunctionTest {
+
+ @Test
+ public void testPowertoolsGzip() throws IOException {
+ JsonNode event = ValidatorConfig.get().getObjectMapper().readTree(this.getClass().getResourceAsStream("/custom_event_gzip.json"));
+ Expression expression = ValidatorConfig.get().getJmesPath().compile("basket.powertools_base64_gzip(hiddenProduct)");
+ JsonNode result = expression.search(event);
+ assertThat(result.getNodeType()).isEqualTo(JsonNodeType.STRING);
+ assertThat(result.asText()).isEqualTo("{ \"id\": 43242, \"name\": \"FooBar XY\", \"price\": 258}");
+ }
+}
diff --git a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/ValidatorTest.java b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/ValidatorTest.java
new file mode 100644
index 000000000..05908e44d
--- /dev/null
+++ b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/ValidatorTest.java
@@ -0,0 +1,249 @@
+package software.amazon.lambda.powertools.validation;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.networknt.schema.JsonSchema;
+import com.networknt.schema.SpecVersion;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import software.amazon.lambda.powertools.validation.model.Basket;
+import software.amazon.lambda.powertools.validation.model.MyCustomEvent;
+import software.amazon.lambda.powertools.validation.model.Product;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.assertj.core.api.Assertions.*;
+import static software.amazon.lambda.powertools.validation.Validator.*;
+import static software.amazon.lambda.powertools.validation.Validator.getJsonSchema;
+
+public class ValidatorTest {
+
+ private JsonSchema schema = getJsonSchema("classpath:/schema_v7.json");
+
+ @BeforeEach
+ public void setup() {
+ ValidatorConfig.get().setSchemaVersion(SpecVersion.VersionFlag.V7);
+ }
+
+ @Test
+ public void testLoadSchemaV7OK() {
+ ValidatorConfig.get().setSchemaVersion(SpecVersion.VersionFlag.V7);
+ JsonSchema jsonSchema = getJsonSchema("classpath:/schema_v7.json", true);
+ assertThat(jsonSchema).isNotNull();
+ assertThat(jsonSchema.getCurrentUri()).asString().isEqualTo("http://example.com/product.json");
+ }
+
+ @Test
+ public void testLoadSchemaV7KO() {
+ ValidatorConfig.get().setSchemaVersion(SpecVersion.VersionFlag.V7);
+ assertThatThrownBy(() -> getJsonSchema("classpath:/schema_v7_ko.json", true))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("The schema classpath:/schema_v7_ko.json is not valid, it does not respect the specification V7");
+ }
+
+ @Test
+ public void testLoadMetaSchema_NoValidation() {
+ ValidatorConfig.get().setSchemaVersion(SpecVersion.VersionFlag.V201909);
+ getJsonSchema("classpath:/schemas/meta_schema_V201909", false);
+ }
+
+ @Test
+ public void testLoadMetaSchemaV2019() {
+ ValidatorConfig.get().setSchemaVersion(SpecVersion.VersionFlag.V201909);
+ JsonSchema jsonSchema = getJsonSchema("classpath:/schemas/meta_schema_V201909", true);
+ assertThat(jsonSchema).isNotNull();
+ }
+
+ @Test
+ public void testLoadMetaSchemaV7() {
+ ValidatorConfig.get().setSchemaVersion(SpecVersion.VersionFlag.V7);
+ JsonSchema jsonSchema = getJsonSchema("classpath:/schemas/meta_schema_V7", true);
+ assertThat(jsonSchema).isNotNull();
+ }
+
+ @Test
+ public void testLoadMetaSchemaV6() {
+ ValidatorConfig.get().setSchemaVersion(SpecVersion.VersionFlag.V6);
+ JsonSchema jsonSchema = getJsonSchema("classpath:/schemas/meta_schema_V6", true);
+ assertThat(jsonSchema).isNotNull();
+ }
+
+ @Test
+ public void testLoadMetaSchemaV4() {
+ ValidatorConfig.get().setSchemaVersion(SpecVersion.VersionFlag.V4);
+ JsonSchema jsonSchema = getJsonSchema("classpath:/schemas/meta_schema_V4", true);
+ assertThat(jsonSchema).isNotNull();
+ }
+
+ @Test
+ public void testLoadSchemaV4OK() {
+ ValidatorConfig.get().setSchemaVersion(SpecVersion.VersionFlag.V4);
+ JsonSchema jsonSchema = getJsonSchema("classpath:/schema_v4.json", true);
+ assertThat(jsonSchema).isNotNull();
+ }
+
+ @Test
+ public void testLoadSchemaNotFound() {
+ assertThatThrownBy(() -> getJsonSchema("classpath:/dev/null"))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("'classpath:/dev/null' is invalid, verify '/dev/null' is in your classpath");
+ }
+
+ @Test
+ public void testValidateJsonNodeOK() throws IOException {
+ JsonNode node = ValidatorConfig.get().getObjectMapper().readTree(this.getClass().getResourceAsStream("/json_ok.json"));
+
+ validate(node, schema);
+ }
+
+ @Test
+ public void testValidateJsonNodeKO() throws IOException {
+ JsonNode node = ValidatorConfig.get().getObjectMapper().readTree(this.getClass().getResourceAsStream("/json_ko.json"));
+
+ assertThatExceptionOfType(ValidationException.class).isThrownBy(() -> validate(node, schema));
+ }
+
+ @Test
+ public void testValidateMapOK() {
+ Map map = new HashMap<>();
+ map.put("id", 43242);
+ map.put("name", "FooBar XY");
+ map.put("price", 258);
+
+ validate(map, schema);
+ }
+
+ @Test
+ public void testValidateMapKO() {
+ Map map = new HashMap<>();
+ map.put("id", 43242);
+ map.put("name", "FooBar XY");
+
+ assertThatExceptionOfType(ValidationException.class).isThrownBy(() -> validate(map, schema));
+ }
+
+ @Test
+ public void testValidateStringOK() {
+ String json = "{\n \"id\": 43242,\n \"name\": \"FooBar XY\",\n \"price\": 258\n}";
+
+ validate(json, schema);
+ }
+
+ @Test
+ public void testValidateStringKO() {
+ String json = "{\n \"id\": 43242,\n \"name\": \"FooBar XY\",\n \"price\": 0\n}";
+
+ assertThatExceptionOfType(ValidationException.class).isThrownBy(() -> validate(json, schema));
+ }
+
+ @Test
+ public void testValidateObjectOK() {
+ Product product = new Product(42, "FooBar", 42);
+ validate(product, schema);
+ }
+
+ @Test
+ public void testValidateObjectKO() {
+ Product product = new Product(42, "FooBar", -12);
+
+ assertThatExceptionOfType(ValidationException.class).isThrownBy(() -> validate(product, schema));
+ }
+
+ @Test
+ public void testValidateSubObjectOK() {
+ Product product = new Product(42, "FooBar", 42);
+ Product product2 = new Product(420, "FooBarBaz", 420);
+ Basket basket = new Basket();
+ basket.add(product);
+ basket.add(product2);
+ MyCustomEvent event = new MyCustomEvent(basket);
+ validate(event, schema, "basket.products[0]");
+ }
+
+ @Test
+ public void testValidateSubObjectKO() {
+ Product product = new Product(42, null, 42);
+ Product product2 = new Product(420, "FooBarBaz", 420);
+ Basket basket = new Basket();
+ basket.add(product);
+ basket.add(product2);
+ MyCustomEvent event = new MyCustomEvent(basket);
+
+ assertThatExceptionOfType(ValidationException.class).isThrownBy(() -> validate(event, schema, "basket.products[0]"));
+ }
+
+ @Test
+ public void testValidateSubObjectListOK() {
+ Product product = new Product(42, "BarBazFoo", 42);
+ Product product2 = new Product(420, "FooBarBaz", 23);
+ Basket basket = new Basket();
+ basket.add(product);
+ basket.add(product2);
+ MyCustomEvent event = new MyCustomEvent(basket);
+
+ validate(event, schema, "basket.products[*]");
+ }
+
+ @Test
+ public void testValidateSubObjectListKO() {
+ Product product = new Product(42, "BarBazFoo", 42);
+ Product product2 = new Product(420, "FooBarBaz", -23);
+ Basket basket = new Basket();
+ basket.add(product);
+ basket.add(product2);
+ MyCustomEvent event = new MyCustomEvent(basket);
+
+ assertThatExceptionOfType(ValidationException.class).isThrownBy(() -> validate(event, schema, "basket.products[*]"));
+ }
+
+ @Test
+ public void testValidateSubObjectNotFound() {
+ Product product = new Product(42, "BarBazFoo", 42);
+ Basket basket = new Basket();
+ basket.add(product);
+ MyCustomEvent event = new MyCustomEvent(basket);
+ assertThatExceptionOfType(ValidationException.class).isThrownBy(() -> validate(event, schema, "basket.product"));
+ }
+
+ @Test
+ public void testValidateSubObjectNotListNorObject() {
+ Product product = new Product(42, "Bar", 42);
+ Product product2 = new Product(420, "FooBarBaz", -23);
+ Basket basket = new Basket();
+ basket.add(product);
+ basket.add(product2);
+ MyCustomEvent event = new MyCustomEvent(basket);
+
+ assertThatThrownBy(() -> validate(event, schema, "basket.products[0].id"))
+ .isInstanceOf(ValidationException.class)
+ .hasMessage("Invalid format for 'basket.products[0].id': 'NUMBER'");
+ }
+
+ @Test
+ public void testValidateSubObjectJsonString() {
+ Basket basket = new Basket();
+ basket.setHiddenProduct("ewogICJpZCI6IDQzMjQyLAogICJuYW1lIjogIkZvb0JhciBYWSIsCiAgInByaWNlIjogMjU4Cn0=");
+ MyCustomEvent event = new MyCustomEvent(basket);
+
+ validate(event, schema, "basket.powertools_base64(hiddenProduct)");
+ }
+
+ @Test
+ public void testValidateSubObjectSimpleString() {
+ Basket basket = new Basket();
+ basket.setHiddenProduct("ghostbuster");
+ MyCustomEvent event = new MyCustomEvent(basket);
+
+ assertThatThrownBy(() -> validate(event, schema, "basket.hiddenProduct"))
+ .isInstanceOf(ValidationException.class)
+ .hasMessage("Invalid format for 'basket.hiddenProduct': 'STRING' and no JSON found in it.");
+ }
+
+ @Test
+ public void testValidateSubObjectWithoutEnvelope() {
+ Product product = new Product(42, "BarBazFoo", 42);
+ validate(product, schema, null);
+ }
+
+}
diff --git a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/KinesisHandler.java b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/KinesisHandler.java
new file mode 100644
index 000000000..6fec83983
--- /dev/null
+++ b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/KinesisHandler.java
@@ -0,0 +1,15 @@
+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.KinesisEvent;
+import software.amazon.lambda.powertools.validation.Validation;
+
+public class KinesisHandler implements RequestHandler {
+
+ @Validation(inboundSchema = "classpath:/schema_v7.json")
+ @Override
+ public String handleRequest(KinesisEvent input, Context context) {
+ return "OK";
+ }
+}
diff --git a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/MyCustomEventHandler.java b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/MyCustomEventHandler.java
new file mode 100644
index 000000000..6ae75dc74
--- /dev/null
+++ b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/MyCustomEventHandler.java
@@ -0,0 +1,15 @@
+package software.amazon.lambda.powertools.validation.handlers;
+
+import com.amazonaws.services.lambda.runtime.Context;
+import com.amazonaws.services.lambda.runtime.RequestHandler;
+import software.amazon.lambda.powertools.validation.Validation;
+import software.amazon.lambda.powertools.validation.model.MyCustomEvent;
+
+public class MyCustomEventHandler implements RequestHandler {
+
+ @Override
+ @Validation(inboundSchema = "classpath:/schema_v7.json", envelope = "basket.products[*]")
+ public String handleRequest(MyCustomEvent input, Context context) {
+ return "OK";
+ }
+}
diff --git a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/SQSHandler.java b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/SQSHandler.java
new file mode 100644
index 000000000..3bb247056
--- /dev/null
+++ b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/SQSHandler.java
@@ -0,0 +1,15 @@
+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 SQSHandler implements RequestHandler {
+
+ @Override
+ @Validation(inboundSchema = "classpath:/schema_v7.json")
+ public String handleRequest(SQSEvent input, Context context) {
+ return "OK";
+ }
+}
diff --git a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/ValidationInboundClasspathHandler.java b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/ValidationInboundClasspathHandler.java
new file mode 100644
index 000000000..59b6cc7b5
--- /dev/null
+++ b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/ValidationInboundClasspathHandler.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.APIGatewayProxyRequestEvent;
+import software.amazon.lambda.powertools.validation.Validation;
+
+
+public class ValidationInboundClasspathHandler implements RequestHandler {
+
+ @Override
+ @Validation(inboundSchema = "classpath:/schema_v7.json")
+ public String handleRequest(APIGatewayProxyRequestEvent input, Context context) {
+ return "OK";
+ }
+}
diff --git a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/ValidationInboundStringHandler.java b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/ValidationInboundStringHandler.java
new file mode 100644
index 000000000..e27f31129
--- /dev/null
+++ b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/ValidationInboundStringHandler.java
@@ -0,0 +1,85 @@
+/*
+ * 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.APIGatewayV2HTTPEvent;
+import software.amazon.lambda.powertools.validation.Validation;
+
+
+public class ValidationInboundStringHandler implements RequestHandler {
+
+ private static final String schema = "{\n" +
+ " \"$schema\": \"http://json-schema.org/draft-07/schema\",\n" +
+ " \"$id\": \"http://example.com/product.json\",\n" +
+ " \"type\": \"object\",\n" +
+ " \"title\": \"Product schema\",\n" +
+ " \"description\": \"JSON schema to validate Products\",\n" +
+ " \"default\": {},\n" +
+ " \"examples\": [\n" +
+ " {\n" +
+ " \"id\": 43242,\n" +
+ " \"name\": \"FooBar XY\",\n" +
+ " \"price\": 258\n" +
+ " }\n" +
+ " ],\n" +
+ " \"required\": [\n" +
+ " \"id\",\n" +
+ " \"name\",\n" +
+ " \"price\"\n" +
+ " ],\n" +
+ " \"properties\": {\n" +
+ " \"id\": {\n" +
+ " \"$id\": \"#/properties/id\",\n" +
+ " \"type\": \"integer\",\n" +
+ " \"title\": \"Id of the product\",\n" +
+ " \"description\": \"Unique identifier of the product\",\n" +
+ " \"default\": 0,\n" +
+ " \"examples\": [\n" +
+ " 43242\n" +
+ " ]\n" +
+ " },\n" +
+ " \"name\": {\n" +
+ " \"$id\": \"#/properties/name\",\n" +
+ " \"type\": \"string\",\n" +
+ " \"title\": \"Name of the product\",\n" +
+ " \"description\": \"Explicit name of the product\",\n" +
+ " \"minLength\": 5,\n" +
+ " \"default\": \"\",\n" +
+ " \"examples\": [\n" +
+ " \"FooBar XY\"\n" +
+ " ]\n" +
+ " },\n" +
+ " \"price\": {\n" +
+ " \"$id\": \"#/properties/price\",\n" +
+ " \"type\": \"number\",\n" +
+ " \"title\": \"Price of the product\",\n" +
+ " \"description\": \"Positive price of the product\",\n" +
+ " \"default\": 0,\n" +
+ " \"exclusiveMinimum\": 0,\n" +
+ " \"examples\": [\n" +
+ " 258.99\n" +
+ " ]\n" +
+ " }\n" +
+ " },\n" +
+ " \"additionalProperties\": true\n" +
+ "}";
+
+ @Override
+ @Validation(inboundSchema = schema)
+ public String handleRequest(APIGatewayV2HTTPEvent 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
new file mode 100644
index 000000000..e5be82c95
--- /dev/null
+++ b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/internal/ValidationAspectTest.java
@@ -0,0 +1,116 @@
+/*
+ * 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.internal;
+
+import com.amazonaws.services.lambda.runtime.Context;
+import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
+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 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.ValidatorConfig;
+import software.amazon.lambda.powertools.validation.handlers.*;
+import software.amazon.lambda.powertools.validation.model.MyCustomEvent;
+
+import java.io.IOException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+
+public class ValidationAspectTest {
+
+ @Mock
+ private Context context;
+
+ @BeforeEach
+ void setUp() {
+ MockitoAnnotations.openMocks(this);
+ }
+
+ @Test
+ public void validate_inputOK_schemaInClasspath_shouldValidate() {
+ ValidationInboundClasspathHandler handler = new ValidationInboundClasspathHandler();
+ APIGatewayProxyRequestEvent event = new APIGatewayProxyRequestEvent();
+ event.setBody("{" +
+ " \"id\": 1," +
+ " \"name\": \"Lampshade\"," +
+ " \"price\": 42" +
+ "}");
+ assertThat(handler.handleRequest(event, context)).isEqualTo("OK");
+ }
+
+ @Test
+ public void validate_inputKO_schemaInClasspath_shouldThrowValidationException() {
+ ValidationInboundClasspathHandler handler = new ValidationInboundClasspathHandler();
+ APIGatewayProxyRequestEvent event = new APIGatewayProxyRequestEvent();
+ event.setBody("{" +
+ " \"id\": 1," +
+ " \"name\": \"Lampshade\"," +
+ " \"price\": -2" +
+ "}");
+ // price is negative
+ assertThatExceptionOfType(ValidationException.class).isThrownBy(() -> handler.handleRequest(event, context));
+ }
+
+ @Test
+ public void validate_inputOK_schemaInString_shouldValidate() {
+ ValidationInboundStringHandler handler = new ValidationInboundStringHandler();
+ APIGatewayV2HTTPEvent event = new APIGatewayV2HTTPEvent();
+ event.setBody("{" +
+ " \"id\": 1," +
+ " \"name\": \"Lampshade\"," +
+ " \"price\": 42" +
+ "}");
+ assertThat(handler.handleRequest(event, context)).isEqualTo("OK");
+ }
+
+ @Test
+ public void validate_inputKO_schemaInString_shouldThrowValidationException() {
+ ValidationInboundStringHandler handler = new ValidationInboundStringHandler();
+ APIGatewayV2HTTPEvent event = new APIGatewayV2HTTPEvent();
+ event.setBody("{" +
+ " \"id\": 1," +
+ " \"name\": \"Lampshade\"" +
+ "}");
+ assertThatExceptionOfType(ValidationException.class).isThrownBy(() -> handler.handleRequest(event, context));
+ }
+
+ @Test
+ public void validate_SQS() throws IOException {
+ SQSEvent event = ValidatorConfig.get().getObjectMapper().readValue(this.getClass().getResourceAsStream("/sqs.json"), SQSEvent.class);
+
+ SQSHandler handler = new SQSHandler();
+ assertThat(handler.handleRequest(event, context)).isEqualTo("OK");
+ }
+
+ @Test
+ public void validate_Kinesis() throws IOException {
+ KinesisEvent event = ValidatorConfig.get().getObjectMapper().readValue(this.getClass().getResourceAsStream("/kinesis.json"), KinesisEvent.class);
+
+ KinesisHandler handler = new KinesisHandler();
+ assertThat(handler.handleRequest(event, context)).isEqualTo("OK");
+ }
+
+ @Test
+ public void validate_CustomObject() throws IOException {
+ MyCustomEvent event = ValidatorConfig.get().getObjectMapper().readValue(this.getClass().getResourceAsStream("/custom_event.json"), MyCustomEvent.class);
+
+ MyCustomEventHandler handler = new MyCustomEventHandler();
+ assertThat(handler.handleRequest(event, context)).isEqualTo("OK");
+ }
+}
diff --git a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/model/Basket.java b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/model/Basket.java
new file mode 100644
index 000000000..acde271ee
--- /dev/null
+++ b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/model/Basket.java
@@ -0,0 +1,33 @@
+package software.amazon.lambda.powertools.validation.model;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class Basket {
+ private List products = new ArrayList<>();
+
+ private String hiddenProduct;
+
+ public List getProducts() {
+ return products;
+ }
+
+ public void setProducts(List products) {
+ this.products = products;
+ }
+
+ public Basket() {
+ }
+
+ public void add(Product product) {
+ products.add(product);
+ }
+
+ public String getHiddenProduct() {
+ return hiddenProduct;
+ }
+
+ public void setHiddenProduct(String hiddenProduct) {
+ this.hiddenProduct = hiddenProduct;
+ }
+}
diff --git a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/model/MyCustomEvent.java b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/model/MyCustomEvent.java
new file mode 100644
index 000000000..5f5f0bab9
--- /dev/null
+++ b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/model/MyCustomEvent.java
@@ -0,0 +1,20 @@
+package software.amazon.lambda.powertools.validation.model;
+
+public class MyCustomEvent {
+ private Basket basket;
+
+ public MyCustomEvent() {
+ }
+
+ public MyCustomEvent(Basket basket) {
+ this.basket = basket;
+ }
+
+ public Basket getBasket() {
+ return basket;
+ }
+
+ public void setBasket(Basket basket) {
+ this.basket = basket;
+ }
+}
diff --git a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/model/Product.java b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/model/Product.java
new file mode 100644
index 000000000..c4c45e697
--- /dev/null
+++ b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/model/Product.java
@@ -0,0 +1,42 @@
+package software.amazon.lambda.powertools.validation.model;
+
+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;
+ }
+}
diff --git a/powertools-validation/src/test/resources/custom_event.json b/powertools-validation/src/test/resources/custom_event.json
new file mode 100644
index 000000000..13103c434
--- /dev/null
+++ b/powertools-validation/src/test/resources/custom_event.json
@@ -0,0 +1,12 @@
+{
+ "basket": {
+ "products" : [
+ {
+ "id": 43242,
+ "name": "FooBar XY",
+ "price": 258
+ }
+ ],
+ "hiddenProduct": "ewogICJpZCI6IDQzMjQyLAogICJuYW1lIjogIkZvb0JhciBYWSIsCiAgInByaWNlIjogMjU4Cn0="
+ }
+}
\ No newline at end of file
diff --git a/powertools-validation/src/test/resources/custom_event_gzip.json b/powertools-validation/src/test/resources/custom_event_gzip.json
new file mode 100644
index 000000000..d212052d0
--- /dev/null
+++ b/powertools-validation/src/test/resources/custom_event_gzip.json
@@ -0,0 +1,12 @@
+{
+ "basket": {
+ "products" : [
+ {
+ "id": 43242,
+ "name": "FooBar XY",
+ "price": 258
+ }
+ ],
+ "hiddenProduct": "H4sIAAAAAAAA/6vmUlBQykxRslIwMTYyMdIBcfMSc1OBAkpu+flOiUUKEZFKYOGCosxkkLiRqQVXLQDnWo6bOAAAAA=="
+ }
+}
\ No newline at end of file
diff --git a/powertools-validation/src/test/resources/json_ko.json b/powertools-validation/src/test/resources/json_ko.json
new file mode 100644
index 000000000..1e566080f
--- /dev/null
+++ b/powertools-validation/src/test/resources/json_ko.json
@@ -0,0 +1,5 @@
+{
+ "id": 43242,
+ "name": "FooBar XY",
+ "price": 0
+}
\ No newline at end of file
diff --git a/powertools-validation/src/test/resources/json_ok.json b/powertools-validation/src/test/resources/json_ok.json
new file mode 100644
index 000000000..427253d66
--- /dev/null
+++ b/powertools-validation/src/test/resources/json_ok.json
@@ -0,0 +1,5 @@
+{
+ "id": 43242,
+ "name": "FooBar XY",
+ "price": 258
+}
\ No newline at end of file
diff --git a/powertools-validation/src/test/resources/kinesis.json b/powertools-validation/src/test/resources/kinesis.json
new file mode 100644
index 000000000..6d99be7e5
--- /dev/null
+++ b/powertools-validation/src/test/resources/kinesis.json
@@ -0,0 +1,38 @@
+{
+ "records": [
+ {
+ "kinesis": {
+ "partitionKey": "partitionKey-03",
+ "kinesisSchemaVersion": "1.0",
+ "data": "ewogICJpZCI6IDQzMjQyLAogICJuYW1lIjogIkZvb0JhciBYWSIsCiAgInByaWNlIjogMjU4Cn0=",
+ "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-04",
+ "kinesisSchemaVersion": "1.0",
+ "data": "ewogICJpZCI6IDQyNSwKICAibmFtZSI6ICJCYXJGb28iLAogICJwcmljZSI6IDQzCn0=",
+ "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-validation/src/test/resources/schema_v4.json b/powertools-validation/src/test/resources/schema_v4.json
new file mode 100644
index 000000000..ae277d476
--- /dev/null
+++ b/powertools-validation/src/test/resources/schema_v4.json
@@ -0,0 +1,22 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "title": "Product",
+ "description": "A product from the catalog",
+ "type": "object",
+ "properties": {
+ "id": {
+ "description": "The unique identifier for a product",
+ "type": "integer"
+ },
+ "name": {
+ "description": "Name of the product",
+ "type": "string"
+ },
+ "price": {
+ "type": "number",
+ "minimum": 0,
+ "exclusiveMinimum": true
+ }
+ },
+ "required": ["id", "name", "price"]
+}
\ No newline at end of file
diff --git a/powertools-validation/src/test/resources/schema_v7.json b/powertools-validation/src/test/resources/schema_v7.json
new file mode 100644
index 000000000..f38272f2d
--- /dev/null
+++ b/powertools-validation/src/test/resources/schema_v7.json
@@ -0,0 +1,55 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema",
+ "$id": "http://example.com/product.json",
+ "type": "object",
+ "title": "Product schema",
+ "description": "JSON schema to validate Products",
+ "default": {},
+ "examples": [
+ {
+ "id": 43242,
+ "name": "FooBar XY",
+ "price": 258
+ }
+ ],
+ "required": [
+ "id",
+ "name",
+ "price"
+ ],
+ "properties": {
+ "id": {
+ "$id": "#/properties/id",
+ "type": "integer",
+ "title": "Id of the product",
+ "description": "Unique identifier of the product",
+ "default": 0,
+ "examples": [
+ 43242
+ ]
+ },
+ "name": {
+ "$id": "#/properties/name",
+ "type": "string",
+ "title": "Name of the product",
+ "description": "Explicit name of the product",
+ "minLength": 5,
+ "default": "",
+ "examples": [
+ "FooBar XY"
+ ]
+ },
+ "price": {
+ "$id": "#/properties/price",
+ "type": "number",
+ "title": "Price of the product",
+ "description": "Positive price of the product",
+ "default": 0,
+ "exclusiveMinimum": 0,
+ "examples": [
+ 258.99
+ ]
+ }
+ },
+ "additionalProperties": true
+}
\ No newline at end of file
diff --git a/powertools-validation/src/test/resources/schema_v7_ko.json b/powertools-validation/src/test/resources/schema_v7_ko.json
new file mode 100644
index 000000000..f54bcb3c7
--- /dev/null
+++ b/powertools-validation/src/test/resources/schema_v7_ko.json
@@ -0,0 +1,54 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema",
+ "type": "object",
+ "title": "Product schema",
+ "description": "JSON schema to validate Products",
+ "default": {},
+ "examples": [
+ {
+ "id": 43242,
+ "name": "FooBar XY",
+ "price": 258
+ }
+ ],
+ "required": [
+ "id",
+ "name",
+ "price"
+ ],
+ "properties": {
+ "id": {
+ "$id": "#/properties/id",
+ "type": "integer",
+ "title": "Id of the product",
+ "description": "Unique identifier of the product",
+ "default": 0,
+ "examples": [
+ 43242
+ ]
+ },
+ "name": {
+ "$id": "#/properties/name",
+ "type": "varchar",
+ "title": "Name of the product",
+ "description": "Explicit name of the product",
+ "minLength": 5,
+ "default": "",
+ "examples": [
+ "FooBar XY"
+ ]
+ },
+ "price": {
+ "$id": "#/properties/price",
+ "type": "number",
+ "title": "Price of the product",
+ "description": "Positive price of the product",
+ "default": 0,
+ "exclusiveMinimum": 0,
+ "examples": [
+ 258.99
+ ]
+ }
+ },
+ "additionalProperties": true
+}
\ No newline at end of file
diff --git a/powertools-validation/src/test/resources/sqs.json b/powertools-validation/src/test/resources/sqs.json
new file mode 100644
index 000000000..9180c5839
--- /dev/null
+++ b/powertools-validation/src/test/resources/sqs.json
@@ -0,0 +1,22 @@
+{
+ "records": [
+ {
+ "messageId": "d9144555-9a4f-4ec3-99a0-fc4e625a8db2",
+ "receiptHandle": "7kam5bfzbDsjtcjElvhSbxeLJbeey3A==",
+ "body": "{\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
From 231293f50d4bd7bb56b11add769e996825bdf581 Mon Sep 17 00:00:00 2001
From: Jerome Van Der Linden
Date: Tue, 20 Oct 2020 00:33:11 +0200
Subject: [PATCH 2/7] add licence headers
---
pom.xml | 7 +++++++
.../lambda/powertools/validation/Validation.java | 3 +--
.../lambda/powertools/validation/Validator.java | 13 +++++++++++++
.../powertools/validation/ValidatorConfig.java | 13 +++++++++++++
.../validation/jmespath/Base64Function.java | 13 +++++++++++++
.../validation/jmespath/Base64GZipFunction.java | 13 +++++++++++++
.../powertools/validation/Base64FunctionTest.java | 13 +++++++++++++
.../validation/Base64GZipFunctionTest.java | 13 +++++++++++++
.../lambda/powertools/validation/ValidatorTest.java | 2 +-
.../validation/handlers/KinesisHandler.java | 13 +++++++++++++
.../validation/handlers/MyCustomEventHandler.java | 13 +++++++++++++
.../powertools/validation/handlers/SQSHandler.java | 13 +++++++++++++
.../lambda/powertools/validation/model/Basket.java | 13 +++++++++++++
.../powertools/validation/model/MyCustomEvent.java | 13 +++++++++++++
.../lambda/powertools/validation/model/Product.java | 13 +++++++++++++
15 files changed, 165 insertions(+), 3 deletions(-)
diff --git a/pom.xml b/pom.xml
index 6e9db7658..cbd0f6ecd 100644
--- a/pom.xml
+++ b/pom.xml
@@ -33,6 +33,7 @@
powertools-sqspowertools-metricspowertools-parameters
+ powertools-validation
@@ -70,6 +71,7 @@
1.65.7.01.0.1
+ 0.5.0
@@ -103,6 +105,11 @@
pomimport
+
+ io.burt
+ jmespath-jackson
+ ${jmespath.version}
+ software.amazon.payloadoffloadingpayloadoffloading-common
diff --git a/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/Validation.java b/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/Validation.java
index 26faea9b8..1c191f031 100644
--- a/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/Validation.java
+++ b/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/Validation.java
@@ -14,7 +14,6 @@
package software.amazon.lambda.powertools.validation;
import com.amazonaws.services.lambda.runtime.Context;
-import com.networknt.schema.SpecVersion;
import com.networknt.schema.SpecVersion.VersionFlag;
import java.io.InputStream;
@@ -24,7 +23,7 @@
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
-import static com.networknt.schema.SpecVersion.VersionFlag.*;
+import static com.networknt.schema.SpecVersion.VersionFlag.V7;
/**
* {@link Validation} is used to specify that the annotated method input and/or output needs to be valid.
diff --git a/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/Validator.java b/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/Validator.java
index 6ea5ec10c..3a9914489 100644
--- a/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/Validator.java
+++ b/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/Validator.java
@@ -1,3 +1,16 @@
+/*
+ * 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;
import com.fasterxml.jackson.core.JsonProcessingException;
diff --git a/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/ValidatorConfig.java b/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/ValidatorConfig.java
index 2bc477c69..3ca8194b0 100644
--- a/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/ValidatorConfig.java
+++ b/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/ValidatorConfig.java
@@ -1,3 +1,16 @@
+/*
+ * 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;
import com.fasterxml.jackson.databind.JsonNode;
diff --git a/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/jmespath/Base64Function.java b/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/jmespath/Base64Function.java
index 47112722f..2a418d086 100644
--- a/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/jmespath/Base64Function.java
+++ b/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/jmespath/Base64Function.java
@@ -1,3 +1,16 @@
+/*
+ * 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.jmespath;
import io.burt.jmespath.Adapter;
diff --git a/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/jmespath/Base64GZipFunction.java b/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/jmespath/Base64GZipFunction.java
index 4336f1e50..e5eb52bd4 100644
--- a/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/jmespath/Base64GZipFunction.java
+++ b/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/jmespath/Base64GZipFunction.java
@@ -1,3 +1,16 @@
+/*
+ * 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.jmespath;
import io.burt.jmespath.Adapter;
diff --git a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/Base64FunctionTest.java b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/Base64FunctionTest.java
index 97837d9b2..1fae8917c 100644
--- a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/Base64FunctionTest.java
+++ b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/Base64FunctionTest.java
@@ -1,3 +1,16 @@
+/*
+ * 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;
import com.fasterxml.jackson.databind.JsonNode;
diff --git a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/Base64GZipFunctionTest.java b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/Base64GZipFunctionTest.java
index 2f413d6d6..ad099516e 100644
--- a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/Base64GZipFunctionTest.java
+++ b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/Base64GZipFunctionTest.java
@@ -1,3 +1,16 @@
+/*
+ * 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;
import com.fasterxml.jackson.databind.JsonNode;
diff --git a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/ValidatorTest.java b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/ValidatorTest.java
index 05908e44d..6d5f573b9 100644
--- a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/ValidatorTest.java
+++ b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/ValidatorTest.java
@@ -14,8 +14,8 @@
import java.util.Map;
import static org.assertj.core.api.Assertions.*;
-import static software.amazon.lambda.powertools.validation.Validator.*;
import static software.amazon.lambda.powertools.validation.Validator.getJsonSchema;
+import static software.amazon.lambda.powertools.validation.Validator.validate;
public class ValidatorTest {
diff --git a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/KinesisHandler.java b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/KinesisHandler.java
index 6fec83983..7132fcb9b 100644
--- a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/KinesisHandler.java
+++ b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/KinesisHandler.java
@@ -1,3 +1,16 @@
+/*
+ * 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;
diff --git a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/MyCustomEventHandler.java b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/MyCustomEventHandler.java
index 6ae75dc74..07954ddff 100644
--- a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/MyCustomEventHandler.java
+++ b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/MyCustomEventHandler.java
@@ -1,3 +1,16 @@
+/*
+ * 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;
diff --git a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/SQSHandler.java b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/SQSHandler.java
index 3bb247056..cd6719b0f 100644
--- a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/SQSHandler.java
+++ b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/handlers/SQSHandler.java
@@ -1,3 +1,16 @@
+/*
+ * 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;
diff --git a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/model/Basket.java b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/model/Basket.java
index acde271ee..548ef4660 100644
--- a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/model/Basket.java
+++ b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/model/Basket.java
@@ -1,3 +1,16 @@
+/*
+ * 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.model;
import java.util.ArrayList;
diff --git a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/model/MyCustomEvent.java b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/model/MyCustomEvent.java
index 5f5f0bab9..12f3f99ca 100644
--- a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/model/MyCustomEvent.java
+++ b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/model/MyCustomEvent.java
@@ -1,3 +1,16 @@
+/*
+ * 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.model;
public class MyCustomEvent {
diff --git a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/model/Product.java b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/model/Product.java
index c4c45e697..fde888b76 100644
--- a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/model/Product.java
+++ b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/model/Product.java
@@ -1,3 +1,16 @@
+/*
+ * 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.model;
public class Product {
From a2d714588b25c6fb0ff083741b46544dc304884d Mon Sep 17 00:00:00 2001
From: Jerome Van Der Linden
Date: Mon, 26 Oct 2020 14:57:20 +0100
Subject: [PATCH 3/7] doc
---
docs/content/utilities/validation.mdx | 292 ++++++++++++++++++
docs/gatsby-config.js | 1 +
.../powertools/validation/Validator.java | 81 ++++-
3 files changed, 364 insertions(+), 10 deletions(-)
create mode 100644 docs/content/utilities/validation.mdx
diff --git a/docs/content/utilities/validation.mdx b/docs/content/utilities/validation.mdx
new file mode 100644
index 000000000..07cef0740
--- /dev/null
+++ b/docs/content/utilities/validation.mdx
@@ -0,0 +1,292 @@
+---
+title: Validation
+description: Utility
+---
+
+import Note from "../../src/components/Note"
+
+This utility provides JSON Schema validation for handler events and responses.
+
+**Key features**
+* Validate incoming events and responses
+* Built-in validation for most common events (API Gateway, SNS, SQS, ...)
+* JMESPath support validate only a sub part of the event
+
+## Install
+
+To install this utility, add the following dependency to your project.
+
+```xml
+
+ software.amazon.lambda
+ powertools-validation
+ 0.5.0-beta
+
+```
+
+## Validating events
+
+You can validate inbound and outbound events using `@Validation` annotation.
+
+You can also use the `Validator#validate()` methods, if you want more control over the validation process such as handling a validation error.you
+
+We support JSON schema version 4, 6, 7 and 201909 (from [jmespath-jackson library](https://github.com/burtcorp/jmespath-java)).
+
+### @Validation annotation
+
+`@Validation` annotation is typically used to validate either inbound or functions' response.
+
+It will fail fast with `ValidationException` if event or response doesn't conform with given JSON Schema.
+
+While it is easier to specify a json schema file in the classpath (using the notation `"classpath:/path/to/schema.json"`), you can also provide a JSON String containing the schema.
+
+```java
+public class MyFunctionHandler implements RequestHandler {
+
+ @Override
+ @Validation(inboundSchema = "classpath:/schema_in.json", outboundSchema = "classpath:/schema_out.json")
+ public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent input, Context context) {
+ // ...
+ return something;
+ }
+}
+```
+
+**NOTE**: It's not a requirement to validate both inbound and outbound schemas - You can either use one, or both.
+
+### Validate function
+
+Validate standalone function is typically used within the Lambda handler, or any other methods that perform data validation.
+
+You can also gracefully handle schema validation errors by catching `ValidationException`.
+
+```java
+import static software.amazon.lambda.powertools.validation.Validator.*;
+
+public class MyFunctionHandler implements RequestHandler {
+
+ @Override
+ public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent input, Context context) {
+ try {
+ validate(input, "classpath:/schema.json");
+ } catch (ValidationException ex) {
+ // do something before throwing it
+ throw ex;
+ }
+
+ // ...
+ return something;
+ }
+}
+```
+**NOTE**: Schemas are stored in memory for reuse, to avoid loading them from file each time.
+
+## Built-in events and responses
+
+For the following events and responses, the Validator will automatically perform validation on the content.
+
+** Events **
+
+ Type of event | Class | Path to content |
+ ------------------------------------------------- | ------------------------------------------------- | -------------------------------------------------
+ API Gateway REST | APIGatewayProxyRequestEvent | `body`
+ API Gateway HTTP | APIGatewayV2HTTPEvent | `body`
+ Application Load Balancer | ApplicationLoadBalancerRequestEvent | `body`
+ Cloudformation Custom Resource | CloudFormationCustomResourceEvent | `resourceProperties`
+ CloudWatch Logs | CloudWatchLogsEvent | `awslogs.powertools_base64_gzip(data)`
+ EventBridge / Cloudwatch | ScheduledEvent | `detail`
+ Kafka | KafkaEvent | `records[*][*].value`
+ Kinesis | KinesisEvent | `Records[*].kinesis.powertools_base64(data)`
+ Kinesis Firehose | KinesisFirehoseEvent | `Records[*].powertools_base64(data)`
+ Kinesis Analytics from Firehose | KinesisAnalyticsFirehoseInputPreprocessingEvent | `Records[*].powertools_base64(data)`
+ Kinesis Analytics from Streams | KinesisAnalyticsStreamsInputPreprocessingEvent | `Records[*].powertools_base64(data)`
+ SNS | SNSEvent | `Records[*].Sns.Message`
+ SQS | SQSEvent | `Records[*].body`
+
+** Responses **
+
+ Type of response | Class | Path to content (envelope)
+ ------------------------------------------------- | ------------------------------------------------- | -------------------------------------------------
+ API Gateway REST | APIGatewayProxyResponseEvent} | `body`
+ API Gateway HTTP | APIGatewayV2HTTPResponse} | `body`
+ API Gateway WebSocket | APIGatewayV2WebSocketResponse} | `body`
+ Load Balancer | ApplicationLoadBalancerResponseEvent} | `body`
+ Kinesis Analytics | KinesisAnalyticsInputPreprocessingResponse} | `Records[*].powertools_base64(data)``
+
+## Custom events and responses
+
+You can also validate any Event or Response type, once you have the appropriate schema.
+
+Sometimes, you might want to validate only a portion of it - This is where the envelope parameter is for.
+
+Envelopes are [JMESPath expressions](https://jmespath.org/tutorial.html) to extract a portion of JSON you want before applying JSON Schema validation.
+
+Here is a custom event where we only want to validate each products:
+
+```json
+{
+ "basket": {
+ "products" : [
+ {
+ "id": 43242,
+ "name": "FooBar XY",
+ "price": 258
+ },
+ {
+ "id": 765,
+ "name": "BarBaz AB",
+ "price": 43.99
+ }
+ ]
+ }
+}
+```
+
+Here is how you'd use the `envelope` parameter to extract the payload inside the products key before validating:
+
+```java
+public class MyCustomEventHandler implements RequestHandler {
+
+ @Override
+ @Validation(inboundSchema = "classpath:/my_custom_event_schema.json",
+ envelope = "basket.products[*]")
+ public String handleRequest(MyCustomEvent input, Context context) {
+ return "OK";
+ }
+}
+```
+
+This is quite powerful because you can use JMESPath Query language to extract records from
+[arrays, slice and dice](https://jmespath.org/tutorial.html#list-and-slice-projections),
+to [pipe expressions](https://jmespath.org/tutorial.html#pipe-expressions)
+and [function](https://jmespath.org/tutorial.html#functions) expressions, where you'd extract what you need before validating the actual payload.
+
+## JMESPath functions
+
+JMESPath functions ensure to make an operation on a specific part of the json.validate
+
+Powertools provides two built-in functions:
+
+### powertools_base64 function
+
+Use `powertools_base64` function to decode any base64 data.
+
+This sample will decode the base64 value within the data key, and decode the JSON string into a valid JSON before we can validate it:
+
+```json
+{
+ "data" : "ewogICJpZCI6IDQzMjQyLAogICJuYW1lIjogIkZvb0JhciBYWSIsCiAgInByaWNlIjogMjU4Cn0="
+}
+```
+
+```java
+public class MyEventHandler implements RequestHandler {
+
+ @Override
+ public String handleRequest(MyEvent myEvent, Context context) {
+ validate(myEvent, "classpath:/schema.json", "powertools_base64(data)");
+ return "OK";
+ }
+}
+```
+
+### powertools_base64_gzip function
+
+Use `powertools_base64_gzip` function to decompress and decode base64 data.
+
+This sample will decompress and decode base64 data:
+
+```json
+{
+ "data" : "H4sIAAAAAAAA/6vmUlBQykxRslIwMTYyMdIBcfMSc1OBAkpu+flOiUUKEZFKYOGCosxkkLiRqQVXLQDnWo6bOAAAAA=="
+}
+```
+
+```java
+public class MyEventHandler implements RequestHandler {
+
+ @Override
+ public String handleRequest(MyEvent myEvent, Context context) {
+ validate(myEvent, "classpath:/schema.json", "powertools_base64_gzip(data)");
+ return "OK";
+ }
+}
+```
+
+**NOTE:** You don't need any function to transform a JSON String into a JSON object, powertools-validation will do it for you.
+In the 2 previous example, data contains JSON. Just provide the function to transform the base64 / gzipped / ... string into a clear JSON string.
+
+### Bring your own JMESPath function
+
+
+This should only be used for advanced use cases where you have special formats not covered by the built-in functions.
+New functions will be added to the 2 built-in ones.
+
+
+
+Your function must extend `io.burt.jmespath.function.BaseFunction`, take a String as parameter and return a String.
+You can read the [doc](https://github.com/burtcorp/jmespath-java#adding-custom-functions) for more information.
+
+Here is an example that takes some xml and transform it into json:
+```java
+public class XMLFunction extends BaseFunction {
+ public Base64Function() {
+ super("powertools_xml", ArgumentConstraints.typeOf(JmesPathType.STRING));
+ }
+
+ @Override
+ protected T callFunction(Adapter runtime, List> arguments) {
+ T value = arguments.get(0).value();
+ String xmlString = runtime.toString(value);
+
+ String jsonString = // ... transform xmlString to json
+
+ return runtime.createString(jsonString);
+ }
+}
+```
+
+Once your function is created, you need to add it to powertools:
+
+```java
+ValidatorConfig.get().addFunction(new XMLFunction());
+```
+
+You can then use it to do your validation:
+```java
+public class MyXMLEventHandler implements RequestHandler {
+
+ @Override
+ public String handleRequest(MyEventWithXML myEvent, Context context) {
+ validate(myEvent, "classpath:/schema.json", "powertools_xml(path.to.xml_data)");
+ return "OK";
+ }
+}
+```
+or using annotation:
+```java
+public class MyXMLEventHandler implements RequestHandler {
+
+ @Override
+ @Validation(inboundSchema="classpath:/schema.json", envelope="powertools_xml(path.to.xml_data)")
+ public String handleRequest(MyEventWithXML myEvent, Context context) {
+ return "OK";
+ }
+}
+```
+
+## Change the schema version
+By default, powertools-validation is configured with [V7](https://json-schema.org/draft-07/json-schema-release-notes.html).
+You can use the `ValidatorConfig` to change that behaviour:
+
+```java
+ValidatorConfig.get().setSchemaVersion(SpecVersion.VersionFlag.V4);
+```
+
+## Advanced ObjectMapper settings
+If you need to configure the Jackson ObjectMapper, you can use the `ValidatorConfig`:
+
+```java
+ObjectMapper objectMapper= ValidatorConfig.get().getObjectMapper();
+// update (de)serializationConfig or other properties
+```
\ No newline at end of file
diff --git a/docs/gatsby-config.js b/docs/gatsby-config.js
index b79126b44..0c9734559 100644
--- a/docs/gatsby-config.js
+++ b/docs/gatsby-config.js
@@ -31,6 +31,7 @@ module.exports = {
'utilities/sqs_large_message_handling',
'utilities/batch',
'utilities/parameters',
+ 'utilities/validation'
],
},
navConfig: {
diff --git a/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/Validator.java b/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/Validator.java
index 3a9914489..388440ab0 100644
--- a/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/Validator.java
+++ b/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/Validator.java
@@ -27,17 +27,33 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
+/**
+ *
+ */
public class Validator {
private static final String CLASSPATH = "classpath:";
private static final ConcurrentHashMap schemas = new ConcurrentHashMap<>();
/**
- * @param obj
- * @param jsonSchema
- * @param envelope
+ * Validate part of a json object against a json schema
+ *
+ * @param obj The object to validate
+ * @param jsonSchema The schema used to validate: either the schema itself or a path to file in the classpath: "classpath:/path/to/schema.json"
+ * @param envelope a path to a sub object within obj
+ */
+ public static void validate(Object obj, String jsonSchema, String envelope) throws ValidationException {
+ validate(obj, getJsonSchema(jsonSchema), envelope);
+ }
+
+ /**
+ * Validate part of a json object against a json schema
+ *
+ * @param obj The object to validate
+ * @param jsonSchema The schema used to validate
+ * @param envelope a path to a sub object within obj
*/
- public static void validate(Object obj, JsonSchema jsonSchema, String envelope) {
+ public static void validate(Object obj, JsonSchema jsonSchema, String envelope) throws ValidationException {
if (envelope == null || envelope.isEmpty()) {
validate(obj, jsonSchema);
return;
@@ -69,11 +85,22 @@ public static void validate(Object obj, JsonSchema jsonSchema, String envelope)
/**
* Validate a json object against a json schema
*
- * @param obj object to validate
- * @param jsonSchema the schema used to validate
+ * @param obj Object to validate
+ * @param jsonSchema The schema used to validate: either the schema itself or a path to file in the classpath: "classpath:/path/to/schema.json"
* @throws ValidationException if validation fails
*/
- public static void validate(Object obj, JsonSchema jsonSchema) {
+ public static void validate(Object obj, String jsonSchema) throws ValidationException {
+ validate(obj, getJsonSchema(jsonSchema));
+ }
+
+ /**
+ * Validate a json object against a json schema
+ *
+ * @param obj Object to validate
+ * @param jsonSchema The schema used to validate
+ * @throws ValidationException if validation fails
+ */
+ public static void validate(Object obj, JsonSchema jsonSchema) throws ValidationException {
JsonNode jsonNode;
try {
jsonNode = ValidatorConfig.get().getObjectMapper().valueToTree(obj);
@@ -84,6 +111,17 @@ public static void validate(Object obj, JsonSchema jsonSchema) {
validate(jsonNode, jsonSchema);
}
+ /**
+ * Validate a json object (in string format) against a json schema
+ *
+ * @param json Json in string format
+ * @param jsonSchema The schema used to validate: either the schema itself or a path to file in the classpath: "classpath:/path/to/schema.json"
+ * @throws ValidationException if validation fails
+ */
+ public static void validate(String json, String jsonSchema) throws ValidationException {
+ validate(json, getJsonSchema(jsonSchema));
+ }
+
/**
* Validate a json object (in string format) against a json schema
*
@@ -105,7 +143,18 @@ public static void validate(String json, JsonSchema jsonSchema) throws Validatio
/**
* Validate a json object (in map format) against a json schema
*
- * @param map map to be transformed in json and validated against the schema
+ * @param map Map to be transformed in json and validated against the schema
+ * @param jsonSchema The schema used to validate: either the schema itself or a path to file in the classpath: "classpath:/path/to/schema.json"
+ * @throws ValidationException if validation fails
+ */
+ public static void validate(Map map, String jsonSchema) throws ValidationException {
+ validate(map, getJsonSchema(jsonSchema));
+ }
+
+ /**
+ * Validate a json object (in map format) against a json schema
+ *
+ * @param map Map to be transformed in json and validated against the schema
* @param jsonSchema the schema used to validate json map
* @throws ValidationException if validation fails
*/
@@ -120,6 +169,18 @@ public static void validate(Map map, JsonSchema jsonSchema) thro
validate(jsonNode, jsonSchema);
}
+ /**
+ * Validate a json object (in JsonNode format) against a json schema.
+ * Perform the actual validation.
+ *
+ * @param jsonNode Json to be validated against the schema
+ * @param jsonSchema The schema used to validate: either the schema itself or a path to file in the classpath: "classpath:/path/to/schema.json"
+ * @throws ValidationException if validation fails
+ */
+ public static void validate(JsonNode jsonNode, String jsonSchema) throws ValidationException {
+ validate(jsonNode, getJsonSchema(jsonSchema));
+ }
+
/**
* Validate a json object (in JsonNode format) against a json schema.
* Perform the actual validation.
@@ -128,7 +189,7 @@ public static void validate(Map map, JsonSchema jsonSchema) thro
* @param jsonSchema the schema to validate json node
* @throws ValidationException if validation fails
*/
- public static void validate(JsonNode jsonNode, JsonSchema jsonSchema) {
+ public static void validate(JsonNode jsonNode, JsonSchema jsonSchema) throws ValidationException {
Set validationMessages = jsonSchema.validate(jsonNode);
if (!validationMessages.isEmpty()) {
String message;
@@ -158,7 +219,7 @@ public static JsonSchema getJsonSchema(String schema) {
* Optional: validate the schema against the version specifications.
* Store it in memory to avoid reloading it.
*
- * @param schema either the schema itself of a "classpath:/path/to/schema.json"
+ * @param schema either the schema itself of a "classpath:/path/to/schema.json"
* @param validateSchema specify if the schema itself must be validated against specifications
* @return the loaded json schema
*/
From b0821e5b50eeef78bcd45f9ff3f2f740733a8a23 Mon Sep 17 00:00:00 2001
From: Jerome Van Der Linden
Date: Mon, 26 Oct 2020 15:48:34 +0100
Subject: [PATCH 4/7] improve exception messages
---
.../main/java/helloworld/AppValidation.java | 67 +++++++++++++++++++
.../src/main/resources/schema.json | 0
.../validation/ValidationException.java | 8 +++
.../powertools/validation/Validator.java | 12 ++--
4 files changed, 81 insertions(+), 6 deletions(-)
create mode 100644 example/HelloWorldFunction/src/main/java/helloworld/AppValidation.java
create mode 100644 example/HelloWorldFunction/src/main/resources/schema.json
diff --git a/example/HelloWorldFunction/src/main/java/helloworld/AppValidation.java b/example/HelloWorldFunction/src/main/java/helloworld/AppValidation.java
new file mode 100644
index 000000000..df3e758fe
--- /dev/null
+++ b/example/HelloWorldFunction/src/main/java/helloworld/AppValidation.java
@@ -0,0 +1,67 @@
+package helloworld;
+
+import com.amazonaws.services.lambda.runtime.Context;
+import com.amazonaws.services.lambda.runtime.RequestHandler;
+import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
+import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
+import com.amazonaws.xray.AWSXRay;
+import com.amazonaws.xray.entities.Entity;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import software.amazon.cloudwatchlogs.emf.model.DimensionSet;
+import software.amazon.cloudwatchlogs.emf.model.Unit;
+import software.amazon.lambda.powertools.logging.PowertoolsLogger;
+import software.amazon.lambda.powertools.logging.PowertoolsLogging;
+import software.amazon.lambda.powertools.metrics.PowertoolsMetrics;
+import software.amazon.lambda.powertools.tracing.PowerTracer;
+import software.amazon.lambda.powertools.tracing.PowertoolsTracing;
+import software.amazon.lambda.powertools.validation.Validation;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import static software.amazon.lambda.powertools.metrics.PowertoolsMetricsLogger.metricsLogger;
+import static software.amazon.lambda.powertools.metrics.PowertoolsMetricsLogger.withSingleMetric;
+import static software.amazon.lambda.powertools.tracing.PowerTracer.putMetadata;
+import static software.amazon.lambda.powertools.tracing.PowerTracer.withEntitySubsegment;
+
+/**
+ * Handler for requests to Lambda function.
+ */
+public class AppValidation implements RequestHandler {
+
+ @Validation(inboundSchema = "classpath:/schema.json")
+ public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) {
+ Map headers = new HashMap<>();
+
+ headers.put("Content-Type", "application/json");
+ headers.put("X-Custom-Header", "application/json");
+
+ APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent()
+ .withHeaders(headers);
+ try {
+ final String pageContents = this.getPageContents("https://checkip.amazonaws.com");
+ String output = String.format("{ \"message\": \"hello world\", \"location\": \"%s\" }", pageContents);
+
+ return response
+ .withStatusCode(200)
+ .withBody(output);
+ } catch (IOException | InterruptedException e) {
+ return response
+ .withBody("{}")
+ .withStatusCode(500);
+ }
+ }
+
+ private String getPageContents(String address) throws IOException {
+ URL url = new URL(address);
+ try (BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream()))) {
+ return br.lines().collect(Collectors.joining(System.lineSeparator()));
+ }
+ }
+}
diff --git a/example/HelloWorldFunction/src/main/resources/schema.json b/example/HelloWorldFunction/src/main/resources/schema.json
new file mode 100644
index 000000000..e69de29bb
diff --git a/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/ValidationException.java b/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/ValidationException.java
index 901d78a44..cf8ebe02b 100644
--- a/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/ValidationException.java
+++ b/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/ValidationException.java
@@ -13,6 +13,10 @@
*/
package software.amazon.lambda.powertools.validation;
+import com.fasterxml.jackson.core.JsonProcessingException;
+
+import java.io.IOException;
+
public class ValidationException extends RuntimeException {
private static final long serialVersionUID = 1133341411263381508L;
@@ -24,4 +28,8 @@ public ValidationException(String message) {
public ValidationException(Exception e) {
super(e);
}
+
+ public ValidationException(String message, Exception e) {
+ super(message, e);
+ }
}
diff --git a/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/Validator.java b/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/Validator.java
index 388440ab0..758b05b6e 100644
--- a/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/Validator.java
+++ b/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/Validator.java
@@ -28,7 +28,7 @@
import java.util.stream.Collectors;
/**
- *
+ * Validation utility, used to manually validate Json against Json Schema
*/
public class Validator {
private static final String CLASSPATH = "classpath:";
@@ -64,7 +64,7 @@ public static void validate(Object obj, JsonSchema jsonSchema, String envelope)
Expression expression = ValidatorConfig.get().getJmesPath().compile(envelope);
subNode = expression.search(jsonNode);
} catch (Exception e) {
- throw new ValidationException(e);
+ throw new ValidationException("Cannot find envelope <"+envelope+"> in the object <"+obj+">", e);
}
if (subNode.getNodeType() == JsonNodeType.ARRAY) {
subNode.forEach(jsonNode -> validate(jsonNode, jsonSchema));
@@ -105,7 +105,7 @@ public static void validate(Object obj, JsonSchema jsonSchema) throws Validation
try {
jsonNode = ValidatorConfig.get().getObjectMapper().valueToTree(obj);
} catch (Exception e) {
- throw new ValidationException(e);
+ throw new ValidationException("Object <"+obj+"> is not valid against the schema provided", e);
}
validate(jsonNode, jsonSchema);
@@ -133,8 +133,8 @@ public static void validate(String json, JsonSchema jsonSchema) throws Validatio
JsonNode jsonNode;
try {
jsonNode = ValidatorConfig.get().getObjectMapper().readTree(json);
- } catch (JsonProcessingException e) {
- throw new ValidationException(e);
+ } catch (Exception e) {
+ throw new ValidationException("Json <"+json+"> is not valid against the schema provided", e);
}
validate(jsonNode, jsonSchema);
@@ -163,7 +163,7 @@ public static void validate(Map map, JsonSchema jsonSchema) thro
try {
jsonNode = ValidatorConfig.get().getObjectMapper().valueToTree(map);
} catch (Exception e) {
- throw new ValidationException(e);
+ throw new ValidationException("Map <"+map+"> cannot be converted to json for validation", e);
}
validate(jsonNode, jsonSchema);
From 81f4c7d399899e621ca93730343548fbb716be34 Mon Sep 17 00:00:00 2001
From: Jerome Van Der Linden
Date: Mon, 26 Oct 2020 15:48:51 +0100
Subject: [PATCH 5/7] sample
---
example/HelloWorldFunction/pom.xml | 9 +++
.../main/java/helloworld/AppValidation.java | 19 +------
.../src/main/resources/schema.json | 55 +++++++++++++++++++
example/template.yaml | 15 +++++
4 files changed, 81 insertions(+), 17 deletions(-)
diff --git a/example/HelloWorldFunction/pom.xml b/example/HelloWorldFunction/pom.xml
index 05f73c5cb..b36279fee 100644
--- a/example/HelloWorldFunction/pom.xml
+++ b/example/HelloWorldFunction/pom.xml
@@ -33,6 +33,11 @@
powertools-parameters0.5.0-beta
+
+ software.amazon.lambda
+ powertools-validation
+ 0.5.0-beta
+ software.amazon.lambdapowertools-sqs
@@ -99,6 +104,10 @@
software.amazon.lambdapowertools-sqs
+
+ software.amazon.lambda
+ powertools-validation
+
diff --git a/example/HelloWorldFunction/src/main/java/helloworld/AppValidation.java b/example/HelloWorldFunction/src/main/java/helloworld/AppValidation.java
index df3e758fe..40135722d 100644
--- a/example/HelloWorldFunction/src/main/java/helloworld/AppValidation.java
+++ b/example/HelloWorldFunction/src/main/java/helloworld/AppValidation.java
@@ -4,18 +4,8 @@
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
-import com.amazonaws.xray.AWSXRay;
-import com.amazonaws.xray.entities.Entity;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-import software.amazon.cloudwatchlogs.emf.model.DimensionSet;
-import software.amazon.cloudwatchlogs.emf.model.Unit;
-import software.amazon.lambda.powertools.logging.PowertoolsLogger;
-import software.amazon.lambda.powertools.logging.PowertoolsLogging;
-import software.amazon.lambda.powertools.metrics.PowertoolsMetrics;
-import software.amazon.lambda.powertools.tracing.PowerTracer;
-import software.amazon.lambda.powertools.tracing.PowertoolsTracing;
import software.amazon.lambda.powertools.validation.Validation;
+import software.amazon.lambda.powertools.validation.Validator;
import java.io.BufferedReader;
import java.io.IOException;
@@ -25,11 +15,6 @@
import java.util.Map;
import java.util.stream.Collectors;
-import static software.amazon.lambda.powertools.metrics.PowertoolsMetricsLogger.metricsLogger;
-import static software.amazon.lambda.powertools.metrics.PowertoolsMetricsLogger.withSingleMetric;
-import static software.amazon.lambda.powertools.tracing.PowerTracer.putMetadata;
-import static software.amazon.lambda.powertools.tracing.PowerTracer.withEntitySubsegment;
-
/**
* Handler for requests to Lambda function.
*/
@@ -51,7 +36,7 @@ public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEv
return response
.withStatusCode(200)
.withBody(output);
- } catch (IOException | InterruptedException e) {
+ } catch (IOException e) {
return response
.withBody("{}")
.withStatusCode(500);
diff --git a/example/HelloWorldFunction/src/main/resources/schema.json b/example/HelloWorldFunction/src/main/resources/schema.json
index e69de29bb..f38272f2d 100644
--- a/example/HelloWorldFunction/src/main/resources/schema.json
+++ b/example/HelloWorldFunction/src/main/resources/schema.json
@@ -0,0 +1,55 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema",
+ "$id": "http://example.com/product.json",
+ "type": "object",
+ "title": "Product schema",
+ "description": "JSON schema to validate Products",
+ "default": {},
+ "examples": [
+ {
+ "id": 43242,
+ "name": "FooBar XY",
+ "price": 258
+ }
+ ],
+ "required": [
+ "id",
+ "name",
+ "price"
+ ],
+ "properties": {
+ "id": {
+ "$id": "#/properties/id",
+ "type": "integer",
+ "title": "Id of the product",
+ "description": "Unique identifier of the product",
+ "default": 0,
+ "examples": [
+ 43242
+ ]
+ },
+ "name": {
+ "$id": "#/properties/name",
+ "type": "string",
+ "title": "Name of the product",
+ "description": "Explicit name of the product",
+ "minLength": 5,
+ "default": "",
+ "examples": [
+ "FooBar XY"
+ ]
+ },
+ "price": {
+ "$id": "#/properties/price",
+ "type": "number",
+ "title": "Price of the product",
+ "description": "Positive price of the product",
+ "default": 0,
+ "exclusiveMinimum": 0,
+ "examples": [
+ 258.99
+ ]
+ }
+ },
+ "additionalProperties": true
+}
\ No newline at end of file
diff --git a/example/template.yaml b/example/template.yaml
index 9f279c2bf..f9b6729f8 100644
--- a/example/template.yaml
+++ b/example/template.yaml
@@ -30,6 +30,21 @@ Resources:
Path: /hello
Method: get
+ HelloWorldValidationFunction:
+ Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
+ Properties:
+ CodeUri: HelloWorldFunction
+ Handler: helloworld.AppValidation::handleRequest
+ Runtime: java8
+ MemorySize: 512
+ Tracing: Active
+ Events:
+ HelloWorld:
+ Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
+ Properties:
+ Path: /hello
+ Method: post
+
HelloWorldStreamFunction:
Type: AWS::Serverless::Function
Properties:
From 94010a8ffe755c22801f90be70267d1ed0ebef2e Mon Sep 17 00:00:00 2001
From: Jerome Van Der Linden
Date: Mon, 26 Oct 2020 17:06:29 +0100
Subject: [PATCH 6/7] review doc
---
docs/content/utilities/validation.mdx | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/docs/content/utilities/validation.mdx b/docs/content/utilities/validation.mdx
index 07cef0740..29abd1e32 100644
--- a/docs/content/utilities/validation.mdx
+++ b/docs/content/utilities/validation.mdx
@@ -5,7 +5,7 @@ description: Utility
import Note from "../../src/components/Note"
-This utility provides JSON Schema validation for handler events and responses.
+This utility provides JSON Schema validation for payloads held within events and response used in AWS Lambda.
**Key features**
* Validate incoming events and responses
@@ -28,15 +28,15 @@ To install this utility, add the following dependency to your project.
You can validate inbound and outbound events using `@Validation` annotation.
-You can also use the `Validator#validate()` methods, if you want more control over the validation process such as handling a validation error.you
+You can also use the `Validator#validate()` methods, if you want more control over the validation process such as handling a validation error.
We support JSON schema version 4, 6, 7 and 201909 (from [jmespath-jackson library](https://github.com/burtcorp/jmespath-java)).
### @Validation annotation
-`@Validation` annotation is typically used to validate either inbound or functions' response.
+`@Validation` annotation is used to validate either inbound or functions' response.
-It will fail fast with `ValidationException` if event or response doesn't conform with given JSON Schema.
+It will fail fast with `ValidationException` if an event or response doesn't conform with given JSON Schema.
While it is easier to specify a json schema file in the classpath (using the notation `"classpath:/path/to/schema.json"`), you can also provide a JSON String containing the schema.
@@ -56,7 +56,7 @@ public class MyFunctionHandler implements RequestHandler
Date: Tue, 27 Oct 2020 10:40:03 +0100
Subject: [PATCH 7/7] complete doc
---
docs/content/utilities/validation.mdx | 41 ++++++++++++++++++++++++++-
1 file changed, 40 insertions(+), 1 deletion(-)
diff --git a/docs/content/utilities/validation.mdx b/docs/content/utilities/validation.mdx
index 29abd1e32..c9d60711e 100644
--- a/docs/content/utilities/validation.mdx
+++ b/docs/content/utilities/validation.mdx
@@ -24,6 +24,45 @@ To install this utility, add the following dependency to your project.
```
+And configure the aspectj-maven-plugin to compile-time weave (CTW) the
+aws-lambda-powertools-java aspects into your project. You may already have this
+plugin in your pom. In that case add the dependency to the `aspectLibraries`
+section.
+
+```xml
+
+
+ ...
+
+ org.codehaus.mojo
+ aspectj-maven-plugin
+ 1.11
+
+ 1.8
+ 1.8
+ 1.8
+
+
+
+ software.amazon.lambda
+ powertools-validation
+
+
+
+
+
+
+
+ compile
+
+
+
+
+ ...
+
+
+```
+
## Validating events
You can validate inbound and outbound events using `@Validation` annotation.
@@ -34,7 +73,7 @@ We support JSON schema version 4, 6, 7 and 201909 (from [jmespath-jackson librar
### @Validation annotation
-`@Validation` annotation is used to validate either inbound or functions' response.
+`@Validation` annotation is used to validate either inbound events or functions' response.
It will fail fast with `ValidationException` if an event or response doesn't conform with given JSON Schema.