Skip to content

Commit 87ae9ef

Browse files
authored
Merge pull request #78 from eclipse-vertx/preload
feat: allow to preload json meta schemas into the repository
2 parents bcc2474 + 7ac1f9f commit 87ae9ef

File tree

9 files changed

+4814
-28
lines changed

9 files changed

+4814
-28
lines changed

src/main/java/io/vertx/json/schema/Draft.java

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -25,30 +25,43 @@ public enum Draft {
2525
/**
2626
* Draft 4 - <a href="http://json-schema.org/draft-04/schema#">http://json-schema.org/draft-04/schema#</a>
2727
*
28-
* Usually used by Swagger 2.0
28+
* Usually used by OpenAPI 3.0
2929
*/
30-
DRAFT4,
30+
DRAFT4("http://json-schema.org/draft-04/schema#"),
3131

3232
/**
3333
* Draft 7 - <a href="http://json-schema.org/draft-07/schema#">http://json-schema.org/draft-07/schema#</a>
3434
*
35-
* Usually used by OpenAPI 3.0
35+
* Commonly used by many projects
3636
*/
37-
DRAFT7,
37+
DRAFT7("http://json-schema.org/draft-07/schema#"),
3838

3939
/**
4040
* Draft 2019-09 - <a href="https://json-schema.org/draft/2019-09/schema">https://json-schema.org/draft/2019-09/schema</a>
4141
*
4242
* Commonly used by many projects
4343
*/
44-
DRAFT201909,
44+
DRAFT201909("https://json-schema.org/draft/2019-09/schema"),
4545

4646
/**
47-
* Draft 2019-09 - <a href="https://json-schema.org/draft/2020-12/schema">https://json-schema.org/draft/2020-12/schema</a>
47+
* Draft 2020-12 - <a href="https://json-schema.org/draft/2020-12/schema">https://json-schema.org/draft/2020-12/schema</a>
4848
*
4949
* Usually used by OpenAPI 3.1
5050
*/
51-
DRAFT202012;
51+
DRAFT202012("https://json-schema.org/draft/2020-12/schema");
52+
53+
private final String identifier;
54+
55+
Draft(String identifier) {
56+
this.identifier = identifier;
57+
}
58+
59+
/**
60+
* @return the identifier of the draft version.
61+
*/
62+
public String getIdentifier() {
63+
return identifier;
64+
}
5265

5366
/**
5467
* Converts a draft number to a {@link Draft} enum value.
@@ -75,25 +88,24 @@ public static Draft from(String string) {
7588
}
7689

7790
/**
78-
* Converts a draft idenfifier to a {@link Draft} enum value.
91+
* Converts a draft identifier to a {@link Draft} enum value.
7992
* @param string The identifier (in URL format)
8093
* @return a Draft enum value
8194
*/
8295
public static Draft fromIdentifier(String string) {
8396
if (string == null) {
8497
throw new IllegalArgumentException("Invalid draft identifier: null");
8598
}
86-
switch (string) {
87-
case "http://json-schema.org/draft-04/schema#":
88-
return DRAFT4;
89-
case "http://json-schema.org/draft-07/schema#":
90-
return DRAFT7;
91-
case "https://json-schema.org/draft/2019-09/schema":
92-
return DRAFT201909;
93-
case "https://json-schema.org/draft/2020-12/schema":
94-
return DRAFT202012;
95-
default:
96-
throw new IllegalArgumentException("Unsupported draft identifier: " + string);
99+
if(DRAFT4.identifier.equals(string)) {
100+
return DRAFT4;
101+
} else if(DRAFT7.identifier.equals(string)) {
102+
return DRAFT7;
103+
} else if(DRAFT201909.identifier.equals(string)) {
104+
return DRAFT201909;
105+
} else if(DRAFT202012.identifier.equals(string)) {
106+
return DRAFT202012;
107+
} else {
108+
throw new IllegalArgumentException("Unsupported draft identifier: " + string);
97109
}
98110
}
99111
}

src/main/java/io/vertx/json/schema/SchemaRepository.java

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,13 @@
1212

1313
import io.vertx.codegen.annotations.Fluent;
1414
import io.vertx.codegen.annotations.VertxGen;
15+
import io.vertx.core.file.FileSystem;
1516
import io.vertx.core.json.JsonObject;
1617
import io.vertx.json.schema.impl.SchemaRepositoryImpl;
1718

1819
/**
1920
* A repository is a holder of dereferenced schemas, it can be used to create validator instances for a specific schema.
20-
*
21+
* <p>
2122
* This is to be used when multiple schema objects compose the global schema to be used for validation.
2223
*
2324
* @author Paulo Lopes
@@ -27,6 +28,7 @@ public interface SchemaRepository {
2728

2829
/**
2930
* Create a repository with some initial configuration.
31+
*
3032
* @param options the initial configuration
3133
* @return a repository
3234
*/
@@ -38,24 +40,45 @@ static SchemaRepository create(JsonSchemaOptions options) {
3840
* Dereferences a schema to the repository.
3941
*
4042
* @param schema a new schema to list
41-
* @throws SchemaException when a schema is already present for the same id
4243
* @return a repository
44+
* @throws SchemaException when a schema is already present for the same id
4345
*/
4446
@Fluent
4547
SchemaRepository dereference(JsonSchema schema) throws SchemaException;
4648

4749
/**
4850
* Dereferences a schema to the repository.
4951
*
50-
* @param uri the source of the schema used for de-referencing, optionally relative to
51-
* {@link JsonSchemaOptions#getBaseUri()}.
52+
* @param uri the source of the schema used for de-referencing, optionally relative to
53+
* {@link JsonSchemaOptions#getBaseUri()}.
5254
* @param schema a new schema to list
53-
* @throws SchemaException when a schema is already present for the same id
5455
* @return a repository
56+
* @throws SchemaException when a schema is already present for the same id
5557
*/
5658
@Fluent
5759
SchemaRepository dereference(String uri, JsonSchema schema) throws SchemaException;
5860

61+
/**
62+
* Preloads the repository with the meta schemas for the related @link {@link Draft} version. The related draft version
63+
* is determined from the {@link JsonSchemaOptions}, in case that no draft is set in the options an
64+
* {@link IllegalStateException} is thrown.
65+
*
66+
* @param fs The Vert.x file system to load the related schema meta files from classpath
67+
* @return a repository
68+
*/
69+
@Fluent
70+
SchemaRepository preloadMetaSchema(FileSystem fs);
71+
72+
/**
73+
* Preloads the repository with the meta schemas for the related draft version.
74+
*
75+
* @param fs The Vert.x file system to load the related schema meta files from classpath
76+
* @param draft The draft version of the meta files to load
77+
* @return a repository
78+
*/
79+
@Fluent
80+
SchemaRepository preloadMetaSchema(FileSystem fs, Draft draft);
81+
5982
/**
6083
* A new validator instance using this repository options.
6184
*
@@ -75,7 +98,7 @@ static SchemaRepository create(JsonSchemaOptions options) {
7598
/**
7699
* A new validator instance overriding this repository options.
77100
*
78-
* @param schema the start validation schema
101+
* @param schema the start validation schema
79102
* @param options the options to be using on the validator instance
80103
* @return the validator
81104
*/
@@ -84,15 +107,15 @@ static SchemaRepository create(JsonSchemaOptions options) {
84107
/**
85108
* A new validator instance overriding this repository options.
86109
*
87-
* @param ref the start validation reference in JSON pointer format
110+
* @param ref the start validation reference in JSON pointer format
88111
* @param options the options to be using on the validator instance
89112
* @return the validator
90113
*/
91114
Validator validator(String ref, JsonSchemaOptions options);
92115

93116
/**
94117
* Tries to resolve all internal and repository local references. External references are not resolved.
95-
*
118+
* <p>
96119
* The result is an object where all references have been resolved. Resolution of references is shallow. This
97120
* should normally not be a problem for this use case.
98121
*
@@ -103,7 +126,7 @@ static SchemaRepository create(JsonSchemaOptions options) {
103126

104127
/**
105128
* Tries to resolve all internal and repository local references. External references are not resolved.
106-
*
129+
* <p>
107130
* The result is an object where all references have been resolved. Resolution of references is shallow. This
108131
* should normally not be a problem for this use case.
109132
*
@@ -115,6 +138,7 @@ static SchemaRepository create(JsonSchemaOptions options) {
115138

116139
/**
117140
* Look up a schema using a JSON pointer notation
141+
*
118142
* @param pointer the JSON pointer
119143
* @return the schema
120144
*/

src/main/java/io/vertx/json/schema/impl/SchemaRepositoryImpl.java

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.vertx.json.schema.impl;
22

3+
import io.vertx.core.file.FileSystem;
34
import io.vertx.core.json.JsonArray;
45
import io.vertx.core.json.JsonObject;
56
import io.vertx.json.schema.*;
@@ -64,6 +65,35 @@ public class SchemaRepositoryImpl implements SchemaRepository {
6465
"else"
6566
);
6667

68+
static final List<String> DRAFT_4_META_FILES = Arrays.asList(
69+
"http://json-schema.org/draft-04/schema"
70+
);
71+
72+
static final List<String> DRAFT_7_META_FILES = Arrays.asList(
73+
"http://json-schema.org/draft-07/schema"
74+
);
75+
76+
static final List<String> DRAFT_201909_META_FILES = Arrays.asList(
77+
"https://json-schema.org/draft/2019-09/schema",
78+
"https://json-schema.org/draft/2019-09/meta/core",
79+
"https://json-schema.org/draft/2019-09/meta/applicator",
80+
"https://json-schema.org/draft/2019-09/meta/validation",
81+
"https://json-schema.org/draft/2019-09/meta/meta-data",
82+
"https://json-schema.org/draft/2019-09/meta/format",
83+
"https://json-schema.org/draft/2019-09/meta/content"
84+
);
85+
86+
static final List<String> DRAFT_202012_META_FILES = Arrays.asList(
87+
"https://json-schema.org/draft/2020-12/schema",
88+
"https://json-schema.org/draft/2020-12/meta/core",
89+
"https://json-schema.org/draft/2020-12/meta/applicator",
90+
"https://json-schema.org/draft/2020-12/meta/validation",
91+
"https://json-schema.org/draft/2020-12/meta/meta-data",
92+
"https://json-schema.org/draft/2020-12/meta/format-annotation",
93+
"https://json-schema.org/draft/2020-12/meta/content",
94+
"https://json-schema.org/draft/2020-12/meta/unevaluated"
95+
);
96+
6797
private final Map<String, JsonSchema> lookup = new HashMap<>();
6898

6999
private final JsonSchemaOptions options;
@@ -88,6 +118,43 @@ public SchemaRepository dereference(String uri, JsonSchema schema) throws Schema
88118
return this;
89119
}
90120

121+
@Override
122+
public SchemaRepository preloadMetaSchema(FileSystem fs) {
123+
if (options.getDraft() == null) {
124+
throw new IllegalStateException("No draft version is defined in the options of the repository");
125+
}
126+
return preloadMetaSchema(fs, options.getDraft());
127+
}
128+
129+
@Override
130+
public SchemaRepository preloadMetaSchema(FileSystem fs, Draft draft) {
131+
List<String> metaSchemaIds;
132+
switch (draft) {
133+
case DRAFT4:
134+
metaSchemaIds = DRAFT_4_META_FILES;
135+
break;
136+
case DRAFT7:
137+
metaSchemaIds = DRAFT_7_META_FILES;
138+
break;
139+
case DRAFT201909:
140+
metaSchemaIds = DRAFT_201909_META_FILES;
141+
break;
142+
case DRAFT202012:
143+
metaSchemaIds = DRAFT_202012_META_FILES;
144+
break;
145+
default:
146+
throw new IllegalStateException();
147+
}
148+
149+
for (String id : metaSchemaIds) {
150+
// read files from classpath
151+
JsonSchema schema = JsonSchema.of(fs.readFileBlocking(id.substring(id.indexOf("://") + 3)).toJsonObject());
152+
// try to extract the '$id' from the schema itself, fallback to old field 'id' and if not present to the given url
153+
dereference(schema.get("$id", schema.get("id", id)), schema);
154+
}
155+
return this;
156+
}
157+
91158
@Override
92159
public Validator validator(JsonSchema schema) {
93160
return new SchemaValidatorImpl(schema, options, Collections.unmodifiableMap(lookup));
@@ -97,6 +164,9 @@ public Validator validator(JsonSchema schema) {
97164
public Validator validator(String ref) {
98165
// resolve the pointer to an absolute path
99166
final URL url = new URL(ref, baseUri);
167+
if ("".equals(url.fragment())) {
168+
url.anchor(""); // normalize hash https://url.spec.whatwg.org/#dom-url-hash
169+
}
100170
final String uri = url.href();
101171
if (lookup.containsKey(uri)) {
102172
return new SchemaValidatorImpl(uri, options, Collections.unmodifiableMap(lookup));
@@ -131,6 +201,9 @@ public Validator validator(String ref, JsonSchemaOptions options) {
131201
// resolve the pointer to an absolute path
132202
final URL url = new URL(ref, baseUri);
133203
final String uri = url.href();
204+
if ("".equals(url.fragment())) {
205+
url.anchor(""); // normalize hash https://url.spec.whatwg.org/#dom-url-hash
206+
}
134207
if (lookup.containsKey(uri)) {
135208
return new SchemaValidatorImpl(uri, config, Collections.unmodifiableMap(lookup));
136209
}
@@ -151,6 +224,9 @@ public JsonObject resolve(JsonSchema schema) {
151224
public JsonObject resolve(String ref) {
152225
// resolve the pointer to an absolute path
153226
final URL url = new URL(ref, baseUri);
227+
if ("".equals(url.fragment())) {
228+
url.anchor(""); // normalize hash https://url.spec.whatwg.org/#dom-url-hash
229+
}
154230
final String uri = url.href();
155231
if (lookup.containsKey(uri)) {
156232
return Ref.resolve(Collections.unmodifiableMap(lookup), baseUri, lookup.get(uri));
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package io.vertx.json.schema;
2+
3+
import io.vertx.core.Vertx;
4+
import io.vertx.core.buffer.Buffer;
5+
import io.vertx.junit5.VertxExtension;
6+
import org.junit.jupiter.api.Assertions;
7+
import org.junit.jupiter.api.extension.ExtendWith;
8+
import org.junit.jupiter.params.ParameterizedTest;
9+
import org.junit.jupiter.params.provider.Arguments;
10+
import org.junit.jupiter.params.provider.MethodSource;
11+
12+
import java.io.IOException;
13+
import java.nio.file.Files;
14+
import java.nio.file.Path;
15+
import java.nio.file.Paths;
16+
import java.util.stream.Stream;
17+
18+
import static io.vertx.json.schema.Draft.DRAFT201909;
19+
import static io.vertx.json.schema.Draft.DRAFT202012;
20+
import static io.vertx.json.schema.Draft.DRAFT4;
21+
import static io.vertx.json.schema.Draft.DRAFT7;
22+
23+
@ExtendWith(VertxExtension.class)
24+
class SchemaDefinitionValidationTest {
25+
26+
private static final Path RESOURCE_PATH = Paths.get("src", "test", "resources", "schema_definition_validation");
27+
28+
private static Stream<Arguments> testSchemaDefinitionValidation() {
29+
return Stream.of(
30+
Arguments.of(DRAFT4, RESOURCE_PATH.resolve("OpenAPI3_0.json")),
31+
Arguments.of(DRAFT7, RESOURCE_PATH.resolve("angular_cli_workspace_schema.json")),
32+
Arguments.of(DRAFT201909, RESOURCE_PATH.resolve("compose_spec.json")),
33+
Arguments.of(DRAFT202012, RESOURCE_PATH.resolve("OpenAPI3_1.json"))
34+
);
35+
}
36+
37+
@ParameterizedTest(name = "{index} test preloadMetaSchema with draft {0}")
38+
@MethodSource
39+
void testSchemaDefinitionValidation(Draft draft, Path schemaPath, Vertx vertx) throws IOException {
40+
JsonSchemaOptions opts = new JsonSchemaOptions().setBaseUri("https://example.org");
41+
SchemaRepository repo = SchemaRepository.create(opts);
42+
repo.preloadMetaSchema(vertx.fileSystem(), draft);
43+
44+
Buffer schemaBuffer = Buffer.buffer(Files.readAllBytes(schemaPath));
45+
JsonSchema schemaToValidate = JsonSchema.of(schemaBuffer.toJsonObject());
46+
47+
OutputUnit res = repo.validator(draft.getIdentifier()).validate(schemaToValidate);
48+
Assertions.assertTrue(res.getValid());
49+
}
50+
}

0 commit comments

Comments
 (0)