diff --git a/docs/utilities/validation.md b/docs/utilities/validation.md index cc1c71bda..226e10bb6 100644 --- a/docs/utilities/validation.md +++ b/docs/utilities/validation.md @@ -83,7 +83,9 @@ 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. -We support JSON schema version 4, 6, 7 and 201909 (from [jmespath-jackson library](https://github.com/burtcorp/jmespath-java)). +We support JSON schema version 4, 6, 7, 2019-09 and 2020-12 using the [NetworkNT JSON Schema Validator](https://github.com/networknt/json-schema-validator). ([Compatibility with JSON Schema versions](https://github.com/networknt/json-schema-validator/blob/master/doc/compatibility.md)). + +The validator is configured to enable format assertions by default even for 2019-09 and 2020-12. ### Validation annotation @@ -228,7 +230,8 @@ and [function](https://jmespath.org/tutorial.html#functions) expressions, where ## Change the schema version -By default, powertools-validation is configured with [V7](https://json-schema.org/draft-07/json-schema-release-notes.html). +By default, powertools-validation is configured to use [V7](https://json-schema.org/draft-07/json-schema-release-notes.html) as the default dialect if [`$schema`](https://json-schema.org/understanding-json-schema/reference/schema#schema) is not explicitly specified within the schema. If [`$schema`](https://json-schema.org/understanding-json-schema/reference/schema#schema) is explicitly specified within the schema, the validator will use the specified dialect. + You can use the `ValidationConfig` to change that behaviour. === "Handler with custom schema version" diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/ValidationALBE2ET.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/ValidationALBE2ET.java index 324c77a34..41696943a 100644 --- a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/ValidationALBE2ET.java +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/ValidationALBE2ET.java @@ -85,7 +85,7 @@ void test_invalidInboundSQSEvent() throws IOException { // THEN // invocation should fail inbound validation and return an error message JsonNode validJsonNode = objectMapper.readTree(invocationResult.getResult()); - assertThat(validJsonNode.get("errorMessage").asText()).contains("$.price: is missing but it is required"); + assertThat(validJsonNode.get("errorMessage").asText()).contains(": required property 'price' not found"); } @Test @@ -99,6 +99,6 @@ void test_invalidOutboundSQSEvent() throws IOException { // THEN // invocation should fail outbound validation and return 400 JsonNode validJsonNode = objectMapper.readTree(invocationResult.getResult()); - assertThat(validJsonNode.get("errorMessage").asText()).contains("$.price: must have an exclusive maximum value of 1000"); + assertThat(validJsonNode.get("errorMessage").asText()).contains("/price: must have an exclusive maximum value of 1000"); } } diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/ValidationApiGWE2ET.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/ValidationApiGWE2ET.java index af7c7d87c..425399c95 100644 --- a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/ValidationApiGWE2ET.java +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/ValidationApiGWE2ET.java @@ -86,7 +86,7 @@ void test_invalidInboundApiGWEvent() throws IOException { // invocation should fail inbound validation and return 400 JsonNode validJsonNode = objectMapper.readTree(invocationResult.getResult()); assertThat(validJsonNode.get("statusCode").asInt()).isEqualTo(400); - assertThat(validJsonNode.get("body").asText()).contains("$.price: is missing but it is required"); + assertThat(validJsonNode.get("body").asText()).contains(": required property 'price' not found"); } @Test @@ -102,6 +102,6 @@ void test_invalidOutboundApiGWEvent() throws IOException { JsonNode validJsonNode = objectMapper.readTree(invocationResult.getResult()); assertThat(validJsonNode.get("statusCode").asInt()).isEqualTo(400); assertThat(validJsonNode.get("body").asText()) - .contains("$.price: must have an exclusive maximum value of 1000"); + .contains("/price: must have an exclusive maximum value of 1000"); } } diff --git a/powertools-validation/pom.xml b/powertools-validation/pom.xml index 0de38c1c1..fa299b591 100644 --- a/powertools-validation/pom.xml +++ b/powertools-validation/pom.xml @@ -65,7 +65,7 @@ com.networknt json-schema-validator - 1.0.87 + 1.4.3 com.amazonaws diff --git a/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/ValidationConfig.java b/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/ValidationConfig.java index ccc5a4c2c..3f643ab00 100644 --- a/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/ValidationConfig.java +++ b/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/ValidationConfig.java @@ -47,9 +47,15 @@ public SpecVersion.VersionFlag getSchemaVersion() { } /** - * Set the version of the json schema specifications (default is V7) + * Set the version of the json schema specifications to use if $schema is not + * explicitly specified within the schema (default is V7). If $schema is + * explicitly specified within the schema is explicitly specified within the + * schema, the validator will use the specified dialect. * * @param version May be V4, V6, V7, V201909 or V202012 + * @see Declaring + * a Dialect */ public void setSchemaVersion(SpecVersion.VersionFlag version) { if (version != jsonSchemaVersion) { diff --git a/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/ValidationUtils.java b/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/ValidationUtils.java index 221e5fb1d..35b309f07 100644 --- a/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/ValidationUtils.java +++ b/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/ValidationUtils.java @@ -21,18 +21,16 @@ import com.fasterxml.jackson.databind.node.JsonNodeType; import com.fasterxml.jackson.databind.node.NullNode; import com.networknt.schema.JsonSchema; +import com.networknt.schema.SchemaLocation; import com.networknt.schema.SchemaValidatorsConfig; import com.networknt.schema.ValidationMessage; -import com.networknt.schema.uri.URITranslator; import io.burt.jmespath.Expression; import java.io.ByteArrayOutputStream; -import java.io.InputStream; import java.util.Collections; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; -import software.amazon.lambda.powertools.validation.internal.ValidationAspect; /** * Validation utility, used to manually validate Json against Json Schema @@ -255,27 +253,26 @@ public static JsonSchema getJsonSchema(String schema, boolean validateSchema) { private static JsonSchema createJsonSchema(String schema) { JsonSchema jsonSchema; + SchemaValidatorsConfig config = SchemaValidatorsConfig.builder().formatAssertionsEnabled(true) + .preloadJsonSchemaRefMaxNestingDepth(10).build(); if (schema.startsWith(CLASSPATH)) { - String filePath = schema.substring(CLASSPATH.length()); - try (InputStream schemaStream = ValidationAspect.class.getResourceAsStream(filePath)) { - - SchemaValidatorsConfig config = new SchemaValidatorsConfig(); - config.addUriTranslator(URITranslator.prefix("https://json-schema.org", "resource:")); - - jsonSchema = ValidationConfig.get().getFactory().getSchema(schemaStream, config); + try { + jsonSchema = ValidationConfig.get().getFactory().getSchema(SchemaLocation.of(schema), config); } catch (Exception e) { + String filePath = schema.substring(CLASSPATH.length()); throw new IllegalArgumentException( - "'" + schema + "' is invalid, verify '" + filePath + "' is in your classpath"); + "'" + schema + "' is invalid, verify '" + filePath + "' is in your classpath", e); } } else { - jsonSchema = ValidationConfig.get().getFactory().getSchema(schema); + jsonSchema = ValidationConfig.get().getFactory().getSchema(schema, config); } return jsonSchema; } private static void validateSchema(String schema, JsonSchema jsonSchema) { - String schemaId = ValidationConfig.get().getSchemaVersion().getId().replace("https://json-schema.org", ""); + String schemaId = jsonSchema.getValidationContext().getMetaSchema().getIri() + .replace("https://json-schema.org", "").replace("http://json-schema.org", ""); try { validate(jsonSchema.getSchemaNode(), getJsonSchema(CLASSPATH + schemaId)); diff --git a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/ValidationUtilsTest.java b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/ValidationUtilsTest.java index d80670669..73c3c6567 100644 --- a/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/ValidationUtilsTest.java +++ b/powertools-validation/src/test/java/software/amazon/lambda/powertools/validation/ValidationUtilsTest.java @@ -48,7 +48,7 @@ public void testLoadSchemaV7OK() { ValidationConfig.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"); + assertThat(jsonSchema.getId()).isEqualTo("http://example.com/product.json"); } @Test @@ -57,7 +57,7 @@ public void testLoadSchemaV7KO() { 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 /draft-07/schema"); + "The schema classpath:/schema_v7_ko.json is not valid, it does not respect the specification /draft-07/schema#"); } @Test diff --git a/powertools-validation/src/test/resources/schema_v7.json b/powertools-validation/src/test/resources/schema_v7.json index f38272f2d..e382b8971 100644 --- a/powertools-validation/src/test/resources/schema_v7.json +++ b/powertools-validation/src/test/resources/schema_v7.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema", + "$schema": "http://json-schema.org/draft-07/schema#", "$id": "http://example.com/product.json", "type": "object", "title": "Product schema", diff --git a/powertools-validation/src/test/resources/schema_v7_ko.json b/powertools-validation/src/test/resources/schema_v7_ko.json index f54bcb3c7..aed187c34 100644 --- a/powertools-validation/src/test/resources/schema_v7_ko.json +++ b/powertools-validation/src/test/resources/schema_v7_ko.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema", + "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "title": "Product schema", "description": "JSON schema to validate Products",