b/161370934 Create extension schema validator with validation for three extensions

Change-Id: I7aeed2ce1248605a68d27c482a2d58b95bccfe7b
diff --git a/build.gradle b/build.gradle
index ea3e4a3..21d1f75 100644
--- a/build.gradle
+++ b/build.gradle
@@ -48,7 +48,9 @@
     // OpenAPI Libraries
     "openapiParser": "1.0.1", // 23 June 2020
     "openapiCore": "1.0.1", // 23 June 2020
-    "openapiSchemaValidator": "1.0.2" // 7 July 2020
+
+    // Json Libraries
+    "jsonSchemaValidator": "1.0.42" // 13 July 2020
 ]
 
 allprojects {
diff --git a/oas-core/build.gradle b/oas-core/build.gradle
index 25acf9d..ff16ae4 100644
--- a/oas-core/build.gradle
+++ b/oas-core/build.gradle
@@ -2,6 +2,8 @@
     implementation "org.openapi4j:openapi-parser:${libVersions.openapiParser}"
     implementation "org.openapi4j:openapi-core:${libVersions.openapiCore}"
     implementation "com.google.inject.extensions:guice-assistedinject:${libVersions.guice}"
+    implementation "com.networknt:json-schema-validator:${libVersions.jsonSchemaValidator}"
+    implementation "org.slf4j:slf4j-api:${libVersions.slf4j}"
 
     testImplementation project(":oas-test")
 }
\ No newline at end of file
diff --git a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/BaseExtension.java b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/BaseExtension.java
index 0526ba4..3c456c7 100644
--- a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/BaseExtension.java
+++ b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/BaseExtension.java
@@ -1,9 +1,12 @@
 package com.apigee.security.oas.extendedvalidator;
 
 import com.fasterxml.jackson.databind.JsonNode;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
-import com.google.errorprone.annotations.Var;
 import com.google.inject.assistedinject.Assisted;
+import com.networknt.schema.ValidationMessage;
+import java.util.Map;
+import java.util.Optional;
 import javax.inject.Inject;
 import org.openapi4j.parser.model.OpenApiSchema;
 
@@ -11,13 +14,16 @@
 final class BaseExtension implements Extension {
   private final String extensionName;
   private final JsonNode extensionContent;
-  private final ImmutableSet<Class<? extends OpenApiSchema>> extensionPath;
+  private final ImmutableList<Map.Entry<Class<? extends OpenApiSchema>, Optional<String>>>
+      extensionPath;
 
   @Inject
   BaseExtension(
-      @Var @Assisted String extensionName,
-      @Var @Assisted JsonNode extensionContent,
-      @Var @Assisted ImmutableSet<Class<? extends OpenApiSchema>> extensionPath) {
+      @Assisted String extensionName,
+      @Assisted JsonNode extensionContent,
+      @Assisted
+          ImmutableList<Map.Entry<Class<? extends OpenApiSchema>, Optional<String>>>
+              extensionPath) {
     this.extensionName = extensionName;
     this.extensionContent = extensionContent;
     this.extensionPath = extensionPath;
@@ -29,7 +35,8 @@
   }
 
   @Override
-  public ImmutableSet<Class<? extends OpenApiSchema>> getExtensionPath() {
+  public ImmutableList<Map.Entry<Class<? extends OpenApiSchema>, Optional<String>>>
+      getExtensionPath() {
     return extensionPath;
   }
 
@@ -40,7 +47,7 @@
 
   /** Validates the extension with given {@link ExtensionValidator}. */
   @Override
-  public boolean validate(@Var ExtensionValidator extensionValidator) {
+  public ImmutableSet<ValidationMessage> validate(ExtensionValidator extensionValidator) {
     return extensionValidator.validate(this);
   }
 }
diff --git a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/BaseExtensionSchemaValidator.java b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/BaseExtensionSchemaValidator.java
new file mode 100644
index 0000000..d7095a3
--- /dev/null
+++ b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/BaseExtensionSchemaValidator.java
@@ -0,0 +1,80 @@
+package com.apigee.security.oas.extendedvalidator;
+
+import static com.apigee.security.oas.extendedvalidator.ExtensionName.valueOfExtensionName;
+import static com.apigee.security.oas.extendedvalidator.ExtensionValidators.defaultErrors;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.flogger.FluentLogger;
+import com.google.common.io.Resources;
+import com.google.errorprone.annotations.Var;
+import com.networknt.schema.JsonSchema;
+import com.networknt.schema.JsonSchemaFactory;
+import com.networknt.schema.ValidationMessage;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.Optional;
+import javax.inject.Inject;
+
+final class BaseExtensionSchemaValidator implements ExtensionSchemaValidator {
+
+  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+  private static final URL LIST_WITH_STRINGS_SCHEMA_URL =
+      Resources.getResource("ListWithStringsSchema.json");
+  private static final URL SECURITY_DEFINITIONS_SCHEMA_URL =
+      Resources.getResource("SecurityDefinitionsSchema.json");
+
+  private final JsonSchemaFactory jsonSchemaFactory;
+
+  @Inject
+  BaseExtensionSchemaValidator(JsonSchemaFactory jsonSchemaFactory) {
+    this.jsonSchemaFactory = jsonSchemaFactory;
+  }
+
+  private JsonSchema getJsonSchemaFromUrl(URL schemaUrl) throws URISyntaxException {
+    return jsonSchemaFactory.getSchema(schemaUrl.toURI());
+  }
+
+  /** Validates {@link Extension} by contrasting it against its appropriate schema. */
+  @Override
+  public ImmutableSet<ValidationMessage> validate(Extension extension) {
+    Optional<ExtensionName> extensionName = valueOfExtensionName(extension.getExtensionName());
+    @Var ImmutableSet<ValidationMessage> errors = ImmutableSet.of();
+
+    if (extensionName.isPresent()) {
+      JsonNode extensionContent = extension.getExtensionContent();
+      switch (extensionName.get()) {
+        case X_SECURITY_TYPE:
+        case X_SECURITY_ALLOW:
+          errors = validateExtensionContent(LIST_WITH_STRINGS_SCHEMA_URL, extensionContent);
+          break;
+        case X_SECURITY_TYPE_DEFINITIONS:
+          errors = validateExtensionContent(SECURITY_DEFINITIONS_SCHEMA_URL, extensionContent);
+          break;
+      }
+
+      // TODO(b/162242995): Add named extension path to each validation message in errors.
+    } else {
+      errors = defaultErrors(extension);
+    }
+
+    logger.atInfo().log("Schema Errors : %s", errors);
+
+    return errors;
+  }
+
+  /** Validates an {@link JsonNode} against its {@link JsonSchema}. */
+  private ImmutableSet<ValidationMessage> validateExtensionContent(
+      URL schemaUrl, JsonNode extensionContent) {
+    try {
+      JsonSchema schema = getJsonSchemaFromUrl(schemaUrl);
+      ImmutableSet<ValidationMessage> errors =
+          ImmutableSet.copyOf(schema.validate(extensionContent));
+      return errors;
+    } catch (URISyntaxException e) {
+      String errorMessage = "Failed to validate extension schema.";
+      logger.atSevere().log("%s due to %s", errorMessage, e.getMessage());
+      throw new ValidationException(errorMessage, e);
+    }
+  }
+}
diff --git a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/Extension.java b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/Extension.java
index 3428e2d..a18a880 100644
--- a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/Extension.java
+++ b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/Extension.java
@@ -1,14 +1,18 @@
 package com.apigee.security.oas.extendedvalidator;
 
 import com.fasterxml.jackson.databind.JsonNode;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
+import com.networknt.schema.ValidationMessage;
+import java.util.Map;
+import java.util.Optional;
 import org.openapi4j.parser.model.OpenApiSchema;
 
 /** Stores details of and validates the extension. */
 public interface Extension {
 
   /** Validates the {@code Extension} with {@link ExtensionValidator}. */
-  boolean validate(ExtensionValidator extensionValidator);
+  ImmutableSet<ValidationMessage> validate(ExtensionValidator extensionValidator);
 
   /** Returns the content of the Extension. */
   JsonNode getExtensionContent();
@@ -17,7 +21,7 @@
    * Returns the path of {@link org.openapi4j.parser.model.OpenApiSchema} classes from the root
    * node.
    */
-  ImmutableSet<Class<? extends OpenApiSchema>> getExtensionPath();
+  ImmutableList<Map.Entry<Class<? extends OpenApiSchema>, Optional<String>>> getExtensionPath();
 
   /** Returns the name of the Extension. */
   String getExtensionName();
diff --git a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/ExtensionFactory.java b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/ExtensionFactory.java
index 22d7b9b..bf974ea 100644
--- a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/ExtensionFactory.java
+++ b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/ExtensionFactory.java
@@ -1,7 +1,9 @@
 package com.apigee.security.oas.extendedvalidator;
 
 import com.fasterxml.jackson.databind.JsonNode;
-import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableList;
+import java.util.Map;
+import java.util.Optional;
 import org.openapi4j.parser.model.OpenApiSchema;
 
 /** Factory to create an {@link Extension}. */
@@ -14,5 +16,5 @@
   Extension create(
       String extensionName,
       JsonNode extensionContent,
-      ImmutableSet<Class<? extends OpenApiSchema>> extensionPath);
+      ImmutableList<Map.Entry<Class<? extends OpenApiSchema>, Optional<String>>> extensionPath);
 }
diff --git a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/ExtensionModule.java b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/ExtensionModule.java
index 0a97951..70c3f7a 100644
--- a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/ExtensionModule.java
+++ b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/ExtensionModule.java
@@ -1,7 +1,10 @@
 package com.apigee.security.oas.extendedvalidator;
 
 import com.google.inject.AbstractModule;
+import com.google.inject.Provides;
 import com.google.inject.assistedinject.FactoryModuleBuilder;
+import com.networknt.schema.JsonSchemaFactory;
+import com.networknt.schema.SpecVersion;
 
 /**
  * Module with instructions for instantiating instances of {@link Extension} and building {@link
@@ -14,5 +17,12 @@
         new FactoryModuleBuilder()
             .implement(Extension.class, BaseExtension.class)
             .build(ExtensionFactory.class));
+
+    bind(ExtensionSchemaValidator.class).to(BaseExtensionSchemaValidator.class);
+  }
+
+  @Provides
+  public JsonSchemaFactory jsonSchemaFactory() {
+    return JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4);
   }
 }
diff --git a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/ExtensionName.java b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/ExtensionName.java
index cacee5a..98eaa6b 100644
--- a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/ExtensionName.java
+++ b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/ExtensionName.java
@@ -1,11 +1,13 @@
 package com.apigee.security.oas.extendedvalidator;
 
-import com.google.common.base.Optional;
 import com.google.errorprone.annotations.Var;
+import java.util.Optional;
 
 /** Stores all the supported {@code x-security-*} extensions as constants. */
 enum ExtensionName {
-  X_SECURITY_TYPE("x-security-type");
+  X_SECURITY_TYPE("x-security-type"),
+  X_SECURITY_ALLOW("x-security-allow"),
+  X_SECURITY_TYPE_DEFINITIONS("x-security-type-definitions");
 
   private final String extensionName;
 
@@ -32,6 +34,6 @@
         extensionEnum = extension;
       }
     }
-    return Optional.fromNullable(extensionEnum);
+    return Optional.ofNullable(extensionEnum);
   }
 }
diff --git a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/ExtensionSchemaValidator.java b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/ExtensionSchemaValidator.java
new file mode 100644
index 0000000..6d8b755
--- /dev/null
+++ b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/ExtensionSchemaValidator.java
@@ -0,0 +1,4 @@
+package com.apigee.security.oas.extendedvalidator;
+
+/** A {@link ExtensionValidator} interface for {@link BaseExtensionSchemaValidator}. */
+interface ExtensionSchemaValidator extends ExtensionValidator {}
diff --git a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/ExtensionValidator.java b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/ExtensionValidator.java
index d944371..9d2d3a0 100644
--- a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/ExtensionValidator.java
+++ b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/ExtensionValidator.java
@@ -1,8 +1,11 @@
 package com.apigee.security.oas.extendedvalidator;
 
+import com.google.common.collect.ImmutableSet;
+import com.networknt.schema.ValidationMessage;
+
 /** Validates a {@link Extension} based on the type of {@code Extension}. */
 public interface ExtensionValidator {
 
   /** Validates a {@link Extension}. */
-  boolean validate(Extension extension);
+  ImmutableSet<ValidationMessage> validate(Extension extension);
 }
diff --git a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/ExtensionValidators.java b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/ExtensionValidators.java
new file mode 100644
index 0000000..6090e9b
--- /dev/null
+++ b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/ExtensionValidators.java
@@ -0,0 +1,61 @@
+package com.apigee.security.oas.extendedvalidator;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.networknt.schema.CustomErrorMessageType;
+import com.networknt.schema.ValidationMessage;
+import java.util.Map;
+import java.util.Optional;
+import org.openapi4j.parser.model.OpenApiSchema;
+
+/**
+ * Utility class to prepare path string and {@link ValidationMessage} for {@link
+ * ExtensionValidator}.
+ */
+final class ExtensionValidators {
+
+  /**
+   * Converts an {@link ImmutableList} {@code extensionPath} into {@link String}.
+   *
+   * <p>A named path is used for logging and passed as an parameter of {@link ValidationMessage} if
+   * an error occurs during validation.
+   */
+  static String prepareNamedPath(
+      ImmutableList<Map.Entry<Class<? extends OpenApiSchema>, Optional<String>>> extensionPath) {
+    StringBuilder namedPath = new StringBuilder();
+    if (extensionPath.size() > 0) {
+      for (Map.Entry<Class<? extends OpenApiSchema>, Optional<String>> pathMap : extensionPath) {
+        Optional<String> namedPathObject = pathMap.getValue();
+        namedPath.append(" -> ");
+        if (namedPathObject.isPresent()) {
+          namedPath.append(namedPathObject.get());
+        } else {
+          namedPath.append(pathMap.getKey().getSimpleName());
+        }
+      }
+    }
+
+    return namedPath.toString();
+  }
+
+  static ValidationMessage prepareValidationMessage(
+      String errorCode, String errorType, String path) {
+    return ValidationMessage.of(errorType, CustomErrorMessageType.of(errorCode), path);
+  }
+
+  /**
+   * Returns an {@link ImmutableSet} with {@code UNSUPPORTED_EXTENSION} error as a {@link
+   * ValidationMessage}.
+   *
+   * <p>Prepares a default error {@link ValidationMessage} for extensions without validators.
+   */
+  static ImmutableSet<ValidationMessage> defaultErrors(Extension extension) {
+    String errorCode = "UNSUPPORTED_EXTENSION";
+    String errorType = extension.getExtensionName() + " is not binded to the validator.";
+    String path = prepareNamedPath(extension.getExtensionPath());
+    ValidationMessage message = prepareValidationMessage(errorCode, errorType, path);
+    return ImmutableSet.of(message);
+  }
+
+  private ExtensionValidators() {}
+}
diff --git a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/ValidationException.java b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/ValidationException.java
new file mode 100644
index 0000000..bc39edd
--- /dev/null
+++ b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/ValidationException.java
@@ -0,0 +1,8 @@
+package com.apigee.security.oas.extendedvalidator;
+
+/** Exception thrown when an error has occurred while validating an {@link Extension}. */
+public final class ValidationException extends RuntimeException {
+  ValidationException(String s, Exception childException) {
+    super(s, childException);
+  }
+}
diff --git a/oas-core/src/main/resources/ListWithStringsSchema.json b/oas-core/src/main/resources/ListWithStringsSchema.json
new file mode 100644
index 0000000..5623873
--- /dev/null
+++ b/oas-core/src/main/resources/ListWithStringsSchema.json
@@ -0,0 +1,28 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "title": "List of strings",
+  "type": "array",
+  "items": {
+    "type": "string"
+  },
+  "tests": [
+    {
+      "id": "VALID_1",
+      "description": "List of strings",
+      "data": ["string1", "string2"],
+      "valid": true
+    },
+    {
+      "id": "VALID_2",
+      "description": "Empty list",
+      "data": [],
+      "valid": true
+    },
+    {
+      "id": "INVALID_1",
+      "description": "List of integer and strings",
+      "data": ["string1", 2, 3],
+      "valid": false
+    }
+  ]
+}
\ No newline at end of file
diff --git a/oas-core/src/main/resources/SecurityDefinitionsSchema.json b/oas-core/src/main/resources/SecurityDefinitionsSchema.json
new file mode 100644
index 0000000..06eb2fb
--- /dev/null
+++ b/oas-core/src/main/resources/SecurityDefinitionsSchema.json
@@ -0,0 +1,78 @@
+{
+    "$schema": "http://json-schema.org/draft-04/schema#",
+    "title": "Security definitions schema",
+    "type": "object",
+    "properties": {
+        "type": {
+            "type": "string"
+        },
+        "inherits-from": {
+            "type": "array",
+            "items": {
+                "type": "string"
+            }
+        }
+    },
+    "required": ["type"],
+    "additionalProperties": false,
+    "tests": [
+        {
+            "id": "VALID_1",
+            "description": "type and inherits-from",
+            "data": {
+                "type": "customType",
+                "inherits-from":  ["string1", "string2"]
+            },
+            "valid": true
+        },
+        {
+            "id": "VALID_2",
+            "description": "only type",
+            "data": {
+                "type": "customType"
+            },
+            "valid": true
+        },
+        {
+            "id": "VALID_3",
+            "description": "type and empty inherits-from",
+            "data": {
+                "type": "pii",
+                "inherits-from": []
+            },
+            "valid": true
+        },
+        {
+            "id": "INVALID_1",
+            "description": "type not of string type",
+            "data": {
+                "type": 1
+            },
+            "valid": false
+        },
+        {
+            "id": "INVALID_2",
+            "description": "inherits-from not of list type",
+            "data": {
+                "type": "customType1",
+                "inherits-from": "customType2"
+            },
+            "valid": false
+        },
+        {
+            "id": "INVALID_3",
+            "description": "Required field 'type' not given",
+            "data": {},
+            "valid": false
+        },
+        {
+            "id": "INVALID_4",
+            "description": "Additional field present",
+            "data": {
+                "type": "customType",
+                "customField": "customString"
+            },
+            "valid": false
+        }
+    ]
+}
\ No newline at end of file
diff --git a/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/BaseExtensionSchemaValidatorTest.java b/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/BaseExtensionSchemaValidatorTest.java
new file mode 100644
index 0000000..1cc69aa
--- /dev/null
+++ b/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/BaseExtensionSchemaValidatorTest.java
@@ -0,0 +1,175 @@
+package com.apigee.security.oas.extendedvalidator;
+
+import static com.apigee.security.oas.extendedvalidator.ExtensionName.X_SECURITY_TYPE;
+import static com.apigee.security.oas.extendedvalidator.ExtensionName.X_SECURITY_TYPE_DEFINITIONS;
+import static com.google.common.collect.Iterables.concat;
+import static com.google.common.collect.Iterables.unmodifiableIterable;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.ThrowableAssert.catchThrowable;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.common.io.Resources;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.networknt.schema.JsonSchemaFactory;
+import com.networknt.schema.ValidationMessage;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.AbstractMap.SimpleImmutableEntry;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Optional;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.runners.Enclosed;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.openapi4j.parser.model.OpenApiSchema;
+import org.openapi4j.parser.model.v3.Operation;
+
+@RunWith(Enclosed.class)
+public class BaseExtensionSchemaValidatorTest {
+
+  private static final URL ARRAY_LIST_SECURITY_TYPES_SCHEMA_URL =
+      Resources.getResource("ListWithStringsSchema.json");
+  private static final URL SECURITY_DEFINITIONS_SCHEMA_URL =
+      Resources.getResource("SecurityDefinitionsSchema.json");
+  private static final Injector injector = Guice.createInjector(new ExtendedValidatorMainModule());
+  private static final JsonNode emptyContent = new ObjectMapper().valueToTree("[]");
+  private static final ImmutableList<Map.Entry<Class<? extends OpenApiSchema>, Optional<String>>>
+      extensionPath =
+          ImmutableList.of(new SimpleImmutableEntry<>(Operation.class, Optional.of("get")));
+
+  @RunWith(Parameterized.class)
+  public static class BaseExtensionSchemaValidatorParamsTest {
+    @Parameter(0)
+    public static String extensionName;
+
+    @Parameter(1)
+    public static JsonNode content;
+
+    @Parameter(2)
+    public static boolean isValid;
+
+    private ExtensionValidator validator;
+    private ExtensionFactory factory;
+
+    /** Setting up validator and factories. */
+    @Before
+    public void setup() {
+      validator = injector.getInstance(ExtensionSchemaValidator.class);
+      factory = injector.getInstance(ExtensionFactory.class);
+    }
+
+    /**
+     * Builds {@link Collection} of {{@link #extensionName}, {@link #isValid}, {@link #content}}
+     * parameters from {@code Schema URL}.
+     */
+    public static Collection<Object[]> buildTestParameters(URL schemaUrl, String extensionName)
+        throws IOException {
+      JsonNode schema = new ObjectMapper().readTree(schemaUrl);
+      JsonNode schemaTests = schema.get("tests");
+      ArrayList<Object[]> testParameters = new ArrayList<>();
+
+      if (schemaTests.isArray()) {
+        for (JsonNode testNode : schemaTests) {
+          testParameters.add(
+              new Object[] {
+                extensionName, testNode.get("data"), testNode.get("valid").asBoolean()
+              });
+        }
+      }
+      return testParameters;
+    }
+
+    /** Builds global test parameters for multiple schemas. */
+    @Parameters(name = "{index}: extension: {0}, isValid: {1}, Data: {2}")
+    public static Collection<Object[]> data() throws IOException {
+
+      Collection<Object[]> arrayListSchemaTestParameters =
+          buildTestParameters(
+              ARRAY_LIST_SECURITY_TYPES_SCHEMA_URL, X_SECURITY_TYPE.getExtensionName());
+      Collection<Object[]> securityDefinitionSchemaTestParameters =
+          buildTestParameters(
+              SECURITY_DEFINITIONS_SCHEMA_URL, X_SECURITY_TYPE_DEFINITIONS.getExtensionName());
+
+      Iterable<Object[]> combinedIterables =
+          unmodifiableIterable(
+              concat(arrayListSchemaTestParameters, securityDefinitionSchemaTestParameters));
+      return Lists.newArrayList(combinedIterables);
+    }
+
+    @Test
+    public void validate_schemaParameterTests_returnsErrorSet() {
+      Extension extension = factory.create(extensionName, content, extensionPath);
+      ImmutableSet<ValidationMessage> errors = validator.validate(extension);
+
+      assertThat(errors.isEmpty()).isEqualTo(isValid);
+    }
+  }
+
+  @RunWith(JUnit4.class)
+  public static class BaseExtensionSchemaValidatorSingleTest {
+
+    @Rule public final MockitoRule rule = MockitoJUnit.rule();
+
+    @Mock private JsonSchemaFactory schemaFactory;
+
+    @InjectMocks private BaseExtensionSchemaValidator validator;
+
+    private ExtensionFactory factory;
+
+    /** Sets up validator and extension factory. */
+    @Before
+    public void setup() {
+      factory = injector.getInstance(ExtensionFactory.class);
+    }
+
+    @Test
+    public void validate_unsupportedExtension_returnsUnsupportedExtensionErrorMessage() {
+      Extension extension = factory.create("x-unsupported", emptyContent, extensionPath);
+      ImmutableSet<ValidationMessage> errors = validator.validate(extension);
+
+      assertThat(errors)
+          .hasSize(1)
+          .first()
+          .extracting(ValidationMessage::getCode)
+          .isEqualTo("UNSUPPORTED_EXTENSION");
+    }
+
+    @Test
+    public void validate_schemaFactoryThrowsUriSyntaxException_shouldThrowValidationException() {
+      doAnswer(
+              invocationOnMock -> {
+                throw new URISyntaxException("", "");
+              })
+          .when(schemaFactory)
+          .getSchema(any(URI.class));
+
+      Extension extension =
+          factory.create(X_SECURITY_TYPE.getExtensionName(), emptyContent, extensionPath);
+
+      assertThat(catchThrowable(() -> validator.validate(extension)))
+          .isInstanceOf(ValidationException.class)
+          .hasRootCauseInstanceOf(URISyntaxException.class)
+          .hasMessageContainingAll("Failed", "validate");
+    }
+  }
+}
diff --git a/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/BaseExtensionTest.java b/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/BaseExtensionTest.java
index 02d9b05..a35f6c1 100644
--- a/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/BaseExtensionTest.java
+++ b/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/BaseExtensionTest.java
@@ -9,9 +9,13 @@
 
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.inject.Guice;
-import java.util.Arrays;
+import java.net.URISyntaxException;
+import java.util.Map;
+import java.util.Optional;
+import org.apache.commons.lang3.tuple.Pair;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -22,7 +26,6 @@
 import org.mockito.junit.MockitoRule;
 import org.openapi4j.parser.model.OpenApiSchema;
 import org.openapi4j.parser.model.v3.Operation;
-import org.openapi4j.parser.model.v3.Path;
 
 @RunWith(JUnit4.class)
 public class BaseExtensionTest {
@@ -34,44 +37,38 @@
 
   private JsonNode node;
   private Extension extension;
-  private ImmutableSet<Class<? extends OpenApiSchema>> extensionPath;
+  private ImmutableList<Map.Entry<Class<? extends OpenApiSchema>, Optional<String>>> extensionPath;
   @Mock private ExtensionValidator extensionValidator;
 
   /** Setting up an {@link Extension} using {@link ExtensionFactory}. */
   @Before
   public void setup() {
     node = mapper.valueToTree("[]");
-    extensionPath = ImmutableSet.copyOf(Arrays.asList(Path.class, Operation.class));
+    extensionPath = ImmutableList.of(Pair.of(Operation.class, Optional.of("get")));
     extension = factory.create(X_SECURITY_TYPE.getExtensionName(), node, extensionPath);
   }
 
   @Test
-  public void create_returnsExtension() {
-    assertThat(extension).isInstanceOf(Extension.class);
-  }
+  public void validate_callsExtensionValidatorsValidate() throws URISyntaxException {
+    when(extensionValidator.validate(any(Extension.class))).thenReturn(ImmutableSet.of());
 
-  @Test
-  public void validate_callsExtensionValidator() {
-    when(extensionValidator.validate(any(Extension.class))).thenReturn(true);
+    extension.validate(extensionValidator);
 
-    boolean isValid = extension.validate(extensionValidator);
-
-    assertThat(isValid).isTrue();
     verify(extensionValidator, atLeastOnce()).validate(any(Extension.class));
   }
 
   @Test
-  public void getExtensionName_returnsString() {
+  public void getExtensionName_returnsStringClassMember() {
     assertThat(extension.getExtensionName()).isEqualTo(X_SECURITY_TYPE.getExtensionName());
   }
 
   @Test
-  public void getExtensionPath_returnsImmutableSetInstance() {
+  public void getExtensionPath_returnsImmutableListClassMember() {
     assertThat(extension.getExtensionPath()).isEqualTo(extensionPath);
   }
 
   @Test
-  public void getExtensionContent_returnsJsonNodeInstance() {
+  public void getExtensionContent_returnsJsonNodeClassMember() {
     assertThat(extension.getExtensionContent()).isEqualTo(node);
   }
 }
diff --git a/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/ExtendedValidatorMainModuleTest.java b/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/ExtendedValidatorMainModuleTest.java
index f5cac11..70810de 100644
--- a/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/ExtendedValidatorMainModuleTest.java
+++ b/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/ExtendedValidatorMainModuleTest.java
@@ -5,6 +5,7 @@
 
 import com.google.inject.Guice;
 import com.google.inject.Injector;
+import com.networknt.schema.JsonSchemaFactory;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -44,4 +45,16 @@
     assertThat(catchThrowable(() -> injector.getInstance(TraversalCoordinator.class)))
         .doesNotThrowAnyException();
   }
+
+  @Test
+  public void getInstance_extensionSchemaValidator_shouldNotFail() {
+    assertThat(catchThrowable(() -> injector.getInstance(ExtensionSchemaValidator.class)))
+        .doesNotThrowAnyException();
+  }
+
+  @Test
+  public void getInstance_jsonSchemaFactory_shouldNotFail() {
+    assertThat(catchThrowable(() -> injector.getInstance(JsonSchemaFactory.class)))
+        .doesNotThrowAnyException();
+  }
 }
diff --git a/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/ExtensionFactoryTest.java b/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/ExtensionFactoryTest.java
index 0216741..76bf6b7 100644
--- a/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/ExtensionFactoryTest.java
+++ b/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/ExtensionFactoryTest.java
@@ -5,45 +5,39 @@
 
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
-import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableList;
 import com.google.inject.Guice;
 import com.google.inject.Injector;
-import java.util.Arrays;
+import java.util.Map;
+import java.util.Optional;
+import org.apache.commons.lang3.tuple.Pair;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 import org.openapi4j.parser.model.OpenApiSchema;
 import org.openapi4j.parser.model.v3.Operation;
-import org.openapi4j.parser.model.v3.Path;
 
 @RunWith(JUnit4.class)
 public class ExtensionFactoryTest {
 
   private JsonNode node;
-  private ImmutableSet<Class<? extends OpenApiSchema>> extensionPath;
+  private ImmutableList<Map.Entry<Class<? extends OpenApiSchema>, Optional<String>>> extensionPath;
   private ExtensionFactory factory;
 
   /** Setting up parameters and {@link ExtensionFactory} instance.. */
   @Before
   public void setup() {
     node = new ObjectMapper().valueToTree("[]");
-    extensionPath = ImmutableSet.copyOf(Arrays.asList(Path.class, Operation.class));
+    extensionPath = ImmutableList.of(Pair.of(Operation.class, Optional.of("get")));
     Injector injector = Guice.createInjector(new ExtensionModule());
     factory = injector.getInstance(ExtensionFactory.class);
   }
 
   @Test
-  public void generateExtension_xSecurityExtensionName_returnsExtension() {
+  public void create_extensionData_returnsExtension() {
     Extension extension = factory.create(X_SECURITY_TYPE.getExtensionName(), node, extensionPath);
 
     assertThat(extension).isInstanceOf(Extension.class);
   }
-
-  @Test
-  public void generateExtension_unsupportedExtensionName_returnsExtension() {
-    Extension extension = factory.create("x-unsupported", node, extensionPath);
-
-    assertThat(extension).isInstanceOf(Extension.class);
-  }
 }
diff --git a/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/ExtensionNameTest.java b/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/ExtensionNameTest.java
index 4024b9d..5570009 100644
--- a/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/ExtensionNameTest.java
+++ b/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/ExtensionNameTest.java
@@ -4,7 +4,7 @@
 import static com.apigee.security.oas.extendedvalidator.ExtensionName.valueOfExtensionName;
 import static org.assertj.core.api.Assertions.assertThat;
 
-import com.google.common.base.Optional;
+import java.util.Optional;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -13,7 +13,7 @@
 public class ExtensionNameTest {
 
   @Test
-  public void valueOfExtensionName_supportedExtension_returnsExtensionNameOptional() {
+  public void valueOfExtensionName_supportedExtension_returnsValidExtensionNameOptional() {
     Optional<ExtensionName> extensionNameOptional =
         valueOfExtensionName(X_SECURITY_TYPE.getExtensionName());
 
diff --git a/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/ExtensionValidatorsTest.java b/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/ExtensionValidatorsTest.java
new file mode 100644
index 0000000..95155f2
--- /dev/null
+++ b/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/ExtensionValidatorsTest.java
@@ -0,0 +1,65 @@
+package com.apigee.security.oas.extendedvalidator;
+
+import static com.apigee.security.oas.extendedvalidator.ExtensionValidators.defaultErrors;
+import static com.apigee.security.oas.extendedvalidator.ExtensionValidators.prepareNamedPath;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.Guice;
+import com.networknt.schema.ValidationMessage;
+import java.util.AbstractMap.SimpleImmutableEntry;
+import java.util.Map;
+import java.util.Optional;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.openapi4j.parser.model.OpenApiSchema;
+import org.openapi4j.parser.model.v3.Operation;
+import org.openapi4j.parser.model.v3.Path;
+
+@RunWith(JUnit4.class)
+public class ExtensionValidatorsTest {
+
+  private static final ExtensionFactory factory =
+      Guice.createInjector(new ExtensionModule()).getInstance(ExtensionFactory.class);
+
+  private ImmutableList<Map.Entry<Class<? extends OpenApiSchema>, Optional<String>>> path;
+  private Extension extension;
+
+  /** Setting up {@link Extension} object. */
+  @Before
+  public void setup() {
+    Map.Entry<Class<? extends OpenApiSchema>, Optional<String>> pathEntry =
+        new SimpleImmutableEntry<Class<? extends OpenApiSchema>, Optional<String>>(
+            Path.class, Optional.of("/users"));
+    Map.Entry<Class<? extends OpenApiSchema>, Optional<String>> operationEntry =
+        new SimpleImmutableEntry<Class<? extends OpenApiSchema>, Optional<String>>(
+            Operation.class, Optional.of("get"));
+    path = ImmutableList.of(pathEntry, operationEntry);
+
+    JsonNode node = new ObjectMapper().valueToTree("[]");
+    extension = factory.create("x-security", node, path);
+  }
+
+  @Test
+  public void prepareNamedPath_extensionPath_returnsStringWithNamedPath() {
+    String namedPath = prepareNamedPath(path);
+
+    assertThat(namedPath).contains("/users", "get");
+  }
+
+  @Test
+  public void defaultErrors_returnsImmutableSetWithUnsupportedExtensionMessage() {
+    ImmutableSet<ValidationMessage> errors = defaultErrors(extension);
+
+    assertThat(errors)
+        .hasSize(1)
+        .first()
+        .extracting(ValidationMessage::getCode)
+        .isEqualTo("UNSUPPORTED_EXTENSION");
+  }
+}