
dev.harrel.jsonschema.Evaluators Maven / Gradle / Ivy
package dev.harrel.jsonschema;
import java.math.BigDecimal;
import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static java.util.Collections.singleton;
import static java.util.Collections.unmodifiableList;
class TypeEvaluator implements Evaluator {
private final Set types;
TypeEvaluator(JsonNode node) {
if (!node.isString() && !node.isArray()) {
throw new IllegalArgumentException();
}
if (node.isString()) {
this.types = singleton(SimpleType.fromName(node.asString()));
} else {
this.types = node.asArray().stream()
.map(JsonNode::asString)
.map(SimpleType::fromName)
.collect(Collectors.toCollection(LinkedHashSet::new));
}
}
@Override
public Result evaluate(EvaluationContext ctx, JsonNode node) {
SimpleType nodeType = node.getNodeType();
if (types.contains(nodeType) || nodeType == SimpleType.INTEGER && types.contains(SimpleType.NUMBER)) {
return Result.success();
} else {
List typeNames = unmodifiableList(types.stream().map(SimpleType::getName).collect(Collectors.toList()));
return Result.failure(String.format("Value is [%s] but should be %s", nodeType.getName(), typeNames));
}
}
}
class ConstEvaluator implements Evaluator {
private final JsonNode constNode;
ConstEvaluator(JsonNode node) {
this.constNode = node;
}
@Override
public Result evaluate(EvaluationContext ctx, JsonNode node) {
return constNode.isEqualTo(node) ? Result.success() : Result.failure("Expected " + constNode.toPrintableString());
}
}
class EnumEvaluator implements Evaluator {
private final List enumNodes;
private final String failMessage;
EnumEvaluator(JsonNode node) {
if (!node.isArray()) {
throw new IllegalArgumentException();
}
this.enumNodes = unmodifiableList(node.asArray());
List printList = enumNodes.stream().map(JsonNode::toPrintableString).collect(Collectors.toList());
this.failMessage = String.format("Expected any of [%s]", printList);
}
@Override
public Result evaluate(EvaluationContext ctx, JsonNode node) {
for (JsonNode enumNode : enumNodes) {
if (enumNode.isEqualTo(node)) {
return Result.success();
}
}
return Result.failure(failMessage);
}
}
class MultipleOfEvaluator implements Evaluator {
private final BigDecimal factor;
MultipleOfEvaluator(JsonNode node) {
if (!node.isNumber()) {
throw new IllegalArgumentException();
}
this.factor = node.asNumber();
}
@Override
public Result evaluate(EvaluationContext ctx, JsonNode node) {
if (!node.isNumber()) {
return Result.success();
}
if (node.asNumber().remainder(factor).doubleValue() == 0.0) {
return Result.success();
} else {
return Result.failure(String.format("%s is not multiple of %s", node.asNumber(), factor));
}
}
}
class MaximumEvaluator implements Evaluator {
private final BigDecimal max;
MaximumEvaluator(JsonNode node) {
if (!node.isNumber()) {
throw new IllegalArgumentException();
}
this.max = node.asNumber();
}
@Override
public Result evaluate(EvaluationContext ctx, JsonNode node) {
if (!node.isNumber()) {
return Result.success();
}
if (node.asNumber().compareTo(max) <= 0) {
return Result.success();
} else {
return Result.failure(String.format("%s is greater than %s", node.asNumber(), max));
}
}
}
class ExclusiveMaximumEvaluator implements Evaluator {
private final BigDecimal max;
ExclusiveMaximumEvaluator(JsonNode node) {
if (!node.isNumber()) {
throw new IllegalArgumentException();
}
this.max = node.asNumber();
}
@Override
public Result evaluate(EvaluationContext ctx, JsonNode node) {
if (!node.isNumber()) {
return Result.success();
}
if (node.asNumber().compareTo(max) < 0) {
return Result.success();
} else {
return Result.failure(String.format("%s is greater or equal to %s", node.asNumber(), max));
}
}
}
class MinimumEvaluator implements Evaluator {
private final BigDecimal min;
MinimumEvaluator(JsonNode node) {
if (!node.isNumber()) {
throw new IllegalArgumentException();
}
this.min = node.asNumber();
}
@Override
public Result evaluate(EvaluationContext ctx, JsonNode node) {
if (!node.isNumber()) {
return Result.success();
}
if (node.asNumber().compareTo(min) >= 0) {
return Result.success();
} else {
return Result.failure(String.format("%s is less than %s", node.asNumber(), min));
}
}
}
class ExclusiveMinimumEvaluator implements Evaluator {
private final BigDecimal min;
ExclusiveMinimumEvaluator(JsonNode node) {
if (!node.isNumber()) {
throw new IllegalArgumentException();
}
this.min = node.asNumber();
}
@Override
public Result evaluate(EvaluationContext ctx, JsonNode node) {
if (!node.isNumber()) {
return Result.success();
}
if (node.asNumber().compareTo(min) > 0) {
return Result.success();
} else {
return Result.failure(String.format("%s is less than or equal to %s", node.asNumber(), min));
}
}
}
class MaxLengthEvaluator implements Evaluator {
private final int maxLength;
MaxLengthEvaluator(JsonNode node) {
if (!node.isInteger()) {
throw new IllegalArgumentException();
}
this.maxLength = node.asInteger().intValueExact();
}
@Override
public Result evaluate(EvaluationContext ctx, JsonNode node) {
if (!node.isString()) {
return Result.success();
}
String string = node.asString();
if (string.codePointCount(0, string.length()) <= maxLength) {
return Result.success();
} else {
return Result.failure(String.format("\"%s\" is longer than %d characters", string, maxLength));
}
}
}
class MinLengthEvaluator implements Evaluator {
private final int minLength;
MinLengthEvaluator(JsonNode node) {
if (!node.isInteger()) {
throw new IllegalArgumentException();
}
this.minLength = node.asInteger().intValueExact();
}
@Override
public Result evaluate(EvaluationContext ctx, JsonNode node) {
if (!node.isString()) {
return Result.success();
}
String string = node.asString();
if (string.codePointCount(0, string.length()) >= minLength) {
return Result.success();
} else {
return Result.failure(String.format("\"%s\" is shorter than %d characters", string, minLength));
}
}
}
class PatternEvaluator implements Evaluator {
private final Pattern pattern;
PatternEvaluator(JsonNode node) {
if (!node.isString()) {
throw new IllegalArgumentException();
}
this.pattern = Pattern.compile(node.asString());
}
@Override
public Result evaluate(EvaluationContext ctx, JsonNode node) {
if (!node.isString()) {
return Result.success();
}
if (pattern.matcher(node.asString()).find()) {
return Result.success();
} else {
return Result.failure(String.format("\"%s\" does not match regular expression [%s]", node.asString(), pattern));
}
}
}
class MaxItemsEvaluator implements Evaluator {
private final int maxItems;
MaxItemsEvaluator(JsonNode node) {
if (!node.isInteger()) {
throw new IllegalArgumentException();
}
this.maxItems = node.asInteger().intValueExact();
}
@Override
public Result evaluate(EvaluationContext ctx, JsonNode node) {
if (!node.isArray()) {
return Result.success();
}
if (node.asArray().size() <= maxItems) {
return Result.success();
} else {
return Result.failure(String.format("Array has more than %d items", maxItems));
}
}
}
class MinItemsEvaluator implements Evaluator {
private final int minItems;
MinItemsEvaluator(JsonNode node) {
if (!node.isInteger()) {
throw new IllegalArgumentException();
}
this.minItems = node.asInteger().intValueExact();
}
@Override
public Result evaluate(EvaluationContext ctx, JsonNode node) {
if (!node.isArray()) {
return Result.success();
}
if (node.asArray().size() >= minItems) {
return Result.success();
} else {
return Result.failure(String.format("Array has less than %d items", minItems));
}
}
}
class UniqueItemsEvaluator implements Evaluator {
private final boolean unique;
UniqueItemsEvaluator(JsonNode node) {
if (!node.isBoolean()) {
throw new IllegalArgumentException();
}
this.unique = node.asBoolean();
}
@Override
public Result evaluate(EvaluationContext ctx, JsonNode node) {
if (!node.isArray() || !unique) {
return Result.success();
}
List parsed = new ArrayList<>();
List jsonNodes = node.asArray();
for (int i = 0; i < jsonNodes.size(); i++) {
JsonNode element = jsonNodes.get(i);
if (parsed.stream().anyMatch(element::isEqualTo)) {
return Result.failure(String.format("Array contains non-unique item at index [%d]", i));
}
parsed.add(element);
}
return Result.success();
}
}
class MaxContainsEvaluator implements Evaluator {
private final int max;
MaxContainsEvaluator(JsonNode node) {
if (!node.isInteger()) {
throw new IllegalArgumentException();
}
this.max = node.asInteger().intValueExact();
}
@Override
public Result evaluate(EvaluationContext ctx, JsonNode node) {
if (!node.isArray()) {
return Result.success();
}
int containsCount = ctx.getSiblingAnnotation(Keyword.CONTAINS, node.getJsonPointer(), List.class)
.map(Collection::size)
.orElse(0);
if (containsCount <= max) {
return Result.success();
} else {
return Result.failure(String.format("Array contains more than %d matching items", max));
}
}
@Override
public int getOrder() {
return 10;
}
}
class MinContainsEvaluator implements Evaluator {
private final int min;
MinContainsEvaluator(JsonNode node) {
if (!node.isInteger()) {
throw new IllegalArgumentException();
}
this.min = node.asInteger().intValueExact();
}
@Override
public Result evaluate(EvaluationContext ctx, JsonNode node) {
if (!node.isArray()) {
return Result.success();
}
int containsCount = ctx.getSiblingAnnotation(Keyword.CONTAINS, node.getJsonPointer(), List.class)
.map(Collection::size)
.orElse(Integer.MAX_VALUE);
if (containsCount >= min) {
return Result.success();
} else {
return Result.failure(String.format("Array contains less than %d matching items", min));
}
}
@Override
public int getOrder() {
return 10;
}
}
class MaxPropertiesEvaluator implements Evaluator {
private final int max;
MaxPropertiesEvaluator(JsonNode node) {
if (!node.isInteger()) {
throw new IllegalArgumentException();
}
this.max = node.asInteger().intValueExact();
}
@Override
public Result evaluate(EvaluationContext ctx, JsonNode node) {
if (!node.isObject()) {
return Result.success();
}
if (node.asObject().size() <= max) {
return Result.success();
} else {
return Result.failure(String.format("Object has more than %d properties", max));
}
}
}
class MinPropertiesEvaluator implements Evaluator {
private final int min;
MinPropertiesEvaluator(JsonNode node) {
if (!node.isInteger()) {
throw new IllegalArgumentException();
}
this.min = node.asInteger().intValueExact();
}
@Override
public Result evaluate(EvaluationContext ctx, JsonNode node) {
if (!node.isObject()) {
return Result.success();
}
if (node.asObject().size() >= min) {
return Result.success();
} else {
return Result.failure(String.format("Object has less than %d properties", min));
}
}
}
class RequiredEvaluator implements Evaluator {
private final List requiredProperties;
RequiredEvaluator(JsonNode node) {
if (!node.isArray()) {
throw new IllegalArgumentException();
}
this.requiredProperties = unmodifiableList(node.asArray().stream().map(JsonNode::asString).collect(Collectors.toList()));
}
@Override
public Result evaluate(EvaluationContext ctx, JsonNode node) {
if (!node.isObject()) {
return Result.success();
}
Set keys = node.asObject().keySet();
if (keys.containsAll(requiredProperties)) {
return Result.success();
} else {
HashSet unsatisfied = new HashSet<>(requiredProperties);
unsatisfied.removeAll(keys);
return Result.failure(String.format("Object does not have some of the required properties [%s]", unsatisfied));
}
}
}
class DependenciesLegacyEvaluator implements Evaluator {
private final DependentRequiredEvaluator requiredDelegate;
private final DependentSchemasEvaluator schemasDelegate;
DependenciesLegacyEvaluator(SchemaParsingContext ctx, JsonNode node) {
if (!node.isObject()) {
throw new IllegalArgumentException();
}
Map> splitMap = node.asObject().entrySet().stream()
.collect(Collectors.partitioningBy(entry -> entry.getValue().isArray(),
Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)));
this.requiredDelegate = new DependentRequiredEvaluator(splitMap.get(true));
this.schemasDelegate = new DependentSchemasEvaluator(ctx, splitMap.get(false));
}
@Override
public Result evaluate(EvaluationContext ctx, JsonNode node) {
Result result = requiredDelegate.evaluate(ctx, node);
if (!result.isValid()) {
return result;
} else {
return schemasDelegate.evaluate(ctx, node);
}
}
}
class DependentRequiredEvaluator implements Evaluator {
private final Map> requiredProperties;
DependentRequiredEvaluator(JsonNode node) {
if (!node.isObject()) {
throw new IllegalArgumentException();
}
this.requiredProperties = toMap(node.asObject());
}
DependentRequiredEvaluator(Map objectNode) {
this.requiredProperties = toMap(objectNode);
}
@Override
public Result evaluate(EvaluationContext ctx, JsonNode node) {
if (!node.isObject()) {
return Result.success();
}
Set objectKeys = node.asObject().keySet();
Set requiredSet = objectKeys
.stream()
.filter(requiredProperties::containsKey)
.map(requiredProperties::get)
.flatMap(List::stream)
.collect(Collectors.toSet());
if (objectKeys.containsAll(requiredSet)) {
return Result.success();
} else {
requiredSet.removeAll(objectKeys);
return Result.failure(String.format("Object does not have some of the required properties [%s]", requiredSet));
}
}
private static Map> toMap(Map objectNode) {
return objectNode.entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getKey, e -> toStringList(e.getValue())));
}
private static List toStringList(JsonNode node) {
return unmodifiableList(node.asArray().stream().map(JsonNode::asString).collect(Collectors.toList()));
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy