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
205 changes: 12 additions & 193 deletions src/main/java/com/networknt/schema/OneOfValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,112 +21,24 @@
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.stream.Collectors;

public class OneOfValidator extends BaseJsonValidator {
private static final Logger logger = LoggerFactory.getLogger(OneOfValidator.class);

private final List<ShortcutValidator> schemas = new ArrayList<ShortcutValidator>();

private static class ShortcutValidator {
private final JsonSchema schema;
private final Map<String, String> constants;

ShortcutValidator(JsonNode schemaNode, JsonSchema parentSchema,
ValidationContext validationContext, JsonSchema schema) {
JsonNode refNode = schemaNode.get(ValidatorTypeCode.REF.getValue());
JsonSchema resolvedRefSchema = refNode != null && refNode.isTextual() ? RefValidator.getRefSchema(parentSchema, validationContext, refNode.textValue()).getSchema() : null;
this.constants = extractConstants(schemaNode, resolvedRefSchema);
this.schema = schema;
}

private Map<String, String> extractConstants(JsonNode schemaNode, JsonSchema resolvedRefSchema) {
Map<String, String> refMap = resolvedRefSchema != null ? extractConstants(resolvedRefSchema.getSchemaNode()) : Collections.<String, String>emptyMap();
Map<String, String> schemaMap = extractConstants(schemaNode);
if (refMap.isEmpty()) {
return schemaMap;
}
if (schemaMap.isEmpty()) {
return refMap;
}
Map<String, String> joined = new HashMap<String, String>();
joined.putAll(schemaMap);
joined.putAll(refMap);
return joined;
}

private Map<String, String> extractConstants(JsonNode schemaNode) {
Map<String, String> result = new HashMap<String, String>();
if (!schemaNode.isObject()) {
return result;
}

JsonNode propertiesNode = schemaNode.get("properties");
if (propertiesNode == null || !propertiesNode.isObject()) {
return result;
}
Iterator<String> fit = propertiesNode.fieldNames();
while (fit.hasNext()) {
String fieldName = fit.next();
JsonNode jsonNode = propertiesNode.get(fieldName);
String constantFieldValue = getConstantFieldValue(jsonNode);
if (constantFieldValue != null && !constantFieldValue.isEmpty()) {
result.put(fieldName, constantFieldValue);
}
}
return result;
}

private String getConstantFieldValue(JsonNode jsonNode) {
if (jsonNode == null || !jsonNode.isObject() || !jsonNode.has("enum")) {
return null;
}
JsonNode enumNode = jsonNode.get("enum");
if (enumNode == null || !enumNode.isArray()) {
return null;
}
if (enumNode.size() != 1) {
return null;
}
JsonNode valueNode = enumNode.get(0);
if (valueNode == null || !valueNode.isTextual()) {
return null;
}
return valueNode.textValue();
}

public boolean allConstantsMatch(JsonNode node) {
for (Map.Entry<String, String> e : constants.entrySet()) {
JsonNode valueNode = node.get(e.getKey());
if (valueNode != null && valueNode.isTextual()) {
boolean match = e.getValue().equals(valueNode.textValue());
if (!match) {
return false;
}
}
}
return true;
}

private JsonSchema getSchema() {
return schema;
}

}
private final List<JsonSchema> schemas = new ArrayList<>();

public OneOfValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.ONE_OF, validationContext);
int size = schemaNode.size();
for (int i = 0; i < size; i++) {
JsonNode childNode = schemaNode.get(i);
JsonSchema childSchema = new JsonSchema(validationContext, schemaPath + "/" + i, parentSchema.getCurrentUri(), childNode, parentSchema);
schemas.add(new ShortcutValidator(childNode, parentSchema, validationContext, childSchema));
schemas.add(new JsonSchema(validationContext, schemaPath + "/" + i, parentSchema.getCurrentUri(), childNode, parentSchema));
}
parseErrorCode(getValidatorType().getErrorCodeKey());
}

