Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add URL mappings #125

Merged
merged 2 commits into from
Apr 1, 2019
Merged
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
24 changes: 20 additions & 4 deletions src/main/java/com/networknt/schema/JsonSchemaFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public static class Builder {
private URLFetcher urlFetcher;
private String defaultMetaSchemaURI;
private Map<String, JsonMetaSchema> jsonMetaSchemas = new HashMap<String, JsonMetaSchema>();
private Map<URL, URL> urlMap = new HashMap<URL, URL>();

public Builder objectMapper(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
Expand Down Expand Up @@ -69,13 +70,19 @@ public Builder addMetaSchemas(Collection<? extends JsonMetaSchema> jsonMetaSchem
return this;
}

public Builder addUrlMappings(Map<URL, URL> map) {
this.urlMap.putAll(map);
return this;
}

public JsonSchemaFactory build() {
// create builtin keywords with (custom) formats.
return new JsonSchemaFactory(
objectMapper == null ? new ObjectMapper() : objectMapper,
urlFetcher == null ? new StandardURLFetcher(): urlFetcher,
defaultMetaSchemaURI,
jsonMetaSchemas
jsonMetaSchemas,
urlMap
);
}
}
Expand All @@ -84,8 +91,9 @@ public JsonSchemaFactory build() {
private final URLFetcher urlFetcher;
private final String defaultMetaSchemaURI;
private final Map<String, JsonMetaSchema> jsonMetaSchemas;
private final Map<URL, URL> urlMap;

private JsonSchemaFactory(ObjectMapper mapper, URLFetcher urlFetcher, String defaultMetaSchemaURI, Map<String, JsonMetaSchema> jsonMetaSchemas) {
private JsonSchemaFactory(ObjectMapper mapper, URLFetcher urlFetcher, String defaultMetaSchemaURI, Map<String, JsonMetaSchema> jsonMetaSchemas, Map<URL, URL> urlMap) {
if (mapper == null) {
throw new IllegalArgumentException("ObjectMapper must not be null");
}
Expand All @@ -101,10 +109,14 @@ private JsonSchemaFactory(ObjectMapper mapper, URLFetcher urlFetcher, String def
if (jsonMetaSchemas.get(defaultMetaSchemaURI) == null) {
throw new IllegalArgumentException("Meta Schema for default Meta Schema URI must be provided");
}
if (urlMap == null) {
throw new IllegalArgumentException("URL Mappings must not be null");
}
this.mapper = mapper;
this.defaultMetaSchemaURI = defaultMetaSchemaURI;
this.urlFetcher = urlFetcher;
this.jsonMetaSchemas = jsonMetaSchemas;
this.urlMap = urlMap;
}

/**
Expand Down Expand Up @@ -135,7 +147,8 @@ public static Builder builder(JsonSchemaFactory blueprint) {
.addMetaSchemas(blueprint.jsonMetaSchemas.values())
.urlFetcher(blueprint.urlFetcher)
.defaultMetaSchemaURI(blueprint.defaultMetaSchemaURI)
.objectMapper(blueprint.mapper);
.objectMapper(blueprint.mapper)
.addUrlMappings(blueprint.urlMap);
}

private JsonSchema newJsonSchema(JsonNode schemaNode, SchemaValidatorsConfig config) {
Expand Down Expand Up @@ -191,8 +204,11 @@ public JsonSchema getSchema(InputStream schemaStream) {
public JsonSchema getSchema(URL schemaURL, SchemaValidatorsConfig config) {
try {
InputStream inputStream = null;
Map<URL, URL> map = (config != null) ? config.getUrlMappings() : new HashMap<URL, URL>(urlMap);
map.putAll(urlMap);
URL mappedURL = map.getOrDefault(schemaURL, schemaURL);
try {
inputStream = urlFetcher.fetch(schemaURL);
inputStream = urlFetcher.fetch(mappedURL);
JsonNode schemaNode = mapper.readTree(inputStream);
final JsonMetaSchema jsonMetaSchema = findMetaSchemaForSchema(schemaNode);

Expand Down
20 changes: 20 additions & 0 deletions src/main/java/com/networknt/schema/SchemaValidatorsConfig.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package com.networknt.schema;

import java.util.HashMap;
import java.util.Map;
import java.net.URL;

public class SchemaValidatorsConfig {
/**
* when validate type, if TYPE_LOOSE = true, will try to convert string to different types to match the type defined in schema.
Expand All @@ -18,6 +22,13 @@ public class SchemaValidatorsConfig {
*/
private boolean elementValidationError = false;

/**
* Map of public, normally internet accessible schema URLs to alternate locations; this allows for offline
* validation of schemas that refer to public URLs. This is merged with any mappings the {@link JsonSchemaFactory}
* may have been built with.
*/
private Map<URL, URL> urlMappings = new HashMap<URL, URL>();

public boolean isTypeLoose() {
return typeLoose;
}
Expand All @@ -26,6 +37,14 @@ public void setTypeLoose(boolean typeLoose) {
this.typeLoose = typeLoose;
}

public Map<URL, URL> getUrlMappings() {
return new HashMap<URL, URL>(urlMappings);
}

public void setUrlMappings(Map<URL, URL> urlMappings) {
this.urlMappings = urlMappings;
}

public boolean isMissingNodeAsError() {
return missingNodeAsError;
}
Expand All @@ -48,5 +67,6 @@ public SchemaValidatorsConfig() {

private void loadDefaultConfig() {
this.typeLoose = true;
this.urlMappings = new HashMap<URL, URL>();
}
}
141 changes: 141 additions & 0 deletions src/test/java/com/networknt/schema/UrlMappingTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package com.networknt.schema;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Map;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.networknt.schema.JsonSchemaFactory.Builder;
import com.networknt.schema.url.URLFactory;

import org.junit.Test;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;

public class UrlMappingTest {

private final ObjectMapper mapper = new ObjectMapper();

/**
* Validate that a JSON URL Mapping file containing the URL Mapping schema is
* schema valid.
*
* @throws IOException if unable to parse the mapping file
*/
@Test
public void testBuilderUrlMappingUrl() throws IOException {
URL mappings = URLFactory.toURL("resource:tests/url_mapping/url-mapping.json");
JsonMetaSchema draftV4 = JsonMetaSchema.getDraftV4();
Builder builder = JsonSchemaFactory.builder()
.defaultMetaSchemaURI(draftV4.getUri())
.addMetaSchema(draftV4)
.addUrlMappings(getUrlMappingsFromUrl(mappings));
JsonSchemaFactory instance = builder.build();
JsonSchema schema = instance.getSchema(new URL(
"https://raw.githubusercontent.com/networknt/json-schema-validator/master/src/test/resources/tests/url_mapping/url-mapping.schema.json"));
assertEquals(0, schema.validate(mapper.readTree(mappings)).size());
}

/**
* Validate that local URL is used when attempting to get a schema that is not
* available publicly. Use the URL http://example.com/invalid/schema/url to use
* a public URL that returns a 404 Not Found. The locally mapped schema is a
* valid, but empty schema.
*
* @throws IOException if unable to parse the mapping file
*/
@Test
public void testBuilderExampleMappings() throws IOException {
JsonSchemaFactory instance = JsonSchemaFactory.getInstance();
URL example = new URL("http://example.com/invalid/schema/url");
// first test that attempting to use example URL throws an error
try {
JsonSchema schema = instance.getSchema(example);
schema.validate(mapper.createObjectNode());
fail("Expected exception not thrown");
} catch (JsonSchemaException ex) {
Throwable cause = ex.getCause();
if (!(cause instanceof FileNotFoundException || cause instanceof UnknownHostException)) {
fail("Unexpected cause for JsonSchemaException");
}
// passing, so do nothing
} catch (Exception ex) {
fail("Unexpected exception thrown");
}
URL mappings = URLFactory.toURL("resource:tests/url_mapping/invalid-schema-url.json");
JsonMetaSchema draftV4 = JsonMetaSchema.getDraftV4();
Builder builder = JsonSchemaFactory.builder()
.defaultMetaSchemaURI(draftV4.getUri())
.addMetaSchema(draftV4)
.addUrlMappings(getUrlMappingsFromUrl(mappings));
instance = builder.build();
JsonSchema schema = instance.getSchema(example);
assertEquals(0, schema.validate(mapper.createObjectNode()).size());
}

/**
* Validate that a JSON URL Mapping file containing the URL Mapping schema is
* schema valid.
*
* @throws IOException if unable to parse the mapping file
*/
@Test
public void testValidatorConfigUrlMappingUrl() throws IOException {
JsonSchemaFactory instance = JsonSchemaFactory.getInstance();
URL mappings = URLFactory.toURL("resource:tests/url_mapping/url-mapping.json");
SchemaValidatorsConfig config = new SchemaValidatorsConfig();
config.setUrlMappings(getUrlMappingsFromUrl(mappings));
JsonSchema schema = instance.getSchema(new URL(
"https://raw.githubusercontent.com/networknt/json-schema-validator/master/src/test/resources/tests/url_mapping/url-mapping.schema.json"),
config);
assertEquals(0, schema.validate(mapper.readTree(mappings)).size());
}

/**
* Validate that local URL is used when attempting to get a schema that is not
* available publicly. Use the URL http://example.com/invalid/schema/url to use
* a public URL that returns a 404 Not Found. The locally mapped schema is a
* valid, but empty schema.
*
* @throws IOException if unable to parse the mapping file
*/
@Test
public void testValidatorConfigExampleMappings() throws IOException {
JsonSchemaFactory instance = JsonSchemaFactory.getInstance();
SchemaValidatorsConfig config = new SchemaValidatorsConfig();
URL example = new URL("http://example.com/invalid/schema/url");
// first test that attempting to use example URL throws an error
try {
JsonSchema schema = instance.getSchema(example, config);
schema.validate(mapper.createObjectNode());
fail("Expected exception not thrown");
} catch (JsonSchemaException ex) {
Throwable cause = ex.getCause();
if (!(cause instanceof FileNotFoundException || cause instanceof UnknownHostException)) {
fail("Unexpected cause for JsonSchemaException");
}
// passing, so do nothing
} catch (Exception ex) {
fail("Unexpected exception thrown");
}
URL mappings = URLFactory.toURL("resource:tests/url_mapping/invalid-schema-url.json");
config.setUrlMappings(getUrlMappingsFromUrl(mappings));
JsonSchema schema = instance.getSchema(example, config);
assertEquals(0, schema.validate(mapper.createObjectNode()).size());
}

private Map<URL, URL> getUrlMappingsFromUrl(URL url) throws MalformedURLException, IOException {
HashMap<URL, URL> map = new HashMap<URL, URL>();
for (JsonNode mapping : mapper.readTree(url)) {
map.put(URLFactory.toURL(mapping.get("publicURL").asText()),
URLFactory.toURL(mapping.get("localURL").asText()));
}
return map;
}
}
3 changes: 3 additions & 0 deletions src/test/resources/tests/url_mapping/example-schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"$schema": "http://json-schema.org/draft-04/schema#"
}
10 changes: 10 additions & 0 deletions src/test/resources/tests/url_mapping/invalid-schema-url.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[
{
"publicURL": "http://json-schema.org/draft-04/schema#",
"localURL": "resource:/draftv4.schema.json"
},
{
"publicURL": "http://example.com/invalid/schema/url",
"localURL": "resource:/tests/url_mapping/example-schema.json"
}
]
10 changes: 10 additions & 0 deletions src/test/resources/tests/url_mapping/url-mapping.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[
{
"publicURL": "http://json-schema.org/draft-04/schema#",
"localURL": "resource:/draftv4.schema.json"
},
{
"publicURL": "https://raw.githubusercontent.com/networknt/json-schema-validator/master/src/test/resources/tests/url_mapping/url-mapping.schema.json",
"localURL": "resource:/tests/url_mapping/url-mapping.schema.json"
}
]
25 changes: 25 additions & 0 deletions src/test/resources/tests/url_mapping/url-mapping.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "json-schema-validator-url-mapping",
"type": "array",
"description": "Data portion of message from JMRI to client for type \"node\"",
"items": {
"type": "object",
"uniqueItems": true,
"properties": {
"publicURL": {
"type": "string",
"description": "Public, presumably internet-accessible, URL for schema"
},
"localURL": {
"type": "string",
"description": "Local URL for schema that will be used when a schema references the public URL"
}
},
"additionalProperties": false,
"required": [
"publicURL",
"localURL"
]
}
}