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
15 changes: 12 additions & 3 deletions src/main/java/com/networknt/schema/AbsoluteIri.java
Original file line number Diff line number Diff line change
Expand Up @@ -105,14 +105,23 @@ public static String resolve(String parent, String iri) {
} else {
scheme = scheme + 3;
}
int slash = parent.lastIndexOf('/');
if (slash != -1 && slash > scheme) {
base = parent.substring(0, slash);
base = parent(base, scheme);
while (iri.startsWith("../")) {
base = parent(base, scheme);
iri = iri.substring(3);
}
return base + "/" + iri;
}
}
}

protected static String parent(String iri, int scheme) {
int slash = iri.lastIndexOf('/');
if (slash != -1 && slash > scheme) {
return iri.substring(0, slash);
}
return iri;
}

/**
* Returns the scheme and authority components of the IRI.
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/networknt/schema/AllOfValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNo
final ObjectNode allOfEntry = (ObjectNode) arrayElements.next();
final JsonNode $ref = allOfEntry.get("$ref");
if (null != $ref) {
final ValidationContext.DiscriminatorContext currentDiscriminatorContext = this.validationContext
final DiscriminatorContext currentDiscriminatorContext = executionContext
.getCurrentDiscriminatorContext();
if (null != currentDiscriminatorContext) {
final ObjectNode discriminator = currentDiscriminatorContext
Expand Down
8 changes: 4 additions & 4 deletions src/main/java/com/networknt/schema/AnyOfValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public class AnyOfValidator extends BaseJsonValidator {
private static final String DISCRIMINATOR_REMARK = "and the discriminator-selected candidate schema didn't pass validation";

private final List<JsonSchema> schemas = new ArrayList<>();
private final ValidationContext.DiscriminatorContext discriminatorContext;
private final DiscriminatorContext discriminatorContext;

public AnyOfValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.ANY_OF, validationContext);
Expand All @@ -42,7 +42,7 @@ public AnyOfValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath
}

if (this.validationContext.getConfig().isOpenAPI3StyleDiscriminators()) {
this.discriminatorContext = new ValidationContext.DiscriminatorContext();
this.discriminatorContext = new DiscriminatorContext();
} else {
this.discriminatorContext = null;
}
Expand All @@ -57,7 +57,7 @@ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNo
ValidatorState state = executionContext.getValidatorState();

if (this.validationContext.getConfig().isOpenAPI3StyleDiscriminators()) {
this.validationContext.enterDiscriminatorContext(this.discriminatorContext, instanceLocation);
executionContext.enterDiscriminatorContext(this.discriminatorContext, instanceLocation);
}

boolean initialHasMatchedNode = state.hasMatchedNode();
Expand Down Expand Up @@ -148,7 +148,7 @@ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNo
}
} finally {
if (this.validationContext.getConfig().isOpenAPI3StyleDiscriminators()) {
this.validationContext.leaveDiscriminatorContextImmediately(instanceLocation);
executionContext.leaveDiscriminatorContextImmediately(instanceLocation);
}

Scope parentScope = collectorContext.exitDynamicScope();
Expand Down
24 changes: 22 additions & 2 deletions src/main/java/com/networknt/schema/BaseJsonValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.networknt.schema.ValidationContext.DiscriminatorContext;
import com.networknt.schema.i18n.DefaultMessageSource;

import org.slf4j.Logger;
Expand Down Expand Up @@ -69,6 +68,20 @@ public BaseJsonValidator(SchemaLocation schemaLocation, JsonNodePath evaluationP
: PathType.DEFAULT;
}

/**
* Copy constructor.
*
* @param copy to copy from
*/
protected BaseJsonValidator(BaseJsonValidator copy) {
super(copy);
this.suppressSubSchemaRetrieval = copy.suppressSubSchemaRetrieval;
this.applyDefaultsStrategy = copy.applyDefaultsStrategy;
this.pathType = copy.pathType;
this.schemaNode = copy.schemaNode;
this.validationContext = copy.validationContext;
}

