diff --git a/src/main/java/uk/gov/hmcts/opal/exception/SchemaConfigurationException.java b/src/main/java/uk/gov/hmcts/opal/exception/SchemaConfigurationException.java new file mode 100644 index 000000000..4490f0a36 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/opal/exception/SchemaConfigurationException.java @@ -0,0 +1,17 @@ +package uk.gov.hmcts.opal.exception; + +public class SchemaConfigurationException extends RuntimeException { + + public SchemaConfigurationException(String msg) { + super(msg); + } + + public SchemaConfigurationException(Throwable t) { + super(t); + } + + public SchemaConfigurationException(String message, Throwable t) { + super(message, t); + } + +} diff --git a/src/main/java/uk/gov/hmcts/opal/service/opal/JsonSchemaValidationService.java b/src/main/java/uk/gov/hmcts/opal/service/opal/JsonSchemaValidationService.java index 804e954be..b52d44ea9 100644 --- a/src/main/java/uk/gov/hmcts/opal/service/opal/JsonSchemaValidationService.java +++ b/src/main/java/uk/gov/hmcts/opal/service/opal/JsonSchemaValidationService.java @@ -11,11 +11,13 @@ import org.springframework.util.StreamUtils; import uk.gov.hmcts.opal.dto.ToJsonString; import uk.gov.hmcts.opal.exception.JsonSchemaValidationException; +import uk.gov.hmcts.opal.exception.SchemaConfigurationException; import java.io.IOException; import java.nio.charset.Charset; import java.nio.file.Path; import java.util.Set; +import java.util.stream.Collectors; import static java.lang.String.format; @@ -26,44 +28,70 @@ public class JsonSchemaValidationService { private static final String PATH_ROOT = "jsonSchemas"; public boolean isValid(String body, String jsonSchemaFileName) { - Set errors = validate(body, jsonSchemaFileName); + Set errors = validate(body, jsonSchemaFileName); if (!errors.isEmpty()) { log.error(":isValid: for JSON schema '{}', found {} validation errors.", jsonSchemaFileName, errors.size()); - for (ValidationMessage msg : errors) { - log.error(":isValid: error: {}", msg.getMessage()); + for (String msg : errors) { + log.error(":isValid: error: {}", msg); } return false; } return true; } - public Set validate(String body, String jsonSchemaFileName) { + public void validateOrError(String body, String jsonSchemaFileName) { + Set errors = validate(body, jsonSchemaFileName); + if (!errors.isEmpty()) { + StringBuilder sb = new StringBuilder(errors.size() >> 7); + sb.append("Validating against JSON schema '") + .append(jsonSchemaFileName) + .append("', found ") + .append(errors.size()) + .append(" validation errors:"); + for (String msg : errors) { + sb.append("\n\t").append(msg); + } + throw new JsonSchemaValidationException(sb.toString()); + } + } + + public Set validate(String body, String jsonSchemaFileName) { String jsonSchemaContents = readJsonSchema(jsonSchemaFileName); var jsonSchema = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7).getSchema(jsonSchemaContents); - return jsonSchema.validate(getJsonNodeFromStringContent(body)); + try { + Set msgs = jsonSchema.validate(getJsonNodeFromStringContent(body)); + return msgs.stream().map(ValidationMessage::getMessage).collect(Collectors.toSet()); + } catch (JsonSchemaValidationException jsve) { + return Set.of(jsve.getMessage()); + } } private JsonNode getJsonNodeFromStringContent(String content) { try { return ToJsonString.getObjectMapper().readTree(content); } catch (JsonProcessingException e) { - throw new JsonSchemaValidationException(e.getMessage(), e); + StringBuilder sb = new StringBuilder(e.getMessage().length() + content.length() + 99); + sb.append(e.getOriginalMessage()) + .append("\n\tContent to validate:\n\"\"\"\n") + .append(content) + .append("\n\"\"\""); + throw new JsonSchemaValidationException(sb.toString(), e); } } private String readJsonSchema(String schemaFileName) { if (schemaFileName.isBlank()) { - throw new JsonSchemaValidationException("A schema filename is required to validate a JSON document."); + throw new SchemaConfigurationException("A schema filename is required to validate a JSON document."); } String filePath = Path.of(PATH_ROOT, schemaFileName).toString(); ClassPathResource cpr = new ClassPathResource(filePath); if (!cpr.exists()) { - throw new JsonSchemaValidationException(format("No JSON Schema file found at '%s'", cpr.getPath())); + throw new SchemaConfigurationException(format("No JSON Schema file found at '%s'", cpr.getPath())); } try { return StreamUtils.copyToString(cpr.getInputStream(), Charset.defaultCharset()); } catch (IOException e) { - throw new JsonSchemaValidationException(format("Problem opening InputStream at '%s'", filePath), e); + throw new SchemaConfigurationException(format("Problem opening InputStream at '%s'", filePath), e); } } diff --git a/src/test/java/uk/gov/hmcts/opal/service/opal/JsonSchemaValidationServiceTest.java b/src/test/java/uk/gov/hmcts/opal/service/opal/JsonSchemaValidationServiceTest.java index 09c0f1d5c..b99648014 100644 --- a/src/test/java/uk/gov/hmcts/opal/service/opal/JsonSchemaValidationServiceTest.java +++ b/src/test/java/uk/gov/hmcts/opal/service/opal/JsonSchemaValidationServiceTest.java @@ -1,17 +1,18 @@ package uk.gov.hmcts.opal.service.opal; -import com.networknt.schema.ValidationMessage; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.junit.jupiter.MockitoExtension; import uk.gov.hmcts.opal.exception.JsonSchemaValidationException; +import uk.gov.hmcts.opal.exception.SchemaConfigurationException; import java.util.Set; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; @ExtendWith(MockitoExtension.class) class JsonSchemaValidationServiceTest { @@ -22,27 +23,27 @@ class JsonSchemaValidationServiceTest { @Test void testIsValid_failBlankSchema() { // Act - JsonSchemaValidationException jsve = assertThrows( - JsonSchemaValidationException.class, + SchemaConfigurationException sce = assertThrows( + SchemaConfigurationException.class, () -> jsonSchemaValidationService.isValid("", " ") ); // Assert assertEquals("A schema filename is required to validate a JSON document.", - jsve.getMessage()); + sce.getMessage()); } @Test void testIsValid_failLoadSchema() { // Act - JsonSchemaValidationException jsve = assertThrows( - JsonSchemaValidationException.class, + SchemaConfigurationException sce = assertThrows( + SchemaConfigurationException.class, () -> jsonSchemaValidationService.isValid("", "nonExistentSchema.json") ); // Assert assertEquals("No JSON Schema file found at 'jsonSchemas/nonExistentSchema.json'", - jsve.getMessage()); + sce.getMessage()); } @Test @@ -52,21 +53,52 @@ void testIsValid_failIsValid() { @Test void testIsValid_failValidate1() { - Set messages = jsonSchemaValidationService + Set messages = jsonSchemaValidationService .validate("", "testSchema.json"); assertEquals(1, messages.size()); assertEquals("$: unknown found, object expected", messages .stream() .findFirst() - .map(ValidationMessage::getMessage) .orElse("")); } @Test void testIsValid_failValidate2() { - Set messages = jsonSchemaValidationService + Set messages = jsonSchemaValidationService .validate("{\"data\": 7}", "testSchema.json"); assertEquals(4, messages.size()); } + @Test + void testIsValid_failValidate3() { + Set messages = jsonSchemaValidationService + .validate("Not valid JSON", "testSchema.json"); + assertEquals(1, messages.size()); + String msg = messages.stream().findFirst().orElse(""); + assertTrue(msg.startsWith("Unrecognized token 'Not': was expecting (JSON String, Number, Array, Object ")); + } + + @Test + void testIsValid_failValidateOrError1() { + // Act + JsonSchemaValidationException sce = assertThrows( + JsonSchemaValidationException.class, + () -> jsonSchemaValidationService.validateOrError("Not Valid JSON", "testSchema.json") + ); + + // Assert + assertTrue(sce.getMessage().startsWith("Validating against JSON schema 'testSchema.json', found 1 validation")); + } + + @Test + void testIsValid_failValidateOrError2() { + // Act + JsonSchemaValidationException sce = assertThrows( + JsonSchemaValidationException.class, + () -> jsonSchemaValidationService.validateOrError("{\"name\": 5}", "testSchema.json") + ); + + // Assert + assertTrue(sce.getMessage().startsWith("Validating against JSON schema 'testSchema.json', found 4 validation")); + } }