Skip to content

Commit 9b73d10

Browse files
authored
Support schema resource (#922)
* Support schema resources * Refactor * Refactor * Refactor * Support uri change in id * Fix schema resource parent and evaluation path * Fix * Fix remote ref paths * Fix * Fix schema location * Support anchors * Refactor * Refactor * Refactor * Refactor shift subschema loading to factory * Fix ref * Refactor ref * Refactor discriminator * Refactor * Refactor validation context * Load validators in constructor * Schema location * Refactor ref validator * Fix enum
1 parent 5a94df7 commit 9b73d10

36 files changed

+1309
-362
lines changed

src/main/java/com/networknt/schema/AbsoluteIri.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,14 +105,23 @@ public static String resolve(String parent, String iri) {
105105
} else {
106106
scheme = scheme + 3;
107107
}
108-
int slash = parent.lastIndexOf('/');
109-
if (slash != -1 && slash > scheme) {
110-
base = parent.substring(0, slash);
108+
base = parent(base, scheme);
109+
while (iri.startsWith("../")) {
110+
base = parent(base, scheme);
111+
iri = iri.substring(3);
111112
}
112113
return base + "/" + iri;
113114
}
114115
}
115116
}
117+
118+
protected static String parent(String iri, int scheme) {
119+
int slash = iri.lastIndexOf('/');
120+
if (slash != -1 && slash > scheme) {
121+
return iri.substring(0, slash);
122+
}
123+
return iri;
124+
}
116125