private static JsonSchema obtainSubSchemaNode(final JsonNode schemaNode, final ValidationContext validationContext) {
final JsonNode node = schemaNode.get("id");

Expand Down Expand Up @@ -112,7 +125,7 @@ protected static void debug(Logger logger, JsonNode node, JsonNode rootNode, Jso
* @param discriminatorPropertyValue the value of the <code>discriminator/propertyName</code> field
* @param jsonSchema the {@link JsonSchema} to check
*/
protected static void checkDiscriminatorMatch(final ValidationContext.DiscriminatorContext currentDiscriminatorContext,
protected static void checkDiscriminatorMatch(final DiscriminatorContext currentDiscriminatorContext,
final ObjectNode discriminator,
final String discriminatorPropertyValue,
final JsonSchema jsonSchema) {
Expand Down Expand Up @@ -249,6 +262,13 @@ public JsonSchema getParentSchema() {
return this.parentSchema;
}

public JsonSchema getEvaluationParentSchema() {
if (this.evaluationParentSchema != null) {
return this.evaluationParentSchema;
}
return getParentSchema();
}

protected JsonSchema fetchSubSchemaNode(ValidationContext validationContext) {
return this.suppressSubSchemaRetrieval ? null : obtainSubSchemaNode(this.schemaNode, validationContext);
}
Expand Down
41 changes: 41 additions & 0 deletions src/main/java/com/networknt/schema/CachedSupplier.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright (c) 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.networknt.schema;

import java.util.function.Supplier;

/**
* Supplier that caches the output.
*
* @param <T> the type cached
*/
public class CachedSupplier<T> implements Supplier<T> {
private final Supplier<T> delegate;
private T cache = null;

public CachedSupplier(Supplier<T> delegate) {
this.delegate = delegate;
}

@Override
public T get() {
if (cache == null) {
cache = delegate.get();
}
return cache;
}

}
41 changes: 41 additions & 0 deletions src/main/java/com/networknt/schema/DiscriminatorContext.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.networknt.schema;

import java.util.HashMap;
import java.util.Map;

import com.fasterxml.jackson.databind.node.ObjectNode;

public class DiscriminatorContext {
private final Map<String, ObjectNode> discriminators = new HashMap<>();

private boolean discriminatorMatchFound = false;

public void registerDiscriminator(final SchemaLocation schemaLocation, final ObjectNode discriminator) {
this.discriminators.put("#" + schemaLocation.getFragment().toString(), discriminator);
}

public ObjectNode getDiscriminatorForPath(final SchemaLocation schemaLocation) {
return this.discriminators.get("#" + schemaLocation.getFragment().toString());
}

public ObjectNode getDiscriminatorForPath(final String schemaLocation) {
return this.discriminators.get(schemaLocation);
}

public void markMatch() {
this.discriminatorMatchFound = true;
}

public boolean isDiscriminatorMatchFound() {
return this.discriminatorMatchFound;
}

/**
* Returns true if we have a discriminator active. In this case no valid match in anyOf should lead to validation failure
*
* @return true in case there are discriminator candidates
*/
public boolean isActive() {
return !this.discriminators.isEmpty();
}
}
60 changes: 57 additions & 3 deletions src/main/java/com/networknt/schema/EnumValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@
package com.networknt.schema;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.DecimalNode;
import com.fasterxml.jackson.databind.node.NullNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.math.BigDecimal;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
Expand All @@ -45,7 +47,10 @@ public EnumValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath,
for (JsonNode n : schemaNode) {
if (n.isNumber()) {
// convert to DecimalNode for number comparison
nodes.add(DecimalNode.valueOf(n.decimalValue()));
nodes.add(processNumberNode(n));
} else if (n.isArray()) {
ArrayNode a = processArrayNode((ArrayNode) n);
nodes.add(a);
} else {
nodes.add(n);
}
Expand All @@ -65,7 +70,6 @@ public EnumValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath,
sb.append("null");
}
}
//
sb.append(']');

error = sb.toString();
Expand All @@ -78,7 +82,11 @@ public EnumValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath,
public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
debug(logger, node, rootNode, instanceLocation);

if (node.isNumber()) node = DecimalNode.valueOf(node.decimalValue());
if (node.isNumber()) {
node = processNumberNode(node);
} else if (node.isArray()) {
node = processArrayNode((ArrayNode) node);
}
if (!nodes.contains(node) && !( this.validationContext.getConfig().isTypeLoose() && isTypeLooseContainsInEnum(node))) {
return Collections.singleton(message().instanceLocation(instanceLocation)
.locale(executionContext.getExecutionConfig().getLocale()).arguments(error).build());
Expand All @@ -105,4 +113,50 @@ private boolean isTypeLooseContainsInEnum(JsonNode node) {
return false;
}

/**
* Processes the number and ensures trailing zeros are stripped.
*
* @param n the node
* @return the node
*/
protected JsonNode processNumberNode(JsonNode n) {
return DecimalNode.valueOf(new BigDecimal(n.decimalValue().toPlainString()));
}

/**
* Processes the array and ensures that numbers within have trailing zeroes stripped.
*
* @param node the node
* @return the node
*/
protected ArrayNode processArrayNode(ArrayNode node) {
if (!hasNumber(node)) {
return node;
}
ArrayNode a = (ArrayNode) node.deepCopy();
for (int x = 0; x < a.size(); x++) {
JsonNode v = a.get(x);
if (v.isNumber()) {
v = processNumberNode(v);
a.set(x, v);
}
}
return a;
}

/**
* Determines if the array node contains a number.
*
* @param node the node
* @return the node
*/
protected boolean hasNumber(ArrayNode node) {
for (int x = 0; x < node.size(); x++) {
JsonNode v = node.get(x);
if (v.isNumber()) {
return true;
}
}
return false;
}
}
18 changes: 18 additions & 0 deletions src/main/java/com/networknt/schema/ExecutionContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,16 @@

package com.networknt.schema;

import java.util.Stack;

/**
* Stores the execution context for the validation run.
*/
public class ExecutionContext {
private ExecutionConfig executionConfig;
private CollectorContext collectorContext;
private ValidatorState validatorState = null;
private Stack<DiscriminatorContext> discriminatorContexts = new Stack<>();

/**
* Creates an execution context.
Expand Down Expand Up @@ -113,4 +116,19 @@ public ValidatorState getValidatorState() {
public void setValidatorState(ValidatorState validatorState) {
this.validatorState = validatorState;
}

public DiscriminatorContext getCurrentDiscriminatorContext() {
if (!this.discriminatorContexts.empty()) {
return this.discriminatorContexts.peek();
}
return null; // this is the case when we get on a schema that has a discriminator, but it's not used in anyOf
}

public void enterDiscriminatorContext(final DiscriminatorContext ctx, @SuppressWarnings("unused") JsonNodePath instanceLocation) {
this.discriminatorContexts.push(ctx);
}

public void leaveDiscriminatorContextImmediately(@SuppressWarnings("unused") JsonNodePath instanceLocation) {
this.discriminatorContexts.pop();
}
}
8 changes: 8 additions & 0 deletions src/main/java/com/networknt/schema/JsonMetaSchema.java
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,14 @@ public String readId(JsonNode schemaNode) {
return readText(schemaNode, this.idKeyword);
}

public String readAnchor(JsonNode schemaNode) {
boolean supportsAnchor = this.keywords.containsKey("$anchor");
if (supportsAnchor) {
return readText(schemaNode, "$anchor");
}
return null;
}

public JsonNode getNodeByFragmentRef(String ref, JsonNode node) {
boolean supportsAnchor = this.keywords.containsKey("$anchor");
String refName = supportsAnchor ? ref.substring(1) : ref;
Expand Down
Loading