Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/main/java/com/networknt/schema/AllOfValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ protected Set<ValidationMessage> validate(ExecutionContext executionContext, Jso
final ObjectNode discriminator = currentDiscriminatorContext
.getDiscriminatorForPath(allOfEntry.get("$ref").asText());
if (null != discriminator) {
registerAndMergeDiscriminator(currentDiscriminatorContext, discriminator,
DiscriminatorValidator.registerAndMergeDiscriminator(currentDiscriminatorContext, discriminator,
this.parentSchema, instanceLocation);
// now we have to check whether we have hit the right target
final String discriminatorPropertyName = discriminator.get("propertyName").asText();
Expand All @@ -98,7 +98,7 @@ protected Set<ValidationMessage> validate(ExecutionContext executionContext, Jso
: discriminatorNode.textValue();

final JsonSchema jsonSchema = this.parentSchema;
checkDiscriminatorMatch(currentDiscriminatorContext, discriminator,
DiscriminatorValidator.checkDiscriminatorMatch(currentDiscriminatorContext, discriminator,
discriminatorPropertyValue, jsonSchema);
}
}
Expand Down
263 changes: 44 additions & 219 deletions src/main/java/com/networknt/schema/BaseJsonValidator.java

Large diffs are not rendered by default.

123 changes: 123 additions & 0 deletions src/main/java/com/networknt/schema/DiscriminatorValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,127 @@ public String getPropertyName() {
public Map<String, String> getMapping() {
return mapping;
}

/**
* Checks based on the current {@link DiscriminatorContext} whether the provided {@link JsonSchema} a match against
* the current discriminator.
*
* @param currentDiscriminatorContext the currently active {@link DiscriminatorContext}
* @param discriminator the discriminator to use for the check
* @param discriminatorPropertyValue the value of the <code>discriminator/propertyName</code> field
* @param jsonSchema the {@link JsonSchema} to check
*/
public static void checkDiscriminatorMatch(final DiscriminatorContext currentDiscriminatorContext,
final ObjectNode discriminator,
final String discriminatorPropertyValue,
final JsonSchema jsonSchema) {
if (discriminatorPropertyValue == null) {
currentDiscriminatorContext.markIgnore();
return;
}

final JsonNode discriminatorMapping = discriminator.get("mapping");
if (null == discriminatorMapping) {
checkForImplicitDiscriminatorMappingMatch(currentDiscriminatorContext,
discriminatorPropertyValue,
jsonSchema);
} else {
checkForExplicitDiscriminatorMappingMatch(currentDiscriminatorContext,
discriminatorPropertyValue,
discriminatorMapping,
jsonSchema);
if (!currentDiscriminatorContext.isDiscriminatorMatchFound()
&& noExplicitDiscriminatorKeyOverride(discriminatorMapping, jsonSchema)) {
checkForImplicitDiscriminatorMappingMatch(currentDiscriminatorContext,
discriminatorPropertyValue,
jsonSchema);
}
}
}

/**
* Rolls up all nested and compatible discriminators to the root discriminator of the type. Detects attempts to redefine
* the <code>propertyName</code> or mappings.
*
* @param currentDiscriminatorContext the currently active {@link DiscriminatorContext}
* @param discriminator the discriminator to use for the check
* @param schema the value of the <code>discriminator/propertyName</code> field
* @param instanceLocation the logging prefix
*/
public static void registerAndMergeDiscriminator(final DiscriminatorContext currentDiscriminatorContext,
final ObjectNode discriminator,
final JsonSchema schema,
final JsonNodePath instanceLocation) {
final JsonNode discriminatorOnSchema = schema.schemaNode.get("discriminator");
if (null != discriminatorOnSchema && null != currentDiscriminatorContext
.getDiscriminatorForPath(schema.schemaLocation)) {
// this is where A -> B -> C inheritance exists, A has the root discriminator and B adds to the mapping
final JsonNode propertyName = discriminatorOnSchema.get("propertyName");
if (null != propertyName) {
throw new JsonSchemaException(instanceLocation + " schema " + schema + " attempts redefining the discriminator property");
}
final ObjectNode mappingOnContextDiscriminator = (ObjectNode) discriminator.get("mapping");
final ObjectNode mappingOnCurrentSchemaDiscriminator = (ObjectNode) discriminatorOnSchema.get("mapping");
if (null == mappingOnContextDiscriminator && null != mappingOnCurrentSchemaDiscriminator) {
// here we have a mapping on a nested discriminator and none on the root discriminator, so we can simply
// make it the root's
discriminator.set("mapping", discriminatorOnSchema);
} else if (null != mappingOnContextDiscriminator && null != mappingOnCurrentSchemaDiscriminator) {
// here we have to merge. The spec doesn't specify anything on this, but here we don't accept redefinition of
// mappings that already exist
final Iterator<Map.Entry<String, JsonNode>> fieldsToAdd = mappingOnCurrentSchemaDiscriminator.fields();
while (fieldsToAdd.hasNext()) {
final Map.Entry<String, JsonNode> fieldToAdd = fieldsToAdd.next();
final String mappingKeyToAdd = fieldToAdd.getKey();
final JsonNode mappingValueToAdd = fieldToAdd.getValue();

final JsonNode currentMappingValue = mappingOnContextDiscriminator.get(mappingKeyToAdd);
if (null != currentMappingValue && currentMappingValue != mappingValueToAdd) {
throw new JsonSchemaException(instanceLocation + "discriminator mapping redefinition from " + mappingKeyToAdd
+ "/" + currentMappingValue + " to " + mappingValueToAdd);
} else if (null == currentMappingValue) {
mappingOnContextDiscriminator.set(mappingKeyToAdd, mappingValueToAdd);
}
}
}
}
currentDiscriminatorContext.registerDiscriminator(schema.schemaLocation, discriminator);
}

private static void checkForImplicitDiscriminatorMappingMatch(final DiscriminatorContext currentDiscriminatorContext,
final String discriminatorPropertyValue,
final JsonSchema schema) {
if (schema.schemaLocation.getFragment().getName(-1).equals(discriminatorPropertyValue)) {
currentDiscriminatorContext.markMatch();
}
}

private static void checkForExplicitDiscriminatorMappingMatch(final DiscriminatorContext currentDiscriminatorContext,
final String discriminatorPropertyValue,
final JsonNode discriminatorMapping,
final JsonSchema schema) {
final Iterator<Map.Entry<String, JsonNode>> explicitMappings = discriminatorMapping.fields();
while (explicitMappings.hasNext()) {
final Map.Entry<String, JsonNode> candidateExplicitMapping = explicitMappings.next();
if (candidateExplicitMapping.getKey().equals(discriminatorPropertyValue)
&& ("#" + schema.schemaLocation.getFragment().toString())
.equals(candidateExplicitMapping.getValue().asText())) {
currentDiscriminatorContext.markMatch();
break;
}
}
}

private static boolean noExplicitDiscriminatorKeyOverride(final JsonNode discriminatorMapping,
final JsonSchema parentSchema) {
final Iterator<Map.Entry<String, JsonNode>> explicitMappings = discriminatorMapping.fields();
while (explicitMappings.hasNext()) {
final Map.Entry<String, JsonNode> candidateExplicitMapping = explicitMappings.next();
if (candidateExplicitMapping.getValue().asText()
.equals(parentSchema.schemaLocation.getFragment().toString())) {
return false;
}
}
return true;
}
}
58 changes: 58 additions & 0 deletions src/main/java/com/networknt/schema/ErrorMessages.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.networknt.schema;

import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;

import com.fasterxml.jackson.databind.JsonNode;

/**
* ErrorMessages.
*/
public class ErrorMessages {
/**
* Gets the custom error message to use.
*
* @param parentSchema the parent schema
* @param errorMessageKeyword the error message keyword
* @param keyword the keyword
* @return the custom error message
*/
public static Map<String, String> getErrorMessage(JsonSchema parentSchema, String errorMessageKeyword,
String keyword) {
final JsonNode message = getMessageNode(errorMessageKeyword, parentSchema.schemaNode, parentSchema, keyword);
if (message != null) {
JsonNode messageNode = message.get(keyword);
if (messageNode != null) {
if (messageNode.isTextual()) {
return Collections.singletonMap("", messageNode.asText());
} else if (messageNode.isObject()) {
Map<String, String> result = new LinkedHashMap<>();
messageNode.fields()
.forEachRemaining(entry -> result.put(entry.getKey(), entry.getValue().textValue()));
if (!result.isEmpty()) {
return result;
}
}
}
}
return Collections.emptyMap();
}

protected static JsonNode getMessageNode(String errorMessageKeyword, JsonNode schemaNode, JsonSchema parentSchema,
String pname) {
if (schemaNode.get(errorMessageKeyword) != null && schemaNode.get(errorMessageKeyword).get(pname) != null) {
return schemaNode.get(errorMessageKeyword);
}
JsonNode messageNode;
messageNode = schemaNode.get(errorMessageKeyword);
if (messageNode == null && parentSchema != null) {
messageNode = parentSchema.schemaNode.get(errorMessageKeyword);
if (messageNode == null) {
return getMessageNode(errorMessageKeyword, parentSchema.schemaNode, parentSchema.getParentSchema(),
pname);
}
}
return messageNode;
}
}
Loading