From ef4cbdd9bc2a3ade851b78f651314b92709f3ced Mon Sep 17 00:00:00 2001
From: p1c2u <maciag.artur@gmail.com>
Date: Wed, 24 Feb 2021 22:31:19 +0000
Subject: [PATCH 1/2] Spec 3.1 schema

---
 README.rst                                    |   10 +-
 openapi_spec_validator/__init__.py            |   58 +-
 openapi_spec_validator/__main__.py            |   12 +-
 .../resources/schemas/v3.1/schema.json        | 1478 +++++++++++++++++
 tests/integration/conftest.py                 |   15 +-
 tests/integration/data/v3.1/empty.yaml        |    1 +
 tests/integration/data/v3.1/petstore.yaml     |  109 ++
 tests/integration/test_main.py                |   25 +-
 ...test_shortcuts.py => test_v2_shortcuts.py} |   85 +-
 tests/integration/test_v2_validator.py        |   42 +
 tests/integration/test_v3_0_shortcuts.py      |  102 ++
 ...test_validate.py => test_v3_0_validate.py} |    8 +-
 ...t_validators.py => test_v3_0_validator.py} |   45 +-
 tests/integration/test_v3_1_shortcuts.py      |   80 +
 tests/integration/test_v3_1_validate.py       |   50 +
 15 files changed, 1954 insertions(+), 166 deletions(-)
 create mode 100644 openapi_spec_validator/resources/schemas/v3.1/schema.json
 create mode 100644 tests/integration/data/v3.1/empty.yaml
 create mode 100644 tests/integration/data/v3.1/petstore.yaml
 rename tests/integration/{test_shortcuts.py => test_v2_shortcuts.py} (53%)
 create mode 100644 tests/integration/test_v2_validator.py
 create mode 100644 tests/integration/test_v3_0_shortcuts.py
 rename tests/integration/{test_validate.py => test_v3_0_validate.py} (91%)
 rename tests/integration/{test_validators.py => test_v3_0_validator.py} (89%)
 create mode 100644 tests/integration/test_v3_1_shortcuts.py
 create mode 100644 tests/integration/test_v3_1_validate.py

diff --git a/README.rst b/README.rst
index 7764f6a..8dd3d87 100644
--- a/README.rst
+++ b/README.rst
@@ -18,13 +18,9 @@ OpenAPI Spec validator
 About
 #####
 
-OpenAPI Spec Validator is a Python library that validates OpenAPI Specs
-against the `OpenAPI 2.0 (aka
-Swagger) <https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md>`__
-and `OpenAPI
-3.0 <https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md>`__
-specification. The validator aims to check for full compliance with the
-Specification.
+OpenAPI Spec Validator is a Python library that validates OpenAPI Specs against the `OpenAPI 2.0 (aka Swagger) <https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md>`__, `OpenAPI 3.0 <https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md>`__ and `OpenAPI 3.1 <https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md>`__ specification.
+
+The validator aims to check for full compliance with the Specification.
 
 Installation
 ############
diff --git a/openapi_spec_validator/__init__.py b/openapi_spec_validator/__init__.py
index 6ffd254..ad675dc 100644
--- a/openapi_spec_validator/__init__.py
+++ b/openapi_spec_validator/__init__.py
@@ -14,9 +14,16 @@
 __license__ = 'Apache License, Version 2.0'
 
 __all__ = [
-    'openapi_v2_spec_validator', 'openapi_v3_spec_validator',
-    'validate_v2_spec', 'validate_v3_spec', 'validate_spec',
-    'validate_v2_spec_url', 'validate_v3_spec_url', 'validate_spec_url',
+    'validate_spec', 'validate_spec_url',
+    'openapi_spec_validator',
+    'openapi_v2_spec_validator',
+    'validate_v2_spec', 'validate_v2_spec_url',
+    'openapi_v3_spec_validator',
+    'validate_v3_spec', 'validate_v3_spec_url',
+    'openapi_v3_0_spec_validator',
+    'validate_v3_0_spec', 'validate_v3_0_spec_url',
+    'openapi_v3_1_spec_validator',
+    'validate_v3_1_spec', 'validate_v3_1_spec_url',
 ]
 
 file_object_handler = FileObjectHandler()
@@ -40,25 +47,48 @@
 )
 
 # v3.0 spec
