Repository: networknt/json-schema-validator
Pull request #649 (2023-02-01 16:33:27)

Improve logging performance
Explanation of changes: https://www.slf4j.org/faq.html#logging_performance

codecov-commenter: # [Codecov](https://codecov.io/gh/networknt/json-schema-validator/pull/649?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=networknt) Report
> Merging [#649](https://codecov.io/gh/networknt/json-schema-validator/pull/649?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=networknt) (3167d4f) into [master](https://codecov.io/gh/networknt/json-schema-validator/commit/5645d568f6d56c444ebcc72b8f930a7bbd446004?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=networknt) (5645d56) will **increase** coverage by `0.11%`.
> The diff coverage is `41.66%`.

:mega: This organization is not using Codecov’s [GitHub App Integration](https://github.com/apps/codecov). We recommend you install it so Codecov can continue to function properly for your repositories. [Learn more](https://about.codecov.io/blog/codecov-is-updating-its-github-integration/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=networknt)

```diff
@@             Coverage Diff              @@
##             master     #649      +/-   ##
============================================
+ Coverage     70.71%   70.83%   +0.11%     
  Complexity      985      985              
============================================
  Files           100      100              
  Lines          3732     3730       -2     
  Branches        780      779       -1     
============================================
+ Hits           2639     2642       +3     
+ Misses          818      816       -2     
+ Partials        275      272       -3     
```


| [Impacted Files](https://codecov.io/gh/networknt/json-schema-validator/pull/649?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=networknt) | Coverage Δ | |
|---|---|---|
| [...ain/java/com/networknt/schema/FormatValidator.java](https://codecov.io/gh/networknt/json-schema-validator/pull/649?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=networknt#diff-c3JjL21haW4vamF2YS9jb20vbmV0d29ya250L3NjaGVtYS9Gb3JtYXRWYWxpZGF0b3IuamF2YQ==) | `91.30% <0.00%> (ø)` | |
| [...in/java/com/networknt/schema/PatternValidator.java](https://codecov.io/gh/networknt/json-schema-validator/pull/649?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=networknt#diff-c3JjL21haW4vamF2YS9jb20vbmV0d29ya250L3NjaGVtYS9QYXR0ZXJuVmFsaWRhdG9yLmphdmE=) | `34.92% <0.00%> (ø)` | |
| [...main/java/com/networknt/schema/JsonMetaSchema.java](https://codecov.io/gh/networknt/json-schema-validator/pull/649?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=networknt#diff-c3JjL21haW4vamF2YS9jb20vbmV0d29ya250L3NjaGVtYS9Kc29uTWV0YVNjaGVtYS5qYXZh) | `80.70% <33.33%> (ø)` | |
| [...etworknt/schema/AdditionalPropertiesValidator.java](https://codecov.io/gh/networknt/json-schema-validator/pull/649?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=networknt#diff-c3JjL21haW4vamF2YS9jb20vbmV0d29ya250L3NjaGVtYS9BZGRpdGlvbmFsUHJvcGVydGllc1ZhbGlkYXRvci5qYXZh) | `75.00% <100.00%> (+1.19%)` | :arrow_up: |
| [...n/java/com/networknt/schema/BaseJsonValidator.java](https://codecov.io/gh/networknt/json-schema-validator/pull/649?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=networknt#diff-c3JjL21haW4vamF2YS9jb20vbmV0d29ya250L3NjaGVtYS9CYXNlSnNvblZhbGlkYXRvci5qYXZh) | `73.33% <100.00%> (+1.43%)` | :arrow_up: |
| [...n/java/com/networknt/schema/DateTimeValidator.java](https://codecov.io/gh/networknt/json-schema-validator/pull/649?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=networknt#diff-c3JjL21haW4vamF2YS9jb20vbmV0d29ya250L3NjaGVtYS9EYXRlVGltZVZhbGlkYXRvci5qYXZh) | `86.66% <100.00%> (ø)` | |
| [...n/java/com/networknt/schema/JsonSchemaFactory.java](https://codecov.io/gh/networknt/json-schema-validator/pull/649?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=networknt#diff-c3JjL21haW4vamF2YS9jb20vbmV0d29ya250L3NjaGVtYS9Kc29uU2NoZW1hRmFjdG9yeS5qYXZh) | `79.89% <100.00%> (+0.97%)` | :arrow_up: |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=networknt). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=networknt)


Patch:
diff --git a/README.md b/README.md
index 7851036..6663db8 100644
--- a/README.md
+++ b/README.md
@@ -4,12 +4,12 @@
 [Subreddit](https://www.reddit.com/r/lightapi/) |
 [Youtube](https://www.youtube.com/channel/UCHCRMWJVXw8iB7zKxF55Byw) |
 [Documentation](https://doc.networknt.com/library/json-schema-validator/) |
-[Javadocs](https://www.javadoc.io/doc/com.networknt/json-schema-validator) |
 [Contribution Guide](https://doc.networknt.com/contribute/) |
 
 [![CI](https://github.com/networknt/json-schema-validator/actions/workflows/ci.yml/badge.svg)](https://github.com/networknt/json-schema-validator/actions/workflows/ci.yml)
 [![Maven Central](https://img.shields.io/maven-central/v/com.networknt/json-schema-validator.svg)](http://search.maven.org/#search%7Cga%7C1%7Cg%3Acom.networknt%20a%3Ajson-schema-validator)
 [![codecov.io](https://codecov.io/github/networknt/json-schema-validator/coverage.svg?branch=master)](https://codecov.io/github/networknt/json-schema-validator?branch=master)
+[![Javadocs](http://www.javadoc.io/badge/com.networknt/json-schema-validator.svg)](https://www.javadoc.io/doc/com.networknt/json-schema-validator)
 
 
 This is a Java implementation of the [JSON Schema Core Draft v4, v6, v7, v2019-09 and v2020-12](http://json-schema.org/latest/json-schema-core.html) specification for JSON schema validation. In addition, it also works for OpenAPI 3.0 request/response validation with some [configuration flags](doc/config.md). For users who want to collect information from a JSON node based on the schema, the [walkers](doc/walkers.md) can help. The default JSON parser is the [Jackson](https://github.com/FasterXML/jackson) that is the most popular one. As it is a key component in our [light-4j](https://github.com/networknt/light-4j) microservices framework to validate request/response against OpenAPI specification for [light-rest-4j](http://www.networknt.com/style/light-rest-4j/) and RPC schema for [light-hybrid-4j](http://www.networknt.com/style/light-hybrid-4j/) at runtime, performance is the most important aspect in the design. 
diff --git a/pom.xml b/pom.xml
index 6edd1d5..f10bec2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -68,7 +68,6 @@
         <version.joni>2.1.41</version.joni>
         <version.logback>1.2.11</version.logback>
         <version.junit>5.7.2</version.junit>
-        <version.mockito>2.7.21</version.mockito>
         <version.hamcrest>2.2</version.hamcrest>
         <version.undertow>2.2.21.Final</version.undertow>
         <version.itu>1.7.0</version.itu>
@@ -126,12 +125,6 @@
             <version>${version.junit}</version>
             <scope>test</scope>
         </dependency>
-        <dependency>
-            <groupId>org.mockito</groupId>
-            <artifactId>mockito-core</artifactId>
-            <version>${version.mockito}</version>
-            <scope>test</scope>
-        </dependency>
         <dependency>
             <groupId>org.hamcrest</groupId>
             <artifactId>hamcrest</artifactId>
diff --git a/src/main/java/com/networknt/schema/AdditionalPropertiesValidator.java b/src/main/java/com/networknt/schema/AdditionalPropertiesValidator.java
index 897093b..9b61a1f 100644
--- a/src/main/java/com/networknt/schema/AdditionalPropertiesValidator.java
+++ b/src/main/java/com/networknt/schema/AdditionalPropertiesValidator.java
@@ -77,7 +77,7 @@ public class AdditionalPropertiesValidator extends BaseJsonValidator implements
     }
 
     public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String at) {
-        if (logger.isDebugEnabled()) debug(logger, node, rootNode, at);
+        debug(logger, node, rootNode, at);
 
         Set<ValidationMessage> errors = new LinkedHashSet<ValidationMessage>();
         if (!node.isObject()) {
diff --git a/src/main/java/com/networknt/schema/BaseJsonValidator.java b/src/main/java/com/networknt/schema/BaseJsonValidator.java
index 094e6ec..284a362 100644
--- a/src/main/java/com/networknt/schema/BaseJsonValidator.java
+++ b/src/main/java/com/networknt/schema/BaseJsonValidator.java
@@ -151,9 +151,7 @@ public abstract class BaseJsonValidator implements JsonValidator {
     }
 
     protected void debug(Logger logger, JsonNode node, JsonNode rootNode, String at) {
-        if (logger.isDebugEnabled()) {
-            logger.debug("validate( " + node + ", " + rootNode + ", " + at + ")");
-        }
+        logger.debug("validate( {}, {}, {})", node, rootNode, at);
     }
 
     protected ValidatorTypeCode getValidatorType() {
diff --git a/src/main/java/com/networknt/schema/DateTimeValidator.java b/src/main/java/com/networknt/schema/DateTimeValidator.java
index 8d7fe28..83cb117 100644
--- a/src/main/java/com/networknt/schema/DateTimeValidator.java
+++ b/src/main/java/com/networknt/schema/DateTimeValidator.java
@@ -79,7 +79,7 @@ public class DateTimeValidator extends BaseJsonValidator implements JsonValidato
             parser.run();
             return true;
         } catch (Exception ex) {
-            logger.error("Invalid " + formatName + ": " + ex.getMessage());
+            logger.error("Invalid {}: {}", formatName, ex.getMessage());
             return false;
         }
     }
diff --git a/src/main/java/com/networknt/schema/FormatValidator.java b/src/main/java/com/networknt/schema/FormatValidator.java
index 9749ec8..a8afa14 100644
--- a/src/main/java/com/networknt/schema/FormatValidator.java
+++ b/src/main/java/com/networknt/schema/FormatValidator.java
@@ -63,7 +63,7 @@ public class FormatValidator extends BaseJsonValidator implements JsonValidator
                 }
             } catch (PatternSyntaxException pse) {
                 // String is considered valid if pattern is invalid
-                logger.error("Failed to apply pattern on " + at + ": Invalid RE syntax [" + format.getName() + "]", pse);
+                logger.error("Failed to apply pattern on {}: Invalid RE syntax [{}]", at, format.getName(), pse);
             }
         }
 
diff --git a/src/main/java/com/networknt/schema/JsonMetaSchema.java b/src/main/java/com/networknt/schema/JsonMetaSchema.java
index 1f81769..5a5fabf 100644
--- a/src/main/java/com/networknt/schema/JsonMetaSchema.java
+++ b/src/main/java/com/networknt/schema/JsonMetaSchema.java
@@ -278,7 +278,7 @@ public class JsonMetaSchema {
             Keyword kw = keywords.get(keyword);
             if (kw == null) {
                 if (UNKNOWN_KEYWORDS.put(keyword, keyword) == null) {
-                    logger.warn("Unknown keyword " + keyword + " - you should define your own Meta Schema. If the keyword is irrelevant for validation, just use a NonValidationKeyword");
+                    logger.warn("Unknown keyword {} - you should define your own Meta Schema. If the keyword is irrelevant for validation, just use a NonValidationKeyword", keyword);
                 }
                 return null;
             }
@@ -289,13 +289,13 @@ public class JsonMetaSchema {
                 logger.error("Error:", e);
                 throw (JsonSchemaException) e.getTargetException();
             } else {
-                logger.warn("Could not load validator " + keyword);
+                logger.warn("Could not load validator {}", keyword);
                 throw new JsonSchemaException(e.getTargetException());
             }
         } catch (JsonSchemaException e) {
             throw e;
         } catch (Exception e) {
-            logger.warn("Could not load validator " + keyword);
+            logger.warn("Could not load validator {}", keyword);
             throw new JsonSchemaException(e);
         }
     }
diff --git a/src/main/java/com/networknt/schema/JsonSchemaFactory.java b/src/main/java/com/networknt/schema/JsonSchemaFactory.java
index 147fe15..0f25a5b 100644
--- a/src/main/java/com/networknt/schema/JsonSchemaFactory.java
+++ b/src/main/java/com/networknt/schema/JsonSchemaFactory.java
@@ -424,9 +424,7 @@ public class JsonSchemaFactory {
             return false;
         }
         boolean result = id.equals(schemaUri.toString());
-        if (logger.isDebugEnabled()) {
-            logger.debug("Matching " + id + " to " + schemaUri.toString() + ": " + result);
-        }
+        logger.debug("Matching {} to {}: {}", id, schemaUri, result);
         return result;
     }
 
diff --git a/src/main/java/com/networknt/schema/PatternValidator.java b/src/main/java/com/networknt/schema/PatternValidator.java
index fe2e54d..01c1b8d 100644
--- a/src/main/java/com/networknt/schema/PatternValidator.java
+++ b/src/main/java/com/networknt/schema/PatternValidator.java
@@ -75,7 +75,7 @@ public class PatternValidator implements JsonValidator {
                 try {
                     compiledPattern = Pattern.compile(pattern);
                 } catch (PatternSyntaxException pse) {
-                    logger.error("Failed to compile pattern : Invalid syntax [" + pattern + "]", pse);
+                    logger.error("Failed to compile pattern : Invalid syntax [{}]", pattern, pse);
                     throw pse;
                 }
             }
@@ -100,7 +100,7 @@ public class PatternValidator implements JsonValidator {
                     return Collections.singleton(buildValidationMessage(at, pattern));
                 }
             } catch (PatternSyntaxException pse) {
-                logger.error("Failed to apply pattern on " + at + ": Invalid syntax [" + pattern + "]", pse);
+                logger.error("Failed to apply pattern on {}: Invalid syntax [{}]", at, pattern, pse);
             }
 
             return Collections.emptySet();
@@ -121,7 +121,7 @@ public class PatternValidator implements JsonValidator {
                 try {
                     compileRegexPattern(pattern, validationContext.getConfig() != null && validationContext.getConfig().isEcma262Validator());
                 } catch (SyntaxException se) {
-                    logger.error("Failed to compile pattern : Invalid syntax [" + pattern + "]", se);
+                    logger.error("Failed to compile pattern : Invalid syntax [{}]", pattern, se);
                     throw se;
                 }
             }
@@ -156,7 +156,7 @@ public class PatternValidator implements JsonValidator {
                     return Collections.singleton(buildValidationMessage(at, pattern));
                 }
             } catch (PatternSyntaxException pse) {
-                logger.error("Failed to apply pattern on " + at + ": Invalid syntax [" + pattern + "]", pse);
+                logger.error("Failed to apply pattern on {}: Invalid syntax [{}]", at, pattern, pse);
             }
 
             return Collections.emptySet();
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);
+    }
+}