diff --git a/src/main/java/com/networknt/schema/ErrorMessageType.java b/src/main/java/com/networknt/schema/ErrorMessageType.java index 226cdc563..9d3b9b201 100644 --- a/src/main/java/com/networknt/schema/ErrorMessageType.java +++ b/src/main/java/com/networknt/schema/ErrorMessageType.java @@ -35,4 +35,8 @@ public interface ErrorMessageType { */ MessageFormat getMessageFormat(); + default String getCustomMessage() { + return null; + } + } diff --git a/src/main/java/com/networknt/schema/FormatKeyword.java b/src/main/java/com/networknt/schema/FormatKeyword.java index f17d0e646..5c7793533 100644 --- a/src/main/java/com/networknt/schema/FormatKeyword.java +++ b/src/main/java/com/networknt/schema/FormatKeyword.java @@ -68,4 +68,11 @@ public JsonValidator newValidator(String schemaPath, JsonNode schemaNode, JsonSc public String getValue() { return type.getValue(); } + + @Override + public void setCustomMessage(String message) { + type.setCustomMessage(message); + } + + } diff --git a/src/main/java/com/networknt/schema/JsonMetaSchema.java b/src/main/java/com/networknt/schema/JsonMetaSchema.java index c9877b9ea..cfa6c3165 100644 --- a/src/main/java/com/networknt/schema/JsonMetaSchema.java +++ b/src/main/java/com/networknt/schema/JsonMetaSchema.java @@ -180,7 +180,8 @@ public static JsonMetaSchema getInstance() { new NonValidationKeyword("$comment"), new NonValidationKeyword("contentMediaType"), new NonValidationKeyword("contentEncoding"), - new NonValidationKeyword("examples") + new NonValidationKeyword("examples"), + new NonValidationKeyword("message") )) .build(); } @@ -374,8 +375,9 @@ public String getIdKeyword() { return idKeyword; } + public JsonValidator newValidator(ValidationContext validationContext, String schemaPath, String keyword /* keyword */, JsonNode schemaNode, - JsonSchema parentSchema) { + JsonSchema parentSchema, String customMessage) { try { Keyword kw = keywords.get(keyword); @@ -385,6 +387,7 @@ public JsonValidator newValidator(ValidationContext validationContext, String sc } return null; } + kw.setCustomMessage(customMessage); return kw.newValidator(schemaPath, schemaNode, parentSchema, validationContext); } catch (InvocationTargetException e) { if (e.getTargetException() instanceof JsonSchemaException) { diff --git a/src/main/java/com/networknt/schema/JsonSchema.java b/src/main/java/com/networknt/schema/JsonSchema.java index 1050c95b2..05f37cec4 100644 --- a/src/main/java/com/networknt/schema/JsonSchema.java +++ b/src/main/java/com/networknt/schema/JsonSchema.java @@ -208,10 +208,12 @@ private Map read(JsonNode schemaNode) { Map validators = new HashMap(); if (schemaNode.isBoolean()) { if (schemaNode.booleanValue()) { - JsonValidator validator = validationContext.newValidator(getSchemaPath(), "true", schemaNode, this); + final String customMessage = getCustomMessage(schemaNode, "true"); + JsonValidator validator = validationContext.newValidator(getSchemaPath(), "true", schemaNode, this, customMessage); validators.put(getSchemaPath() + "/true", validator); } else { - JsonValidator validator = validationContext.newValidator(getSchemaPath(), "false", schemaNode, this); + final String customMessage = getCustomMessage(schemaNode, "false"); + JsonValidator validator = validationContext.newValidator(getSchemaPath(), "false", schemaNode, this, customMessage); validators.put(getSchemaPath() + "/false", validator); } } else { @@ -219,8 +221,8 @@ private Map read(JsonNode schemaNode) { while (pnames.hasNext()) { String pname = pnames.next(); JsonNode nodeToUse = pname.equals("if") ? schemaNode : schemaNode.get(pname); - - JsonValidator validator = validationContext.newValidator(getSchemaPath(), pname, nodeToUse, this); + String customMessage = getCustomMessage(schemaNode, pname); + JsonValidator validator = validationContext.newValidator(getSchemaPath(), pname, nodeToUse, this, customMessage); if (validator != null) { validators.put(getSchemaPath() + "/" + pname, validator); @@ -234,6 +236,24 @@ private Map read(JsonNode schemaNode) { return validators; } + private String getCustomMessage(JsonNode schemaNode, String pname) { + final JsonSchema parentSchema = getParentSchema(); + final JsonNode message = getMessageNode(schemaNode, parentSchema); + if(message != null && message.get(pname) != null) { + return message.get(pname).asText(); + } + return null; + } + + private JsonNode getMessageNode(JsonNode schemaNode, JsonSchema parentSchema) { + JsonNode nodeContainingMessage; + if (parentSchema == null) + nodeContainingMessage = schemaNode; + else + nodeContainingMessage = parentSchema.schemaNode; + return nodeContainingMessage.get("message"); + } + /************************ START OF VALIDATE METHODS **********************************/ public Set validate(JsonNode jsonNode, JsonNode rootNode, String at) { diff --git a/src/main/java/com/networknt/schema/Keyword.java b/src/main/java/com/networknt/schema/Keyword.java index 66bf35be1..0f9283b19 100644 --- a/src/main/java/com/networknt/schema/Keyword.java +++ b/src/main/java/com/networknt/schema/Keyword.java @@ -22,5 +22,9 @@ public interface Keyword { String getValue(); + default void setCustomMessage(String message) { + //setCustom message + } + JsonValidator newValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) throws JsonSchemaException, Exception; } diff --git a/src/main/java/com/networknt/schema/ValidationContext.java b/src/main/java/com/networknt/schema/ValidationContext.java index 5efcc6547..a7d247497 100644 --- a/src/main/java/com/networknt/schema/ValidationContext.java +++ b/src/main/java/com/networknt/schema/ValidationContext.java @@ -53,8 +53,8 @@ public ValidationContext(URIFactory uriFactory, URNFactory urnFactory, JsonMetaS } public JsonValidator newValidator(String schemaPath, String keyword /* keyword */, JsonNode schemaNode, - JsonSchema parentSchema) { - return metaSchema.newValidator(this, schemaPath, keyword, schemaNode, parentSchema); + JsonSchema parentSchema, String customMessage) { + return metaSchema.newValidator(this, schemaPath, keyword, schemaNode, parentSchema, customMessage); } public String resolveSchemaId(JsonNode schemaNode) { diff --git a/src/main/java/com/networknt/schema/ValidationMessage.java b/src/main/java/com/networknt/schema/ValidationMessage.java index b0bc1b6ed..b68ec5623 100644 --- a/src/main/java/com/networknt/schema/ValidationMessage.java +++ b/src/main/java/com/networknt/schema/ValidationMessage.java @@ -16,6 +16,8 @@ package com.networknt.schema; +import org.apache.commons.lang3.StringUtils; + import java.text.MessageFormat; import java.util.Arrays; import java.util.Map; @@ -114,7 +116,8 @@ public void setType(String type) { public static ValidationMessage of(String type, ErrorMessageType errorMessageType, String at, String... arguments) { ValidationMessage.Builder builder = new ValidationMessage.Builder(); builder.code(errorMessageType.getErrorCode()).path(at).arguments(arguments) - .format(errorMessageType.getMessageFormat()).type(type); + .format(errorMessageType.getMessageFormat()).type(type) + .customMessage(errorMessageType.getCustomMessage()); return builder.build(); } @@ -132,6 +135,7 @@ public static class Builder { private String[] arguments; private Map details; private MessageFormat format; + private String customMessage; public Builder type(String type) { this.type = type; @@ -163,6 +167,11 @@ public Builder format(MessageFormat format) { return this; } + public Builder customMessage(String customMessage) { + this.customMessage = customMessage; + return this; + } + public ValidationMessage build() { ValidationMessage msg = new ValidationMessage(); msg.setType(type); @@ -179,7 +188,12 @@ public ValidationMessage build() { objs[i] = arguments[i - 1]; } } - msg.setMessage(format.format(objs)); + if(StringUtils.isNotBlank(customMessage)) { + msg.setMessage(customMessage); + } else { + msg.setMessage(format.format(objs)); + } + } return msg; diff --git a/src/main/java/com/networknt/schema/ValidatorTypeCode.java b/src/main/java/com/networknt/schema/ValidatorTypeCode.java index 1a9c31374..c8a372830 100644 --- a/src/main/java/com/networknt/schema/ValidatorTypeCode.java +++ b/src/main/java/com/networknt/schema/ValidatorTypeCode.java @@ -87,6 +87,7 @@ public JsonValidator newValidator(String schemaPath, JsonNode schemaNode, JsonSc private final String value; private final String errorCode; private final MessageFormat messageFormat; + private String customMessage; private final String errorCodeKey; private final Class validator; private final long versionCode; @@ -99,6 +100,7 @@ private ValidatorTypeCode(String value, String errorCode, MessageFormat messageF this.errorCodeKey = value + "ErrorCode"; this.validator = validator; this.versionCode = versionCode; + this.customMessage = null; } public static List getNonFormatKeywords(SpecVersion.VersionFlag versionFlag) { @@ -148,6 +150,14 @@ public MessageFormat getMessageFormat() { return messageFormat; } + public void setCustomMessage(String message) { + this.customMessage = message; + } + + public String getCustomMessage() { + return customMessage; + } + public String getErrorCodeKey() { return errorCodeKey; } diff --git a/src/test/java/com/networknt/schema/Issue426Test.java b/src/test/java/com/networknt/schema/Issue426Test.java new file mode 100644 index 000000000..5c9b8ac62 --- /dev/null +++ b/src/test/java/com/networknt/schema/Issue426Test.java @@ -0,0 +1,42 @@ +package com.networknt.schema; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.Assert; +import org.junit.Test; + +import java.io.InputStream; +import java.util.Set; + +/** + * Validating custom message + */ +public class Issue426Test { + protected JsonSchema getJsonSchemaFromStreamContentV7(InputStream schemaContent) { + JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7); + return factory.getSchema(schemaContent); + } + + protected JsonNode getJsonNodeFromStreamContent(InputStream content) throws Exception { + ObjectMapper mapper = new ObjectMapper(); + return mapper.readTree(content); + } + + @Test + public void shouldWorkV7() throws Exception { + String schemaPath = "/schema/issue426-v7.json"; + String dataPath = "/data/issue426.json"; + InputStream schemaInputStream = getClass().getResourceAsStream(schemaPath); + JsonSchema schema = getJsonSchemaFromStreamContentV7(schemaInputStream); + InputStream dataInputStream = getClass().getResourceAsStream(dataPath); + JsonNode node = getJsonNodeFromStreamContent(dataInputStream); + Set errors = schema.validate(node); + Assert.assertEquals(2, errors.size()); + final JsonNode message = schema.schemaNode.get("message"); + for(ValidationMessage error : errors) { + //validating custom message + Assert.assertEquals(message.get(error.getType()).asText(), error.getMessage()); + } + } +} + diff --git a/src/test/resources/data/issue426.json b/src/test/resources/data/issue426.json new file mode 100644 index 000000000..a1d63078a --- /dev/null +++ b/src/test/resources/data/issue426.json @@ -0,0 +1,9 @@ +{ + "firstName": {}, + "foo": [ + 1, + 2, + 3, + 4 + ] +} diff --git a/src/test/resources/schema/issue426-v7.json b/src/test/resources/schema/issue426-v7.json new file mode 100644 index 000000000..9b01e2b98 --- /dev/null +++ b/src/test/resources/schema/issue426-v7.json @@ -0,0 +1,20 @@ +{ + "$id": "https://example.com/person.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Person", + "type": "object", + "properties": { + "firstName": { + "type": "string", + "description": "The person's first name." + }, + "foo": { + "type": "array", + "maxItems": 3 + } + }, + "message": { + "maxItems" : "MaxItem must be 3 only", + "type" : "Invalid type" + } +}