-schema_v3, schema_v3_url = get_openapi_schema('3.0')
-openapi_v3_validator_factory = JSONSpecValidatorFactory(
-    schema_v3, schema_v3_url,
+schema_v3_0, schema_v3_0_url = get_openapi_schema('3.0')
+openapi_v3_0_validator_factory = JSONSpecValidatorFactory(
+    schema_v3_0, schema_v3_0_url,
     resolver_handlers=default_handlers,
 )
-openapi_v3_spec_validator = SpecValidator(
-    openapi_v3_validator_factory,
+openapi_v3_0_spec_validator = SpecValidator(
+    openapi_v3_0_validator_factory,
+    resolver_handlers=default_handlers,
+)
+
+# v3.1 spec
+schema_v3_1, schema_v3_1_url = get_openapi_schema('3.1')
+openapi_v3_1_validator_factory = JSONSpecValidatorFactory(
+    schema_v3_1, schema_v3_1_url,
+    resolver_handlers=default_handlers,
+)
+openapi_v3_1_spec_validator = SpecValidator(
+    openapi_v3_1_validator_factory,
     resolver_handlers=default_handlers,
 )
 
 # shortcuts
-validate_v2_spec = validate_spec_factory(openapi_v2_spec_validator.validate)
+validate_v2_spec = validate_spec_factory(
+    openapi_v2_spec_validator.validate)
 validate_v2_spec_url = validate_spec_url_factory(
     openapi_v2_spec_validator.validate, default_handlers)
 
-validate_v3_spec = validate_spec_factory(openapi_v3_spec_validator.validate)
-validate_v3_spec_url = validate_spec_url_factory(
-    openapi_v3_spec_validator.validate, default_handlers)
+validate_v3_0_spec = validate_spec_factory(
+    openapi_v3_0_spec_validator.validate)
+validate_v3_0_spec_url = validate_spec_url_factory(
+    openapi_v3_0_spec_validator.validate, default_handlers)
+
+validate_v3_1_spec = validate_spec_factory(
+    openapi_v3_1_spec_validator.validate)
+validate_v3_1_spec_url = validate_spec_url_factory(
+    openapi_v3_1_spec_validator.validate, default_handlers)
 
 # aliases to the latest version
-validate_spec = validate_v3_spec
-validate_spec_url = validate_v3_spec_url
+schema_v3, schema_v3_url = schema_v3_0, schema_v3_0_url
+openapi_v3_validator_factory = openapi_v3_0_validator_factory
+openapi_v3_spec_validator = openapi_v3_0_spec_validator
+validate_v3_spec = validate_v3_0_spec
+validate_v3_spec_url = validate_v3_0_spec_url
+validate_spec = validate_v3_0_spec
+validate_spec_url = validate_v3_0_spec_url
diff --git a/openapi_spec_validator/__main__.py b/openapi_spec_validator/__main__.py
index eb2884a..7d31c3a 100644
--- a/openapi_spec_validator/__main__.py
+++ b/openapi_spec_validator/__main__.py
@@ -3,7 +3,8 @@
 import sys
 
 from openapi_spec_validator import (
-    openapi_v2_spec_validator, openapi_v3_spec_validator,
+    openapi_v2_spec_validator, openapi_v3_0_spec_validator,
+    openapi_v3_1_spec_validator
 )
 from openapi_spec_validator.exceptions import ValidationError
 from openapi_spec_validator.readers import read_from_stdin, read_from_filename
@@ -20,10 +21,10 @@ def main(args=None):
     parser.add_argument('filename', help="Absolute or relative path to file")
     parser.add_argument(
         '--schema',
-        help="OpenAPI schema (default: 3.0.0)",
+        help="OpenAPI schema (default: 3.1)",
         type=str,
-        choices=['2.0', '3.0.0'],
-        default='3.0.0'
+        choices=['2.0', '3.0', '3.1'],
+        default='3.0'
     )
     args = parser.parse_args(args)
 
@@ -42,7 +43,8 @@ def main(args=None):
     # choose the validator
     validators = {
         '2.0': openapi_v2_spec_validator,
-        '3.0.0': openapi_v3_spec_validator,
+        '3.0': openapi_v3_0_spec_validator,
+        '3.1': openapi_v3_1_spec_validator,
     }
     validator = validators[args.schema]
 
diff --git a/openapi_spec_validator/resources/schemas/v3.1/schema.json b/openapi_spec_validator/resources/schemas/v3.1/schema.json
new file mode 100644
index 0000000..f78d1f8
--- /dev/null
+++ b/openapi_spec_validator/resources/schemas/v3.1/schema.json
@@ -0,0 +1,1478 @@
+{
+  "title": "A JSON Schema for OpenAPI 3.1.X.",
+  "$schema": "https://json-schema.org/draft/2020-12/schema",
+  "description": "Validation schema for OpenAPI Specification 3.1.X.",
+  "type": "object",
+  "required": [
+    "openapi",
+    "info"
+  ],
+  "anyOf": [
+    {
+      "required": [
+        "components"
+      ]
+    },
+    {
+      "required": [
+        "paths"
+      ]
+    },
+    {
+      "required": [
+        "webhooks"
+      ]
+    }
+  ],
+  "allOf": [
+    {
+      "$ref": "#/$defs/Mixins/$defs/Extensible"
+    }
+  ],
+  "properties": {
+    "openapi": {
+      "type": "string",
+      "pattern": "^3\\.1\\.\\d(-.+)?$"
+    },
+    "jsonSchemaDialect": {
+      "type": "string",
+      "format": "uri"
+    },
+    "info": {
+      "$ref": "#/$defs/Objects/$defs/Info"
+    },
+    "externalDocs": {
+      "$ref": "#/$defs/Objects/$defs/ExternalDocumentation"
+    },
+    "servers": {
+      "type": "array",
+      "items": {
+        "$ref": "#/$defs/Objects/$defs/Server"
+      }
+    },
+    "security": {
+      "type": "array",
+      "items": {
+        "$ref": "#SecurityRequirement"
+      }
+    },
+    "tags": {
+      "type": "array",
+      "items": {
+        "$ref": "#Tag"
+      }
+    },
+    "paths": {
+      "$ref": "#/$defs/Objects/$defs/Paths"
+    },
+    "webhooks": {
+      "$comment": "TODO: Implement meta-schema for webhooks"
+    },
+    "components": {
+      "$ref": "#/$defs/Objects/$defs/Components"
+    }
+  },
+  "unevaluatedProperties": false,
+  "$defs": {
+    "Mixins": {
+      "$defs": {
+        "Describable": {
+          "properties": {
+            "description": {
+              "type": "string"
+            }
+          }
+        },
+        "Requireable": {
+          "properties": {
+            "required": {
+              "type": "boolean",
+              "default": false
+            }
+          }
+        },
+        "Deprecatable": {
+          "properties": {
+            "deprecated": {
+              "type": "boolean",
+              "default": false
+            }
+          }
+        },
+        "DescReqDep": {
+          "allOf": [
+            {
+              "$ref": "#/$defs/Mixins/$defs/Describable"
+            },
+            {
+              "$ref": "#/$defs/Mixins/$defs/Requireable"
+            },
+            {
+              "$ref": "#/$defs/Mixins/$defs/Deprecatable"
+            }
+          ]
+        },
+        "Extensible": {
+          "patternProperties": {
+            "^x-": true
+          }
+        },
+        "WithSingleExample": {
+          "properties": {
+            "example": true,
+            "examples": false
+          }
+        },
+        "WithExampleObjects": {
+          "properties": {
+            "example": false,
+            "examples": {
+              "type": "object",
+              "additionalProperties": {
+                "$ref": "#/$defs/Objects/$defs/Example"
+              }
+            }
+          }
+        },
+        "WithExamples": {
+          "oneOf": [
+            {
+              "$ref": "#/$defs/Mixins/$defs/WithSingleExample"
+            },
+            {
+              "$ref": "#/$defs/Mixins/$defs/WithExampleObjects"
+            }
+          ]
+        },
+        "WithSchema": {
+          "properties": {
+            "schema": {
+              "$ref": "#Schema"
+            },
+            "content": false
+          }
+        },
+        "WithSchemaAndExamples": {
+          "allOf": [
+            {
+              "$ref": "#/$defs/Mixins/$defs/WithSchema"
+            },
+            {
+              "$ref": "#/$defs/Mixins/$defs/WithExamples"
+            }
+          ]
+        },
+        "WithContent": {
+          "properties": {
+            "schema": false,
+            "content": {
+              "type": "object",
+              "additionalProperties": {
+                "$ref": "#MediaType"
+              },
+              "minProperties": 1,
+              "maxProperties": 1
+            }
+          }
+        },
+        "WithNameAndLocation": {
+          "required": [
+            "name",
+            "in"
+          ],
+          "properties": {
+            "name": {
+              "type": "string"
+            },
+            "in": {
+              "type": "string",
+              "enum": [
+                "path",
+                "query",
+                "header",
+                "cookie"
+              ]
+            }
+          }
+        },
+        "WithExplodeAndReserved": {
+          "properties": {
+            "explode": {
+              "type": "boolean"
+            },
+            "allowReserved": {
+              "type": "boolean",
+              "default": false
+            }
+          }
+        },
+        "WithEmpty": {
+          "properties": {
+            "allowEmptyValue": {
+              "type": "boolean",
+              "default": false
+            }
+          }
+        },
+        "WithExplodeReservedAndEmpty": {
+          "allOf": [
+            {
+              "$ref": "#/$defs/Mixins/$defs/WithExplodeAndReserved"
+            },
+            {
+              "$ref": "#/$defs/Mixins/$defs/WithEmpty"
+            }
+          ]
+        },
+        "StyledSimple": {
+          "allOf": [
+            {
+              "$ref": "#/$defs/Mixins/$defs/WithExplodeReservedAndEmpty"
+            }
+          ],
+          "properties": {
+            "style": {
+              "type": "string",
+              "const": "simple",
+              "default": "simple"
+            }
+          }
+        },
+        "StyledMatrix": {
+          "properties": {
+            "style": {
+              "type": "string",
+              "enum": [
+                "matrix",
+                "label",
+                "simple"
+              ],
+              "default": "simple"
+            }
+          }
+        },
+        "StyledFormOnly": {
+          "allOf": [
+            {
+              "$ref": "#/$defs/Mixins/$defs/WithExplodeReservedAndEmpty"
+            }
+          ],
+          "properties": {
+            "style": {
+              "type": "string",
+              "const": "form",
+              "default": "form"
+            }
+          }
+        },
+        "StyledFormComplexNoDefaultOrEmpty": {
+          "allOf": [
+            {
+              "$ref": "#/$defs/Mixins/$defs/WithExplodeAndReserved"
+            }
+          ],
+          "properties": {
+            "style": {
+              "type": "string",
+              "enum": [
+                "form",
+                "spaceDelimited",
+                "pipeDelimited",
+                "deepObject"
+              ]
+            }
+          }
+        },
+        "StyledFormComplex": {
+          "allOf": [
+            {
+              "$ref": "#/$defs/Mixins/$defs/StyledFormComplexNoDefaultOrEmpty"
+            },
+            {
+              "$ref": "#/$defs/Mixins/$defs/WithExplodeReservedAndEmpty"
+            }
+          ],
+          "properties": {
+            "style": {
+              "default": "form"
+            }
+          }
+        }
+      }
+    },
+    "Objects": {
+      "$defs": {
+        "Reference": {
+          "$anchor": "Reference",
+          "type": "object",
+          "required": [
+            "$ref"
+          ],
+          "properties": {
+            "$ref": {
+              "type": "string",
+              "format": "uri-reference"
+            }
+          }
+        },
+        "Info": {
+          "$anchor": "Info",
+          "type": "object",
+          "required": [
+            "title",
+            "version"
+          ],
+          "allOf": [
+            {
+              "$ref": "#/$defs/Mixins/$defs/Describable"
+            },
+            {
+              "$ref": "#/$defs/Mixins/$defs/Extensible"
+            }
+          ],
+          "properties": {
+            "title": {
+              "type": "string"
+            },
+            "summary": {
+              "type": "string"
+            },
+            "termsOfService": {
+              "type": "string",
+              "format": "uri-reference"
+            },
+            "contact": {
+              "$ref": "#/$defs/Objects/$defs/Info/$defs/Contact"
+            },
+            "license": {
+              "$ref": "#/$defs/Objects/$defs/Info/$defs/License"
+            }
+          },
+          "$defs": {
+            "Contact": {
+              "$anchor": "Contact",
+              "type": "object",
+              "allOf": [
+                {
+                  "$ref": "#/$defs/Mixins/$defs/Extensible"
+                }
+              ],
+              "properties": {
+                "name": {
+                  "type": "string"
+                },
+                "url": {
+                  "type": "string",
+                  "format": "uri-reference"
+                },
+                "email": {
+                  "type": "string",
+                  "format": "email"
+                }
+              },
+              "unevaluatedProperties": false
+            },
+            "License": {
+              "$anchor": "License",
+              "type": "object",
+              "required": [
+                "name"
+              ],
+              "allOf": [
+                {
+                  "$ref": "#/$defs/Mixins/$defs/Extensible"
+                }
+              ],
+              "properties": {
+                "name": {
+                  "type": "string"
+                },
+                "url": {
+                  "type": "string",
+                  "format": "uri-reference"
+                },
+                "identifier": {
+                  "type": "string"
+                }
+              },
+              "unevaluatedProperties": false
+            }
+          }
+        },
+        "Server": {
+          "$anchor": "Server",
+          "type": "object",
+          "required": [
+            "url"
+          ],
+          "allOf": [
+            {
+              "$ref": "#/$defs/Mixins/$defs/Describable"
+            },
+            {
+              "$ref": "#/$defs/Mixins/$defs/Extensible"
+            }
+          ],
+          "properties": {
+            "url": {
+              "type": "string"
+            },
+            "variables": {
+              "type": "object",
+              "additionalProperties": {
+                "type": "object",
+                "required": [
+                  "default"
+                ],
+                "allOf": [
+                  {
+                    "$ref": "#/$defs/Mixins/$defs/Describable"
+                  },
+                  {
+                    "$ref": "#/$defs/Mixins/$defs/Extensible"
+                  }
+                ],
+                "properties": {
+                  "enum": {
+                    "type": "array",
+                    "items": {
+                      "type": "string"
+                    },
+                    "minItems": 1
+                  },
+                  "default": {
+                    "type": "string"
+                  }
+                },
+                "unevaluatedProperties": false
+              }
+            }
+          },
+          "unevaluatedProperties": false
+        },
+        "Response": {
+          "$anchor": "Response",
+          "oneOf": [
+            {
+              "$ref": "#/$defs/Objects/$defs/Reference"
+            },
+            {
+              "type": "object",
+              "required": [
+                "description"
+              ],
+              "allOf": [
+                {
+                  "$ref": "#/$defs/Mixins/$defs/Describable"
+                },
+                {
+                  "$ref": "#/$defs/Mixins/$defs/Extensible"
+                }
+              ],
+              "properties": {
+                "headers": {
+                  "type": "object",
+                  "additionalProperties": {
+                    "$ref": "#Header"
+                  }
+                },
+                "content": {
+                  "type": "object",
+                  "additionalProperties": {
+                    "$ref": "#MediaType"
+                  }
+                },
+                "links": {
+                  "type": "object",
+                  "additionalProperteis": {
+                    "$ref": "#Link"
+                  }
+                }
+              },
+              "unevaluatedProperties": false
+            }
+          ]
+        },
+        "MediaType": {
+          "$anchor": "MediaType",
+          "type": "object",
+          "allOf": [
+            {
+              "$ref": "#/$defs/Mixins/$defs/WithSchemaAndExamples"
+            }
+          ],
+          "properties": {
+            "encoding": {
+              "type": "object",
+              "allOf": [
+                {
+                  "$ref": "#/$defs/Mixins/$defs/StyledFormComplexNoDefaultOrEmpty"
+                }
+              ],
+              "properties": {
+                "contentType": {
+                  "type": "string"
+                },
+                "headers": {
+                  "type": "object",
+                  "additionalProperties": {
+                    "$ref": "#Header"
+                  }
+                }
+              }
+            }
+          },
+          "unevaluatedProperties": false
+        },
+        "Example": {
+          "$anchor": "Example",
+          "oneOf": [
+            {
+              "$ref": "#/$defs/Objects/$defs/Reference"
+            },
+            {
+              "type": "object",
+              "allOf": [
+                {
+                  "$ref": "#/$defs/Mixins/$defs/Describable"
+                },
+                {
+                  "$ref": "#/$defs/Mixins/$defs/Extensible"
+                }
+              ],
+              "properties": {
+                "summary": {
+                  "type": "string"
+                },
+                "value": true,
+                "externalValue": {
+                  "type": "string",
+                  "format": "uri-reference"
+                }
+              },
+              "unevaluatedProperties": false
+            }
+          ]
+        },
+        "Header": {
+          "$anchor": "Header",
+          "oneOf": [
+            {
+              "$ref": "#/$defs/Objects/$defs/Reference"
+            },
+            {
+              "type": "object",
+              "required": [
+                "schema"
+              ],
+              "allOf": [
+                {
+                  "$ref": "#/$defs/Mixins/$defs/DescReqDep"
+                },
+                {
+                  "$ref": "#/$defs/Mixins/$defs/Extensible"
+                }
+              ],
+              "oneOf": [
+                {
+                  "allOf": [
+                    {
+                      "$ref": "#/$defs/Mixins/$defs/WithSchemaAndExamples"
+                    },
+                    {
+                      "$ref": "#/$defs/Mixins/$defs/StyledSimple"
+                    }
+                  ]
+                },
+                {
+                  "allOf": [
+                    {
+                      "$ref": "#/$defs/Mixins/$defs/WithContent"
+                    },
+                    {
+                      "$ref": "#/$defs/Mixins/$defs/WithEmpty"
+                    }
+                  ]
+                }
+              ],
+              "unevaluatedProperties": false
+            }
+          ]
+        },
+        "Paths": {
+          "$anchor": "Paths",
+          "type": "object",
+          "allOf": [
+            {
+              "$ref": "#/$defs/Mixins/$defs/Extensible"
+            }
+          ],
+          "patternProperties": {
+            "^/": {
+              "type": "object",
+              "oneOf": [
+                {
+                  "$ref": "#/$defs/Objects/$defs/Reference"
+                },
+                {
+                  "$ref": "#/$defs/Objects/$defs/PathItem"
+                }
+              ]
+            }
+          },
+          "unevaluatedProperties": false
+        },
+        "PathItem": {
+          "$anchor": "PathItem",
+          "type": "object",
+          "allOf": [
+            {
+              "$ref": "#/$defs/Mixins/$defs/Describable"
+            },
+            {
+              "$ref": "#/$defs/Mixins/$defs/Extensible"
+            }
+          ],
+          "properties": {
+            "summary": {
+              "type": "string"
+            },
+            "servers": {
+              "type": "array",
+              "items": {
+                "$ref": "#/$defs/Objects/$defs/Server"
+              }
+            },
+            "parameters": {
+              "type": "array",
+              "items": {
+                "oneOf": [
+                  {
+                    "$ref": "#/$defs/Objects/$defs/Parameter"
+                  },
+                  {
+                    "$ref": "#/$defs/Objects/$defs/Reference"
+                  }
+                ]
+              },
+              "uniqueItems": true
+            }
+          }
+        },
+        "Operation": {
+          "$anchor": "Operation",
+          "type": "object",
+          "allOf": [
+            {
+              "$ref": "#/$defs/Mixins/$defs/Describable"
+            },
+            {
+              "$ref": "#/$defs/Mixins/$defs/Deprecatable"
+            },
+            {
+              "$ref": "#/$defs/Mixins/$defs/Extensible"
+            }
+          ],
+          "properties": {
+            "tags": {
+              "type": "array",
+              "items": {
+                "type": "string"
+              }
+            },
+            "summary": {
+              "type": "string"
+            },
+            "externalDocs": {
+              "$ref": "#/$defs/Objects/$defs/ExternalDocumentation"
+            },
+            "operationId": {
+              "type": "string"
+            },
+            "parameters": {
+              "type": "array",
+              "items": {
+                "$ref": "#/$defs/Objects/$defs/Parameter"
+              }
+            },
+            "requestBody": {
+              "$ref": "#/$defs/Objects/$defs/RequestBody"
+            },
+            "responses": {
+              "$ref": "#/$defs/Objects/$defs/Responses"
+            },
+            "callback": {
+              "$ref": "#/$defs/Objects/$defs/Callback"
+            },
+            "security": {
+              "type": "array",
+              "items": {
+                "$ref": "#/$defs/Objects/$defs/SecurityRequirement"
+              }
+            },
+            "servers": {
+              "type": "array",
+              "items": {
+                "$ref": "#/$defs/Objects/$defs/Server"
+              }
+            }
+          },
+          "unevaluatedProperties": false
+        },
+        "Responses": {
+          "$anchor": "Responses",
+          "type": "object",
+          "allOf": [
+            {
+              "$ref": "#/$defs/Mixins/$defs/Extensible"
+            }
+          ],
+          "properties": {
+            "default": {
+              "$ref": "#Response"
+            }
+          },
+          "patternProperties": {
+            "[1-5](?:\\d{2}|XX)": {
+              "$ref": "#Response"
+            }
+          },
+          "minProperties": 1,
+          "unevaluatedProperties": false
+        },
+        "SecurityRequirement": {
+          "$anchor": "SecurityRequirement",
+          "type": "object",
+          "additionalProperties": {
+            "type": "array",
+            "items": {
+              "type": "string"
+            }
+          }
+        },
+        "Tag": {
+          "$anchor": "Tag",
+          "type": "object",
+          "required": [
+            "name"
+          ],
+          "allOf": [
+            {
+              "$ref": "#/$defs/Mixins/$defs/Describable"
+            },
+            {
+              "$ref": "#/$defs/Mixins/$defs/Extensible"
+            }
+          ],
+          "properties": {
+            "name": {
+              "type": "string"
+            },
+            "externalDocs": {
+              "$ref": "#/$defs/Objects/$defs/ExternalDocumentation"
+            }
+          },
+          "unevaluatedProperties": false
+        },
+        "ExternalDocumentation": {
+          "$anchor": "ExternalDocumentation",
+          "allOf": [
+            {
+              "$ref": "#/$defs/Mixins/$defs/Extensible"
+            }
+          ],
+          "type": "object",
+          "required": [
+            "url"
+          ],
+          "properties": {
+            "description": {
+              "type": "string"
+            },
+            "url": {
+              "type": "string",
+              "format": "uri-reference"
+            }
+          }
+        },
+        "Parameter": {
+          "$anchor": "Parameter",
+          "oneOf": [
+            {
+              "$ref": "#/$defs/Objects/$defs/Reference"
+            },
+            {
+              "type": "object",
+              "allOf": [
+                {
+                  "$ref": "#/$defs/Mixins/$defs/WithNameAndLocation"
+                },
+                {
+                  "$ref": "#/$defs/Mixins/$defs/Extensible"
+                },
+                {
+                  "oneOf": [
+                    {
+                      "allOf": [
+                        {
+                          "$ref": "#PathParam"
+                        },
+                        {
+                          "$ref": "#/$defs/Mixins/$defs/Describable"
+                        },
+                        {
+                          "$ref": "#/$defs/Mixins/$defs/Deprecatable"
+                        },
+                        {
+                          "required": [
+                            "required"
+                          ],
+                          "properties": {
+                            "required": {
+                              "const": true,
+                              "default": true
+                            }
+                          }
+                        }
+                      ]
+                    },
+                    {
+                      "allOf": [
+                        {
+                          "anyOf": [
+                            {
+                              "$ref": "#QueryParam"
+                            },
+                            {
+                              "$ref": "#HeaderParam"
+                            },
+                            {
+                              "$ref": "#CookieParam"
+                            }
+                          ]
+                        },
+                        {
+                          "$ref": "#/$defs/Mixins/$defs/DescReqDep"
+                        }
+                      ]
+                    }
+                  ]
+                }
+              ],
+              "oneOf": [
+                {
+                  "allOf": [
+                    {
+                      "required": [
+                        "schema"
+                      ]
+                    },
+                    {
+                      "$ref": "#/$defs/Mixins/$defs/WithSchemaAndExamples"
+                    },
+                    {
+                      "oneOf": [
+                        {
+                          "allOf": [
+                            {
+                              "$ref": "#PathParam"
+                            },
+                            {
+                              "$ref": "#/$defs/Mixins/$defs/StyledMatrix"
+                            }
+                          ]
+                        },
+                        {
+                          "allOf": [
+                            {
+                              "$ref": "#QueryParam"
+                            },
+                            {
+                              "$ref": "#/$defs/Mixins/$defs/StyledFormComplex"
+                            }
+                          ]
+                        },
+                        {
+                          "allOf": [
+                            {
+                              "$ref": "#HeaderParam"
+                            },
+                            {
+                              "$ref": "#/$defs/Mixins/$defs/StyledSimple"
+                            }
+                          ]
+                        },
+                        {
+                          "allOf": [
+                            {
+                              "$ref": "#CookieParam"
+                            },
+                            {
+                              "$ref": "#/$defs/Mixins/$defs/StyledFormOnly"
+                            }
+                          ]
+                        }
+                      ]
+                    }
+                  ]
+                },
+                {
+                  "allOf": [
+                    {
+                      "required": [
+                        "content"
+                      ]
+                    },
+                    {
+                      "$ref": "#/$defs/Mixins/$defs/WithContent"
+                    },
+                    {
+                      "$ref": "#/$defs/Mixins/$defs/WithEmpty"
+                    }
+                  ]
+                }
+              ],
+              "unevaluatedProperties": false
+            }
+          ],
+          "$defs": {
+            "PathParam": {
+              "$anchor": "PathParam",
+              "properties": {
+                "in": {
+                  "const": "path"
+                }
+              }
+            },
+            "QueryParam": {
+              "$anchor": "QueryParam",
+              "properties": {
+                "in": {
+                  "const": "query"
+                }
+              }
+            },
+            "HeaderParam": {
+              "$anchor": "HeaderParam",
+              "properties": {
+                "in": {
+                  "const": "header"
+                }
+              }
+            },
+            "CookieParam": {
+              "$anchor": "CookieParam",
+              "properties": {
+                "in": {
+                  "const": "cookie"
+                }
+              }
+            }
+          }
+        },
+        "RequestBody": {
+          "$anchor": "RequestBody",
+          "required": [
+            "content"
+          ],
+          "allOf": [
+            {
+              "$ref": "#/$defs/Mixins/$defs/Describable"
+            },
+            {
+              "$ref": "#/$defs/Mixins/$defs/Requireable"
+            },
+            {
+              "$ref": "#/$defs/Mixins/$defs/Extensible"
+            }
+          ],
+          "properties": {
+            "content": {
+              "type": "object",
+              "additionalProperties": {
+                "$ref": "#MediaType"
+              }
+            }
+          },
+          "unevaluatedProperties": false
+        },
+        "SecurityScheme": {
+          "$anchor": "SecurityScheme",
+          "oneOf": [
+            {
+              "$ref": "#/$defs/Objects/$defs/Reference"
+            },
+            {
+              "type": "object",
+              "required": [
+                "type"
+              ],
+              "allOf": [
+                {
+                  "$ref": "#/$defs/Mixins/$defs/Describable"
+                },
+                {
+                  "$ref": "#/$defs/Mixins/$defs/Extensible"
+                }
+              ],
+              "properties": {
+                "type": {
+                  "string": null,
+                  "enum": [
+                    "apiKey",
+                    "http",
+                    "bearer",
+                    "mutualTLS",
+                    "oauth2",
+                    "openIdConnect"
+                  ],
+                  "oneOf": [
+                    {
+                      "$ref": "#APIKey"
+                    },
+                    {
+                      "$ref": "#HTTP"
+                    },
+                    {
+                      "$ref": "#MutualTLS"
+                    },
+                    {
+                      "$ref": "#OAuth2"
+                    },
+                    {
+                      "$ref": "#OpenIdConnect"
+                    }
+                  ]
+                }
+              },
+              "unevaluatedProperties": false
+            }
+          ],
+          "$defs": {
+            "APIKey": {
+              "$anchor": "APIKey",
+              "properties": {
+                "type": {
+                  "const": "apiKey"
+                }
+              },
+              "allOf": [
+                {
+                  "$ref": "#/$defs/Mixins/$defs/WithNameAndLocation"
+                }
+              ],
+              "not": {
+                "$ref": "#PathParam"
+              }
+            },
+            "HTTP": {
+              "$anchor": "HTTP",
+              "required": [
+                "scheme"
+              ],
+              "properties": {
+                "scheme": {
+                  "type": "string"
+                }
+              },
+              "oneOf": [
+                {
+                  "properties": {
+                    "type": {
+                      "const": "http"
+                    }
+                  }
+                },
+                {
+                  "properties": {
+                    "type": {
+                      "const": "bearer"
+                    },
+                    "bearerFormat": {
+                      "type": "string"
+                    }
+                  }
+                }
+              ]
+            },
+            "MutalTLS": {
+              "$anchor": "MutualTLS",
+              "properties": {
+                "type": {
+                  "const": "mutualTLS"
+                }
+              }
+            },
+            "OAuth2": {
+              "$anchor": "OAuth2",
+              "required": [
+                "flows"
+              ],
+              "properties": {
+                "type": {
+                  "const": "oauth2"
+                },
+                "flows": {
+                  "$ref": "#OAuthFlows"
+                }
+              }
+            },
+            "OpenIdConnect": {
+              "$anchor": "OpenIdConnect",
+              "required": [
+                "openIdConnectUrl"
+              ],
+              "properties": {
+                "type": {
+                  "const": "openIdConnect"
+                },
+                "openIdConnectUrl": {
+                  "type": "string",
+                  "format": "uri"
+                }
+              }
+            }
+          }
+        },
+        "OAuthFlows": {
+          "$anchor": "OAuthFlows",
+          "type": "object",
+          "allOf": [
+            {
+              "$ref": "#/$defs/Mixins/$defs/Extensible"
+            }
+          ],
+          "properties": {
+            "implicit": {
+              "allOf": [
+                {
+                  "required": [
+                    "scopes"
+                  ]
+                },
+                {
+                  "$ref": "#CommonFlow"
+                },
+                {
+                  "$ref": "#AuthorizationFlow"
+                },
+                {
+                  "$ref": "#/$defs/Mixins/$defs/Extensible"
+                }
+              ],
+              "unevaluatedProperties": false
+            },
+            "password": {
+              "allOf": [
+                {
+                  "$ref": "#CommonFlow"
+                },
+                {
+                  "$ref": "#TokenFlow"
+                },
+                {
+                  "$ref": "#/$defs/Mixins/$defs/Extensible"
+                }
+              ],
+              "unevaluatedProperties": false
+            },
+            "clientCredentials": {
+              "allOf": [
+                {
+                  "$ref": "#CommonFlow"
+                },
+                {
+                  "$ref": "#TokenFlow"
+                },
+                {
+                  "$ref": "#/$defs/Mixins/$defs/Extensible"
+                }
+              ],
+              "unevaluatedProperties": false
+            },
+            "authorizationCode": {
+              "allOf": [
+                {
+                  "$ref": "#CommonFlow"
+                },
+                {
+                  "$ref": "#TokenFlow"
+                },
+                {
+                  "$ref": "#AuthorizationFlow"
+                },
+                {
+                  "$ref": "#/$defs/Mixins/$defs/Extensible"
+                }
+              ],
+              "unevaluatedProperties": false
+            }
+          },
+          "unevaluatedProperties": false,
+          "$defs": {
+            "Common": {
+              "$anchor": "CommonFlow",
+              "properties": {
+                "refreshUrl": {
+                  "type": "string",
+                  "format": "uri-reference"
+                },
+                "scopes": {
+                  "type": "object",
+                  "additionalProperties": {
+                    "type": "string"
+                  }
+                }
+              }
+            },
+            "Token": {
+              "$anchor": "TokenFlow",
+              "required": [
+                "tokenUrl"
+              ],
+              "properties": {
+                "tokenUrl": {
+                  "type": "string",
+                  "format": "uri-reference"
+                }
+              }
+            },
+            "Authorization": {
+              "$anchor": "AuthorizationFlow",
+              "required": [
+                "authorizationUrl"
+              ],
+              "properties": {
+                "authorizationUrl": {
+                  "type": "string",
+                  "format": "uri-reference"
+                }
+              }
+            }
+          }
+        },
+        "Link": {
+          "$anchor": "Link",
+          "oneOf": [
+            {
+              "$ref": "#/$defs/Objects/$defs/Reference"
+            },
+            {
+              "type": "object",
+              "allOf": [
+                {
+                  "$ref": "#/$defs/Mixins/$defs/Describable"
+                },
+                {
+                  "$ref": "#/$defs/Mixins/$defs/Extensible"
+                }
+              ],
+              "oneOf": [
+                {
+                  "properties": {
+                    "operationRef": {
+                      "type": "string",
+                      "format": "uri-reference"
+                    },
+                    "operationId": false
+                  }
+                },
+                {
+                  "properties": {
+                    "operationRef": false,
+                    "operationId": {
+                      "type": "string"
+                    }
+                  }
+                }
+              ],
+              "properties": {
+                "parameters": {
+                  "type": "object",
+                  "additionalProperties": true
+                },
+                "requestBody": true,
+                "server": {
+                  "$ref": "#/$defs/Objects/$defs/Server"
+                }
+              },
+              "unevaluatedProperties": false
+            }
+          ]
+        },
+        "Callback": {
+          "$anchor": "Callback",
+          "oneOf": [
+            {
+              "$ref": "#/$defs/Objects/$defs/Reference"
+            },
+            {
+              "type": "object",
+              "allOf": [
+                {
+                  "$ref": "#/$defs/Mixins/$defs/Extensible"
+                }
+              ],
+              "unevaluatedProperties": false
+            }
+          ]
+        },
+        "Schema": {
+          "$anchor": "Schema",
+          "oneOf": [
+            {
+              "$ref": "#/$defs/Objects/$defs/Reference"
+            },
+            {
+              "type": "object",
+              "allOf": [
+                {
+                  "$ref": "https://json-schema.org/draft/2020-12/schema"
+                },
+                {
+                  "$ref": "#/$defs/Objects/$defs/Schema/$defs/Extensions"
+                },
+                {
+                  "$ref": "#/$defs/Mixins/$defs/Extensible"
+                }
+              ],
+              "unevaluatedProperties": false
+            }
+          ],
+          "$defs": {
+            "Extensions": {
+              "$anchor": "SchemaExtensions",
+              "properties": {
+                "discriminator": {
+                  "$ref": "#/$defs/Objects/$defs/Discriminator"
+                },
+                "externalDocs": {
+                  "$ref": "#/$defs/Objects/$defs/ExternalDocumentation"
+                },
+                "xml": {
+                  "$ref": "#XML"
+                }
+              }
+            },
+            "Discriminator": {
+              "$anchor": "Discriminator",
+              "type": "object",
+              "required": [
+                "propertyName"
+              ],
+              "properties": {
+                "propertyName": {
+                  "type": "string"
+                },
+                "mapping": {
+                  "type": "object",
+                  "additionalProperties": {
+                    "type": "string"
+                  }
+                }
+              }
+            },
+            "XML": {
+              "$anchor": "XML",
+              "allOf": [
+                {
+                  "$ref": "#/$defs/Mixins/$defs/Extensible"
+                },
+                {
+                  "type": "object",
+                  "properties": {
+                    "name": {
+                      "type": "string"
+                    },
+                    "namespace": {
+                      "type": "string",
+                      "format": "url"
+                    },
+                    "prefix": {
+                      "type": "string"
+                    },
+                    "attribute": {
+                      "type": "boolean",
+                      "default": false
+                    },
+                    "wrapped": {
+                      "type": "boolean",
+                      "default": false
+                    }
+                  }
+                }
+              ],
+              "additionalProperties": false
+            }
+          }
+        },
+        "Components": {
+          "$anchor": "Components",
+          "type": "object",
+          "allOf": [
+            {
+              "$ref": "#/$defs/Mixins/$defs/Extensible"
+            },
+            {
+              "additionalProperties": {
+                "$comment": "All sub-objects have the same property name constraints",
+                "type": "object",
+                "propertyNames": {
+                  "pattern": "^[a-zA-Z0-9\\.\\-_]+$"
+                }
+              }
+            }
+          ],
+          "properties": {
+            "schemas": {
+              "additionalProperties": {
+                "$ref": "#/$defs/Objects/$defs/Schema"
+              }
+            },
+            "responses": {
+              "additionalProperties": {
+                "$ref": "#/$defs/Objects/$defs/Responses"
+              }
+            },
+            "parameters": {
+              "additionalProperties": {
+                "$ref": "#/$defs/Objects/$defs/Parameter"
+              }
+            },
+            "pathItems": {
+              "additionalProperties": {
+                "$ref": "#/$defs/Objects/$defs/PathItem"
+              }
+            },
+            "examples": {
+              "additionalProperties": {
+                "$ref": "#/$defs/Objects/$defs/Example"
+              }
+            },
+            "requestBodies": {
+              "additionalProperties": {
+                "$ref": "#/$defs/Objects/$defs/RequestBody"
+              }
+            },
+            "headers": {
+              "additionalProperties": {
+                "$ref": "#/$defs/Objects/$defs/Header"
+              }
+            },
+            "links": {
+              "additionalProperties": {
+                "$ref": "#/$defs/Objects/$defs/Link"
+              }
+            },
+            "callbacks": {
+              "additionalProperties": {
+                "$ref": "#/$defs/Objects/$defs/Callback"
+              }
+            }
+          },
+          "unevaluatedProperties": false
+        }
+      }
+    }
+  }
+}
diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py
index a83ced1..74c7f23 100644
--- a/tests/integration/conftest.py
+++ b/tests/integration/conftest.py
@@ -5,8 +5,10 @@
 from six.moves.urllib.parse import urlunparse
 from yaml import safe_load
 
-from openapi_spec_validator import (openapi_v3_spec_validator,
-                                    openapi_v2_spec_validator)
+from openapi_spec_validator import (
+    openapi_v2_spec_validator, openapi_v3_0_spec_validator,
+    openapi_v3_1_spec_validator,
+)
 from openapi_spec_validator.schemas import read_yaml_file
 
 
@@ -42,8 +44,13 @@ def factory():
 
 
 @pytest.fixture
-def validator():
-    return openapi_v3_spec_validator
+def v3_0_validator():
+    return openapi_v3_0_spec_validator
+
+
+@pytest.fixture
+def v3_1_validator():
+    return openapi_v3_1_spec_validator
 
 
 @pytest.fixture
diff --git a/tests/integration/data/v3.1/empty.yaml b/tests/integration/data/v3.1/empty.yaml
new file mode 100644
index 0000000..9151fcb
--- /dev/null
+++ b/tests/integration/data/v3.1/empty.yaml
@@ -0,0 +1 @@
+openapi: "3.1.0"
\ No newline at end of file
diff --git a/tests/integration/data/v3.1/petstore.yaml b/tests/integration/data/v3.1/petstore.yaml
new file mode 100644
index 0000000..ea4218d
--- /dev/null
+++ b/tests/integration/data/v3.1/petstore.yaml
@@ -0,0 +1,109 @@
+openapi: "3.1.0"
+info:
+  version: 1.0.0
+  title: Swagger Petstore
+  license:
+    name: MIT
+servers:
+  - url: http://petstore.swagger.io/v1
+paths:
+  /pets:
+    get:
+      summary: List all pets
+      operationId: listPets
+      tags:
+        - pets
+      parameters:
+        - name: limit
+          in: query
+          description: How many items to return at one time (max 100)
+          required: false
+          schema:
+            type: integer
+            format: int32
+      responses:
+        200:
+          description: An paged array of pets
+          headers:
+            x-next:
+              description: A link to the next page of responses
+              schema:
+                type: string
+          content:
+            application/json:    
+              schema:
+                $ref: "#/components/schemas/Pets"
+        default:
+          description: unexpected error
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/Error"
+    post:
+      summary: Create a pet
+      operationId: createPets
+      tags:
+        - pets
+      responses:
+        '201':
+          description: Null response
+        default:
+          description: unexpected error
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/Error"
+  /pets/{petId}:
+    get:
+      summary: Info for a specific pet
+      operationId: showPetById
+      tags:
+        - pets
+      parameters:
+        - name: petId
+          in: path
+          required: true
+          description: The id of the pet to retrieve
+          schema:
+            type: string
+      responses:
+        '200':
+          description: Expected response to a valid request
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/Pets"
+        default:
+          description: unexpected error
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/Error"
+components:
+  schemas:
+    Pet:
+      required:
+        - id
+        - name
+      properties:
+        id:
+          type: integer
+          format: int64
+        name:
+          type: string
+        tag:
+          type: string
+    Pets:
+      type: array
+      items:
+        $ref: "#/components/schemas/Pet"
+    Error:
+      required:
+        - code
+        - message
+      properties:
+        code:
+          type: integer
+          format: int32
+        message:
+          type: string
\ No newline at end of file
diff --git a/tests/integration/test_main.py b/tests/integration/test_main.py
index bbc1e1f..3c196f0 100644
--- a/tests/integration/test_main.py
+++ b/tests/integration/test_main.py
@@ -7,18 +7,25 @@
 
 
 def test_schema_default():
-    """Test default schema is 3.0.0"""
+    """Test default schema is 3.0"""
     testargs = ['./tests/integration/data/v3.0/petstore.yaml']
     main(testargs)
 
 
-def test_schema_v3():
-    """No errors when calling proper v3 file."""
-    testargs = ['--schema', '3.0.0',
+def test_schema_v3_0():
+    """No errors when calling proper v3.0 file."""
+    testargs = ['--schema', '3.0',
                 './tests/integration/data/v3.0/petstore.yaml']
     main(testargs)
 
 
+def test_schema_v3_1():
+    """No errors when calling proper v3.1 file."""
+    testargs = ['--schema', '3.1',
+                './tests/integration/data/v3.1/petstore.yaml']
+    main(testargs)
+
+
 def test_schema_v2():
     """No errors when calling with proper v2 file."""
     testargs = ['--schema', '2.0',
@@ -36,19 +43,19 @@ def test_schema_unknown():
 
 def test_validation_error():
     """SystemExit on running with ValidationError."""
-    testargs = ['--schema', '3.0.0',
+    testargs = ['--schema', '3.0',
                 './tests/integration/data/v2.0/petstore.yaml']
     with pytest.raises(SystemExit):
         main(testargs)
 
 
 @mock.patch(
-    'openapi_spec_validator.__main__.openapi_v3_spec_validator.validate',
+    'openapi_spec_validator.__main__.openapi_v3_0_spec_validator.validate',
     side_effect=Exception,
 )
 def test_unknown_error(m_validate):
     """SystemExit on running with unknown error."""
-    testargs = ['--schema', '3.0.0',
+    testargs = ['--schema', '3.0',
                 './tests/integration/data/v2.0/petstore.yaml']
     with pytest.raises(SystemExit):
         main(testargs)
@@ -62,12 +69,12 @@ def test_nonexisting_file():
 
 
 def test_schema_stdin():
-    """Test schema from STDIN"""
+    """Test schema is 3.0 from STDIN"""
     spes_path = './tests/integration/data/v3.0/petstore.yaml'
     with open(spes_path, 'r') as spec_file:
         spec_lines = spec_file.readlines()
     spec_io = StringIO("".join(spec_lines))
 
-    testargs = ['-']
+    testargs = ['--schema', '3.0', '-']
     with mock.patch('openapi_spec_validator.__main__.sys.stdin', spec_io):
         main(testargs)
diff --git a/tests/integration/test_shortcuts.py b/tests/integration/test_v2_shortcuts.py
similarity index 53%
rename from tests/integration/test_shortcuts.py
rename to tests/integration/test_v2_shortcuts.py
index f27aa18..e54f129 100644
--- a/tests/integration/test_shortcuts.py
+++ b/tests/integration/test_v2_shortcuts.py
@@ -1,10 +1,8 @@
 import pytest
 
 from openapi_spec_validator import (
-    validate_spec, validate_spec_url,
     validate_v2_spec, validate_v2_spec_url,
-    validate_spec_url_factory,
-    openapi_v2_spec_validator, openapi_v3_spec_validator,
+    validate_spec_url_factory, openapi_v2_spec_validator,
 )
 from openapi_spec_validator.exceptions import OpenAPIValidationError
 from openapi_spec_validator.handlers.urllib import UrllibHandler
@@ -23,19 +21,6 @@ def test_failed(self, spec):
             validate_v2_spec(spec)
 
 
-class BaseTestValidValidteSpec:
-
-    def test_valid(self, spec):
-        validate_spec(spec)
-
-
-class BaseTestFaliedValidateSpec:
-
-    def test_failed(self, spec):
-        with pytest.raises(OpenAPIValidationError):
-            validate_spec(spec)
-
-
 class BaseTestValidValidateSpecUrl:
 
     @pytest.fixture
@@ -70,34 +55,6 @@ def test_failed(self, spec_url):
             validate_v2_spec_url(spec_url)
 
 
-class BaseTestValidValidateV3SpecUrl(BaseTestValidValidateSpecUrl):
-
-    @pytest.fixture
-    def validate_spec_url_callable(self, urllib_handlers):
-        return validate_spec_url_factory(
-            openapi_v3_spec_validator.validate, urllib_handlers)
-
-    def test_default_valid(self, spec_url):
-        validate_spec_url(spec_url)
-
-    def test_urllib_valid(self, validate_spec_url_callable, spec_url):
-        validate_spec_url_callable(spec_url)
-
-
-class BaseTestFaliedValidateSpecUrl:
-
-    def test_failed(self, spec_url):
-        with pytest.raises(OpenAPIValidationError):
-            validate_spec_url(spec_url)
-
-
-class TestLocalEmptyExample(BaseTestFaliedValidateSpec):
-
-    @pytest.fixture
-    def spec(self, factory):
-        return factory.spec_from_file("data/v3.0/empty.yaml")
-
-
 class TestLocalPetstoreV2Example(BaseTestValidValidteV2Spec):
 
     @pytest.fixture
@@ -105,13 +62,6 @@ def spec(self, factory):
         return factory.spec_from_file("data/v2.0/petstore.yaml")
 
 
-class TestLocalPetstoreExample(BaseTestValidValidteSpec):
-
-    @pytest.fixture
-    def spec(self, factory):
-        return factory.spec_from_file("data/v3.0/petstore.yaml")
-
-
 class TestPetstoreV2Example(BaseTestValidValidateV2SpecUrl):
 
     @pytest.fixture
@@ -143,36 +93,3 @@ def spec_url(self):
             'f25a1d44cff9669703257173e562376cc5bd0ec6/examples/v2.0/'
             'yaml/petstore-expanded.yaml'
         )
-
-
-class TestPetstoreExample(BaseTestValidValidateV3SpecUrl):
-
-    @pytest.fixture
-    def spec_url(self):
-        return (
-            'https://raw.githubusercontent.com/OAI/OpenAPI-Specification/'
-            'f75f8486a1aae1a7ceef92fbc63692cb2556c0cd/examples/v3.0/'
-            'petstore.yaml'
-        )
-
-
-class TestApiWithExampe(BaseTestValidValidateV3SpecUrl):
-
-    @pytest.fixture
-    def spec_url(self):
-        return (
-            'https://raw.githubusercontent.com/OAI/OpenAPI-Specification/'
-            'f75f8486a1aae1a7ceef92fbc63692cb2556c0cd/examples/v3.0/'
-            'api-with-examples.yaml'
-        )
-
-
-class TestPetstoreExpandedExample(BaseTestValidValidateV3SpecUrl):
-
-    @pytest.fixture
-    def spec_url(self):
-        return (
-            'https://raw.githubusercontent.com/OAI/OpenAPI-Specification/'
-            '970566d5ca236a5ce1a02fb7d617fdbd07df88db/examples/v3.0/'
-            'api-with-examples.yaml'
-        )
diff --git a/tests/integration/test_v2_validator.py b/tests/integration/test_v2_validator.py
new file mode 100644
index 0000000..be187a3
--- /dev/null
+++ b/tests/integration/test_v2_validator.py
@@ -0,0 +1,42 @@
+from openapi_spec_validator.exceptions import OpenAPIValidationError
+
+
+class TestSpecValidatorIterErrors(object):
+
+    def test_parameter_default_value_wrong_type(self, swagger_validator):
+        spec = {
+            'swagger': '2.0',
+            'info': {
+                'title': 'Test Api',
+                'version': '0.0.1',
+            },
+            'paths': {
+                '/test/': {
+                    'get': {
+                        'responses': {
+                            '200': {
+                                'description': 'OK',
+                                'schema': {'type': 'object'},
+                            },
+                        },
+                        'parameters': [
+                            {
+                                'name': 'param1',
+                                'in': 'query',
+                                'type': 'integer',
+                                'default': 'invaldtype',
+                            },
+                        ],
+                    },
+                },
+            },
+        }
+
+        errors = swagger_validator.iter_errors(spec)
+
+        errors_list = list(errors)
+        assert len(errors_list) == 1
+        assert errors_list[0].__class__ == OpenAPIValidationError
+        assert errors_list[0].message == (
+            "'invaldtype' is not of type integer"
+        )
diff --git a/tests/integration/test_v3_0_shortcuts.py b/tests/integration/test_v3_0_shortcuts.py
new file mode 100644
index 0000000..4262f7b
--- /dev/null
+++ b/tests/integration/test_v3_0_shortcuts.py
@@ -0,0 +1,102 @@
+import pytest
+
+from openapi_spec_validator import (
+    validate_v3_0_spec, validate_v3_0_spec_url,
+    validate_spec_url_factory, openapi_v3_0_spec_validator,
+)
+from openapi_spec_validator.exceptions import OpenAPIValidationError
+from openapi_spec_validator.handlers.urllib import UrllibHandler
+
+
+class BaseTestValidValidteSpec:
+
+    def test_valid(self, spec):
+        validate_v3_0_spec(spec)
+
+
+class BaseTestFaliedValidateSpec:
+
+    def test_failed(self, spec):
+        with pytest.raises(OpenAPIValidationError):
+            validate_v3_0_spec(spec)
+
+
+class BaseTestValidValidateSpecUrl:
+
+    @pytest.fixture
+    def urllib_handlers(self):
+        all_urls_handler = UrllibHandler('http', 'https', 'file')
+        return {
+            '<all_urls>': all_urls_handler,
+            'http': UrllibHandler('http'),
+            'https': UrllibHandler('https'),
+            'file': UrllibHandler('file'),
+        }
+
+
+class BaseTestValidValidateV3SpecUrl(BaseTestValidValidateSpecUrl):
+
+    @pytest.fixture
+    def validate_spec_url_callable(self, urllib_handlers):
+        return validate_spec_url_factory(
+            openapi_v3_0_spec_validator.validate, urllib_handlers)
+
+    def test_default_valid(self, spec_url):
+        validate_v3_0_spec_url(spec_url)
+
+    def test_urllib_valid(self, validate_spec_url_callable, spec_url):
+        validate_spec_url_callable(spec_url)
+
+
+class BaseTestFaliedValidateSpecUrl:
+
+    def test_failed(self, spec_url):
+        with pytest.raises(OpenAPIValidationError):
+            validate_v3_0_spec_url(spec_url)
+
+
+class TestLocalEmptyv3Example(BaseTestFaliedValidateSpec):
+
+    @pytest.fixture
+    def spec(self, factory):
+        return factory.spec_from_file("data/v3.0/empty.yaml")
+
+
+class TestLocalPetstoreV3Example(BaseTestValidValidteSpec):
+
+    @pytest.fixture
+    def spec(self, factory):
+        return factory.spec_from_file("data/v3.0/petstore.yaml")
+
+
+class TestPetstoreV3Example(BaseTestValidValidateV3SpecUrl):
+
+    @pytest.fixture
+    def spec_url(self):
+        return (
+            'https://raw.githubusercontent.com/OAI/OpenAPI-Specification/'
+            'f75f8486a1aae1a7ceef92fbc63692cb2556c0cd/examples/v3.0/'
+            'petstore.yaml'
+        )
+
+
+class TestApiWithV3Exampe(BaseTestValidValidateV3SpecUrl):
+
+    @pytest.fixture
+    def spec_url(self):
+        return (
+            'https://raw.githubusercontent.com/OAI/OpenAPI-Specification/'
+            'f75f8486a1aae1a7ceef92fbc63692cb2556c0cd/examples/v3.0/'
+            'api-with-examples.yaml'
+        )
+
+
+class TestPetstoreV3ExpandedExample(BaseTestValidValidateV3SpecUrl):
+
+    @pytest.fixture
+    def spec_url(self):
+        return (
+            'https://raw.githubusercontent.com/OAI/OpenAPI-Specification/'
+            '970566d5ca236a5ce1a02fb7d617fdbd07df88db/examples/v3.0/'
+            'api-with-examples.yaml'
+        )
diff --git a/tests/integration/test_validate.py b/tests/integration/test_v3_0_validate.py
similarity index 91%
rename from tests/integration/test_validate.py
rename to tests/integration/test_v3_0_validate.py
index 9bb3e08..e96f6c8 100644
--- a/tests/integration/test_validate.py
+++ b/tests/integration/test_v3_0_validate.py
@@ -9,8 +9,8 @@ class BaseTestValidOpeAPIv3Validator(object):
     def spec_url(self):
         return ''
 
-    def test_valid(self, validator, spec, spec_url):
-        return validator.validate(spec, spec_url=spec_url)
+    def test_valid(self, v3_0_validator, spec, spec_url):
+        return v3_0_validator.validate(spec, spec_url=spec_url)
 
 
 class BaseTestFailedOpeAPIv3Validator(object):
@@ -19,9 +19,9 @@ class BaseTestFailedOpeAPIv3Validator(object):
     def spec_url(self):
         return ''
 
-    def test_failed(self, validator, spec, spec_url):
+    def test_failed(self, v3_0_validator, spec, spec_url):
         with pytest.raises(OpenAPIValidationError):
-            validator.validate(spec, spec_url=spec_url)
+            v3_0_validator.validate(spec, spec_url=spec_url)
 
 
 class TestLocalEmptyExample(BaseTestFailedOpeAPIv3Validator):
diff --git a/tests/integration/test_validators.py b/tests/integration/test_v3_0_validator.py
similarity index 89%
rename from tests/integration/test_validators.py
rename to tests/integration/test_v3_0_validator.py
index 5172b88..a395427 100644
--- a/tests/integration/test_validators.py
+++ b/tests/integration/test_v3_0_validator.py
@@ -1,3 +1,5 @@
+import pytest
+
 from openapi_spec_validator.exceptions import (
     ExtraParametersError, UnresolvableParameterError, OpenAPIValidationError,
     DuplicateOperationIDError,
@@ -6,6 +8,10 @@
 
 class TestSpecValidatorIterErrors(object):
 
+    @pytest.fixture
+    def validator(self, v3_0_validator):
+        return v3_0_validator
+
     def test_empty(self, validator):
         spec = {}
 
@@ -296,45 +302,6 @@ def test_parameter_default_value_wrong_type(self, validator):
             "'invaldtype' is not of type integer"
         )
 
-    def test_parameter_default_value_wrong_type_swagger(self,
-                                                        swagger_validator):
-        spec = {
-            'swagger': '2.0',
-            'info': {
-                'title': 'Test Api',
-                'version': '0.0.1',
-            },
-            'paths': {
-                '/test/': {
-                    'get': {
-                        'responses': {
-                            '200': {
-                                'description': 'OK',
-                                'schema': {'type': 'object'},
-                            },
-                        },
-                        'parameters': [
-                            {
-                                'name': 'param1',
-                                'in': 'query',
-                                'type': 'integer',
-                                'default': 'invaldtype',
-                            },
-                        ],
-                    },
-                },
-            },
-        }
-
-        errors = swagger_validator.iter_errors(spec)
-
-        errors_list = list(errors)
-        assert len(errors_list) == 1
-        assert errors_list[0].__class__ == OpenAPIValidationError
-        assert errors_list[0].message == (
-            "'invaldtype' is not of type integer"
-        )
-
     def test_parameter_default_value_with_reference(self, validator):
         spec = {
             'openapi': '3.0.0',
diff --git a/tests/integration/test_v3_1_shortcuts.py b/tests/integration/test_v3_1_shortcuts.py
new file mode 100644
index 0000000..496e02d
--- /dev/null
+++ b/tests/integration/test_v3_1_shortcuts.py
@@ -0,0 +1,80 @@
+import pytest
+
+from openapi_spec_validator import (
+    validate_v3_1_spec, validate_v3_1_spec_url,
+    validate_spec_url_factory, openapi_v3_1_spec_validator,
+)
+from openapi_spec_validator.exceptions import OpenAPIValidationError
+from openapi_spec_validator.handlers.urllib import UrllibHandler
+
+
+class BaseTestValidValidteSpec:
+
+    def test_valid(self, spec):
+        validate_v3_1_spec(spec)
+
+
+class BaseTestFaliedValidateSpec:
+
+    def test_failed(self, spec):
+        with pytest.raises(OpenAPIValidationError):
+            validate_v3_1_spec(spec)
+
+
+class BaseTestValidValidateSpecUrl:
+
+    @pytest.fixture
+    def urllib_handlers(self):
+        all_urls_handler = UrllibHandler('http', 'https', 'file')
+        return {
+            '<all_urls>': all_urls_handler,
+            'http': UrllibHandler('http'),
+            'https': UrllibHandler('https'),
+            'file': UrllibHandler('file'),
+        }
+
+
+class BaseTestValidValidateV3SpecUrl(BaseTestValidValidateSpecUrl):
+
+    @pytest.fixture
+    def validate_spec_url_callable(self, urllib_handlers):
+        return validate_spec_url_factory(
+            openapi_v3_1_spec_validator.validate, urllib_handlers)
+
+    def test_default_valid(self, spec_url):
+        validate_v3_1_spec_url(spec_url)
+
+    def test_urllib_valid(self, validate_spec_url_callable, spec_url):
+        validate_spec_url_callable(spec_url)
+
+
+class BaseTestFaliedValidateSpecUrl:
+
+    def test_failed(self, spec_url):
+        with pytest.raises(OpenAPIValidationError):
+            validate_v3_1_spec_url(spec_url)
+
+
+class TestLocalEmptyv3Example(BaseTestFaliedValidateSpec):
+
+    @pytest.fixture
+    def spec(self, factory):
+        return factory.spec_from_file("data/v3.1/empty.yaml")
+
+
+class TestLocalPetstoreV3Example(BaseTestValidValidteSpec):
+
+    @pytest.fixture
+    def spec(self, factory):
+        return factory.spec_from_file("data/v3.1/petstore.yaml")
+
+
+class TestRemoteV3WebhookExampe(BaseTestValidValidateV3SpecUrl):
+
+    @pytest.fixture
+    def spec_url(self):
+        return (
+            'https://raw.githubusercontent.com/OAI/OpenAPI-Specification/'
+            'f1adc846131b33be72df6a0c87e5e5da59dde0ff/examples/v3.1/'
+            'webhook-example.yaml'
+        )
diff --git a/tests/integration/test_v3_1_validate.py b/tests/integration/test_v3_1_validate.py
new file mode 100644
index 0000000..f5fee6d
--- /dev/null
+++ b/tests/integration/test_v3_1_validate.py
@@ -0,0 +1,50 @@
+import pytest
+
+from openapi_spec_validator.exceptions import OpenAPIValidationError
+
+
+class BaseTestValidOpeAPIv3Validator(object):
+
+    @pytest.fixture
+    def spec_url(self):
+        return ''
+
+    def test_valid(self, v3_1_validator, spec, spec_url):
+        return v3_1_validator.validate(spec, spec_url=spec_url)
+
+
+class BaseTestFailedOpeAPIv3Validator(object):
+
+    @pytest.fixture
+    def spec_url(self):
+        return ''
+
+    def test_failed(self, v3_1_validator, spec, spec_url):
+        with pytest.raises(OpenAPIValidationError):
+            v3_1_validator.validate(spec, spec_url=spec_url)
+
+
+class TestLocalEmptyExample(BaseTestFailedOpeAPIv3Validator):
+
+    @pytest.fixture
+    def spec(self, factory):
+        return factory.spec_from_file("data/v3.1/empty.yaml")
+
+
+class TestLocalPetstoreExample(BaseTestValidOpeAPIv3Validator):
+
+    @pytest.fixture
+    def spec(self, factory):
+        return factory.spec_from_file("data/v3.1/petstore.yaml")
+
+
+class TestRemoteV3WebhookExampe(BaseTestValidOpeAPIv3Validator):
+
+    @pytest.fixture
+    def spec(self, factory):
+        url = (
+            'https://raw.githubusercontent.com/OAI/OpenAPI-Specification/'
+            'f1adc846131b33be72df6a0c87e5e5da59dde0ff/examples/v3.1/'
+            'webhook-example.yaml'
+        )
+        return factory.spec_from_url(url)

From 21186d7eb8365472fe303f79aebf951721f14ca3 Mon Sep 17 00:00:00 2001
From: p1c2u <maciag.artur@gmail.com>
Date: Sat, 27 Feb 2021 19:59:38 +0000
Subject: [PATCH 2/2] Separate schema validators refactor

---
 openapi_spec_validator/__init__.py            |  26 +-
 openapi_spec_validator/decorators.py          |  44 ---
 .../dereferencing/decorators.py               |  23 ++
 .../dereferencing/dereferencers.py            |  22 ++
 .../dereferencing/managers.py                 |  73 ++++
 openapi_spec_validator/dereferencing/utils.py |   2 +
 openapi_spec_validator/factories.py           |  86 +++--
 openapi_spec_validator/generators.py          |  42 ---
 openapi_spec_validator/managers.py            |  36 --
 openapi_spec_validator/schemas.py             |   3 +-
 openapi_spec_validator/validators.py          |  23 +-
 tests/integration/test_v3_1_validator.py      | 351 ++++++++++++++++++
 12 files changed, 548 insertions(+), 183 deletions(-)
 create mode 100644 openapi_spec_validator/dereferencing/decorators.py
 create mode 100644 openapi_spec_validator/dereferencing/dereferencers.py
 create mode 100644 openapi_spec_validator/dereferencing/managers.py
 create mode 100644 openapi_spec_validator/dereferencing/utils.py
 delete mode 100644 openapi_spec_validator/generators.py
 delete mode 100644 openapi_spec_validator/managers.py
 create mode 100644 tests/integration/test_v3_1_validator.py

diff --git a/openapi_spec_validator/__init__.py b/openapi_spec_validator/__init__.py
index ad675dc..2cc7174 100644
--- a/openapi_spec_validator/__init__.py
+++ b/openapi_spec_validator/__init__.py
@@ -1,10 +1,14 @@
 # -*- coding: utf-8 -*-
+from jsonschema.validators import Draft4Validator, Draft7Validator
+
 from openapi_spec_validator.shortcuts import (
     validate_spec_factory, validate_spec_url_factory,
 )
 from openapi_spec_validator.handlers import UrlHandler, FileObjectHandler
 from openapi_spec_validator.schemas import get_openapi_schema
-from openapi_spec_validator.factories import JSONSpecValidatorFactory
+from openapi_spec_validator.factories import (
+    OpenAPIValidatorFactory, OpenAPISpecValidatorFactory,
+)
 from openapi_spec_validator.validators import SpecValidator
 
 __author__ = 'Artur Maciag'
@@ -35,9 +39,14 @@
     'file': UrlHandler('file'),
 }
 
+# schemas
+draft4_validator_factory = OpenAPIValidatorFactory(Draft4Validator)
+draft7_validator_factory = OpenAPIValidatorFactory(Draft7Validator)
+
 # v2.0 spec
-schema_v2, schema_v2_url = get_openapi_schema('2.0')
-openapi_v2_validator_factory = JSONSpecValidatorFactory(
+schema_v2, schema_v2_url = get_openapi_schema('2.0', Draft4Validator)
+openapi_v2_validator_factory = OpenAPISpecValidatorFactory(
+    draft4_validator_factory,
     schema_v2, schema_v2_url,
     resolver_handlers=default_handlers,
 )
@@ -47,8 +56,9 @@
 )
 
 # v3.0 spec
-schema_v3_0, schema_v3_0_url = get_openapi_schema('3.0')
-openapi_v3_0_validator_factory = JSONSpecValidatorFactory(
+schema_v3_0, schema_v3_0_url = get_openapi_schema('3.0', Draft4Validator)
+openapi_v3_0_validator_factory = OpenAPISpecValidatorFactory(
+    draft4_validator_factory,
     schema_v3_0, schema_v3_0_url,
     resolver_handlers=default_handlers,
 )
@@ -58,8 +68,10 @@
 )
 
 # v3.1 spec
-schema_v3_1, schema_v3_1_url = get_openapi_schema('3.1')
-openapi_v3_1_validator_factory = JSONSpecValidatorFactory(
+# @TODO: change to 2020-12 validator when released
+schema_v3_1, schema_v3_1_url = get_openapi_schema('3.1', Draft7Validator)
+openapi_v3_1_validator_factory = OpenAPISpecValidatorFactory(
+    draft7_validator_factory,
     schema_v3_1, schema_v3_1_url,
     resolver_handlers=default_handlers,
 )
diff --git a/openapi_spec_validator/decorators.py b/openapi_spec_validator/decorators.py
index 27eba96..b609e0e 100644
--- a/openapi_spec_validator/decorators.py
+++ b/openapi_spec_validator/decorators.py
@@ -1,49 +1,5 @@
 """OpenAPI spec validator decorators module."""
 from functools import wraps
-import logging
-
-from openapi_spec_validator.managers import VisitingManager
-
-log = logging.getLogger(__name__)
-
-
-class DerefValidatorDecorator:
-    """Dereferences instance if it is a $ref before passing it for validation.
-
-    :param instance_resolver: Resolves refs in the openapi service spec
-    """
-    def __init__(self, instance_resolver):
-        self.instance_resolver = instance_resolver
-        self.visiting = VisitingManager()
-
-    def __call__(self, func):
-        def wrapped(validator, schema_element, instance, schema):
-            if not isinstance(instance, dict) or '$ref' not in instance:
-                for res in func(validator, schema_element, instance, schema):
-                    yield res
-                return
-
-            ref = instance['$ref']
-
-            # ref already visited
-            if ref in self.visiting:
-                return
-
-            self._attach_scope(instance)
-            with self.visiting.visit(ref):
-                with self.instance_resolver.resolving(ref) as target:
-                    for res in func(validator, schema_element, target, schema):
-                        yield res
-
-        return wrapped
-
-    def _attach_scope(self, instance):
-        log.debug('Attaching x-scope to %s', instance)
-        if 'x-scope' in instance:
-            log.debug('Ref %s already has scope attached', instance['$ref'])
-            return
-
-        instance['x-scope'] = list(self.instance_resolver._scopes_stack)
 
 
 class ValidationErrorWrapper(object):
diff --git a/openapi_spec_validator/dereferencing/decorators.py b/openapi_spec_validator/dereferencing/decorators.py
new file mode 100644
index 0000000..55f7540
--- /dev/null
+++ b/openapi_spec_validator/dereferencing/decorators.py
@@ -0,0 +1,23 @@
+"""OpenAPI spec validator dereferencing decorators module."""
+from openapi_spec_validator.dereferencing.utils import is_ref
+
+
+class DerefValidatorDecorator:
+    """Dereferences instance if it is a $ref before passing it for validation.
+
+    :param instance_resolver: Resolves refs in the openapi service spec
+    """
+    def __init__(self, spec_dereferencer):
+        self.spec_dereferencer = spec_dereferencer
+
+    def __call__(self, func):
+        def wrapped(validator, schema_element, instance, schema):
+            if is_ref(instance):
+                with self.spec_dereferencer.dereference(instance) as deref:
+                    for res in func(validator, schema_element, deref, schema):
+                        yield res
+            else:
+                for res in func(validator, schema_element, instance, schema):
+                    yield res
+
+        return wrapped
diff --git a/openapi_spec_validator/dereferencing/dereferencers.py b/openapi_spec_validator/dereferencing/dereferencers.py
new file mode 100644
index 0000000..68352c7
--- /dev/null
+++ b/openapi_spec_validator/dereferencing/dereferencers.py
@@ -0,0 +1,22 @@
+import logging
+
+from openapi_spec_validator.dereferencing.managers import ResolverManager
+from openapi_spec_validator.dereferencing.utils import is_ref
+
+log = logging.getLogger(__name__)
+
+
+class Dereferencer(object):
+
+    def __init__(self, spec_resolver):
+        self.resolver_manager = ResolverManager(spec_resolver)
+
+    def dereference(self, item):
+        log.info("Dereferencing %s", item)
+        if item is None or not is_ref(item):
+            return item
+
+        ref = item['$ref']
+        with self.resolver_manager.in_scope(item) as resolver:
+            with resolver.resolving(ref) as target:
+                return target
diff --git a/openapi_spec_validator/dereferencing/managers.py b/openapi_spec_validator/dereferencing/managers.py
new file mode 100644
index 0000000..4f94e8b
--- /dev/null
+++ b/openapi_spec_validator/dereferencing/managers.py
@@ -0,0 +1,73 @@
+"""OpenAPI spec validator dereferencing managers module."""
+from contextlib import contextmanager
+import logging
+
+log = logging.getLogger(__name__)
+
+
+class VisitingManager(dict):
+    """Visiting manager. Mark keys which being visited."""
+
+    @contextmanager
+    def visit(self, key):
+        """Visits key and marks as visited.
+        Support context manager interface.
+
+        :param key: key being visited.
+        """
+        self[key] = key
+        try:
+            yield key
+        finally:
+            del self[key]
+
+
+class SpecDereferencer:
+    """Dereferences instance if it is a $ref before passing it for validation.
+
+    :param instance_resolver: Resolves refs in the openapi service spec
+    :param visiting_manager: Visiting manager
+    """
+    def __init__(self, instance_resolver, visiting_manager, scope='x-scope'):
+        self.instance_resolver = instance_resolver
+        self.visiting_manager = visiting_manager
+        self.scope = scope
+
+    @contextmanager
+    def dereference(self, instance):
+        ref = instance['$ref']
+        if ref in self.visiting_manager:
+            log.debug('Ref %s already visited', ref)
+            return
+
+        self._attach_scope(instance)
+        with self.visiting_manager.visit(ref):
+            with self.instance_resolver.resolving(ref) as target:
+                yield target
+
+    def _attach_scope(self, instance):
+        log.info('Attaching scope to %s', instance)
+        if self.scope in instance:
+            log.debug('Ref %s already has scope attached', instance['$ref'])
+            return
+
+        log.debug('Attaching scope to %s', instance)
+        instance[self.scope] = list(self.instance_resolver._scopes_stack)
+
+
+class ResolverManager(object):
+    def __init__(self, resolver, scope='x-scope'):
+        self.resolver = resolver
+        self.scope = scope
+
+    @contextmanager
+    def in_scope(self, item):
+        if self.scope not in item:
+            yield self.resolver
+        else:
+            saved_scope_stack = self.resolver._scopes_stack
+            try:
+                self.resolver._scopes_stack = item[self.scope]
+                yield self.resolver
+            finally:
+                self.resolver._scopes_stack = saved_scope_stack
diff --git a/openapi_spec_validator/dereferencing/utils.py b/openapi_spec_validator/dereferencing/utils.py
new file mode 100644
index 0000000..3dd72e9
--- /dev/null
+++ b/openapi_spec_validator/dereferencing/utils.py
@@ -0,0 +1,2 @@
+def is_ref(spec):
+    return isinstance(spec, dict) and '$ref' in spec
diff --git a/openapi_spec_validator/factories.py b/openapi_spec_validator/factories.py
index 956235a..75c2be7 100644
--- a/openapi_spec_validator/factories.py
+++ b/openapi_spec_validator/factories.py
@@ -1,54 +1,80 @@
 """OpenAPI spec validator factories module."""
-from jsonschema import validators
-from jsonschema.validators import Draft4Validator, RefResolver
+from jsonschema.validators import extend, Draft4Validator, RefResolver
+from six import iteritems
 
-from openapi_spec_validator.generators import (
-    SpecValidatorsGeneratorFactory,
+from openapi_spec_validator.dereferencing.decorators import (
+    DerefValidatorDecorator,
+)
+from openapi_spec_validator.dereferencing.managers import (
+    VisitingManager, SpecDereferencer,
 )
 
 
-class Draft4ExtendedValidatorFactory(Draft4Validator):
-    """Draft4Validator with extra validators factory that follows $refs
+class OpenAPIValidatorFactory(object):
+    """Validator factory with extra validators that follows $refs
     in the schema being validated."""
 
-    @classmethod
-    def from_resolver(cls, spec_resolver):
-        """Creates a customized Draft4ExtendedValidator.
+    validators = {
+        '$ref',
+        'properties',
+        'additionalProperties',
+        'patternProperties',
+        'type',
+        'dependencies',
+        'required',
+        'minProperties',
+        'maxProperties',
+        'allOf',
+        'oneOf',
+        'anyOf',
+        'not',
+    }
+
+    def __init__(self, schema_validator_class):
+        self.schema_validator_class = schema_validator_class
+
+    def from_resolver(self, spec_resolver):
+        """Creates an OpenAPIValidator.
 
         :param spec_resolver: resolver for the spec
         :type resolver: :class:`jsonschema.RefResolver`
         """
-        spec_validators = cls._get_spec_validators(spec_resolver)
-        return validators.extend(Draft4Validator, spec_validators)
-
-    @classmethod
-    def _get_spec_validators(cls, spec_resolver):
-        generator = SpecValidatorsGeneratorFactory.from_spec_resolver(
-            spec_resolver)
-        return dict(list(generator))
-
-
-class JSONSpecValidatorFactory:
+        visiting = VisitingManager()
+        dereferencer = SpecDereferencer(spec_resolver, visiting)
+        spec_validators = self._get_spec_validators(dereferencer)
+        return extend(self.schema_validator_class, spec_validators)
+
+    def _get_spec_validators(self, dereferencer):
+        deref = DerefValidatorDecorator(dereferencer)
+        validators_iter = iteritems(self.schema_validator_class.VALIDATORS)
+        return dict(
+            (key, deref(validator_callable))
+            for key, validator_callable in validators_iter
+            if key in self.validators
+        )
+
+
+class OpenAPISpecValidatorFactory:
     """
-    Json documents validator factory against a json schema.
+    OpenAPI Spec validator factory against a spec schema.
 
     :param schema: schema for validation.
     :param schema_url: schema base uri.
     """
 
-    schema_validator_class = Draft4Validator
-    spec_validator_factory = Draft4ExtendedValidatorFactory
-
-    def __init__(self, schema, schema_url='', resolver_handlers=None):
+    def __init__(
+        self, spec_validator_factory, schema,
+        schema_url='', resolver_handlers=None,
+    ):
+        self.spec_validator_factory = spec_validator_factory
         self.schema = schema
         self.schema_url = schema_url
         self.resolver_handlers = resolver_handlers or ()
 
-        self.schema_validator_class.check_schema(self.schema)
-
     @property
     def schema_resolver(self):
-        return self._get_resolver(self.schema_url, self.schema)
+        return RefResolver(
+            self.schema_url, self.schema, handlers=self.resolver_handlers)
 
     def create(self, spec_resolver):
         """Creates json documents validator from spec resolver.
@@ -63,7 +89,3 @@ def create(self, spec_resolver):
 
         return validator_cls(
             self.schema, resolver=self.schema_resolver)
-
-    def _get_resolver(self, base_uri, referrer):
-        return RefResolver(
-            base_uri, referrer, handlers=self.resolver_handlers)
diff --git a/openapi_spec_validator/generators.py b/openapi_spec_validator/generators.py
deleted file mode 100644
index 74c3143..0000000
--- a/openapi_spec_validator/generators.py
+++ /dev/null
@@ -1,42 +0,0 @@
-"""OpenAPI spec validator generators module."""
-import logging
-
-from six import iteritems
-from jsonschema.validators import Draft4Validator
-
-from openapi_spec_validator.decorators import DerefValidatorDecorator
-
-log = logging.getLogger(__name__)
-
-
-class SpecValidatorsGeneratorFactory:
-    """Generator factory for customized validators that follows $refs
-    in the schema being validated.
-    """
-    validators = {
-        '$ref',
-        'properties',
-        'additionalProperties',
-        'patternProperties',
-        'type',
-        'dependencies',
-        'required',
-        'minProperties',
-        'maxProperties',
-        'allOf',
-        'oneOf',
-        'anyOf',
-        'not',
-    }
-
-    @classmethod
-    def from_spec_resolver(cls, spec_resolver):
-        """Creates validators generator for the spec resolver.
-
-        :param spec_resolver: resolver for the spec
-        :type instance_resolver: :class:`jsonschema.RefResolver`
-        """
-        deref = DerefValidatorDecorator(spec_resolver)
-        for key, validator_callable in iteritems(Draft4Validator.VALIDATORS):
-            if key in cls.validators:
-                yield key, deref(validator_callable)
diff --git a/openapi_spec_validator/managers.py b/openapi_spec_validator/managers.py
deleted file mode 100644
index a4810e1..0000000
--- a/openapi_spec_validator/managers.py
+++ /dev/null
@@ -1,36 +0,0 @@
-"""OpenAPI spec validator managers module."""
-from contextlib import contextmanager
-
-
-class VisitingManager(dict):
-    """Visiting manager. Mark keys which being visited."""
-
-    @contextmanager
-    def visit(self, key):
-        """Visits key and marks as visited.
-        Support context manager interface.
-
-        :param key: key being visited.
-        """
-        self[key] = key
-        try:
-            yield key
-        finally:
-            del self[key]
-
-
-class ResolverManager(object):
-    def __init__(self, resolver):
-        self.resolver = resolver
-
-    @contextmanager
-    def in_scope(self, item, scope='x-scope'):
-        if scope not in item:
-            yield self.resolver
-        else:
-            saved_scope_stack = self.resolver._scopes_stack
-            try:
-                self.resolver._scopes_stack = item[scope]
-                yield self.resolver
-            finally:
-                self.resolver._scopes_stack = saved_scope_stack
diff --git a/openapi_spec_validator/schemas.py b/openapi_spec_validator/schemas.py
index c2857c0..deae680 100644
--- a/openapi_spec_validator/schemas.py
+++ b/openapi_spec_validator/schemas.py
@@ -8,12 +8,13 @@
 from openapi_spec_validator.loaders import ExtendedSafeLoader
 
 
-def get_openapi_schema(version):
+def get_openapi_schema(version, schema_validator_class):
     path = 'resources/schemas/v{0}/schema.json'.format(version)
     path_resource = resource_filename('openapi_spec_validator', path)
     path_full = os.path.join(os.path.dirname(__file__), path_resource)
     schema = read_yaml_file(path_full)
     schema_url = parse.urljoin('file:', request.pathname2url(path_full))
+    schema_validator_class.check_schema(schema)
     return schema, schema_url
 
 
diff --git a/openapi_spec_validator/validators.py b/openapi_spec_validator/validators.py
index 5bedea8..86433cb 100644
--- a/openapi_spec_validator/validators.py
+++ b/openapi_spec_validator/validators.py
@@ -5,38 +5,19 @@
 from openapi_schema_validator import OAS30Validator, oas30_format_checker
 from six import iteritems
 
+from openapi_spec_validator.dereferencing.dereferencers import Dereferencer
+from openapi_spec_validator.dereferencing.managers import ResolverManager
 from openapi_spec_validator.exceptions import (
     ParameterDuplicateError, ExtraParametersError, UnresolvableParameterError,
     OpenAPIValidationError, DuplicateOperationIDError,
 )
 from openapi_spec_validator.decorators import ValidationErrorWrapper
-from openapi_spec_validator.managers import ResolverManager
 
 log = logging.getLogger(__name__)
 
 wraps_errors = ValidationErrorWrapper(OpenAPIValidationError)
 
 
-def is_ref(spec):
-    return isinstance(spec, dict) and '$ref' in spec
-
-
-class Dereferencer(object):
-
-    def __init__(self, spec_resolver):
-        self.resolver_manager = ResolverManager(spec_resolver)
-
-    def dereference(self, item):
-        log.debug("Dereferencing %s", item)
-        if item is None or not is_ref(item):
-            return item
-
-        ref = item['$ref']
-        with self.resolver_manager.in_scope(item) as resolver:
-            with resolver.resolving(ref) as target:
-                return target
-
-
 class SpecValidator(object):
 
     def __init__(self, validator_factory, resolver_handlers):
diff --git a/tests/integration/test_v3_1_validator.py b/tests/integration/test_v3_1_validator.py
new file mode 100644
index 0000000..7718d53
--- /dev/null
+++ b/tests/integration/test_v3_1_validator.py
@@ -0,0 +1,351 @@
+import pytest
+
+from openapi_spec_validator.exceptions import (
+    ExtraParametersError, UnresolvableParameterError, OpenAPIValidationError,
+    DuplicateOperationIDError,
+)
+
+
+class TestSpecValidatorIterErrors(object):
+
+    @pytest.fixture
+    def validator(self, v3_1_validator):
+        return v3_1_validator
+
+    def test_empty(self, validator):
+        spec = {}
+
+        errors = validator.iter_errors(spec)
+
+        errors_list = list(errors)
+        assert len(errors_list) == 3
+        assert errors_list[0].__class__ == OpenAPIValidationError
+        assert errors_list[0].message == "'openapi' is a required property"
+        assert errors_list[1].__class__ == OpenAPIValidationError
+        assert errors_list[1].message == "'info' is a required property"
+        assert errors_list[2].__class__ == OpenAPIValidationError
+        # no paths, components or webhooks
+        assert errors_list[2].message == \
+            "{} is not valid under any of the given schemas"
+
+    def test_info_empty(self, validator):
+        spec = {
+            'openapi': '3.1.0',
+            'info': {},
+            'paths': {},
+        }
+
+        errors = validator.iter_errors(spec)
+
+        errors_list = list(errors)
+        assert errors_list[0].__class__ == OpenAPIValidationError
+        assert errors_list[0].message == "'title' is a required property"
+
+    @pytest.mark.parametrize('field', ['paths', 'components', 'webhooks'])
+    def test_minimalistic(self, validator, field):
+        spec = {
+            'openapi': '3.1.0',
+            'info': {
+                'title': 'Test Api',
+                'version': '0.0.1',
+            },
+            field: {},
+        }
+
+        errors = validator.iter_errors(spec)
+
+        errors_list = list(errors)
+        assert errors_list == []
+
+    def test_same_parameters_names(self, validator):
+        spec = {
+            'openapi': '3.1.0',
+            'info': {
+                'title': 'Test Api',
+                'version': '0.0.1',
+            },
+            'paths': {
+                '/test/{param1}': {
+                    'parameters': [
+                        {
+                            'name': 'param1',
+                            'in': 'query',
+                            'schema': {
+                              'type': 'integer',
+                            },
+                        },
+                        {
+                            'name': 'param1',
+                            'in': 'path',
+                            'schema': {
+                              'type': 'integer',
+                            },
+                            'required': True,
+                        },
+                    ],
+                },
+            },
+        }
+
+        errors = validator.iter_errors(spec)
+
+        errors_list = list(errors)
+        assert errors_list == []
+
+    def test_same_operation_ids(self, validator):
+        spec = {
+            'openapi': '3.1.0',
+            'info': {
+                'title': 'Test Api',
+                'version': '0.0.1',
+            },
+            'paths': {
+                '/test': {
+                    'get': {
+                        'operationId': 'operation1',
+                        'responses': {
+                            'default': {
+                                'description': 'default response',
+                            },
+                        },
+                    },
+                    'post': {
+                        'operationId': 'operation1',
+                        'responses': {
+                            'default': {
+                                'description': 'default response',
+                            },
+                        },
+                    },
+                },
+                '/test2': {
+                    'get': {
+                        'operationId': 'operation1',
+                        'responses': {
+                            'default': {
+                                'description': 'default response',
+                            },
+                        },
+                    },
+                },
+            },
+        }
+
+        errors = validator.iter_errors(spec)
+
+        errors_list = list(errors)
+        assert len(errors_list) == 2
+        assert errors_list[0].__class__ == DuplicateOperationIDError
+        assert errors_list[1].__class__ == DuplicateOperationIDError
+
+    def test_allow_allof_required_no_properties(self, validator):
+        spec = {
+            'openapi': '3.1.0',
+            'info': {
+                'title': 'Test Api',
+                'version': '0.0.1',
+            },
+            'paths': {},
+            'components': {
+                'schemas': {
+                    'Credit': {
+                        'type': 'object',
+                        'properties': {
+                            'clientId': {'type': 'string'},
+                        }
+                    },
+                    'CreditCreate': {
+                        'allOf': [
+                            {
+                                '$ref': '#/components/schemas/Credit'
+                            },
+                            {
+                                'required': ['clientId']
+                            }
+                        ]
+                    }
+                },
+            },
+        }
+
+        errors = validator.iter_errors(spec)
+        errors_list = list(errors)
+        assert errors_list == []
+
+    def test_extra_parameters_in_required(self, validator):
+        spec = {
+            'openapi': '3.1.0',
+            'info': {
+                'title': 'Test Api',
+                'version': '0.0.1',
+            },
+            'paths': {},
+            'components': {
+                'schemas': {
+                    'testSchema': {
+                        'type': 'object',
+                        'required': [
+                            'testparam1',
+                        ]
+                    }
+                },
+            },
+        }
+
+        errors = validator.iter_errors(spec)
+
+        errors_list = list(errors)
+        assert errors_list[0].__class__ == ExtraParametersError
+        assert errors_list[0].message == (
+            "Required list has not defined properties: ['testparam1']"
+        )
+
+    def test_undocumented_parameter(self, validator):
+        spec = {
+            'openapi': '3.1.0',
+            'info': {
+                'title': 'Test Api',
+                'version': '0.0.1',
+            },
+            'paths': {
+                '/test/{param1}/{param2}': {
+                    'get': {
+                        'responses': {
+                            'default': {
+                                'description': 'default response',
+                            },
+                        },
+                    },
+                    'parameters': [
+                        {
+                            'name': 'param1',
+                            'in': 'path',
+                            'schema': {
+                                'type': 'integer',
+                            },
+                            'required': True,
+                        },
+                    ],
+                },
+            },
+        }
+
+        errors = validator.iter_errors(spec)
+
+        errors_list = list(errors)
+        assert errors_list[0].__class__ == UnresolvableParameterError
+        assert errors_list[0].message == (
+            "Path parameter 'param2' for 'get' operation in "
+            "'/test/{param1}/{param2}' was not resolved"
+        )
+
+    def test_default_value_wrong_type(self, validator):
+        spec = {
+            'openapi': '3.1.0',
+            'info': {
+                'title': 'Test Api',
+                'version': '0.0.1',
+            },
+            'paths': {},
+            'components': {
+                'schemas': {
+                    'test': {
+                        'type': 'integer',
+                        'default': 'invaldtype',
+                    },
+                },
+            },
+        }
+
+        errors = validator.iter_errors(spec)
+
+        errors_list = list(errors)
+        assert len(errors_list) == 1
+        assert errors_list[0].__class__ == OpenAPIValidationError
+        assert errors_list[0].message == (
+            "'invaldtype' is not of type integer"
+        )
+
+    def test_parameter_default_value_wrong_type(self, validator):
+        spec = {
+            'openapi': '3.1.0',
+            'info': {
+                'title': 'Test Api',
+                'version': '0.0.1',
+            },
+            'paths': {
+                '/test/{param1}': {
+                    'get': {
+                        'responses': {
+                            'default': {
+                                'description': 'default response',
+                            },
+                        },
+                    },
+                    'parameters': [
+                        {
+                            'name': 'param1',
+                            'in': 'path',
+                            'schema': {
+                                'type': 'integer',
+                                'default': 'invaldtype',
+                            },
+                            'required': True,
+                        },
+                    ],
+                },
+            },
+        }
+
+        errors = validator.iter_errors(spec)
+
+        errors_list = list(errors)
+        assert len(errors_list) == 1
+        assert errors_list[0].__class__ == OpenAPIValidationError
+        assert errors_list[0].message == (
+            "'invaldtype' is not of type integer"
+        )
+
+    def test_parameter_default_value_with_reference(self, validator):
+        spec = {
+            'openapi': '3.1.0',
+            'info': {
+                'title': 'Test Api',
+                'version': '0.0.1',
+            },
+            'paths': {
+                '/test/': {
+                    'get': {
+                        'responses': {
+                            'default': {
+                                'description': 'default response',
+                            },
+                        },
+                        'parameters': [
+                            {
+                                'name': 'param1',
+                                'in': 'query',
+                                'schema': {
+                                    'allOf': [{
+                                        '$ref': '#/components/schemas/type',
+                                    }],
+                                    'default': 1,
+                                },
+                            },
+                        ],
+                    },
+                },
+            },
+            'components': {
+                'schemas': {
+                    'type': {
+                        'type': 'integer',
+                    }
+                },
+            },
+        }
+
+        errors = validator.iter_errors(spec)
+
+        errors_list = list(errors)
+        assert errors_list == []