public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String at) {
Set<ValidationMessage> errors = new LinkedHashSet<ValidationMessage>();
Set<ValidationMessage> errors = new LinkedHashSet<>();

// As oneOf might contain multiple schemas take a backup of evaluatedProperties.
Set<String> backupEvaluatedProperties = CollectorContext.getInstance().copyEvaluatedProperties();
Expand All @@ -143,28 +55,13 @@ public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String
state.setComplexValidator(true);

int numberOfValidSchema = 0;
Set<ValidationMessage> childErrors = new LinkedHashSet<ValidationMessage>();
// validate that only a single element has been received in the oneOf node
// validation should not continue, as it contradicts the oneOf requirement of only one
// if(node.isObject() && node.size()>1) {
// errors = Collections.singleton(buildValidationMessage(at, ""));
// return Collections.unmodifiableSet(errors);
// }
Set<ValidationMessage> childErrors = new LinkedHashSet<>();

for (ShortcutValidator validator : schemas) {
for (JsonSchema schema : schemas) {
Set<ValidationMessage> schemaErrors = null;
// Reset state in case the previous validator did not match
state.setMatchedNode(true);

//This prevents from collecting all the error messages in proper format.
/* if (!validator.allConstantsMatch(node)) {
// take a shortcut: if there is any constant that does not match,
// we can bail out of the validation
continue;
}*/

// get the current validator
JsonSchema schema = validator.schema;
if (!state.isWalkEnabled()) {
schemaErrors = schema.validate(node, rootNode, at);
} else {
Expand All @@ -188,37 +85,15 @@ public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String

childErrors.addAll(schemaErrors);
}
Set<ValidationMessage> childNotRequiredErrors = childErrors.stream().filter(error -> !ValidatorTypeCode.REQUIRED.getValue().equals(error.getType())).collect(Collectors.toSet());

// ensure there is always an "OneOf" error reported if number of valid schemas is not equal to 1.
if (numberOfValidSchema > 1) {
final ValidationMessage message = getMultiSchemasValidErrorMsg(at);
if (numberOfValidSchema != 1) {
ValidationMessage message = buildValidationMessage(at, Integer.toString(numberOfValidSchema));
if (failFast) {
throw new JsonSchemaException(message);
}
errors.add(message);
}

// ensure there is always an "OneOf" error reported if number of valid schemas is not equal to 1.
else if (numberOfValidSchema < 1) {
if (!childNotRequiredErrors.isEmpty()) {
childErrors = childNotRequiredErrors;
}
if (!childErrors.isEmpty()) {
if (childErrors.size() > 1) {
Set<ValidationMessage> notAdditionalPropertiesOnly = new LinkedHashSet<>(childErrors.stream()
.filter((ValidationMessage validationMessage) -> !ValidatorTypeCode.ADDITIONAL_PROPERTIES.getValue().equals(validationMessage.getType()))
.sorted((vm1, vm2) -> compareValidationMessages(vm1, vm2))
.collect(Collectors.toList()));
if (notAdditionalPropertiesOnly.size() > 0) {
childErrors = notAdditionalPropertiesOnly;
}
}
errors.addAll(childErrors);
}
if (failFast) {
throw new JsonSchemaException(errors.toString());
}
errors.addAll(childErrors);
}

// Make sure to signal parent handlers we matched
Expand All @@ -238,85 +113,29 @@ else if (numberOfValidSchema < 1) {
}
}

/**
* Sort <code>ValidationMessage</code> by its type
* @return
*/
private static int compareValidationMessages(ValidationMessage vm1, ValidationMessage vm2) {
// ValidationMessage's type has smaller index in the list below has high priority
final List<String> typeCodes = Arrays.asList(
ValidatorTypeCode.TYPE.getValue(),
ValidatorTypeCode.DATETIME.getValue(),
ValidatorTypeCode.UUID.getValue(),
ValidatorTypeCode.ID.getValue(),
ValidatorTypeCode.EXCLUSIVE_MAXIMUM.getValue(),
ValidatorTypeCode.EXCLUSIVE_MINIMUM.getValue(),
ValidatorTypeCode.TRUE.getValue(),
ValidatorTypeCode.FALSE.getValue(),
ValidatorTypeCode.CONST.getValue(),
ValidatorTypeCode.CONTAINS.getValue(),
ValidatorTypeCode.PROPERTYNAMES.getValue()
);

final int index1 = typeCodes.indexOf(vm1.getType());
final int index2 = typeCodes.indexOf(vm2.getType());

if (index1 >= 0) {
if (index2 >= 0) {
return Integer.compare(index1, index2);
} else {
return -1;
}
} else {
if (index2 >= 0) {
return 1;
} else {
return vm1.getCode().compareTo(vm2.getCode());
}
}
}

private void resetValidatorState() {
ValidatorState state = (ValidatorState) CollectorContext.getInstance().get(ValidatorState.VALIDATOR_STATE_KEY);
state.setComplexValidator(false);
state.setMatchedNode(true);
}

public List<JsonSchema> getChildSchemas() {
List<JsonSchema> childJsonSchemas = new ArrayList<JsonSchema>();
for (ShortcutValidator shortcutValidator: schemas ) {
childJsonSchemas.add(shortcutValidator.getSchema());
}
return childJsonSchemas;
}

@Override
public Set<ValidationMessage> walk(JsonNode node, JsonNode rootNode, String at, boolean shouldValidateSchema) {
HashSet<ValidationMessage> validationMessages = new LinkedHashSet<ValidationMessage>();
if (shouldValidateSchema) {
validationMessages.addAll(validate(node, rootNode, at));
} else {
for (ShortcutValidator validator : schemas) {
validator.schema.walk(node, rootNode, at , shouldValidateSchema);
for (JsonSchema schema : schemas) {
schema.walk(node, rootNode, at, shouldValidateSchema);
}
}
return validationMessages;
}

private ValidationMessage getMultiSchemasValidErrorMsg(String at){
String msg="";
for(ShortcutValidator schema: schemas){
String schemaValue = schema.getSchema().getSchemaNode().toString();
msg = msg.concat(schemaValue);
}

return ValidationMessage.of(getValidatorType().getValue(), ValidatorTypeCode.ONE_OF, at, schemaPath, msg);
}

@Override
public void preloadJsonSchema() {
for (final ShortcutValidator scValidator: schemas) {
scValidator.getSchema().initializeValidators();
for (JsonSchema schema: schemas) {
schema.initializeValidators();
}
}
}
2 changes: 1 addition & 1 deletion src/main/resources/jsv-messages.properties
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ minimum = {0}: must have a minimum value of {1}
multipleOf = {0}: must be multiple of {1}
not = {0}: should not be valid to the schema {1}
notAllowed = {0}.{1}: is not allowed but it is in the data
oneOf = {0}: should be valid to one and only one of schema, but more than one are valid: {1}
oneOf = {0}: should be valid to one and only one schema, but {1} are valid
pattern = {0}: does not match the regex pattern {1}
patternProperties = {0}: has some error with 'pattern properties'
prefixItems = {0}[{1}]: no validator found at this index
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/jsv-messages_de.properties
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ minimum = {0} muss mindestens den Wert {1} haben
multipleOf = {0} muss ein Vielfaches von {1} sein
not = {0} darf nicht g�ltig sein f�r das Schema {1}
notAllowed = {0}.{1} ist nicht erlaubt und darf folglich nicht auftreten
oneOf = {0} darf nur f�r ein einziges Schema g�ltig sein, aber mehr als ein Schema ist g�ltig: {1}
oneOf = {0} sollte f�r genau ein Schema g�ltig sein, aber {1} sind g�ltig
pattern = {0} stimmt nicht mit dem regul�ren Ausdruck {1} �berein
patternProperties = {0} stimmt nicht �berein mit dem Format definiert in 'pattern properties'
properties = {0}: Ein Fehler mit 'properties' ist aufgetreten
Expand Down
3 changes: 2 additions & 1 deletion src/main/resources/jsv-messages_fa_IR.properties
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ minimum = {0}: باید حداقل {1} ویژگی داشته باشد
multipleOf = {0}: باید مضرب از {1} باشد
not = {0}: نباید برای طرحواره معتبر باشد {1}
notAllowed = {0}.{1}: مجاز نیست اما در داده ها وجود دارد
oneOf = {0}: باید برای یک و تنها یکی از طرحواره ها معتبر باشد، اما بیش از یکی معتبر است: {1}
# needs to be re-worked by a native speaker
#oneOf = {0}: باید برای یک و تنها یکی از طرحواره ها معتبر باشد، اما بیش از یکی معتبر است: {1}
pattern = {0}: با الگوی regex مطابقت ندارد {1}
patternProperties = {0}: دارای مقداری خطا با 'خواص الگو'
prefixItems = {0}[{1}]: هیچ اعتبارسنجی در این فهرست یافت نشد
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/jsv-messages_fr.properties
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ minimum = {0}: doit avoir une valeur minimale de {1}
multipleOf = {0}: doit �tre un multiple de {1}
not = {0}: ne doit pas �tre valide pour le sch�ma {1}
notAllowed = {0}.{1} n'est pas autoris� mais est dans les donn�es
oneOf = {0}: devrait �tre valide pour un et un seul des sch�mas, mais plus d'un sont valides : {1}
oneOf = {0}: doit �tre valide pour un et un seul sch�ma, mais {1} sont valides
pattern = {0} ne correspond pas � l'expression r�guli�re {1}
patternProperties = {0}: a des erreurs avec 'pattern properties'
properties = {0} : a une erreur avec 'properties'
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/jsv-messages_it.properties
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ minimum={0}: deve avere un valore minimo di {1}
multipleOf={0}: deve essere un multiplo di {1}
not={0}: non dovrebbe essere valido per lo schema {1}
notAllowed={0}.{1}: non � consentito ma � nel dato
oneOf={0}: dovrebbe essere valido a uno e solo uno schema, ma pi� di uno sono validi: {1}
oneOf={0}: dovrebbe essere valido per uno e un solo schema, ma {1} sono validi
pattern={0}: non corrisponde alla regex {1}
patternProperties={0}: ha qualche errore con ''pattern properties''
prefixItems={0}[{1}]: nessun validatore trovato a quest''indice
Expand Down
Loading