Skip to content

Commit

Permalink
introduce subschema
Browse files Browse the repository at this point in the history
  • Loading branch information
sebastian-toepfer committed Dec 3, 2023
1 parent 896b9a8 commit 165bcf2
Show file tree
Hide file tree
Showing 15 changed files with 252 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ public interface JsonSchema extends JsonValue {

Optional<Keyword> keywordByName(String name);

Optional<JsonSubSchema> asSubSchema(String name);

default JsonSchema rootSchema() {
return this;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,10 @@ public Optional<Keyword> keywordByName(String name) {
public JsonValue.ValueType getValueType() {
throw new UnsupportedOperationException("Not supported yet.");
}

@Override
public Optional<JsonSubSchema> asSubSchema(String name) {
throw new UnsupportedOperationException("Not supported yet.");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ public Optional<Keyword> keywordByName(String name) {
public JsonValue.ValueType getValueType() {
throw new UnsupportedOperationException("Not supported yet.");
}

@Override
public Optional<JsonSubSchema> asSubSchema(String name) {
throw new UnsupportedOperationException("Not supported yet.");
}
}
.rootSchema(),
is(sameInstance(root))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,16 @@
import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.toList;

import io.github.sebastiantoepfer.jsonschema.InstanceType;
import io.github.sebastiantoepfer.jsonschema.JsonSubSchema;
import io.github.sebastiantoepfer.jsonschema.Validator;
import io.github.sebastiantoepfer.jsonschema.core.codition.AllOfCondition;
import io.github.sebastiantoepfer.jsonschema.core.codition.ApplicatorBasedCondtion;
import io.github.sebastiantoepfer.jsonschema.core.codition.AssertionBasedCondition;
import io.github.sebastiantoepfer.jsonschema.core.codition.Condition;
import io.github.sebastiantoepfer.jsonschema.core.vocab.core.VocabularyKeywordType;
import io.github.sebastiantoepfer.jsonschema.keyword.Keyword;
import io.github.sebastiantoepfer.jsonschema.keyword.KeywordType;
import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.VocabularyDefinition;
import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.VocabularyDefinitions;
import jakarta.json.JsonObject;
Expand All @@ -41,13 +44,10 @@
import java.util.Optional;
import java.util.stream.Stream;

final class DefaultJsonSchema extends AbstractJsonValueSchema {
final class DefaultJsonObjectSchema extends AbstractJsonValueSchema {

private final Keywords keywords;

public DefaultJsonSchema(final JsonObject value) {
public DefaultJsonObjectSchema(final JsonObject value) {
super(value);
keywords = new Keywords(vocabulary());
}

@Override
Expand All @@ -65,18 +65,32 @@ public Optional<Keyword> keywordByName(final String name) {
return keywords().filter(k -> k.hasName(name)).findFirst();
}

private Stream<Keyword> keywords() {
final Keywords keywords = new Keywords(vocabulary());
return asJsonObject().keySet().stream().map(propertyName -> keywords.createKeywordFor(this, propertyName));
}

private Collection<VocabularyDefinition> vocabulary() {
return new KeywordSearch(new VocabularyKeywordType())
.searchForKeywordIn(this)
final KeywordType keywordType = new VocabularyKeywordType();
return Optional
.ofNullable(asJsonObject().get(keywordType.name()))
.map(keywordValue -> keywordType.createKeyword(this))
.filter(VocabularyDefinitions.class::isInstance)
.map(VocabularyDefinitions.class::cast)
.stream()
.flatMap(VocabularyDefinitions::definitions)
.toList();
}

private Stream<Keyword> keywords() {
return asJsonObject().keySet().stream().map(propertyName -> keywords.createKeywordFor(this, propertyName));
@Override
public Optional<JsonSubSchema> asSubSchema(final String name) {
return Optional
.ofNullable(asJsonObject().get(name))
.filter(value ->
Stream.of(InstanceType.BOOLEAN, InstanceType.OBJECT).anyMatch(type -> type.isInstance(value))
)
.map(new DefaultJsonSchemaFactory()::create)
.map(subSchema -> new DefaultJsonSubSchema(this, subSchema));
}

private Optional<Condition<JsonValue>> asContraint(final Keyword keyword) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public JsonSchema create(final JsonValue schema) {
} else if (schema.equals(JsonValue.EMPTY_JSON_OBJECT)) {
result = new EmptyJsonSchema();
} else {
result = new DefaultJsonSchema(schema.asJsonObject());
result = new DefaultJsonObjectSchema(schema.asJsonObject());
}
return result;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,23 +24,50 @@
package io.github.sebastiantoepfer.jsonschema.core;

import io.github.sebastiantoepfer.jsonschema.JsonSchema;
import io.github.sebastiantoepfer.jsonschema.JsonSubSchema;
import io.github.sebastiantoepfer.jsonschema.Validator;
import io.github.sebastiantoepfer.jsonschema.keyword.Keyword;
import io.github.sebastiantoepfer.jsonschema.keyword.KeywordType;
import jakarta.json.JsonObject;
import java.util.Objects;
import java.util.Optional;

@Deprecated
final class KeywordSearch {
final class DefaultJsonSubSchema implements JsonSubSchema {

private final KeywordType keywordType;
private final JsonSchema owner;
private final JsonSchema schema;

public KeywordSearch(final KeywordType keywordType) {
this.keywordType = Objects.requireNonNull(keywordType);
public DefaultJsonSubSchema(final JsonSchema owner, final JsonSchema schema) {
this.owner = Objects.requireNonNull(owner);
this.schema = Objects.requireNonNull(schema);
}

public Optional<Keyword> searchForKeywordIn(final JsonSchema schema) {
return Optional
.ofNullable(schema.asJsonObject().get(keywordType.name()))
.map(keywordValue -> keywordType.createKeyword(schema));
@Override
public JsonSchema owner() {
return owner;
}

@Override
public Validator validator() {
return schema.validator();
}

@Override
public Optional<Keyword> keywordByName(final String name) {
return schema.keywordByName(name);
}

@Override
public Optional<JsonSubSchema> asSubSchema(final String name) {
return schema.asSubSchema(name);
}

@Override
public ValueType getValueType() {
return schema.getValueType();
}

@Override
public JsonObject asJsonObject() {
return schema.asJsonObject();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
*/
package io.github.sebastiantoepfer.jsonschema.core;

import io.github.sebastiantoepfer.jsonschema.JsonSubSchema;
import io.github.sebastiantoepfer.jsonschema.Validator;
import io.github.sebastiantoepfer.jsonschema.core.codition.NoCondition;
import io.github.sebastiantoepfer.jsonschema.keyword.Keyword;
Expand All @@ -44,4 +45,9 @@ public Validator validator() {
public Optional<Keyword> keywordByName(final String name) {
return Optional.empty();
}

@Override
public Optional<JsonSubSchema> asSubSchema(final String name) {
return Optional.empty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
*/
package io.github.sebastiantoepfer.jsonschema.core;

import io.github.sebastiantoepfer.jsonschema.JsonSubSchema;
import io.github.sebastiantoepfer.jsonschema.Validator;
import io.github.sebastiantoepfer.jsonschema.core.codition.UnfulfillableCondition;
import io.github.sebastiantoepfer.jsonschema.keyword.Keyword;
Expand All @@ -44,4 +45,9 @@ public Validator validator() {
public Optional<Keyword> keywordByName(final String name) {
return Optional.empty();
}

@Override
public Optional<JsonSubSchema> asSubSchema(final String name) {
return Optional.empty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
*/
package io.github.sebastiantoepfer.jsonschema.core;

import io.github.sebastiantoepfer.jsonschema.JsonSubSchema;
import io.github.sebastiantoepfer.jsonschema.Validator;
import io.github.sebastiantoepfer.jsonschema.core.codition.NoCondition;
import io.github.sebastiantoepfer.jsonschema.keyword.Keyword;
Expand All @@ -44,4 +45,9 @@ public Validator validator() {
public Optional<Keyword> keywordByName(final String name) {
return Optional.empty();
}

@Override
public Optional<JsonSubSchema> asSubSchema(final String name) {
return Optional.empty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@

import io.github.sebastiantoepfer.jsonschema.InstanceType;
import io.github.sebastiantoepfer.jsonschema.JsonSchema;
import io.github.sebastiantoepfer.jsonschema.JsonSchemas;
import io.github.sebastiantoepfer.jsonschema.JsonSubSchema;
import io.github.sebastiantoepfer.jsonschema.Validator;
import io.github.sebastiantoepfer.jsonschema.keyword.Annotation;
Expand All @@ -49,22 +48,20 @@ public String name() {

@Override
public Keyword createKeyword(final JsonSchema schema) {
return new ItemsKeyword(schema, JsonSchemas.load(schema.asJsonObject().get(name())));
return schema.asSubSchema(name()).map(ItemsKeyword::new).orElseThrow(IllegalArgumentException::new);
}

private class ItemsKeyword implements Applicator, Annotation, JsonSubSchema {

private final JsonSchema owner;
private final JsonSchema schema;
private final JsonSubSchema schema;

public ItemsKeyword(final JsonSchema owner, final JsonSchema schema) {
this.owner = Objects.requireNonNull(owner);
public ItemsKeyword(final JsonSubSchema schema) {
this.schema = Objects.requireNonNull(schema);
}

@Override
public JsonSchema owner() {
return owner;
return schema.owner();
}

@Override
Expand All @@ -82,6 +79,11 @@ public Optional<Keyword> keywordByName(final String name) {
return schema.keywordByName(name);
}

@Override
public Optional<JsonSubSchema> asSubSchema(final String name) {
return schema.asSubSchema(name);
}

@Override
public boolean hasName(final String name) {
return Objects.equals(name(), name);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;

import io.github.sebastiantoepfer.jsonschema.JsonSubSchema;
import io.github.sebastiantoepfer.jsonschema.Validator;
import io.github.sebastiantoepfer.jsonschema.keyword.Keyword;
import jakarta.json.JsonArray;
Expand Down Expand Up @@ -72,5 +73,10 @@ public Validator validator() {
public Optional<Keyword> keywordByName(final String name) {
return Optional.empty();
}

@Override
public Optional<JsonSubSchema> asSubSchema(final String name) {
return Optional.empty();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,15 @@
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.junit.jupiter.api.Assertions.assertThrows;

import jakarta.json.Json;
import jakarta.json.JsonObject;
import jakarta.json.JsonValue;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

class DefaultJsonSchemaTest {
class DefaultJsonObjectSchemaTest {

private final DefaultJsonSchema schema = new DefaultJsonSchema(
private final DefaultJsonObjectSchema schema = new DefaultJsonObjectSchema(
Json
.createObjectBuilder()
.add("type", "array")
Expand Down Expand Up @@ -66,22 +65,24 @@ void should_be_invalid_for_object() {
}

@Test
void should_not_be_loadable_without_mandantory_core_vocabulary() {
final JsonObject invalidSchema = Json
.createObjectBuilder()
.add(
"$vocabulary",
Json.createObjectBuilder().add("https://json-schema.org/draft/2020-12/vocab/core", false)
)
.build();
void should_not_be_usable_without_mandantory_core_vocabulary() {
final DefaultJsonObjectSchema invalidSchema = new DefaultJsonObjectSchema(
Json
.createObjectBuilder()
.add(
"$vocabulary",
Json.createObjectBuilder().add("https://json-schema.org/draft/2020-12/vocab/core", false)
)
.build()
);

Assertions.assertThrows(Exception.class, () -> new DefaultJsonSchema(invalidSchema));
assertThrows(IllegalArgumentException.class, () -> invalidSchema.keywordByName("$vocabulary"));
}

@Test
void should_find_keyword_by_name() {
assertThat(
new DefaultJsonSchema(
new DefaultJsonObjectSchema(
Json
.createObjectBuilder()
.add(
Expand All @@ -98,7 +99,7 @@ void should_find_keyword_by_name() {
@Test
void should_return_empty_for_non_existing_keyword() {
assertThat(
new DefaultJsonSchema(
new DefaultJsonObjectSchema(
Json
.createObjectBuilder()
.add(
Expand All @@ -111,4 +112,31 @@ void should_return_empty_for_non_existing_keyword() {
isEmpty()
);
}

@Test
void should_return_empty_if_non_subschema_exists_under_the_given_name() {
assertThat(
new DefaultJsonObjectSchema(Json.createObjectBuilder().add("test", "hallo").build())
.asSubSchema("properties"),
isEmpty()
);
}

@Test
void should_return_subschema_if_subschema_exists_under_the_given_name() {
assertThat(
new DefaultJsonObjectSchema(Json.createObjectBuilder().add("test", JsonValue.FALSE).build())
.asSubSchema("test"),
isPresent()
);
}

@Test
void should_return_empty_if_given_name_not_resolve_to_a_valid_schematype() {
assertThat(
new DefaultJsonObjectSchema(Json.createObjectBuilder().add("test", JsonValue.EMPTY_JSON_ARRAY).build())
.asSubSchema("test"),
isEmpty()
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ void should_return_emptyjsonschema_for_emptyobject() {
void should_return_defaultjsonschema_for_everything_else() {
assertThat(
new DefaultJsonSchemaFactory().create(Json.createObjectBuilder().add("type", "string").build()),
is(instanceOf(DefaultJsonSchema.class))
is(instanceOf(DefaultJsonObjectSchema.class))
);
}
}
Loading

0 comments on commit 165bcf2

Please sign in to comment.