Repository: networknt/json-schema-validator
Pull request #646 (2023-02-01 16:07:20)

Add ability to detect spec version optionally
There could be cases when schemas are managed by third-parties, in this case client side logic can either use spec version defined in schema if available or some default one. Having current implementation of `SpecVersionDetector` it's not really convenient to catch exception to fallback to some default value then. Also exceptions are quite heavy mechanism, thus new method is proposed which returns an empty `Optional` value, if the schema tag is not present.

Also small refactoring of `SpecVersionDetector` class is done in order to make it true-utility class:
 - class is marked as `final`
 - private constructor is added


Patch:
diff --git a/src/main/java/com/networknt/schema/SpecVersionDetector.java b/src/main/java/com/networknt/schema/SpecVersionDetector.java
index 1d70841..7be7ac2 100644
--- a/src/main/java/com/networknt/schema/SpecVersionDetector.java
+++ b/src/main/java/com/networknt/schema/SpecVersionDetector.java
@@ -18,49 +18,69 @@ package com.networknt.schema;
 
 import com.fasterxml.jackson.databind.JsonNode;
 
+import java.util.Optional;
+
 /**
  * This class is used to detect schema version
  *
  * @author Subhajitdas298
  * @since 25/06/20
  */
-public class SpecVersionDetector {
+public final class SpecVersionDetector {
 
     private static final String SCHEMA_TAG = "$schema";
 
+    private SpecVersionDetector() {
+        // Prevent instantiation of this utility class
+    }
+
     /**
-     * Detects schema version based on the schema tag
+     * Detects schema version based on the schema tag: if the schema tag is not present, throws
+     * {@link JsonSchemaException} with the corresponding message, otherwise - returns the detected spec version.
      *
-     * @param jsonNode Json Node to read from
-     * @return Spec version
+     * @param jsonNode JSON Node to read from
+     * @return Spec version if present, otherwise throws an exception
      */
     public static SpecVersion.VersionFlag detect(JsonNode jsonNode) {
-        JsonNode schemaTag = jsonNode.get(SCHEMA_TAG);
-        if (schemaTag == null) {
-            throw new JsonSchemaException("'" + SCHEMA_TAG + "' tag is not present");
-        }
+        return detectOptionalVersion(jsonNode).orElseThrow(
+                () -> new JsonSchemaException("'" + SCHEMA_TAG + "' tag is not present")
+        );
+    }
+
+    /**
+     * Detects schema version based on the schema tag: if the schema tag is not present, returns an empty {@link
+     * Optional} value, otherwise - returns the detected spec version wrapped into {@link Optional}.
+     *
+     * @param jsonNode JSON Node to read from
+     * @return Spec version if present, otherwise empty
+     */
+    public static Optional<SpecVersion.VersionFlag> detectOptionalVersion(JsonNode jsonNode) {
+        return Optional.ofNullable(jsonNode.get(SCHEMA_TAG)).map(schemaTag -> {
+
+            final boolean forceHttps = true;
+            final boolean removeEmptyFragmentSuffix = true;
 
-        final boolean forceHttps = true;
-        final boolean removeEmptyFragmentSuffix = true;
+            String schemaTagValue = schemaTag.asText();
+            String schemaUri = JsonSchemaFactory.normalizeMetaSchemaUri(schemaTagValue, forceHttps,
+                    removeEmptyFragmentSuffix);
 
-        String schemaTagValue = schemaTag.asText();
-        String schemaUri = JsonSchemaFactory.normalizeMetaSchemaUri(schemaTagValue, forceHttps, removeEmptyFragmentSuffix);
-        if (schemaUri.equals(JsonMetaSchema.getV4().getUri())) {
-            return SpecVersion.VersionFlag.V4;
-        }
-        if (schemaUri.equals(JsonMetaSchema.getV6().getUri())) {
-            return SpecVersion.VersionFlag.V6;
-        }
-        if (schemaUri.equals(JsonMetaSchema.getV7().getUri())) {
-            return SpecVersion.VersionFlag.V7;
-        }
-        if (schemaUri.equals(JsonMetaSchema.getV201909().getUri())) {
-            return SpecVersion.VersionFlag.V201909;
-        }
-        if (schemaUri.equals(JsonMetaSchema.getV202012().getUri())) {
-            return SpecVersion.VersionFlag.V202012;
-        }
-        throw new JsonSchemaException("'" + schemaTagValue + "' is unrecognizable schema");
+            if (schemaUri.equals(JsonMetaSchema.getV4().getUri())) {
+                return SpecVersion.VersionFlag.V4;
+            }
+            if (schemaUri.equals(JsonMetaSchema.getV6().getUri())) {
+                return SpecVersion.VersionFlag.V6;
+            }
+            if (schemaUri.equals(JsonMetaSchema.getV7().getUri())) {
+                return SpecVersion.VersionFlag.V7;
+            }
+            if (schemaUri.equals(JsonMetaSchema.getV201909().getUri())) {
+                return SpecVersion.VersionFlag.V201909;
+            }
+            if (schemaUri.equals(JsonMetaSchema.getV202012().getUri())) {
+                return SpecVersion.VersionFlag.V202012;
+            }
+            throw new JsonSchemaException("'" + schemaTagValue + "' is unrecognizable schema");
+        });
     }
 
 }


Test patch:
diff --git a/src/test/java/com/networknt/schema/SpecVersionDetectorTest.java b/src/test/java/com/networknt/schema/SpecVersionDetectorTest.java
index 6433709..13a689f 100644
--- a/src/test/java/com/networknt/schema/SpecVersionDetectorTest.java
+++ b/src/test/java/com/networknt/schema/SpecVersionDetectorTest.java
@@ -2,11 +2,13 @@ package com.networknt.schema;
 
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.CsvSource;
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.Optional;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -42,4 +44,13 @@ class SpecVersionDetectorTest {
         JsonSchemaException exception = assertThrows(JsonSchemaException.class, () -> SpecVersionDetector.detect(node));
         assertEquals(expectedError, exception.getMessage());
     }
-}
\ No newline at end of file
+
+    @Test
+    void detectOptionalSpecVersion() throws IOException {
+        InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream(
+                "data/schemaTagMissing.json");
+        JsonNode node = mapper.readTree(in);
+        Optional<SpecVersion.VersionFlag> flag = SpecVersionDetector.detectOptionalVersion(node);
+        assertEquals(Optional.empty(), flag);
+    }
+}