Skip to content
Draft
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
79 changes: 79 additions & 0 deletions lib/src/integration/gsonTest/GsonStandaloneNodeTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import dev.harrel.jsonschema.*;
import dev.harrel.jsonschema.internal.StandaloneNode;
import dev.harrel.jsonschema.providers.GsonModule;
import dev.harrel.jsonschema.providers.GsonNode;
import dev.harrel.jsonschema.util.JsonNodeMock;
import org.junit.jupiter.api.Test;

import java.util.Map;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;

class GsonStandaloneNodeTest extends ProviderTestBundle {
// factory to be promoted to prod code someday
private final JsonNodeFactory standaloneFactory = new JsonNodeFactory() {
private final Gson gson = new GsonBuilder()
.registerTypeHierarchyAdapter(JsonNode.class, new GsonModule.TypeAdapter())
.create();

@Override
public dev.harrel.jsonschema.JsonNode wrap(Object node) {
return switch (node) {
case StandaloneNode sNode when sNode.getJsonPointer().isEmpty() -> sNode;
case StandaloneNode sNode -> sNode.copy("");
case JsonNode otherNode -> gson.fromJson(gson.toJson(otherNode), JsonNode.class);
case JsonElement providerNode -> gson.fromJson(gson.toJson(providerNode), JsonNode.class);
default ->
throw new IllegalArgumentException("Cannot wrap an instance of " + node.getClass().getName());
};
}

@Override
public dev.harrel.jsonschema.JsonNode create(String rawJson) {
return gson.fromJson(rawJson, JsonNode.class);
}
};

@Override
public JsonNodeFactory getJsonNodeFactory() {
return standaloneFactory;
}

@Test
void shouldInstantiateValidatorFactory() {
new ValidatorFactory();
}

@Test
void shouldPassForGsonFactory() {
new ValidatorFactory()
.withJsonNodeFactory(new GsonNode.Factory())
.validate("{}", "{}");
}

@Test
void shouldFailForDefaultFactory() {
assertThatThrownBy(() -> new ValidatorFactory().validate("{}", "{}"))
.isInstanceOf(NoClassDefFoundError.class);
}

@Test
void shouldWrapForValidArgument() {
JsonElement object = new Gson().toJsonTree(Map.of());
JsonNode wrap = new GsonNode.Factory().wrap(object);
assertThat(wrap).isNotNull();
assertThat(wrap.getNodeType()).isEqualTo(SimpleType.OBJECT);
}

@Test
void shouldWrapRudimentaryJsonNodeImpl() {
JsonNodeFactory factory = getJsonNodeFactory();
JsonNode node = factory.wrap(new JsonNodeMock());
assertThat(node.isBoolean()).isTrue();
assertThat(node.asBoolean()).isFalse();
}
}
94 changes: 94 additions & 0 deletions lib/src/integration/jacksonTest/JacksonStandaloneNodeTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import dev.harrel.jsonschema.JsonNodeFactory;
import dev.harrel.jsonschema.ProviderTestBundle;
import dev.harrel.jsonschema.SimpleType;
import dev.harrel.jsonschema.ValidatorFactory;
import dev.harrel.jsonschema.internal.StandaloneNode;
import dev.harrel.jsonschema.providers.GsonNode;
import dev.harrel.jsonschema.providers.JacksonModule;
import dev.harrel.jsonschema.providers.JacksonNode;
import dev.harrel.jsonschema.util.JsonNodeMock;
import org.assertj.core.api.AssertionsForClassTypes;
import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.io.UncheckedIOException;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

