Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Returning error messages from applicators #52

Merged
merged 6 commits into from
Sep 8, 2023
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
6 changes: 4 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ jobs:
uses: ./.github/workflows/tests.yml

sonar-cloud:
if: '!github.event.pull_request.head.repo.fork'
runs-on: ubuntu-latest
needs: tests

Expand All @@ -27,8 +28,9 @@ jobs:
- name: Setup java
uses: actions/setup-java@v3
with:
distribution: ${{ vars.JAVA_VENDOR }}
java-version: ${{ vars.JAVA_VERSION }}
# Not using variables to allow running from forks
distribution: corretto
java-version: 17

- name: Setup Gradle
uses: gradle/gradle-build-action@v2
Expand Down
15 changes: 9 additions & 6 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ jobs:
- name: Setup java
uses: actions/setup-java@v3
with:
distribution: ${{ vars.JAVA_VENDOR }}
java-version: ${{ vars.JAVA_VERSION }}
# Not using variables to allow running from forks
distribution: corretto
java-version: 17

- name: Setup Gradle
uses: gradle/gradle-build-action@v2
Expand Down Expand Up @@ -56,8 +57,9 @@ jobs:
- name: Setup java
uses: actions/setup-java@v3
with:
distribution: ${{ vars.JAVA_VENDOR }}
java-version: ${{ vars.JAVA_VERSION }}
# Not using variables to allow running from forks
distribution: corretto
java-version: 17

- name: Setup Gradle
uses: gradle/gradle-build-action@v2
Expand Down Expand Up @@ -85,8 +87,9 @@ jobs:
- name: Setup java
uses: actions/setup-java@v3
with:
distribution: ${{ vars.JAVA_VENDOR }}
java-version: ${{ vars.JAVA_VERSION }}
# Not using variables to allow running from forks
distribution: corretto
java-version: 17

- name: Setup Gradle
uses: gradle/gradle-build-action@v2
Expand Down
20 changes: 0 additions & 20 deletions src/main/java/dev/harrel/jsonschema/Applicator.java

This file was deleted.

149 changes: 111 additions & 38 deletions src/main/java/dev/harrel/jsonschema/Applicators.java
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ public Result evaluate(EvaluationContext ctx, JsonNode node) {
.count() == filtered.size();
return valid ? Result.success(unmodifiableSet(filtered.keySet())) : Result.failure();
}

