From 4d2faba23d5be5fd00a46c0498a1737f38de25e9 Mon Sep 17 00:00:00 2001 From: "Stacy W. Smith" Date: Tue, 27 Jun 2023 11:06:02 -0600 Subject: [PATCH] Always normalize uri keys of JsonSchemaFactory.jsonMetaSchemas on both read and write. Previously, when writing to the JsonSchemaFactory.jsonMetaSchemas hash map, the uri key was not normalize, but upon reading from the hash map, the key was normalized. In addition, if a key is not found in this hash map, the standard meta schema for the uri is loaded. The result was that an attempt to augment a standard meta schema with custom formatters did not work because the standard meta schema was always used instead of the custom augmented meta schema. This change normalizes the uri keys of JsonSchemaFactory.jsonMetaSchemas on both read and write. Normalizing the uri keys on both reads and writes also eliminates the need for the forceHttps and removeEmptyFragmentSuffix arguments to the normalizeMetaSchemaUri() method. --- .../networknt/schema/JsonSchemaFactory.java | 41 ++++++------- .../networknt/schema/SpecVersionDetector.java | 6 +- .../com/networknt/schema/Issue832Test.java | 59 +++++++++++++++++++ .../schema/UnknownMetaSchemaTest.java | 44 ++------------ src/test/resources/data/issue832.json | 4 ++ src/test/resources/schema/issue832-v7.json | 16 +++++ 6 files changed, 101 insertions(+), 69 deletions(-) create mode 100644 src/test/java/com/networknt/schema/Issue832Test.java create mode 100644 src/test/resources/data/issue832.json create mode 100644 src/test/resources/schema/issue832-v7.json diff --git a/src/main/java/com/networknt/schema/JsonSchemaFactory.java b/src/main/java/com/networknt/schema/JsonSchemaFactory.java index f01b7209d..0f3a5ecd1 100644 --- a/src/main/java/com/networknt/schema/JsonSchemaFactory.java +++ b/src/main/java/com/networknt/schema/JsonSchemaFactory.java @@ -50,8 +50,6 @@ public static class Builder { private URNFactory urnFactory; private final Map jsonMetaSchemas = new HashMap(); private final Map uriMap = new HashMap(); - private boolean forceHttps = true; - private boolean removeEmptyFragmentSuffix = true; private boolean enableUriSchemaCache = true; private final CompositeURITranslator uriTranslators = new CompositeURITranslator(); @@ -129,13 +127,13 @@ public Builder uriFetcher(final URIFetcher uriFetcher, final Iterable sc } public Builder addMetaSchema(final JsonMetaSchema jsonMetaSchema) { - this.jsonMetaSchemas.put(jsonMetaSchema.getUri(), jsonMetaSchema); + this.jsonMetaSchemas.put(normalizeMetaSchemaUri(jsonMetaSchema.getUri()) , jsonMetaSchema); return this; } public Builder addMetaSchemas(final Collection jsonMetaSchemas) { for (JsonMetaSchema jsonMetaSchema : jsonMetaSchemas) { - this.jsonMetaSchemas.put(jsonMetaSchema.getUri(), jsonMetaSchema); + addMetaSchema(jsonMetaSchema); } return this; } @@ -163,13 +161,21 @@ public Builder addUrnFactory(URNFactory urnFactory) { return this; } + /** + * @deprecated No longer necessary. + * @param forceHttps ignored. + * @return this builder. + */ public Builder forceHttps(boolean forceHttps) { - this.forceHttps = forceHttps; return this; } + /** + * @deprecated No longer necessary. + * @param removeEmptyFragmentSuffix ignored. + * @return this builder. + */ public Builder removeEmptyFragmentSuffix(boolean removeEmptyFragmentSuffix) { - this.removeEmptyFragmentSuffix = removeEmptyFragmentSuffix; return this; } @@ -189,8 +195,6 @@ public JsonSchemaFactory build() { urnFactory, jsonMetaSchemas, uriMap, - forceHttps, - removeEmptyFragmentSuffix, enableUriSchemaCache, uriTranslators ); @@ -207,8 +211,6 @@ public JsonSchemaFactory build() { private final Map jsonMetaSchemas; private final Map uriMap; private final ConcurrentMap uriSchemaCache = new ConcurrentHashMap(); - private final boolean forceHttps; - private final boolean removeEmptyFragmentSuffix; private final boolean enableUriSchemaCache; @@ -221,8 +223,6 @@ private JsonSchemaFactory( final URNFactory urnFactory, final Map jsonMetaSchemas, final Map uriMap, - final boolean forceHttps, - final boolean removeEmptyFragmentSuffix, final boolean enableUriSchemaCache, final CompositeURITranslator uriTranslators) { if (jsonMapper == null) { @@ -237,7 +237,7 @@ private JsonSchemaFactory( throw new IllegalArgumentException("URIFetcher must not be null"); } else if (jsonMetaSchemas == null || jsonMetaSchemas.isEmpty()) { throw new IllegalArgumentException("Json Meta Schemas must not be null or empty"); - } else if (jsonMetaSchemas.get(defaultMetaSchemaURI) == null) { + } else if (jsonMetaSchemas.get(normalizeMetaSchemaUri(defaultMetaSchemaURI)) == null) { throw new IllegalArgumentException("Meta Schema for default Meta Schema URI must be provided"); } else if (uriMap == null) { throw new IllegalArgumentException("URL Mappings must not be null"); @@ -252,8 +252,6 @@ private JsonSchemaFactory( this.urnFactory = urnFactory; this.jsonMetaSchemas = jsonMetaSchemas; this.uriMap = uriMap; - this.forceHttps = forceHttps; - this.removeEmptyFragmentSuffix = removeEmptyFragmentSuffix; this.enableUriSchemaCache = enableUriSchemaCache; this.uriTranslators = uriTranslators; } @@ -348,7 +346,7 @@ private JsonMetaSchema findMetaSchemaForSchema(final JsonNode schemaNode) { if (uriNode != null && !uriNode.isNull() && !uriNode.isTextual()) { throw new JsonSchemaException("Unknown MetaSchema: " + uriNode.toString()); } - final String uri = uriNode == null || uriNode.isNull() ? defaultMetaSchemaURI : normalizeMetaSchemaUri(uriNode.textValue(), forceHttps, removeEmptyFragmentSuffix); + final String uri = uriNode == null || uriNode.isNull() ? defaultMetaSchemaURI : normalizeMetaSchemaUri(uriNode.textValue()); final JsonMetaSchema jsonMetaSchema = jsonMetaSchemas.computeIfAbsent(uri, this::fromId); return jsonMetaSchema; } @@ -504,17 +502,12 @@ private boolean isYaml(final URI schemaUri) { return (".yml".equals(extension) || ".yaml".equals(extension)); } - static protected String normalizeMetaSchemaUri(String u, boolean forceHttps, boolean removeEmptyFragmentSuffix) { + static protected String normalizeMetaSchemaUri(String u) { try { URI uri = new URI(u); - String scheme = forceHttps ? "https" : uri.getScheme(); - URI newUri = new URI(scheme, uri.getUserInfo(), uri.getHost(), uri.getPort(), uri.getPath(), null, null); + URI newUri = new URI("https", uri.getUserInfo(), uri.getHost(), uri.getPort(), uri.getPath(), null, null); - if (!removeEmptyFragmentSuffix && u.endsWith("#")) { - return newUri + "#"; - } else { - return newUri.toString(); - } + return newUri.toString(); } catch (URISyntaxException e) { throw new JsonSchemaException("Wrong MetaSchema URI: " + u); } diff --git a/src/main/java/com/networknt/schema/SpecVersionDetector.java b/src/main/java/com/networknt/schema/SpecVersionDetector.java index 452703ac4..63bcb1083 100644 --- a/src/main/java/com/networknt/schema/SpecVersionDetector.java +++ b/src/main/java/com/networknt/schema/SpecVersionDetector.java @@ -58,12 +58,8 @@ public static VersionFlag detect(JsonNode jsonNode) { public static Optional detectOptionalVersion(JsonNode jsonNode) { return Optional.ofNullable(jsonNode.get(SCHEMA_TAG)).map(schemaTag -> { - final boolean forceHttps = true; - final boolean removeEmptyFragmentSuffix = true; - String schemaTagValue = schemaTag.asText(); - String schemaUri = JsonSchemaFactory.normalizeMetaSchemaUri(schemaTagValue, forceHttps, - removeEmptyFragmentSuffix); + String schemaUri = JsonSchemaFactory.normalizeMetaSchemaUri(schemaTagValue); return VersionFlag.fromId(schemaUri) .orElseThrow(() -> new JsonSchemaException("'" + schemaTagValue + "' is unrecognizable schema")); diff --git a/src/test/java/com/networknt/schema/Issue832Test.java b/src/test/java/com/networknt/schema/Issue832Test.java new file mode 100644 index 000000000..1235ddffe --- /dev/null +++ b/src/test/java/com/networknt/schema/Issue832Test.java @@ -0,0 +1,59 @@ +package com.networknt.schema; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.networknt.schema.format.AbstractFormat; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +public class Issue832Test { + private class NoMatchFormat extends AbstractFormat { + public NoMatchFormat() { + super("no_match", "always fail match"); + } + + @Override + public boolean matches(String value) { + return false; + } + } + + private JsonSchemaFactory buildV7PlusNoFormatSchemaFactory() { + List formats; + formats = new ArrayList<>(); + formats.add(new NoMatchFormat()); + + JsonMetaSchema jsonMetaSchema = JsonMetaSchema.builder( + JsonMetaSchema.getV7().getUri(), + JsonMetaSchema.getV7()) + .addFormats(formats) + .build(); + return new JsonSchemaFactory.Builder().defaultMetaSchemaURI(jsonMetaSchema.getUri()).addMetaSchema(jsonMetaSchema).build(); + } + + protected JsonNode getJsonNodeFromStreamContent(InputStream content) throws IOException { + ObjectMapper mapper = new ObjectMapper(); + return mapper.readTree(content); + } + + @Test + public void testV7WithNonMatchingCustomFormat() throws IOException { + String schemaPath = "/schema/issue832-v7.json"; + String dataPath = "/data/issue832.json"; + InputStream schemaInputStream = getClass().getResourceAsStream(schemaPath); + JsonSchemaFactory factory = buildV7PlusNoFormatSchemaFactory(); + JsonSchema schema = factory.getSchema(schemaInputStream); + InputStream dataInputStream = getClass().getResourceAsStream(dataPath); + JsonNode node = getJsonNodeFromStreamContent(dataInputStream); + Set errors = schema.validate(node); + // Both the custom no_match format and the standard email format should fail. + // This ensures that both the standard and custom formatters have been invoked. + Assertions.assertEquals(2, errors.size()); + } +} diff --git a/src/test/java/com/networknt/schema/UnknownMetaSchemaTest.java b/src/test/java/com/networknt/schema/UnknownMetaSchemaTest.java index 601119d84..7e6a6d49d 100644 --- a/src/test/java/com/networknt/schema/UnknownMetaSchemaTest.java +++ b/src/test/java/com/networknt/schema/UnknownMetaSchemaTest.java @@ -59,8 +59,6 @@ public void testSchema3() throws IOException { @Test public void testNormalize() throws JsonSchemaException { - final boolean forceHttps = true; - final boolean removeEmptyFragmentSuffix = true; String uri01 = "http://json-schema.org/draft-07/schema"; String uri02 = "http://json-schema.org/draft-07/schema#"; @@ -68,44 +66,10 @@ public void testNormalize() throws JsonSchemaException { String uri04 = "http://json-schema.org/draft-07/schema?key=value&key2=value2"; String expected = "https://json-schema.org/draft-07/schema"; - Assertions.assertEquals(expected, JsonSchemaFactory.normalizeMetaSchemaUri(uri01, forceHttps, removeEmptyFragmentSuffix)); - Assertions.assertEquals(expected, JsonSchemaFactory.normalizeMetaSchemaUri(uri02, forceHttps, removeEmptyFragmentSuffix)); - Assertions.assertEquals(expected, JsonSchemaFactory.normalizeMetaSchemaUri(uri03, forceHttps, removeEmptyFragmentSuffix)); - Assertions.assertEquals(expected, JsonSchemaFactory.normalizeMetaSchemaUri(uri04, forceHttps, removeEmptyFragmentSuffix)); - - } - - @Test - public void testNormalizeForceHttpsDisabled() throws JsonSchemaException { - final boolean forceHttps = false; - final boolean removeEmptyFragmentSuffix = true; - - String uri01 = "http://json-schema.org/draft-07/schema"; - String uri02 = "http://json-schema.org/draft-07/schema#"; - String uri03 = "http://json-schema.org/draft-07/schema?key=value"; - String uri04 = "http://json-schema.org/draft-07/schema?key=value&key2=value2"; - String expected = "http://json-schema.org/draft-07/schema"; - - Assertions.assertEquals(expected, JsonSchemaFactory.normalizeMetaSchemaUri(uri01, forceHttps, removeEmptyFragmentSuffix)); - Assertions.assertEquals(expected, JsonSchemaFactory.normalizeMetaSchemaUri(uri02, forceHttps, removeEmptyFragmentSuffix)); - Assertions.assertEquals(expected, JsonSchemaFactory.normalizeMetaSchemaUri(uri03, forceHttps, removeEmptyFragmentSuffix)); - Assertions.assertEquals(expected, JsonSchemaFactory.normalizeMetaSchemaUri(uri04, forceHttps, removeEmptyFragmentSuffix)); - - } - - @Test - public void testNormalizeRemovingEmptyFragmentSuffixDisabled() throws JsonSchemaException { - final boolean forceHttps = true; - final boolean removeEmptyFragmentSuffix = false; - - String uri01 = "http://json-schema.org/draft-07/schema#"; - String uri02 = "http://json-schema.org/draft-07/schema?key=value#"; - String uri03 = "http://json-schema.org/draft-07/schema?key=value&key2=value2#"; - String expected = "https://json-schema.org/draft-07/schema#"; - - Assertions.assertEquals(expected, JsonSchemaFactory.normalizeMetaSchemaUri(uri01, forceHttps, removeEmptyFragmentSuffix)); - Assertions.assertEquals(expected, JsonSchemaFactory.normalizeMetaSchemaUri(uri02, forceHttps, removeEmptyFragmentSuffix)); - Assertions.assertEquals(expected, JsonSchemaFactory.normalizeMetaSchemaUri(uri03, forceHttps, removeEmptyFragmentSuffix)); + Assertions.assertEquals(expected, JsonSchemaFactory.normalizeMetaSchemaUri(uri01)); + Assertions.assertEquals(expected, JsonSchemaFactory.normalizeMetaSchemaUri(uri02)); + Assertions.assertEquals(expected, JsonSchemaFactory.normalizeMetaSchemaUri(uri03)); + Assertions.assertEquals(expected, JsonSchemaFactory.normalizeMetaSchemaUri(uri04)); } } diff --git a/src/test/resources/data/issue832.json b/src/test/resources/data/issue832.json new file mode 100644 index 000000000..c84fe2332 --- /dev/null +++ b/src/test/resources/data/issue832.json @@ -0,0 +1,4 @@ +{ + "foo": "does not match", + "contact": "not an email address" +} diff --git a/src/test/resources/schema/issue832-v7.json b/src/test/resources/schema/issue832-v7.json new file mode 100644 index 000000000..94044736b --- /dev/null +++ b/src/test/resources/schema/issue832-v7.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "properties": { + "foo": { + "type": "string", + "format": "no_match" + }, + "contact": { + "type": "string", + "format": "email" + } + }, + "required": ["foo", "contact"], + "type": "object" +}