class JacksonStandaloneNodeTest extends ProviderTestBundle {
// factory to be promoted to prod code someday
private final JsonNodeFactory standaloneFactory = new JsonNodeFactory() {
private final ObjectMapper mapper = new ObjectMapper().registerModule(new JacksonModule());

@Override
public dev.harrel.jsonschema.JsonNode wrap(Object node) {
return switch (node) {
case StandaloneNode sNode when sNode.getJsonPointer().isEmpty() -> sNode;
case StandaloneNode sNode -> sNode.copy("");
case dev.harrel.jsonschema.JsonNode otherNode -> mapper.convertValue(otherNode, dev.harrel.jsonschema.JsonNode.class);
case JsonNode providerNode -> mapper.convertValue(providerNode, dev.harrel.jsonschema.JsonNode.class);
default -> throw new IllegalArgumentException("Cannot wrap an instance of " + node.getClass().getName());
};
}

@Override
public dev.harrel.jsonschema.JsonNode create(String rawJson) {
try {
return mapper.readValue(rawJson, dev.harrel.jsonschema.JsonNode.class);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
};

@Override
public JsonNodeFactory getJsonNodeFactory() {
return standaloneFactory;
}

@Test
void shouldInstantiateValidatorFactory() {
new ValidatorFactory();
}

@Test
void shouldPassForJacksonFactory() {
new ValidatorFactory()
.withJsonNodeFactory(new JacksonNode.Factory())
.validate("{}", "{}");
}

@Test
void shouldPassForDefaultFactory() {
new ValidatorFactory().validate("{}", "{}");
}

@Test
void shouldFailForGsonFactory() {
AssertionsForClassTypes.assertThatThrownBy(
() -> new ValidatorFactory()
.withJsonNodeFactory(new GsonNode.Factory())
.validate("{}", "{}"))
.isInstanceOf(NoClassDefFoundError.class);
}

@Test
void shouldWrapForValidArgument() throws JsonProcessingException {
JsonNode object = new ObjectMapper().readTree("{}");
dev.harrel.jsonschema.JsonNode wrap = new JacksonNode.Factory().wrap(object);
assertThat(wrap).isNotNull();
assertThat(wrap.getNodeType()).isEqualTo(SimpleType.OBJECT);
}

@Test
void shouldFailWrapForInvalidArgument() {
dev.harrel.jsonschema.JsonNode node = new JsonNodeMock();
JacksonNode.Factory factory = new JacksonNode.Factory();
assertThatThrownBy(() -> factory.wrap(node))
.isInstanceOf(IllegalArgumentException.class);
}
}
2 changes: 1 addition & 1 deletion lib/src/integration/jacksonTest/JacksonTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ void shouldFailForGsonFactory() {
@Test
void shouldWrapForValidArgument() throws JsonProcessingException {
JsonNode object = new ObjectMapper().readTree("{}");
JacksonNode wrap = new JacksonNode.Factory().wrap(object);
dev.harrel.jsonschema.JsonNode wrap = new JacksonNode.Factory().wrap(object);
assertThat(wrap).isNotNull();
assertThat(wrap.getNodeType()).isEqualTo(SimpleType.OBJECT);
}
Expand Down
4 changes: 3 additions & 1 deletion lib/src/main/java/dev/harrel/jsonschema/Applicators.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package dev.harrel.jsonschema;

import dev.harrel.jsonschema.internal.StandaloneNode;

import java.math.BigInteger;
import java.util.*;
import java.util.regex.Pattern;
Expand Down Expand Up @@ -358,7 +360,7 @@ public Result evaluate(EvaluationContext ctx, JsonNode node) {

boolean valid = true;
for (String propName : node.asObject().keySet()) {
valid = ctx.resolveInternalRefAndValidate(schemaRef, new StringNode(propName, node.getJsonPointer())) && valid;
valid = ctx.resolveInternalRefAndValidate(schemaRef, new StandaloneNode(node.getJsonPointer(), SimpleType.STRING, propName)) && valid;
}
return valid ? Result.success() : Result.failure();
}
Expand Down
10 changes: 5 additions & 5 deletions lib/src/main/java/dev/harrel/jsonschema/Evaluators.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,26 +48,26 @@ class ConstEvaluator implements Evaluator {

@Override
public Result evaluate(EvaluationContext ctx, JsonNode node) {
boolean valid = canUseNativeEquals(constNode) && canUseNativeEquals(node) ? constNode.equals(node) : JsonNodeUtil.equals(constNode, node);
boolean valid = canUseNativeEquals(constNode, node) ? constNode.equals(node) : JsonNodeUtil.equals(constNode, node);
return valid ? Result.success() : Result.formattedFailure("const", node.toPrintableString(), constNode.toPrintableString());
}
}

class EnumEvaluator implements Evaluator {
private final JsonNode arrayNode;
private final Set<JsonNode> enumNodes;
private final boolean canUseNativeEquals;

EnumEvaluator(JsonNode node) {
if (!node.isArray()) {
throw new IllegalArgumentException();
}
this.arrayNode = node;
this.enumNodes = unmodifiableSet(new LinkedHashSet<>(node.asArray()));
this.canUseNativeEquals = canUseNativeEquals(node);
}

@Override
public Result evaluate(EvaluationContext ctx, JsonNode node) {
if (canUseNativeEquals && canUseNativeEquals(node)) {
if (canUseNativeEquals(arrayNode, node)) {
return enumNodes.contains(node) ? Result.success() : Result.formattedFailure("enum", createArgsSupplier(node));
} else {
for (JsonNode enumNode : enumNodes) {
Expand Down Expand Up @@ -387,7 +387,7 @@ public Result evaluate(EvaluationContext ctx, JsonNode node) {
}

List<JsonNode> jsonNodes = node.asArray();
if (canUseNativeEquals(node)) {
if (canUseNativeEquals(node, node)) {
Set<JsonNode> parsed = new HashSet<>();
for (int i = 0; i < jsonNodes.size(); i++) {
if (!parsed.add(jsonNodes.get(i))) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package dev.harrel.jsonschema;

import dev.harrel.jsonschema.internal.StandaloneNode;

final class JsonNodeFactoryWrapper implements JsonNodeFactory {
private final JsonNodeFactory delegate;

JsonNodeFactoryWrapper(JsonNodeFactory delegate) {
this.delegate = delegate;
}

@Override
public JsonNode wrap(Object node) {
if (node instanceof StandaloneNode) {
StandaloneNode sNode = (StandaloneNode) node;
return sNode.getJsonPointer().isEmpty() ? sNode : sNode.copy("");
}
return delegate.wrap(node);
}

@Override
public JsonNode create(String rawJson) {
return delegate.create(rawJson);
}
}
59 changes: 0 additions & 59 deletions lib/src/main/java/dev/harrel/jsonschema/StringNode.java

This file was deleted.

6 changes: 3 additions & 3 deletions lib/src/main/java/dev/harrel/jsonschema/ValidatorFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
private final Map<URI, Dialect> dialects = new HashMap<>(Dialects.OFFICIAL_DIALECTS);
private Dialect defaultDialect = new Dialects.Draft2020Dialect();
private EvaluatorFactory evaluatorFactory;
private Supplier<JsonNodeFactory> schemaNodeFactory = JacksonNode.Factory::new;
private Supplier<JsonNodeFactory> schemaNodeFactory = () -> new JacksonNode.Factory(); // cannot be a method reference due to eager class loading

Check warning on line 20 in lib/src/main/java/dev/harrel/jsonschema/ValidatorFactory.java

View check run for this annotation

SonarQubeCloud / [json-schema] SonarCloud Code Analysis

Replace this lambda with method reference 'JacksonNode.Factory::new'.

See more on https://sonarcloud.io/project/issues?id=harrel56_json-schema&issues=AZ8xsTb6_AXk3ALWEeIT&open=AZ8xsTb6_AXk3ALWEeIT&pullRequest=396
private Supplier<JsonNodeFactory> instanceNodeFactory = schemaNodeFactory;
private SchemaResolver schemaResolver = new DefaultSchemaResolver();
private MessageProvider messageProvider = MessageProvider.fromLocale(Locale.getDefault());
Expand Down Expand Up @@ -116,8 +116,8 @@
public ValidatorFactory withJsonNodeFactories(JsonNodeFactory schemaNodeFactory, JsonNodeFactory instanceNodeFactory) {
Objects.requireNonNull(schemaNodeFactory);
Objects.requireNonNull(instanceNodeFactory);
this.schemaNodeFactory = () -> schemaNodeFactory;
this.instanceNodeFactory = () -> instanceNodeFactory;
this.schemaNodeFactory = () -> new JsonNodeFactoryWrapper(schemaNodeFactory);
this.instanceNodeFactory = () -> new JsonNodeFactoryWrapper(instanceNodeFactory);
return this;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.HashMap;

Check warning on line 8 in lib/src/main/java/dev/harrel/jsonschema/internal/AbstractJsonNode.java

View check run for this annotation

SonarQubeCloud / [json-schema] SonarCloud Code Analysis

Remove this unused import 'java.util.HashMap'.

See more on https://sonarcloud.io/project/issues?id=harrel56_json-schema&issues=AZ8xsTZa_AXk3ALWEeIM&open=AZ8xsTZa_AXk3ALWEeIM&pullRequest=396
import java.util.List;
import java.util.Map;
import java.util.Objects;
Expand Down Expand Up @@ -124,12 +124,4 @@
protected abstract List<JsonNode> createArray();
protected abstract Map<String, JsonNode> createObject();
protected abstract SimpleType computeNodeType(T node);

protected static boolean canConvertToInteger(BigDecimal bigDecimal) {
return bigDecimal.scale() <= 0 || bigDecimal.stripTrailingZeros().scale() <= 0;
}

protected static <K, V> HashMap<K, V> newHashMap(int realCapacity) {
return new HashMap<>((int) Math.ceil(realCapacity / 0.75));
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,27 @@

package dev.harrel.jsonschema.internal;

import dev.harrel.jsonschema.JsonNode;

import java.math.BigDecimal;
import java.util.HashMap;

/**
* Internal helper class.
*/
public final class InternalProviderUtil {
private InternalProviderUtil() {}

public static boolean canUseNativeEquals(JsonNode node) {
return node instanceof AbstractJsonNode<?>;
public static boolean canUseNativeEquals(JsonNode n1, JsonNode n2) {
return n1 instanceof StandaloneNode && n2 instanceof StandaloneNode ||
n1 instanceof AbstractJsonNode<?> && n2 instanceof AbstractJsonNode<?>;
}

public static boolean canConvertToInteger(BigDecimal bigDecimal) {
return bigDecimal.scale() <= 0 || bigDecimal.stripTrailingZeros().scale() <= 0;
}

public static <K, V> HashMap<K, V> newHashMap(int realCapacity) {

Check warning on line 24 in lib/src/main/java/dev/harrel/jsonschema/internal/InternalProviderUtil.java

View check run for this annotation

SonarQubeCloud / [json-schema] SonarCloud Code Analysis

The return type of this method should be an interface such as "Map" rather than the implementation "HashMap".

See more on https://sonarcloud.io/project/issues?id=harrel56_json-schema&issues=AZ8xsTbK_AXk3ALWEeIN&open=AZ8xsTbK_AXk3ALWEeIN&pullRequest=396
return new HashMap<>((int) Math.ceil(realCapacity / 0.75));
}
}
Loading
Loading