@Override
public int getOrder() {
return 10;
Expand Down Expand Up @@ -185,12 +186,14 @@ public Result evaluate(EvaluationContext ctx, JsonNode node) {
.stream()
.filter(e -> schemaRefs.containsKey(e.getKey()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

boolean valid = filtered
.entrySet()
.stream()
.map(e -> new AbstractMap.SimpleEntry<>(schemaRefs.get(e.getKey()), e.getValue()))
.filter(e -> ctx.resolveInternalRefAndValidate(e.getKey(), e.getValue()))
.count() == filtered.size();

return valid ? Result.success(unmodifiableSet(filtered.keySet())) : Result.failure();
}
}
Expand Down Expand Up @@ -235,7 +238,7 @@ public Result evaluate(EvaluationContext ctx, JsonNode node) {
}
}

class DependentSchemasEvaluator implements Applicator {
class DependentSchemasEvaluator implements Evaluator {
private final Map<String, String> dependentSchemas;

DependentSchemasEvaluator(SchemaParsingContext ctx, JsonNode node) {
Expand All @@ -248,24 +251,31 @@ class DependentSchemasEvaluator implements Applicator {
}

@Override
public boolean apply(EvaluationContext ctx, JsonNode node) {
public Set<String> getVocabularies() {
return APPLICATOR_VOCABULARY;
}

@Override
public Result evaluate(EvaluationContext ctx, JsonNode node) {
if (!node.isObject()) {
return true;
return Result.success();
}


List<String> fields = node.asObject().keySet()
.stream()
.filter(dependentSchemas::containsKey)
.collect(Collectors.toList());
return fields.stream()

boolean result = fields.stream()
.map(dependentSchemas::get)
.filter(ref -> ctx.resolveInternalRefAndValidate(ref, node))
.count() == fields.size();

return result ? Result.success() : Result.failure();
}
}

class PropertyNamesEvaluator implements Applicator {
class PropertyNamesEvaluator implements Evaluator {
private final String schemaRef;

PropertyNamesEvaluator(SchemaParsingContext ctx, JsonNode node) {
Expand All @@ -276,19 +286,26 @@ class PropertyNamesEvaluator implements Applicator {
}

@Override
public boolean apply(EvaluationContext ctx, JsonNode node) {
public Set<String> getVocabularies() {
return APPLICATOR_VOCABULARY;
}

@Override
public Result evaluate(EvaluationContext ctx, JsonNode node) {
if (!node.isObject()) {
return true;
return Result.success();
}

Map<String, JsonNode> object = node.asObject();
return object.keySet().stream()
boolean valid = object.keySet().stream()
.filter(propName -> ctx.resolveInternalRefAndValidate(schemaRef, new StringNode(propName, node.getJsonPointer())))
.count() == object.size();

return valid ? Result.success() : Result.failure();
}
}

class IfThenElseEvaluator implements Applicator {
class IfThenElseEvaluator implements Evaluator {
private final String ifRef;
private final Optional<String> thenRef;
private final Optional<String> elseRef;
Expand All @@ -305,20 +322,29 @@ class IfThenElseEvaluator implements Applicator {
}

@Override
public boolean apply(EvaluationContext ctx, JsonNode node) {
public Set<String> getVocabularies() {
return APPLICATOR_VOCABULARY;
}

@Override
public Result evaluate(EvaluationContext ctx, JsonNode node) {
if (ctx.resolveInternalRefAndValidate(ifRef, node)) {
return thenRef
boolean valid = thenRef
.map(ref -> ctx.resolveInternalRefAndValidate(ref, node))
.orElse(true);

return valid ? Result.success() : Result.failure("Value matches against schema from 'if' but does not match against schema from 'then'");
} else {
return elseRef
boolean valid = elseRef
.map(ref -> ctx.resolveInternalRefAndValidate(ref, node))
.orElse(true);

return valid ? Result.success() : Result.failure("Value does not match against schema from 'if' and 'else'");
}
}
}

class AllOfEvaluator implements Applicator {
class AllOfEvaluator implements Evaluator {
private final List<String> refs;

AllOfEvaluator(SchemaParsingContext ctx, JsonNode node) {
Expand All @@ -329,14 +355,26 @@ class AllOfEvaluator implements Applicator {
}

@Override
public boolean apply(EvaluationContext ctx, JsonNode node) {
return refs.stream()
.filter(pointer -> ctx.resolveInternalRefAndValidate(pointer, node))
.count() == refs.size();
public Set<String> getVocabularies() {
return APPLICATOR_VOCABULARY;
}

@Override
public Result evaluate(EvaluationContext ctx, JsonNode node) {
List<Integer> unmatchedIndexes = IntStream.range(0, refs.size())
.filter(i -> !ctx.resolveInternalRefAndValidate(refs.get(i), node))
.boxed()
.collect(Collectors.toList());

if (unmatchedIndexes.isEmpty()) {
return Result.success();
}

return Result.failure(String.format("Value does not match against the schemas at indexes %s", unmatchedIndexes));
}
}

class AnyOfEvaluator implements Applicator {
class AnyOfEvaluator implements Evaluator {
private final List<String> refs;

AnyOfEvaluator(SchemaParsingContext ctx, JsonNode node) {
Expand All @@ -347,14 +385,21 @@ class AnyOfEvaluator implements Applicator {
}

@Override
public boolean apply(EvaluationContext ctx, JsonNode node) {
return refs.stream()
public Set<String> getVocabularies() {
return APPLICATOR_VOCABULARY;
}

@Override
public Result evaluate(EvaluationContext ctx, JsonNode node) {
boolean valid = refs.stream()
.filter(pointer -> ctx.resolveInternalRefAndValidate(pointer, node))
.count() > 0;

return valid ? Result.success() : Result.failure("Value does not match against any of the schemas");
}
}

class OneOfEvaluator implements Applicator {
class OneOfEvaluator implements Evaluator {
private final List<String> refs;

OneOfEvaluator(SchemaParsingContext ctx, JsonNode node) {
Expand All @@ -365,17 +410,32 @@ class OneOfEvaluator implements Applicator {
}

@Override
public boolean apply(EvaluationContext ctx, JsonNode node) {
return refs.stream()
.filter(uri -> ctx.resolveInternalRefAndValidate(uri, node))
.count() == 1;
public Set<String> getVocabularies() {
return APPLICATOR_VOCABULARY;
}

@Override
public Result evaluate(EvaluationContext ctx, JsonNode node) {
List<Integer> matchedIndexes = IntStream.range(0, refs.size())
.filter(i -> ctx.resolveInternalRefAndValidate(refs.get(i), node))
.boxed()
.collect(Collectors.toList());

if (matchedIndexes.size() == 1) {
EmilsOzolins marked this conversation as resolved.
Show resolved Hide resolved
return Result.success();
}

if (matchedIndexes.isEmpty()) {
return Result.failure("Value does not match against any of the schemas");
}

return Result.failure(String.format("Value matches against more than one schema. Matched schema indexes %s", matchedIndexes));
}
}

class NotEvaluator implements Applicator {
class NotEvaluator implements Evaluator {
private final String schemaUri;


NotEvaluator(SchemaParsingContext ctx, JsonNode node) {
if (!node.isObject() && !node.isBoolean()) {
throw new IllegalArgumentException();
Expand All @@ -384,12 +444,18 @@ class NotEvaluator implements Applicator {
}

@Override
public boolean apply(EvaluationContext ctx, JsonNode node) {
return !ctx.resolveInternalRefAndValidate(schemaUri, node);
public Set<String> getVocabularies() {
return APPLICATOR_VOCABULARY;
}

@Override
public Result evaluate(EvaluationContext ctx, JsonNode node) {
boolean valid = !ctx.resolveInternalRefAndValidate(schemaUri, node);
return valid ? Result.success() : Result.failure("Value matches against given schema but it must not");
}
}

class UnevaluatedItemsEvaluator implements Applicator {
class UnevaluatedItemsEvaluator implements Evaluator {
private final String schemaRef;
private final String parentPath;

Expand All @@ -408,9 +474,9 @@ public Set<String> getVocabularies() {
}

@Override
public boolean apply(EvaluationContext ctx, JsonNode node) {
public Result evaluate(EvaluationContext ctx, JsonNode node) {
if (!node.isArray()) {
return true;
return Result.success();
}

List<EvaluationItem> evaluationItems = unmodifiableList(ctx.getAnnotations().stream()
Expand All @@ -420,9 +486,12 @@ public boolean apply(EvaluationContext ctx, JsonNode node) {
.stream()
.filter(arrayNode -> evaluationItems.stream().noneMatch(a -> a.getInstanceLocation().startsWith(arrayNode.getJsonPointer())))
.collect(Collectors.toList());
return array.stream()

boolean valid = array.stream()
.filter(arrayNode -> ctx.resolveInternalRefAndValidate(schemaRef, arrayNode))
.count() == array.size();

return valid ? Result.success() : Result.failure();
}

@Override
Expand All @@ -435,7 +504,7 @@ private String getSchemaPath(EvaluationItem item) {
}
}

class UnevaluatedPropertiesEvaluator implements Applicator {
class UnevaluatedPropertiesEvaluator implements Evaluator {
private final String schemaRef;
private final String parentPath;

Expand All @@ -454,22 +523,26 @@ public Set<String> getVocabularies() {
}

@Override
public boolean apply(EvaluationContext ctx, JsonNode node) {
public Result evaluate(EvaluationContext ctx, JsonNode node) {
if (!node.isObject()) {
return true;
return Result.success();
}

List<EvaluationItem> evaluationItems = unmodifiableList(ctx.getAnnotations().stream()
.filter(a -> getSchemaPath(a).startsWith(parentPath))
.collect(Collectors.toList()));

List<JsonNode> array = node.asObject()
.values()
.stream()
.filter(propertyNode -> evaluationItems.stream().noneMatch(a -> a.getInstanceLocation().startsWith(propertyNode.getJsonPointer())))
.collect(Collectors.toList());
return array.stream()

boolean valid = array.stream()
.filter(propertyNode -> ctx.resolveInternalRefAndValidate(schemaRef, propertyNode))
.count() == array.size();

return valid ? Result.success() : Result.failure();
}

@Override
Expand Down
Loading