117126
/**
118127
* Returns the scheme and authority components of the IRI.

src/main/java/com/networknt/schema/AllOfValidator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNo
6969
final ObjectNode allOfEntry = (ObjectNode) arrayElements.next();
7070
final JsonNode $ref = allOfEntry.get("$ref");
7171
if (null != $ref) {
72-
final ValidationContext.DiscriminatorContext currentDiscriminatorContext = this.validationContext
72+
final DiscriminatorContext currentDiscriminatorContext = executionContext
7373
.getCurrentDiscriminatorContext();
7474
if (null != currentDiscriminatorContext) {
7575
final ObjectNode discriminator = currentDiscriminatorContext

src/main/java/com/networknt/schema/AnyOfValidator.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public class AnyOfValidator extends BaseJsonValidator {
3030
private static final String DISCRIMINATOR_REMARK = "and the discriminator-selected candidate schema didn't pass validation";
3131

3232
private final List<JsonSchema> schemas = new ArrayList<>();
33-
private final ValidationContext.DiscriminatorContext discriminatorContext;
33+
private final DiscriminatorContext discriminatorContext;
3434

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

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

5959
if (this.validationContext.getConfig().isOpenAPI3StyleDiscriminators()) {
60-
this.validationContext.enterDiscriminatorContext(this.discriminatorContext, instanceLocation);
60+
executionContext.enterDiscriminatorContext(this.discriminatorContext, instanceLocation);
6161
}
6262

6363
boolean initialHasMatchedNode = state.hasMatchedNode();
@@ -148,7 +148,7 @@ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNo
148148
}
149149
} finally {
150150
if (this.validationContext.getConfig().isOpenAPI3StyleDiscriminators()) {
151-
this.validationContext.leaveDiscriminatorContextImmediately(instanceLocation);
151+
executionContext.leaveDiscriminatorContextImmediately(instanceLocation);
152152
}
153153

154154
Scope parentScope = collectorContext.exitDynamicScope();

src/main/java/com/networknt/schema/BaseJsonValidator.java

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818

1919
import com.fasterxml.jackson.databind.JsonNode;
2020
import com.fasterxml.jackson.databind.node.ObjectNode;
21-
import com.networknt.schema.ValidationContext.DiscriminatorContext;
2221
import com.networknt.schema.i18n.DefaultMessageSource;
2322

2423
import org.slf4j.Logger;
@@ -69,6 +68,20 @@ public BaseJsonValidator(SchemaLocation schemaLocation, JsonNodePath evaluationP
6968
: PathType.DEFAULT;
7069
}
7170

71+
/**
72+
* Copy constructor.
73+
*
74+
* @param copy to copy from
75+
*/
76+
protected BaseJsonValidator(BaseJsonValidator copy) {
77+
super(copy);
78+
this.suppressSubSchemaRetrieval = copy.suppressSubSchemaRetrieval;
79+
this.applyDefaultsStrategy = copy.applyDefaultsStrategy;
80+
this.pathType = copy.pathType;
81+
this.schemaNode = copy.schemaNode;
82+
this.validationContext = copy.validationContext;
83+
}
84+
7285
private static JsonSchema obtainSubSchemaNode(final JsonNode schemaNode, final ValidationContext validationContext) {
7386
final JsonNode node = schemaNode.get("id");
7487

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

265+
public JsonSchema getEvaluationParentSchema() {
266+
if (this.evaluationParentSchema != null) {
267+
return this.evaluationParentSchema;
268+
}
269+
return getParentSchema();
270+
}
271+
252272
protected JsonSchema fetchSubSchemaNode(ValidationContext validationContext) {
253273
return this.suppressSubSchemaRetrieval ? null : obtainSubSchemaNode(this.schemaNode, validationContext);
254274
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright (c) 2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.networknt.schema;
17+
18+
import java.util.function.Supplier;
19+
20+
/**
21+
* Supplier that caches the output.
22+
*
23+
* @param <T> the type cached
24+
*/
25+
public class CachedSupplier<T> implements Supplier<T> {
26+
private final Supplier<T> delegate;
27+
private T cache = null;
28+
29+
public CachedSupplier(Supplier<T> delegate) {
30+
this.delegate = delegate;
31+
}
32+
33+
@Override
34+
public T get() {
35+
if (cache == null) {
36+
cache = delegate.get();
37+
}
38+
return cache;
39+
}
40+
41+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package com.networknt.schema;
2+
3+
import java.util.HashMap;
4+
import java.util.Map;
5+
6+
import com.fasterxml.jackson.databind.node.ObjectNode;
7+
8+
public class DiscriminatorContext {
9+
private final Map<String, ObjectNode> discriminators = new HashMap<>();
10+
11+
private boolean discriminatorMatchFound = false;
12+
13+
public void registerDiscriminator(final SchemaLocation schemaLocation, final ObjectNode discriminator) {
14+
this.discriminators.put("#" + schemaLocation.getFragment().toString(), discriminator);
15+
}
16+
17+
public ObjectNode getDiscriminatorForPath(final SchemaLocation schemaLocation) {
18+
return this.discriminators.get("#" + schemaLocation.getFragment().toString());
19+
}
20+
21+
public ObjectNode getDiscriminatorForPath(final String schemaLocation) {
22+
return this.discriminators.get(schemaLocation);
23+
}
24+
25+
public void markMatch() {
26+
this.discriminatorMatchFound = true;
27+
}
28+
29+
public boolean isDiscriminatorMatchFound() {
30+
return this.discriminatorMatchFound;
31+
}
32+
33+
/**
34+
* Returns true if we have a discriminator active. In this case no valid match in anyOf should lead to validation failure
35+
*
36+
* @return true in case there are discriminator candidates
37+
*/
38+
public boolean isActive() {
39+
return !this.discriminators.isEmpty();
40+
}
41+
}

src/main/java/com/networknt/schema/EnumValidator.java

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@
1717
package com.networknt.schema;
1818

1919
import com.fasterxml.jackson.databind.JsonNode;
20+
import com.fasterxml.jackson.databind.node.ArrayNode;
2021
import com.fasterxml.jackson.databind.node.DecimalNode;
2122
import com.fasterxml.jackson.databind.node.NullNode;
2223
import org.slf4j.Logger;
2324
import org.slf4j.LoggerFactory;
2425

26+
import java.math.BigDecimal;
2527
import java.util.Collections;
2628
import java.util.HashSet;
2729
import java.util.Set;
@@ -45,7 +47,10 @@ public EnumValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath,
4547
for (JsonNode n : schemaNode) {
4648
if (n.isNumber()) {
4749
// convert to DecimalNode for number comparison
48-
nodes.add(DecimalNode.valueOf(n.decimalValue()));
50+
nodes.add(processNumberNode(n));
51+
} else if (n.isArray()) {
52+
ArrayNode a = processArrayNode((ArrayNode) n);
53+
nodes.add(a);
4954
} else {
5055
nodes.add(n);
5156
}
@@ -65,7 +70,6 @@ public EnumValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath,
6570
sb.append("null");
6671
}
6772
}
68-
//
6973
sb.append(']');
7074

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

81-
if (node.isNumber()) node = DecimalNode.valueOf(node.decimalValue());
85+
if (node.isNumber()) {
86+
node = processNumberNode(node);
87+
} else if (node.isArray()) {
88+
node = processArrayNode((ArrayNode) node);
89+
}
8290
if (!nodes.contains(node) && !( this.validationContext.getConfig().isTypeLoose() && isTypeLooseContainsInEnum(node))) {
8391
return Collections.singleton(message().instanceLocation(instanceLocation)
8492
.locale(executionContext.getExecutionConfig().getLocale()).arguments(error).build());
@@ -105,4 +113,50 @@ private boolean isTypeLooseContainsInEnum(JsonNode node) {
105113
return false;
106114
}
107115

116+
/**
117+
* Processes the number and ensures trailing zeros are stripped.
118+
*
119+
* @param n the node
120+
* @return the node
121+
*/
122+
protected JsonNode processNumberNode(JsonNode n) {
123+
return DecimalNode.valueOf(new BigDecimal(n.decimalValue().toPlainString()));
124+
}
125+
126+
/**
127+
* Processes the array and ensures that numbers within have trailing zeroes stripped.
128+
*
129+
* @param node the node
130+
* @return the node
131+
*/
132+
protected ArrayNode processArrayNode(ArrayNode node) {
133+
if (!hasNumber(node)) {
134+
return node;
135+
}
136+
ArrayNode a = (ArrayNode) node.deepCopy();
137+
for (int x = 0; x < a.size(); x++) {
138+
JsonNode v = a.get(x);
139+
if (v.isNumber()) {
140+
v = processNumberNode(v);
141+
a.set(x, v);
142+
}
143+
}
144+
return a;
145+
}
146+
147+
/**
148+
* Determines if the array node contains a number.
149+
*
150+
* @param node the node
151+
* @return the node
152+
*/
153+
protected boolean hasNumber(ArrayNode node) {
154+
for (int x = 0; x < node.size(); x++) {
155+
JsonNode v = node.get(x);
156+
if (v.isNumber()) {
157+
return true;
158+
}
159+
}
160+
return false;
161+
}
108162
}

src/main/java/com/networknt/schema/ExecutionContext.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,16 @@
1616

1717
package com.networknt.schema;
1818

19+
import java.util.Stack;
20+
1921
/**
2022
* Stores the execution context for the validation run.
2123
*/
2224
public class ExecutionContext {
2325
private ExecutionConfig executionConfig;
2426
private CollectorContext collectorContext;
2527
private ValidatorState validatorState = null;
28+
private Stack<DiscriminatorContext> discriminatorContexts = new Stack<>();
2629

2730
/**
2831
* Creates an execution context.
@@ -113,4 +116,19 @@ public ValidatorState getValidatorState() {
113116
public void setValidatorState(ValidatorState validatorState) {
114117
this.validatorState = validatorState;
115118
}
119+
120+
public DiscriminatorContext getCurrentDiscriminatorContext() {
121+
if (!this.discriminatorContexts.empty()) {
122+
return this.discriminatorContexts.peek();
123+
}
124+
return null; // this is the case when we get on a schema that has a discriminator, but it's not used in anyOf
125+
}
126+
127+
public void enterDiscriminatorContext(final DiscriminatorContext ctx, @SuppressWarnings("unused") JsonNodePath instanceLocation) {
128+
this.discriminatorContexts.push(ctx);
129+
}
130+
131+
public void leaveDiscriminatorContextImmediately(@SuppressWarnings("unused") JsonNodePath instanceLocation) {
132+
this.discriminatorContexts.pop();
133+
}
116134
}

src/main/java/com/networknt/schema/JsonMetaSchema.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,14 @@ public String readId(JsonNode schemaNode) {
226226
return readText(schemaNode, this.idKeyword);
227227
}
228228

229+
public String readAnchor(JsonNode schemaNode) {
230+
boolean supportsAnchor = this.keywords.containsKey("$anchor");
231+
if (supportsAnchor) {
232+
return readText(schemaNode, "$anchor");
233+
}
234+
return null;
235+
}
236+
229237
public JsonNode getNodeByFragmentRef(String ref, JsonNode node) {
230238
boolean supportsAnchor = this.keywords.containsKey("$anchor");
231239
String refName = supportsAnchor ? ref.substring(1) : ref;

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy