Skip to content

Commit 82ca8e5

Browse files
issue668: handle references to yaml sub-schemas (#670)
1 parent 5b03a98 commit 82ca8e5

File tree

8 files changed

+99
-8
lines changed

8 files changed

+99
-8
lines changed

pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,11 @@
7878
<artifactId>jackson-databind</artifactId>
7979
<version>${version.jackson}</version>
8080
</dependency>
81+
<dependency>
82+
<groupId>com.fasterxml.jackson.dataformat</groupId>
83+
<artifactId>jackson-dataformat-yaml</artifactId>
84+
<version>${version.jackson}</version>
85+
</dependency>
8186
<dependency>
8287
<groupId>org.slf4j</groupId>
8388
<artifactId>slf4j-api</artifactId>

src/main/java/com/networknt/schema/JsonSchemaFactory.java

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import com.fasterxml.jackson.databind.JsonNode;
2020
import com.fasterxml.jackson.databind.ObjectMapper;
21+
import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;
2122
import com.networknt.schema.uri.*;
2223
import com.networknt.schema.urn.URNFactory;
2324
import org.slf4j.Logger;
@@ -41,6 +42,7 @@ public class JsonSchemaFactory {
4142

4243
public static class Builder {
4344
private ObjectMapper objectMapper = new ObjectMapper();
45+
private YAMLMapper yamlMapper = new YAMLMapper();
4446
private String defaultMetaSchemaURI;
4547
private final Map<String, URIFactory> uriFactoryMap = new HashMap<String, URIFactory>();
4648
private final Map<String, URIFetcher> uriFetcherMap = new HashMap<String, URIFetcher>();
@@ -77,6 +79,11 @@ public Builder objectMapper(final ObjectMapper objectMapper) {
7779
return this;
7880
}
7981

82+
public Builder yamlMapper(final YAMLMapper yamlMapper) {
83+
this.yamlMapper = yamlMapper;
84+
return this;
85+
}
86+
8087
public Builder defaultMetaSchemaURI(final String defaultMetaSchemaURI) {
8188
this.defaultMetaSchemaURI = defaultMetaSchemaURI;
8289
return this;
@@ -154,6 +161,7 @@ public JsonSchemaFactory build() {
154161
// create builtin keywords with (custom) formats.
155162
return new JsonSchemaFactory(
156163
objectMapper == null ? new ObjectMapper() : objectMapper,
164+
yamlMapper == null ? new YAMLMapper(): yamlMapper,
157165
defaultMetaSchemaURI,
158166
new URISchemeFactory(uriFactoryMap),
159167
new URISchemeFetcher(uriFetcherMap),
@@ -166,7 +174,8 @@ public JsonSchemaFactory build() {
166174
}
167175
}
168176

169-
private final ObjectMapper mapper;
177+
private final ObjectMapper jsonMapper;
178+
private final YAMLMapper yamlMapper;
170179
private final String defaultMetaSchemaURI;
171180
private final URISchemeFactory uriFactory;
172181
private final URISchemeFetcher uriFetcher;
@@ -179,7 +188,8 @@ public JsonSchemaFactory build() {
179188

180189

181190
private JsonSchemaFactory(
182-
final ObjectMapper mapper,
191+
final ObjectMapper jsonMapper,
192+
final YAMLMapper yamlMapper,
183193
final String defaultMetaSchemaURI,
184194
final URISchemeFactory uriFactory,
185195
final URISchemeFetcher uriFetcher,
@@ -188,8 +198,10 @@ private JsonSchemaFactory(
188198
final Map<String, String> uriMap,
189199
final boolean forceHttps,
190200
final boolean removeEmptyFragmentSuffix) {
191-
if (mapper == null) {
201+
if (jsonMapper == null) {
192202
throw new IllegalArgumentException("ObjectMapper must not be null");
203+
} else if (yamlMapper == null) {
204+
throw new IllegalArgumentException("YAMLMapper must not be null");
193205
} else if (defaultMetaSchemaURI == null || defaultMetaSchemaURI.trim().isEmpty()) {
194206
throw new IllegalArgumentException("defaultMetaSchemaURI must not be null or empty");
195207
} else if (uriFactory == null) {
@@ -203,7 +215,8 @@ private JsonSchemaFactory(
203215
} else if (uriMap == null) {
204216
throw new IllegalArgumentException("URL Mappings must not be null");
205217
}
206-
this.mapper = mapper;
218+
this.jsonMapper = jsonMapper;
219+
this.yamlMapper = yamlMapper;
207220
this.defaultMetaSchemaURI = defaultMetaSchemaURI;
208221
this.uriFactory = uriFactory;
209222
this.uriFetcher = uriFetcher;
@@ -274,7 +287,8 @@ public static Builder builder(final JsonSchemaFactory blueprint) {
274287
Builder builder = builder()
275288
.addMetaSchemas(blueprint.jsonMetaSchemas.values())
276289
.defaultMetaSchemaURI(blueprint.defaultMetaSchemaURI)
277-
.objectMapper(blueprint.mapper)
290+
.objectMapper(blueprint.jsonMapper)
291+
.yamlMapper(blueprint.yamlMapper)
278292
.addUriMappings(blueprint.uriMap);
279293

280294
for (Map.Entry<String, URIFactory> entry : blueprint.uriFactory.getURIFactories().entrySet()) {
@@ -319,7 +333,7 @@ public URIFactory getUriFactory() {
319333

320334
public JsonSchema getSchema(final String schema, final SchemaValidatorsConfig config) {
321335
try {
322-
final JsonNode schemaNode = mapper.readTree(schema);
336+
final JsonNode schemaNode = jsonMapper.readTree(schema);
323337
return newJsonSchema(null, schemaNode, config);
324338
} catch (IOException ioe) {
325339
logger.error("Failed to load json schema!", ioe);
@@ -333,7 +347,7 @@ public JsonSchema getSchema(final String schema) {
333347

334348
public JsonSchema getSchema(final InputStream schemaStream, final SchemaValidatorsConfig config) {
335349
try {
336-
final JsonNode schemaNode = mapper.readTree(schemaStream);
350+
final JsonNode schemaNode = jsonMapper.readTree(schemaStream);
337351
return newJsonSchema(null, schemaNode, config);
338352
} catch (IOException ioe) {
339353
logger.error("Failed to load json schema!", ioe);
@@ -370,7 +384,14 @@ public JsonSchema getSchema(final URI schemaUri, final SchemaValidatorsConfig co
370384

371385
try {
372386
inputStream = this.uriFetcher.fetch(mappedUri);
373-
final JsonNode schemaNode = mapper.readTree(inputStream);
387+
388+
final JsonNode schemaNode;
389+
if (isYaml(mappedUri)) {
390+
schemaNode = yamlMapper.readTree(inputStream);
391+
} else {
392+
schemaNode = jsonMapper.readTree(inputStream);
393+
}
394+
374395
final JsonMetaSchema jsonMetaSchema = findMetaSchemaForSchema(schemaNode);
375396

376397
JsonSchema jsonSchema;
@@ -428,6 +449,19 @@ private boolean idMatchesSourceUri(final JsonMetaSchema metaSchema, final JsonNo
428449
return result;
429450
}
430451

452+
private boolean isYaml(final URI schemaUri) {
453+
final String schemeSpecificPart = schemaUri.getSchemeSpecificPart();
454+
final int idx = schemeSpecificPart.lastIndexOf('.');
455+
456+
if (idx == -1) {
457+
// no extension; assume json
458+
return false;
459+
}
460+
461+
final String extension = schemeSpecificPart.substring(idx);
462+
return (".yml".equals(extension) || ".yaml".equals(extension));
463+
}
464+
431465
static protected String normalizeMetaSchemaUri(String u, boolean forceHttps, boolean removeEmptyFragmentSuffix) {
432466
try {
433467
URI uri = new URI(u);
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package com.networknt.schema;
2+
3+
import com.fasterxml.jackson.databind.JsonNode;
4+
import com.fasterxml.jackson.databind.ObjectMapper;
5+
import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;
6+
import org.hamcrest.MatcherAssert;
7+
import org.hamcrest.Matchers;
8+
import org.junit.jupiter.api.Test;
9+
10+
import java.io.InputStream;
11+
12+
class Issue668Test {
13+
protected JsonSchema getJsonSchemaFromStreamContent(InputStream schemaContent) throws Exception {
14+
JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7);
15+
YAMLMapper mapper = new YAMLMapper();
16+
JsonNode node = mapper.readTree(schemaContent);
17+
return factory.getSchema(node);
18+
}
19+
20+
protected JsonNode getJsonNodeFromStreamContent(InputStream content) throws Exception {
21+
ObjectMapper mapper = new ObjectMapper();
22+
return mapper.readTree(content);
23+
}
24+
25+
@Test
26+
void shouldHandleReferencesToYaml() throws Exception {
27+
String schemaPath = "/schema/issue668.yml";
28+
String dataPath = "/data/issue668.json";
29+
InputStream schemaInputStream = getClass().getResourceAsStream(schemaPath);
30+
JsonSchema schema = getJsonSchemaFromStreamContent(schemaInputStream);
31+
InputStream dataInputStream = getClass().getResourceAsStream(dataPath);
32+
JsonNode node = getJsonNodeFromStreamContent(dataInputStream);
33+
MatcherAssert.assertThat(schema.validate(node), Matchers.empty());
34+
}
35+
}

src/test/resources/data/issue668.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"sub1": {},
3+
"sub2": {},
4+
"sub3": {}
5+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
type: object
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
type: object
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"type": "object"}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
$id: resource:/schema/issue668
2+
type: object
3+
properties:
4+
sub1:
5+
$ref: issue668-sub1.yml
6+
sub2:
7+
$ref: issue668-sub2.yaml
8+
sub3:
9+
$ref: issue668-sub3

0 commit comments

Comments
 (0)