Skip to content

Commit

Permalink
Refactor of paths (#915)
Browse files Browse the repository at this point in the history
* Refactor path

* Refactor

* Fix paths in validation messages

* Fix paths in validation messages

* Reduce repeated calls

* Refactor schema path

* Refactor validation message

* Refactor validation message

* Refactor validation message

* Fix

* Fix test expectations

* Add evaluationPath to walk event

* Rename

* Refactor

* Fix walk

* Rename schemaPath to schemaLocation

* Fix items tuple schema location

* Rename at to instanceLocation

* Rename keyWordName to keyword

* Use factory method instead of reflection

* change getValidators to use evaluation path and fix paths

* Refactor message building

* Refactor to use message builder

* Refactor parse error code

* Fix evaluationPath for IfValidator

* Fix paths for additionalItems

* Refactor to make use of ValidatorTypeCode optional to create a BaseJsonValidator

* Refactor to remove customMessage and errorCodeKey from the ValidatorTypeCode

* Update javadocs

* Add keyword to JsonValidator

* Add javadoc

* Support customized formats

* Add configuration for annotation allow lists

* Optimise

* Optimise required property evaluation

* Set schema location

* Validator sort

* Fix inconsistent validation for anyOf

* Fix incorrect test expectation

* Add schema location

* Use schema location

* Refactor

* Rename resolve to append

* Use schema retrieval uri if $id not present

* Refactor schema location

* Optimise performance

* Update walkers doc
  • Loading branch information
justin-tay authored Jan 19, 2024
1 parent 5dd0be3 commit 5a94df7
Show file tree
Hide file tree
Showing 118 changed files with 3,778 additions and 1,366 deletions.
30 changes: 17 additions & 13 deletions doc/walkers.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,20 @@ public interface JsonSchemaWalker {
* cutting concerns like logging or instrumentation. This method also performs
* the validation if {@code shouldValidateSchema} is set to true. <br>
* <br>
* {@link BaseJsonValidator#walk(JsonNode, JsonNode, String, boolean)} provides
* a default implementation of this method. However keywords that parse
* {@link BaseJsonValidator#walk(ExecutionContext, JsonNode, JsonNode, JsonNodePath, boolean)} provides
* a default implementation of this method. However validators that parse
* sub-schemas should override this method to call walk method on those
* subschemas.
* sub-schemas.
*
* @param executionContext ExecutionContext
* @param node JsonNode
* @param rootNode JsonNode
* @param at String
* @param instanceLocation JsonNodePath
* @param shouldValidateSchema boolean
* @return a set of validation messages if shouldValidateSchema is true.
*/
Set<ValidationMessage> walk(JsonNode node, JsonNode rootNode, String at, boolean shouldValidateSchema);
Set<ValidationMessage> walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode,
JsonNodePath instanceLocation, boolean shouldValidateSchema);
}

```
Expand All @@ -41,10 +43,11 @@ The JSONValidator interface extends this new interface thus allowing all the val
* validate method if shouldValidateSchema is enabled.
*/
@Override
public Set<ValidationMessage> walk(JsonNode node, JsonNode rootNode, String at, boolean shouldValidateSchema) {
public Set<ValidationMessage> walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode,
JsonNodePath instanceLocation, boolean shouldValidateSchema);
Set<ValidationMessage> validationMessages = new LinkedHashSet<ValidationMessage>();
if (shouldValidateSchema) {
validationMessages = validate(node, rootNode, at);
validationMessages = validate(executionContext, node, rootNode, instanceLocation);
}
return validationMessages;
}
Expand All @@ -54,7 +57,7 @@ The JSONValidator interface extends this new interface thus allowing all the val
A new walk method added to the JSONSchema class allows us to walk through the JSONSchema.

```java
public ValidationResult walk(JsonNode node, boolean shouldValidateSchema) {
public ValidationResult walk(JsonNode node, boolean shouldValidateSchema) {
// Create the collector context object.
CollectorContext collectorContext = new CollectorContext();
// Set the collector context in thread info, this is unique for every thread.
Expand All @@ -66,7 +69,7 @@ A new walk method added to the JSONSchema class allows us to walk through the JS
ValidationResult validationResult = new ValidationResult(errors, collectorContext);
return validationResult;
}

@Override
public Set<ValidationMessage> walk(JsonNode node, JsonNode rootNode, String at, boolean shouldValidateSchema) {
Set<ValidationMessage> validationMessages = new LinkedHashSet<ValidationMessage>();
Expand Down Expand Up @@ -174,14 +177,15 @@ Following snippet shows the details captured by WalkEvent instance.
```java
public class WalkEvent {

private String schemaPath;
private ExecutionContext executionContext;
private SchemaLocation schemaLocation;
private JsonNodePath evaluationPath;
private JsonNode schemaNode;
private JsonSchema parentSchema;
private String keyWordName;
private String keyword;
private JsonNode node;
private JsonNode rootNode;
private String at;

private JsonNodePath instanceLocation;
```

### Sample Flow
Expand Down
179 changes: 179 additions & 0 deletions src/main/java/com/networknt/schema/AbsoluteIri.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
/*
* 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.Objects;

/**
* The absolute IRI is an IRI without the fragment.
* <p>
* absolute-IRI = scheme ":" ihier-part [ "?" iquery ]
* <p>
* This does not attempt to validate whether the value really conforms to an
* absolute IRI format as in earlier drafts the IDs are not defined as such.
*/
public class AbsoluteIri {
private final String value;

/**
* Constructs a new IRI given the value.
*
* @param value
*/
public AbsoluteIri(String value) {
this.value = value;
}

/**
* Constructs a new IRI given the value.
*
* @param iri the value
* @return the new absolute IRI
*/
public static AbsoluteIri of(String iri) {
return new AbsoluteIri(iri);
}

/**
* Constructs a new IRI by parsing the given string and then resolving it
* against this IRI.
*
* @param iri to resolve
* @return the new absolute IRI
*/
public AbsoluteIri resolve(String iri) {
return new AbsoluteIri(resolve(this.value, iri));
}

/**
* Gets the scheme of the IRI.
*
* @return the scheme
*/
public String getScheme() {
return getScheme(this.value);
}

/**
* Returns the scheme and authority components of the IRI.
*
* @return the scheme and authority components
*/
protected String getSchemeAuthority() {
return getSchemeAuthority(this.value);
}

/**
* Constructs a new IRI by parsing the given string and then resolving it
* against this IRI.
*
* @param parent the parent absolute IRI
* @param iri to resolve
* @return the new absolute IRI
*/
public static String resolve(String parent, String iri) {
if (iri.contains(":")) {
// IRI is absolute
return iri;
} else {
// IRI is relative to this
if (parent == null) {
return iri;
}
if (iri.startsWith("/")) {
// IRI is relative to this root
return getSchemeAuthority(parent) + iri;
} else {
// IRI is relative to this path
String base = parent;
int scheme = parent.indexOf("://");
if (scheme == -1) {
scheme = 0;
} else {
scheme = scheme + 3;
}
int slash = parent.lastIndexOf('/');
if (slash != -1 && slash > scheme) {
base = parent.substring(0, slash);
}
return base + "/" + iri;
}
}
}

/**
* Returns the scheme and authority components of the IRI.
*
* @param iri to parse
* @return the scheme and authority components
*/
protected static String getSchemeAuthority(String iri) {
if (iri == null) {
return "";
}
// iri refers to root
int start = iri.indexOf("://");
if (start == -1) {
start = 0;
} else {
start = start + 3;
}
int end = iri.indexOf('/', start);
return end != -1 ? iri.substring(0, end) : iri;
}

/**
* Returns the scheme of the IRI.
*
* @param iri to parse
* @return the scheme
*/
public static String getScheme(String iri) {
if (iri == null) {
return "";
}
// iri refers to root
int start = iri.indexOf(":");
if (start == -1) {
return "";
} else {
return iri.substring(0, start);
}
}

@Override
public String toString() {
return this.value;
}

@Override
public int hashCode() {
return Objects.hash(this.value);
}

@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
AbsoluteIri other = (AbsoluteIri) obj;
return Objects.equals(this.value, other.value);
}

}
37 changes: 24 additions & 13 deletions src/main/java/com/networknt/schema/AbstractJsonValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,34 @@

package com.networknt.schema;

import com.fasterxml.jackson.databind.JsonNode;
public abstract class AbstractJsonValidator implements JsonValidator {
private final SchemaLocation schemaLocation;
private final JsonNodePath evaluationPath;
private final Keyword keyword;

import java.util.Collections;
import java.util.Set;
public AbstractJsonValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, Keyword keyword) {
this.schemaLocation = schemaLocation;
this.evaluationPath = evaluationPath;
this.keyword = keyword;
}

public abstract class AbstractJsonValidator implements JsonValidator {
@Override
public SchemaLocation getSchemaLocation() {
return schemaLocation;
}

public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node) {
return validate(executionContext, node, node, PathType.LEGACY.getRoot());
@Override
public JsonNodePath getEvaluationPath() {
return evaluationPath;
}

@Override
public Set<ValidationMessage> walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, String at, boolean shouldValidateSchema) {
Set<ValidationMessage> validationMessages = Collections.emptySet();
if (shouldValidateSchema) {
validationMessages = validate(executionContext, node, rootNode, at);
}
return validationMessages;
}
public String getKeyword() {
return keyword.getValue();
}

@Override
public String toString() {
return getEvaluationPath().getName(-1);
}
}
Loading

0 comments on commit 5a94df7

Please sign in to comment.