From ffb34ce5b61710b87f4434a486b74978d5725f78 Mon Sep 17 00:00:00 2001 From: Nestor Carvantes Date: Mon, 1 Mar 2021 22:14:12 -0800 Subject: [PATCH] feat: add HAPI validator lambda fn (#221) --- javaHapiValidatorLambda/.gitignore | 14 ++ javaHapiValidatorLambda/pom.xml | 185 ++++++++++++++++++ javaHapiValidatorLambda/serverless.yml | 44 +++++ .../java/software/amazon/fwoa/Handler.java | 35 ++++ .../java/software/amazon/fwoa/Validator.java | 161 +++++++++++++++ .../amazon/fwoa/ValidatorResponse.java | 25 +++ .../software/amazon/fwoa/models/IgFile.java | 33 ++++ .../software/amazon/fwoa/models/IgIndex.java | 20 ++ .../resources/implementationGuides/.gitkeep | 0 .../src/main/resources/log4j2.xml | 15 ++ .../software/amazon/fwoa/ValidatorTest.java | 90 +++++++++ 11 files changed, 622 insertions(+) create mode 100644 javaHapiValidatorLambda/.gitignore create mode 100644 javaHapiValidatorLambda/pom.xml create mode 100644 javaHapiValidatorLambda/serverless.yml create mode 100644 javaHapiValidatorLambda/src/main/java/software/amazon/fwoa/Handler.java create mode 100644 javaHapiValidatorLambda/src/main/java/software/amazon/fwoa/Validator.java create mode 100644 javaHapiValidatorLambda/src/main/java/software/amazon/fwoa/ValidatorResponse.java create mode 100644 javaHapiValidatorLambda/src/main/java/software/amazon/fwoa/models/IgFile.java create mode 100644 javaHapiValidatorLambda/src/main/java/software/amazon/fwoa/models/IgIndex.java create mode 100644 javaHapiValidatorLambda/src/main/resources/implementationGuides/.gitkeep create mode 100644 javaHapiValidatorLambda/src/main/resources/log4j2.xml create mode 100644 javaHapiValidatorLambda/src/test/java/software/amazon/fwoa/ValidatorTest.java diff --git a/javaHapiValidatorLambda/.gitignore b/javaHapiValidatorLambda/.gitignore new file mode 100644 index 00000000..3b4a416a --- /dev/null +++ b/javaHapiValidatorLambda/.gitignore @@ -0,0 +1,14 @@ +*.class +target +/bin/ +/.settings/ +.project +.classpath + +# Package Files +*.jar +*.war +*.ear + +# Serverless directories +.serverless diff --git a/javaHapiValidatorLambda/pom.xml b/javaHapiValidatorLambda/pom.xml new file mode 100644 index 00000000..8cc6396c --- /dev/null +++ b/javaHapiValidatorLambda/pom.xml @@ -0,0 +1,185 @@ + + 4.0.0 + + software.amazon.fwoa + fwoa-hapi-validator + jar + dev + fwoa-hapi-validator + + + 1.8 + 1.8 + UTF-8 + + + + + org.junit + junit-bom + 5.7.1 + pom + import + + + + + + com.amazonaws + aws-lambda-java-core + 1.2.1 + + + com.amazonaws + aws-lambda-java-events + 2.2.9 + + + com.amazonaws + aws-lambda-java-log4j2 + 1.2.0 + + + com.google.code.gson + gson + 2.8.6 + + + org.apache.logging.log4j + log4j-api + 2.13.0 + + + org.apache.logging.log4j + log4j-core + 2.13.2 + + + org.apache.logging.log4j + log4j-slf4j18-impl + 2.13.0 + + + com.amazonaws + aws-xray-recorder-sdk-core + 2.4.0 + + + com.amazonaws + aws-xray-recorder-sdk-aws-sdk-core + 2.4.0 + + + com.amazonaws + aws-xray-recorder-sdk-aws-sdk-v2 + 2.4.0 + + + com.amazonaws + aws-xray-recorder-sdk-aws-sdk-v2-instrumentor + 2.4.0 + + + ca.uhn.hapi.fhir + hapi-fhir-structures-r4 + 5.3.0 + + + ca.uhn.hapi.fhir + hapi-fhir-validation + 5.3.0 + + + ca.uhn.hapi.fhir + hapi-fhir-validation-resources-r4 + 5.3.0 + + + com.fasterxml.jackson.core + jackson-core + 2.12.1 + + + io.github.classgraph + classgraph + 4.8.102 + + + org.projectlombok + lombok + 1.18.16 + provided + + + commons-codec + commons-codec + 1.15 + + + + + org.junit.jupiter + junit-jupiter + test + + + + + + + + maven-compiler-plugin + 3.8.1 + + + maven-surefire-plugin + 2.22.2 + + + + org.apache.maven.plugins + maven-shade-plugin + 2.3 + + false + + + + package + + shade + + + + + + + META-INF/TE-050AC.SF + META-INF/TE-050AC.RSA + + + + + + + + com.github.edwgiz + maven-shade-plugin.log4j2-cachefile-transformer + 2.8.1 + + + + + + + diff --git a/javaHapiValidatorLambda/serverless.yml b/javaHapiValidatorLambda/serverless.yml new file mode 100644 index 00000000..4de08367 --- /dev/null +++ b/javaHapiValidatorLambda/serverless.yml @@ -0,0 +1,44 @@ +# +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +service: fhir-service-validator + +custom: + stage: ${opt:stage, self:provider.stage} + region: ${opt:region, self:provider.region} + +provider: + name: aws + region: us-west-2 + stage: dev + runtime: java11 + logRetentionInDays: 1827 # 5 years + stackTags: + FHIR_SERVICE: 'fhir-service-validator-${self:custom.region}-${self:custom.stage}' + tracing: + lambda: true + +package: + artifact: target/fwoa-hapi-validator-dev.jar + +functions: + validator: + handler: software.amazon.fwoa.Handler + timeout: 60 + memorySize: 2048 + provisionedConcurrency: 5 + +resources: + - Parameters: + Stage: + Type: String + Default: ${self:custom.stage} + Description: 'The deployment stage (e.g. dev, qa, prod). Default: dev' + - Outputs: + ValidatorLambdaAlias: + Description: Validator lambda function alias + Value: !Ref ValidatorProvConcLambdaAlias # serverless by convention capitalizes first letter and suffixes with "ProvConcLambdaAlias" + Export: + Name: !Join ['-', ['fhir-service-validator-lambda', !Ref Stage]] diff --git a/javaHapiValidatorLambda/src/main/java/software/amazon/fwoa/Handler.java b/javaHapiValidatorLambda/src/main/java/software/amazon/fwoa/Handler.java new file mode 100644 index 00000000..1f84464a --- /dev/null +++ b/javaHapiValidatorLambda/src/main/java/software/amazon/fwoa/Handler.java @@ -0,0 +1,35 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.fwoa; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class Handler implements RequestHandler { + + private final Validator validator; + + public Handler() { + log.info("Creating the Validator instance for the first time..."); + validator = new Validator(); + + log.info("Validating once to force the loading of all the validator related classes"); + // Validating a complex Patient yields better results. validating a trivial "empty" Patient won't load all the validation classes. + String someSyntheaPatient = "{\"resourceType\":\"Patient\",\"id\":\"a8bc0c9f-47b3-ee31-60c6-fb8ce8077ac7\",\"text\":{\"status\":\"generated\",\"div\":\"
Generated by Synthea.Version identifier: master-branch-latest-2-gfd2217b\\n . Person seed: -5969330820059413579 Population seed: 1614314878171
\"},\"extension\":[{\"url\":\"http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName\",\"valueString\":\"Son314 Vandervort697\"},{\"url\":\"http://hl7.org/fhir/StructureDefinition/patient-birthPlace\",\"valueAddress\":{\"city\":\"New Bedford\",\"state\":\"Massachusetts\",\"country\":\"US\"}},{\"url\":\"http://synthetichealth.github.io/synthea/disability-adjusted-life-years\",\"valueDecimal\":1.1872597438165626},{\"url\":\"http://synthetichealth.github.io/synthea/quality-adjusted-life-years\",\"valueDecimal\":70.81274025618343}],\"identifier\":[{\"system\":\"https://github.com/synthetichealth/synthea\",\"value\":\"a8bc0c9f-47b3-ee31-60c6-fb8ce8077ac7\"},{\"type\":{\"coding\":[{\"system\":\"http://terminology.hl7.org/CodeSystem/v2-0203\",\"code\":\"MR\",\"display\":\"Medical Record Number\"}],\"text\":\"Medical Record Number\"},\"system\":\"http://hospital.smarthealthit.org\",\"value\":\"a8bc0c9f-47b3-ee31-60c6-fb8ce8077ac7\"},{\"type\":{\"coding\":[{\"system\":\"http://terminology.hl7.org/CodeSystem/v2-0203\",\"code\":\"SS\",\"display\":\"Social Security Number\"}],\"text\":\"Social Security Number\"},\"system\":\"http://hl7.org/fhir/sid/us-ssn\",\"value\":\"999-49-6778\"},{\"type\":{\"coding\":[{\"system\":\"http://terminology.hl7.org/CodeSystem/v2-0203\",\"code\":\"DL\",\"display\":\"Driver's License\"}],\"text\":\"Driver's License\"},\"system\":\"urn:oid:2.16.840.1.113883.4.3.25\",\"value\":\"S99922723\"},{\"type\":{\"coding\":[{\"system\":\"http://terminology.hl7.org/CodeSystem/v2-0203\",\"code\":\"PPN\",\"display\":\"Passport Number\"}],\"text\":\"Passport Number\"},\"system\":\"http://standardhealthrecord.org/fhir/StructureDefinition/passportNumber\",\"value\":\"X72123203X\"}],\"name\":[{\"use\":\"official\",\"family\":\"Beier427\",\"given\":[\"Minnie888\"],\"prefix\":[\"Mrs.\"]},{\"use\":\"maiden\",\"family\":\"Jaskolski867\",\"given\":[\"Minnie888\"],\"prefix\":[\"Mrs.\"]}],\"telecom\":[{\"system\":\"phone\",\"value\":\"555-390-9260\",\"use\":\"home\"}],\"gender\":\"female\",\"birthDate\":\"1949-01-01\",\"address\":[{\"extension\":[{\"url\":\"http://hl7.org/fhir/StructureDefinition/geolocation\",\"extension\":[{\"url\":\"latitude\",\"valueDecimal\":41.83492774608349},{\"url\":\"longitude\",\"valueDecimal\":-70.58336455010793}]}],\"line\":[\"862 Sauer Station Suite 31\"],\"city\":\"Plymouth\",\"state\":\"Massachusetts\",\"country\":\"US\"}],\"maritalStatus\":{\"coding\":[{\"system\":\"http://terminology.hl7.org/CodeSystem/v3-MaritalStatus\",\"code\":\"M\",\"display\":\"M\"}],\"text\":\"M\"},\"multipleBirthInteger\":3,\"communication\":[{\"language\":{\"coding\":[{\"system\":\"urn:ietf:bcp:47\",\"code\":\"en-US\",\"display\":\"English\"}],\"text\":\"English\"}}]}"; + validator.validate(someSyntheaPatient); + log.info("Validator is ready"); + } + + @Override + public ValidatorResponse handleRequest(String event, Context context) { + ValidatorResponse validate = validator.validate(event); + return validate; + + } +} diff --git a/javaHapiValidatorLambda/src/main/java/software/amazon/fwoa/Validator.java b/javaHapiValidatorLambda/src/main/java/software/amazon/fwoa/Validator.java new file mode 100644 index 00000000..748c0fc6 --- /dev/null +++ b/javaHapiValidatorLambda/src/main/java/software/amazon/fwoa/Validator.java @@ -0,0 +1,161 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.fwoa; + +import java.io.InputStream; +import java.util.stream.Collectors; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.gson.Gson; +import com.google.gson.JsonSyntaxException; + +import org.hl7.fhir.common.hapi.validation.support.CommonCodeSystemsTerminologyService; +import org.hl7.fhir.common.hapi.validation.support.InMemoryTerminologyServerValidationSupport; +import org.hl7.fhir.common.hapi.validation.support.PrePopulatedValidationSupport; +import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain; +import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator; +import org.hl7.fhir.r4.model.CodeSystem; +import org.hl7.fhir.r4.model.StructureDefinition; +import org.hl7.fhir.r4.model.ValueSet; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; +import ca.uhn.fhir.parser.IParser; +import ca.uhn.fhir.parser.StrictErrorHandler; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.validation.FhirValidator; +import ca.uhn.fhir.validation.ValidationResult; +import io.github.classgraph.ClassGraph; +import io.github.classgraph.Resource; +import io.github.classgraph.ResourceList; +import io.github.classgraph.ScanResult; +import lombok.extern.slf4j.Slf4j; +import software.amazon.fwoa.models.IgFile; +import software.amazon.fwoa.models.IgIndex; + +/** + * This class is a wrapper around the HAPI FhirValidator. + * The FhirValidator is built using default settings and the available implementation guides are loaded into it. + */ +@Slf4j +public class Validator { + private static final Gson GSON = new Gson(); + public static final String IMPLEMENTATION_GUIDES_FOLDER = "implementationGuides"; + + private final FhirValidator validator; + + public Validator() { + // To learn more about the different ways to configure FhirInstanceValidator see: https://hapifhir.io/hapi-fhir/docs/validation/validation_support_modules.html + FhirContext ctx = FhirContext.forR4(); + + // Create a chain that will hold our modules + ValidationSupportChain supportChain = new ValidationSupportChain(); + + // DefaultProfileValidationSupport supplies base FHIR definitions. This is generally required + // even if you are using custom profiles, since those profiles will derive from the base + // definitions. + DefaultProfileValidationSupport defaultSupport = new DefaultProfileValidationSupport(ctx); + supportChain.addValidationSupport(defaultSupport); + + // This module supplies several code systems that are commonly used in validation + supportChain.addValidationSupport(new CommonCodeSystemsTerminologyService(ctx)); + + // This module implements terminology services for in-memory code validation + supportChain.addValidationSupport(new InMemoryTerminologyServerValidationSupport(ctx)); + + // Create a PrePopulatedValidationSupport which can be used to load custom definitions. + PrePopulatedValidationSupport prepopulatedValidationSupport = loadIgs(ctx); + supportChain.addValidationSupport(prepopulatedValidationSupport); + + // Create a validator using the FhirInstanceValidator module. + FhirInstanceValidator validatorModule = new FhirInstanceValidator(supportChain); + validator = ctx.newValidator().registerValidatorModule(validatorModule); + } + + public ValidatorResponse validate(String resourceAsJsonText) { + try { + ValidationResult result = validator.validateWithResult(resourceAsJsonText); + return toValidatorResponse(result); + } catch (JsonSyntaxException | NullPointerException | IllegalArgumentException | InvalidRequestException e) { + return ValidatorResponse.builder() + .isSuccessful(false) + .errorMessages(ImmutableList.of(ValidatorErrorMessage.builder() + .msg("Invalid JSON") + .severity("error") + .build())) + .build(); + } + } + + private ValidatorResponse toValidatorResponse(ValidationResult result) { + return ValidatorResponse.builder() + .isSuccessful(result.isSuccessful()) + .errorMessages(result.getMessages().stream() + .map(singleValidationMessage -> ValidatorErrorMessage.builder() + .severity(singleValidationMessage.getSeverity().getCode()) + .msg(singleValidationMessage.getLocationString() + " - " + singleValidationMessage.getMessage()) + .build()) + .collect(Collectors.toList()) + ) + .build(); + } + + private PrePopulatedValidationSupport loadIgs(final FhirContext ctx) { + + final ImmutableSet allowedResourceTypes = ImmutableSet.of("StructureDefinition", "CodeSystem", "ValueSet"); + + IParser parser = ctx.newJsonParser(); + parser.setParserErrorHandler(new StrictErrorHandler()); + final PrePopulatedValidationSupport prePopulatedValidationSupport = new PrePopulatedValidationSupport(ctx); + + try (ScanResult allFiles = new ClassGraph().acceptPaths(IMPLEMENTATION_GUIDES_FOLDER).rejectPaths(IMPLEMENTATION_GUIDES_FOLDER + "/*/*").scan()) { + ResourceList jsonResources = allFiles.getResourcesWithExtension("json"); + + ResourceList indexFiles = jsonResources.filter(x -> x.getPath().endsWith(".index.json")); + + for (Resource indexFile : indexFiles) { + IgIndex igIndex = GSON.fromJson(indexFile.getContentAsString(), IgIndex.class); + for (IgFile file : igIndex.files) { + if (allowedResourceTypes.contains(file.resourceType)) { + + String igResourcePath = indexFile.getPath().replace(".index.json", file.filename); + log.info("loading {}", igResourcePath); + ResourceList resourcesWithPath = allFiles.getResourcesWithPath(igResourcePath); + if (resourcesWithPath.isEmpty()) { + throw new RuntimeException("The following file is declared in .index.json but does not exist: " + igResourcePath); + } + Resource resource = resourcesWithPath.get(0); + try (InputStream inputStream = resource.open()) { + switch (file.resourceType) { + case "StructureDefinition": + prePopulatedValidationSupport.addStructureDefinition(parser.parseResource(StructureDefinition.class, inputStream)); + break; + case "CodeSystem": + prePopulatedValidationSupport.addCodeSystem(parser.parseResource(CodeSystem.class, inputStream)); + break; + case "ValueSet": + prePopulatedValidationSupport.addValueSet(parser.parseResource(ValueSet.class, inputStream)); + break; + default: + // cannot happen since we checked for allowedResourceTypes + break; + } + } catch (Exception e) { + log.error("Failed to load Implementation guides", e); + throw new RuntimeException(e); + } + } + } + } + } catch (Exception e) { + log.error("Failed to load Implementation guides", e); + throw new RuntimeException(e); + } + + return prePopulatedValidationSupport; + } +} diff --git a/javaHapiValidatorLambda/src/main/java/software/amazon/fwoa/ValidatorResponse.java b/javaHapiValidatorLambda/src/main/java/software/amazon/fwoa/ValidatorResponse.java new file mode 100644 index 00000000..5254c1d2 --- /dev/null +++ b/javaHapiValidatorLambda/src/main/java/software/amazon/fwoa/ValidatorResponse.java @@ -0,0 +1,25 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.fwoa; + +import java.util.List; + +import lombok.Builder; +import lombok.Value; + +@Builder +@Value +class ValidatorResponse { + private boolean isSuccessful; + private List errorMessages; +} + +@Builder +@Value +class ValidatorErrorMessage { + private String severity; + private String msg; +} diff --git a/javaHapiValidatorLambda/src/main/java/software/amazon/fwoa/models/IgFile.java b/javaHapiValidatorLambda/src/main/java/software/amazon/fwoa/models/IgFile.java new file mode 100644 index 00000000..dc6f746a --- /dev/null +++ b/javaHapiValidatorLambda/src/main/java/software/amazon/fwoa/models/IgFile.java @@ -0,0 +1,33 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.fwoa.models; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +public class IgFile { + @SerializedName("filename") + @Expose + public String filename; + @SerializedName("resourceType") + @Expose + public String resourceType; + @SerializedName("id") + @Expose + public String id; + @SerializedName("url") + @Expose + public String url; + @SerializedName("version") + @Expose + public String version; + @SerializedName("type") + @Expose + public String type; + @SerializedName("kind") + @Expose + public String kind; +} diff --git a/javaHapiValidatorLambda/src/main/java/software/amazon/fwoa/models/IgIndex.java b/javaHapiValidatorLambda/src/main/java/software/amazon/fwoa/models/IgIndex.java new file mode 100644 index 00000000..f7ec8c60 --- /dev/null +++ b/javaHapiValidatorLambda/src/main/java/software/amazon/fwoa/models/IgIndex.java @@ -0,0 +1,20 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.fwoa.models; + +import java.util.List; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +public class IgIndex { + @SerializedName("index-version") + @Expose + public Integer indexVersion; + @SerializedName("files") + @Expose + public List files = null; +} diff --git a/javaHapiValidatorLambda/src/main/resources/implementationGuides/.gitkeep b/javaHapiValidatorLambda/src/main/resources/implementationGuides/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/javaHapiValidatorLambda/src/main/resources/log4j2.xml b/javaHapiValidatorLambda/src/main/resources/log4j2.xml new file mode 100644 index 00000000..f631e963 --- /dev/null +++ b/javaHapiValidatorLambda/src/main/resources/log4j2.xml @@ -0,0 +1,15 @@ + + + + + + %d{yyyy-MM-dd HH:mm:ss} %X{AWSRequestId} %-5p %c{1}:%L - %m%n + + + + + + + + + diff --git a/javaHapiValidatorLambda/src/test/java/software/amazon/fwoa/ValidatorTest.java b/javaHapiValidatorLambda/src/test/java/software/amazon/fwoa/ValidatorTest.java new file mode 100644 index 00000000..3755adaa --- /dev/null +++ b/javaHapiValidatorLambda/src/test/java/software/amazon/fwoa/ValidatorTest.java @@ -0,0 +1,90 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.fwoa; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.google.common.collect.ImmutableList; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +class ValidatorTest { + + public static final ValidatorResponse INVALID_JSON_VALIDATOR_RESPONSE = ValidatorResponse.builder() + .isSuccessful(false) + .errorMessages(ImmutableList.of(ValidatorErrorMessage.builder() + .msg("Invalid JSON") + .severity("error") + .build())) + .build(); + static Validator validator; + + + @BeforeAll + static void setup() { + // Creating the HAPI validator takes several seconds. It's ok to reuse the same validator across tests to speed up tests + validator = new Validator(); + } + + @Test + void simple_patient() { + String resourceText = "{\"resourceType\":\"Patient\"}"; + ValidatorResponse validatorResponse = validator.validate(resourceText); + + assertTrue(validatorResponse.isSuccessful()); + } + + @Test + void empty() { + String resourceText = ""; + + assertEquals(validator.validate(resourceText), INVALID_JSON_VALIDATOR_RESPONSE); + } + + @Test + void array() { + String resourceText = "[1,2,3]"; + + assertEquals(validator.validate(resourceText), INVALID_JSON_VALIDATOR_RESPONSE); + } + + @Test + void null_json() { + String resourceText = "null"; + + assertEquals(validator.validate(resourceText), INVALID_JSON_VALIDATOR_RESPONSE); + } + + @Test + void null_java() { + String resourceText = null; + + assertEquals(validator.validate(resourceText), INVALID_JSON_VALIDATOR_RESPONSE); + } + + @Test + void number_json() { + String resourceText = "123"; + + assertEquals(validator.validate(resourceText), INVALID_JSON_VALIDATOR_RESPONSE); + } + + @Test + void boolean_json() { + String resourceText = "true"; + + assertEquals(validator.validate(resourceText), INVALID_JSON_VALIDATOR_RESPONSE); + } + + @Test + void bad_json() { + String resourceText = "{a:<>}}}"; + + assertEquals(validator.validate(resourceText), INVALID_JSON_VALIDATOR_RESPONSE); + } +}