From f7ef3c942660e0a7c5a54667173ef2b9537291d3 Mon Sep 17 00:00:00 2001 From: Jason Desrosiers Date: Tue, 2 Mar 2021 14:39:49 -0800 Subject: [PATCH 1/7] Add schemas for 3.1 --- .gitmodules | 3 + package.json | 3 + schemas/v3.1/README.md | 35 + schemas/v3.1/dialect/base.schema.json | 27 + schemas/v3.1/meta/base.schema.json | 79 ++ schemas/v3.1/openapi3-examples | 1 + schemas/v3.1/schema.json | 1327 +++++++++++++++++++++++++ schemas/v3.1/schema.yaml | 902 +++++++++++++++++ schemas/v3.1/test.js | 50 + schemas/v3.1/validate.js | 30 + 10 files changed, 2457 insertions(+) create mode 100644 .gitmodules create mode 100644 schemas/v3.1/README.md create mode 100644 schemas/v3.1/dialect/base.schema.json create mode 100644 schemas/v3.1/meta/base.schema.json create mode 160000 schemas/v3.1/openapi3-examples create mode 100644 schemas/v3.1/schema.json create mode 100644 schemas/v3.1/schema.yaml create mode 100644 schemas/v3.1/test.js create mode 100644 schemas/v3.1/validate.js diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000..93a5196406 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "schemas/v3.1/openapi3-examples"] + path = schemas/v3.1/openapi3-examples + url = git@github.com:Mermade/openapi3-examples.git diff --git a/package.json b/package.json index 01be1c8d5c..59851cb7dc 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,10 @@ ], "dependencies": {}, "devDependencies": { + "@hyperjump/json-schema": "^0.17.0", + "chai": "^4.3.1", "mdv": "^1.0.7", + "mocha": "^8.3.0", "yaml": "^1.8.3" }, "keywords": [ diff --git a/schemas/v3.1/README.md b/schemas/v3.1/README.md new file mode 100644 index 0000000000..04eba1cec1 --- /dev/null +++ b/schemas/v3.1/README.md @@ -0,0 +1,35 @@ +# OpenAPI 3.1.X JSON Schema + +Here you can find the JSON Schema for validating OpenAPI definitions of versions +3.1.X. + +As a reminder, the JSON Schema is not the source of truth for the Specification. +In cases of conflicts between the Specification itself and the JSON Schema, the +Specification wins. Also, some Specification constraints cannot be represented +with the JSON Schema so it's highly recommended to employ other methods to +ensure compliance. + +The iteration version of the JSON Schema can be found in the `$id` field. For +example, the value of `$id: https://spec.openapis.org/oas/3.1/schema/2021-03-02` +means this iteration was created on March 2nd, 2021. + +To submit improvements to the schema, modify the schema.yaml file only. + +The TSC will then: +- Run tests on the updated schema +- Update the iteration version +- Convert the schema.yaml to schema.json +- Publish the new version + +## Tests +The test suite is included as a git submodule of https://github.com/Mermade/openapi3-examples. + +```bash +npx mocha test.js +``` + +You can also validate a document individually. + +```bash +node validate.js path/to/document/to/validate.yaml +``` diff --git a/schemas/v3.1/dialect/base.schema.json b/schemas/v3.1/dialect/base.schema.json new file mode 100644 index 0000000000..1f65d64e5d --- /dev/null +++ b/schemas/v3.1/dialect/base.schema.json @@ -0,0 +1,27 @@ +{ + "$id": "https://spec.openapis.org/oas/3.1/dialect/base", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$vocabulary": { + "https://json-schema.org/draft/2020-12/vocab/core": true, + "https://json-schema.org/draft/2020-12/vocab/applicator": true, + "https://json-schema.org/draft/2020-12/vocab/unevaluated": true, + "https://json-schema.org/draft/2020-12/vocab/validation": true, + "https://json-schema.org/draft/2020-12/vocab/meta-data": true, + "https://json-schema.org/draft/2020-12/vocab/format-annotation": true, + "https://json-schema.org/draft/2020-12/vocab/content": true, + "https://spec.openapis.org/oas/3.1/vocab/base": false + }, + "$dynamicAnchor": "meta", + + "title": "OpenAPI 3.1 Schema Object Dialect", + "allOf": [ + { "$ref": "https://json-schema.org/draft/2020-12/meta/core" }, + { "$ref": "https://json-schema.org/draft/2020-12/meta/applicator" }, + { "$ref": "https://json-schema.org/draft/2020-12/meta/unevaluated" }, + { "$ref": "https://json-schema.org/draft/2020-12/meta/validation" }, + { "$ref": "https://json-schema.org/draft/2020-12/meta/meta-data" }, + { "$ref": "https://json-schema.org/draft/2020-12/meta/format-annotation" }, + { "$ref": "https://json-schema.org/draft/2020-12/meta/content" }, + { "$ref": "https://spec.openapis.org/oas/3.1/meta/base" } + ] +} diff --git a/schemas/v3.1/meta/base.schema.json b/schemas/v3.1/meta/base.schema.json new file mode 100644 index 0000000000..00c127dc48 --- /dev/null +++ b/schemas/v3.1/meta/base.schema.json @@ -0,0 +1,79 @@ +{ + "$id": "https://spec.openapis.org/oas/3.1/meta/base", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$vocabulary": { + "https://spec.openapis.org/oas/3.1/vocab/base": true + }, + "$dynamicAnchor": "meta", + "title": "OAS Base vocabulary", + + "type": ["object", "boolean"], + "properties": { + "example": true, + "discriminator": { "$ref": "#/$defs/Discriminator" }, + "externalDocs": { "$ref": "#/$defs/ExternalDocs" }, + "xml": { "$ref": "#/$defs/Xml" } + }, + "$defs": { + "Extensible": { + "patternProperties": { + "^x-": true + } + }, + "Discriminator": { + "$ref": "#/$defs/Extensible", + "type": "object", + "properties": { + "propertyName": { + "type": "string" + }, + "mapping": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "required": ["propertyName"], + "unevaluatedProperties": false + }, + "ExternalDocs": { + "$ref": "#/$defs/Extensible", + "type": "object", + "properties": { + "url": { + "type": "string", + "format": "uri-reference" + }, + "description": { + "type": "string" + } + }, + "required": ["url"], + "unevaluatedProperties": false + }, + "Xml": { + "$ref": "#/$defs/Extensible", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "namespace": { + "type": "string", + "format": "uri" + }, + "prefix": { + "type": "string" + }, + "attribute": { + "type": "boolean" + }, + "wrapped": { + "type": "boolean" + } + }, + "unevaluatedProperties": false + } + } +} diff --git a/schemas/v3.1/openapi3-examples b/schemas/v3.1/openapi3-examples new file mode 160000 index 0000000000..9c2997e1a2 --- /dev/null +++ b/schemas/v3.1/openapi3-examples @@ -0,0 +1 @@ +Subproject commit 9c2997e1a25919a8182080cc43a4db06d2dc775d diff --git a/schemas/v3.1/schema.json b/schemas/v3.1/schema.json new file mode 100644 index 0000000000..a6117cfa3d --- /dev/null +++ b/schemas/v3.1/schema.json @@ -0,0 +1,1327 @@ +{ + "$id": "https://spec.openapis.org/oas/3.1/schema/2021-03-02", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "openapi": { + "type": "string" + }, + "info": { + "$ref": "#/$defs/info" + }, + "jsonSchemaDialect": { + "type": "string" + }, + "servers": { + "$ref": "#/$defs/server" + }, + "paths": { + "$ref": "#/$defs/paths" + }, + "webhooks": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/path-item-or-reference" + } + }, + "components": { + "$ref": "#/$defs/components" + }, + "security": { + "type": "array", + "items": { + "$ref": "#/$defs/security-requirement" + } + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/$defs/tag" + } + }, + "externalDocs": { + "$ref": "#/$defs/external-documentation" + } + }, + "required": [ + "openapi", + "info" + ], + "anyOf": [ + { + "required": [ + "paths" + ] + }, + { + "required": [ + "components" + ] + }, + { + "required": [ + "webhooks" + ] + } + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false, + "$defs": { + "info": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "summary": { + "type": "string" + }, + "description": { + "type": "string" + }, + "termsOfService": { + "type": "string" + }, + "contact\"": { + "$ref": "#/$defs/contact" + }, + "license": { + "$ref": "#/$defs/license" + }, + "version": { + "type": "string" + } + }, + "required": [ + "title", + "version" + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "contact": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "email": { + "type": "string" + } + }, + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "license": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "identifier": { + "type": "string" + }, + "url": { + "$ref\"": "#/$defs/uri" + } + }, + "required": [ + "name" + ], + "oneOf": [ + { + "required": [ + "identifier" + ] + }, + { + "required": [ + "url" + ] + } + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "server": { + "type": "object", + "properties": { + "url": { + "$ref": "#/$defs/uri" + }, + "description": { + "type": "string" + }, + "variables": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/server-variable" + } + } + }, + "required": [ + "url" + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "server-variable": { + "type": "object", + "properties": { + "enum": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1 + }, + "default": { + "type": "string" + }, + "descriptions": { + "type": "string" + } + }, + "required": [ + "default" + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "components": { + "type": "object", + "properties": { + "schemas": { + "type": "object", + "additionalProperties": { + "$dynamicRef": "#meta" + } + }, + "responses": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/response-or-reference" + } + }, + "parameters": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/parameter-or-reference" + } + }, + "examples": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/example-or-reference" + } + }, + "requestBodies": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/request-body-or-reference" + } + }, + "headers": { + "type": "object", + "additionalProperties": { + "$ref\"": "#/$defs/header-or-reference" + } + }, + "securitySchemes": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/security-scheme-or-reference" + } + }, + "links": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/link-or-reference" + } + }, + "callbacks": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/callbacks-or-reference" + } + }, + "pathItems": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/path-item-or-reference" + } + } + }, + "patternProperties": { + "": { + "propertyNames": { + "pattern": "^[a-zA-Z0-9._-]+$" + } + } + }, + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "paths": { + "type": "object", + "patternProperties": { + "^/": { + "$ref": "#/$defs/path-item" + } + }, + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "path-item": { + "type": "object", + "properties": { + "summary": { + "type": "string" + }, + "description": { + "type": "string" + }, + "servers": { + "type": "array", + "items": { + "$ref": "#/$defs/server" + } + }, + "parameters": { + "type": "array", + "items": { + "$ref": "#/$defs/parameter-or-reference" + } + } + }, + "patternProperties": { + "^get|post|delete|options|head|patch|trace$": { + "$ref": "#/$defs/operation" + } + }, + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "path-item-or-reference": { + "if": { + "required": [ + "$ref" + ] + }, + "then": { + "$ref": "#/$defs/reference" + }, + "else": { + "$ref": "#/$defs/path-item" + } + }, + "operation": { + "type": "object", + "properties": { + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "summary": { + "type": "string" + }, + "description": { + "type": "string" + }, + "externalDocs": { + "$ref": "#/$defs/external-documentation" + }, + "operationId": { + "type": "string" + }, + "parameters": { + "type": "array", + "items": { + "$ref\"": "#/$defs/parameter-or-reference" + } + }, + "requestBody": { + "$ref": "#/$defs/request-body-or-reference" + }, + "responses": { + "$ref": "#/$defs/responses" + }, + "callbacks": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/callbacks-or-reference" + } + }, + "deprecated": { + "default": false, + "type": "boolean" + }, + "security": { + "type": "array", + "items": { + "$ref": "#/$defs/security-requirement" + } + }, + "servers": { + "type": "array", + "items": { + "$ref": "#/$defs/server" + } + } + }, + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "external-documentation": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "url": { + "$ref": "#/$defs/uri" + } + }, + "required": [ + "url" + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "parameter": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "in": { + "enum": [ + "query", + "header", + "path", + "cookie" + ] + }, + "description": { + "type": "string" + }, + "required": { + "default": false, + "type": "boolean" + }, + "deprecated": { + "default": false, + "type": "boolean" + }, + "allowEmptyValue": { + "default": false, + "type": "boolean" + } + }, + "required": [ + "in" + ], + "dependentSchemas": { + "schema": { + "properties": { + "style": { + "type": "string" + }, + "explode": { + "type": "boolean" + }, + "allowReserved": { + "default": false, + "type": "boolean" + }, + "schema": { + "$dynamicRef": "#meta" + } + }, + "allOf": [ + { + "$ref": "#/$defs/examples" + }, + { + "$ref": "#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-path" + }, + { + "$ref": "#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-header" + }, + { + "$ref": "#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-query" + }, + { + "$ref": "#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-cookie" + }, + { + "$ref": "#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-form" + } + ], + "$defs": { + "styles-for-path": { + "if": { + "properties": { + "in": { + "const": "path" + } + }, + "required": [ + "in" + ] + }, + "then": { + "properties": { + "style": { + "default": "simple", + "enum": [ + "matrix", + "label", + "simple" + ] + }, + "required": { + "const": true + } + }, + "required": [ + "required" + ] + } + }, + "styles-for-header": { + "if": { + "properties": { + "in": { + "const": "header" + } + }, + "required": [ + "in" + ] + }, + "then": { + "properties": { + "style": { + "default": "simple", + "enum": [ + "simple" + ] + } + } + } + }, + "styles-for-query": { + "if": { + "properties": { + "in": { + "const": "query" + } + }, + "required": [ + "in" + ] + }, + "then": { + "properties": { + "style": { + "default": "form", + "enum": [ + "form", + "spaceDelimited", + "pipeDelimited", + "deepObject" + ] + } + } + } + }, + "styles-for-cookie": { + "if": { + "properties": { + "in": { + "const": "cookie" + } + }, + "required": [ + "in" + ] + }, + "then": { + "properties": { + "style": { + "default": "form", + "enum": [ + "form" + ] + } + } + } + }, + "styles-for-form": { + "if": { + "properties": { + "style": { + "const": "form" + } + }, + "required": [ + "style" + ] + }, + "then": { + "properties": { + "explode": { + "default": true + } + } + }, + "else": { + "properties": { + "explode": { + "default": false + } + } + } + } + } + }, + "content": { + "properties": { + "content": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/media-type" + } + } + } + } + }, + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "parameter-or-reference": { + "if": { + "required": [ + "$ref" + ] + }, + "then": { + "$ref": "#/$defs/reference" + }, + "else": { + "$ref": "#/$defs/parameter" + } + }, + "request-body": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "content": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/media-type" + } + }, + "required": { + "default": false, + "type": "boolean" + } + }, + "required": [ + "content" + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "request-body-or-reference": { + "if": { + "required": [ + "$ref" + ] + }, + "then": { + "$ref": "#/$defs/reference" + }, + "else": { + "$ref": "#/$defs/request-body" + } + }, + "media-type": { + "type": "object", + "properties": { + "schema": { + "$dynamicRef": "#meta" + }, + "encoding": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/encoding" + } + } + }, + "allOf": [ + { + "$ref": "#/$defs/specification-extensions" + }, + { + "$ref": "#/$defs/examples" + } + ], + "unevaluatedProperties": false + }, + "encoding": { + "type": "object", + "properties": { + "contentType": { + "type": "string" + }, + "headers": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/header-or-reference" + } + }, + "style": { + "default": "form", + "enum": [ + "form", + "spaceDelimited", + "pipeDelimited", + "deepObject" + ] + }, + "explode": { + "type": "boolean" + }, + "allowReserved": { + "default": false, + "type": "boolean" + } + }, + "allOf": [ + { + "$ref": "#/$defs/specification-extensions" + }, + { + "$ref": "#/$defs/encoding/$defs/explode-default" + } + ], + "unevaluatedProperties": false, + "$defs": { + "explode-default": { + "if": { + "properties": { + "style": { + "const": "form" + } + }, + "required": [ + "style" + ] + }, + "then": { + "properties": { + "explode": { + "default": true + } + } + }, + "else": { + "properties": { + "explode": { + "default": false + } + } + } + } + } + }, + "responses": { + "type": "object", + "properties": { + "default": { + "$ref": "#/$defs/response-or-reference" + } + }, + "patternProperties": { + "[1-5][0-9X]{2}": { + "$ref": "#/$defs/response-or-reference" + } + }, + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "response": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "headers": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/header-or-reference" + } + }, + "content": { + "type": "object", + "addtionalProperties": { + "$ref": "#/$defs/media-type" + } + }, + "links": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/link-or-reference" + } + } + }, + "required": [ + "description" + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "response-or-reference": { + "if": { + "required": [ + "$ref" + ] + }, + "then": { + "$ref": "#/$defs/reference" + }, + "else": { + "$ref": "#/$defs/response" + } + }, + "callbacks": { + "type": "object", + "$ref": "#/$defs/specification-extensions", + "additionalProperties": { + "$ref": "#/$defs/path-item-or-reference" + } + }, + "callbacks-or-reference": { + "if": { + "required": [ + "$ref" + ] + }, + "then": { + "$ref": "#/$defs/reference" + }, + "else": { + "$ref": "#/$defs/callbacks" + } + }, + "example": { + "type": "object", + "properties": { + "summary": { + "type": "string" + }, + "description": { + "type": "string" + }, + "value": true, + "externalValue": { + "$ref": "#/$defs/uri" + } + }, + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "example-or-reference": { + "if": { + "required": [ + "$ref" + ] + }, + "then": { + "$ref": "#/$defs/reference" + }, + "else": { + "$ref": "#/$defs/example" + } + }, + "link": { + "type": "object", + "properties": { + "operationRef": { + "$ref": "#/$defs/uri" + }, + "operationId": true, + "parameters": { + "$ref": "#/$defs/map-of-strings" + }, + "requestBody": true, + "description": { + "type": "string" + }, + "body": { + "$ref": "#/$defs/server" + } + }, + "oneOf": [ + { + "required": [ + "operationRef" + ] + }, + { + "required": [ + "operationId" + ] + } + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "link-or-reference": { + "if": { + "required": [ + "$ref" + ] + }, + "then": { + "$ref": "#/$defs/reference" + }, + "else": { + "$ref": "#/$defs/link" + } + }, + "header": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "required": { + "default": false, + "type": "boolean" + }, + "deprecated": { + "default": false, + "type": "boolean" + }, + "allowEmptyValue": { + "default": false, + "type": "boolean" + } + }, + "dependentSchemas": { + "schema": { + "properties": { + "style": { + "default": "simple", + "enum": [ + "simple" + ] + }, + "explode": { + "default": false, + "type": "boolean" + }, + "allowReserved": { + "default": false, + "type": "boolean" + }, + "schema": { + "$dynamicRef": "#meta" + } + }, + "$ref": "#/$defs/examples" + }, + "content": { + "properties": { + "content": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/media-type" + } + } + } + } + }, + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "header-or-reference": { + "if": { + "required": [ + "$ref" + ] + }, + "then": { + "$ref": "#/$defs/reference" + }, + "else": { + "$ref": "#/$defs/header" + } + }, + "tag": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "externalDocs": { + "$ref": "#/$defs/external-documentation" + } + }, + "required": [ + "name" + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "reference": { + "type": "object", + "properties": { + "$ref": { + "$ref": "#/$defs/uri" + }, + "summary": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "unevaluatedProperties": false + }, + "schema": { + "$dynamicAnchor": "meta", + "$ref": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "security-scheme": { + "type": "object", + "properties": { + "type": { + "enum": [ + "apiKey", + "http", + "mutualTLS", + "oauth2", + "openIdConnect" + ] + }, + "description": { + "type": "string" + } + }, + "required": [ + "type" + ], + "allOf": [ + { + "$ref": "#/$defs/specification-extensions" + }, + { + "$ref": "#/$defs/security-scheme/$defs/type-apikey" + }, + { + "$ref": "#/$defs/security-scheme/$defs/type-http" + }, + { + "$ref": "#/$defs/security-scheme/$defs/type-http-bearer" + }, + { + "$ref": "#/$defs/security-scheme/$defs/type-oauth2" + }, + { + "$ref": "#/$defs/security-scheme/$defs/type-oidc" + } + ], + "unevaluatedProperties": false, + "$defs": { + "type-apikey": { + "if": { + "properties": { + "type": { + "const": "apiKey" + } + }, + "required": [ + "type" + ] + }, + "then": { + "properties": { + "name": { + "type": "string" + }, + "in": { + "enum": [ + "query", + "header", + "cookie" + ] + } + }, + "required": [ + "name", + "in" + ] + } + }, + "type-http": { + "if": { + "properties": { + "type": { + "const": "http" + } + }, + "required": [ + "type" + ] + }, + "then": { + "properties": { + "scheme": { + "type": "string" + } + }, + "required": [ + "scheme" + ] + } + }, + "type-http-bearer": { + "if": { + "properties": { + "type": { + "const": "http" + }, + "scheme": { + "const": "bearer" + } + }, + "required": [ + "type", + "scheme" + ] + }, + "then": { + "properties": { + "bearerFormat": { + "type": "string" + } + }, + "required": [ + "scheme" + ] + } + }, + "type-oauth2": { + "if": { + "properties": { + "type": { + "const": "oauth2" + } + }, + "required": [ + "type" + ] + }, + "then": { + "properties": { + "flows": { + "$ref": "#/$defs/oauth-flows" + } + }, + "required": [ + "flows" + ] + } + }, + "type-oidc": { + "if": { + "properties": { + "type": { + "const": "openIdConnect" + } + }, + "required": [ + "type" + ] + }, + "then": { + "properties": { + "openIdConnectUrl": { + "$ref": "#/$defs/uri" + } + }, + "required": [ + "openIdConnectUrl" + ] + } + } + } + }, + "security-scheme-or-reference": { + "if": { + "required": [ + "$ref" + ] + }, + "then": { + "$ref": "#/$defs/reference" + }, + "else": { + "$ref": "#/$defs/security-scheme" + } + }, + "oauth-flows": { + "type": "object", + "properties": { + "implicit": { + "$ref": "#/$defs/oauth-flows/$defs/implicit" + }, + "password": { + "$ref": "#/$defs/oauth-flows/$defs/password" + }, + "clientCredentials": { + "$ref\"": "#/$defs/oauth-flows/$defs/client-credentials" + }, + "authorizationCode": { + "$ref": "#/$defs/oauth-flows/$defs/authorization-code" + } + }, + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false, + "$defs": { + "implicit": { + "type": "object", + "properties": { + "authorizationUrl": { + "type": "string" + }, + "refreshUrl": { + "type": "string" + }, + "scopes": { + "$ref": "#/$defs/map-of-strings" + } + }, + "required": [ + "authorizationUrl", + "scopes" + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "password": { + "type": "object", + "properties": { + "tokenUrl": { + "type": "string" + }, + "refreshUrl": { + "type": "string" + }, + "scopes": { + "$ref": "#/$defs/map-of-strings" + } + }, + "required": [ + "tokenUrl", + "scopes" + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "client-credentials": { + "type": "object", + "properties": { + "tokenUrl": { + "type": "string" + }, + "refreshUrl": { + "type": "string" + }, + "scopes": { + "$ref": "#/$defs/map-of-strings" + } + }, + "required": [ + "tokenUrl", + "scopes" + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "authorization-code": { + "type": "object", + "properties": { + "authorizationUrl": { + "type": "string" + }, + "tokenUrl": { + "type": "string" + }, + "refreshUrl": { + "type": "string" + }, + "scopes": { + "$ref": "#/$defs/map-of-strings" + } + }, + "required": [ + "authorizationUrl", + "tokenUrl", + "scopes" + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + } + } + }, + "security-requirement": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "specification-extensions": { + "patternProperties": { + "^x-": true + } + }, + "examples": { + "properties": { + "example": true, + "examples": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/example-or-reference" + } + } + } + }, + "uri": { + "type": "string", + "format": "uri" + }, + "map-of-strings": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } +} diff --git a/schemas/v3.1/schema.yaml b/schemas/v3.1/schema.yaml new file mode 100644 index 0000000000..e342298091 --- /dev/null +++ b/schemas/v3.1/schema.yaml @@ -0,0 +1,902 @@ +$id: 'https://spec.openapis.org/oas/3.1/schema/2021-03-02' +$schema: 'https://json-schema.org/draft/2020-12/schema' + +properties: + openapi: + type: string + info: + $ref: '#/$defs/info' + jsonSchemaDialect: + type: string + servers: + $ref: '#/$defs/server' + paths: + $ref: '#/$defs/paths' + webhooks: + type: object + additionalProperties: + $ref: '#/$defs/path-item-or-reference' + components: + $ref: '#/$defs/components' + security: + type: array + items: + $ref: '#/$defs/security-requirement' + tags: + type: array + items: + $ref: '#/$defs/tag' + externalDocs: + $ref: '#/$defs/external-documentation' +required: + - openapi + - info +anyOf: + - required: + - paths + - required: + - components + - required: + - webhooks +$ref: '#/$defs/specification-extensions' +unevaluatedProperties: false + +$defs: + info: + type: object + properties: + title: + type: string + summary: + type: string + description: + type: string + termsOfService: + type: string + contact": + $ref: '#/$defs/contact' + license: + $ref: '#/$defs/license' + version: + type: string + required: + - title + - version + $ref: '#/$defs/specification-extensions' + unevaluatedProperties: false + + contact: + type: object + properties: + name: + type: string + url: + type: string + email: + type: string + $ref: '#/$defs/specification-extensions' + unevaluatedProperties: false + + license: + type: object + properties: + name: + type: string + identifier: + type: string + url: + $ref": '#/$defs/uri' + required: + - name + oneOf: + - required: + - identifier + - required: + - url + $ref: '#/$defs/specification-extensions' + unevaluatedProperties: false + + server: + type: object + properties: + url: + $ref: '#/$defs/uri' + description: + type: string + variables: + type: object + additionalProperties: + $ref: '#/$defs/server-variable' + required: + - url + $ref: '#/$defs/specification-extensions' + unevaluatedProperties: false + + server-variable: + type: object + properties: + enum: + type: array + items: + type: string + minItems: 1 + default: + type: string + descriptions: + type: string + required: + - default + $ref: '#/$defs/specification-extensions' + unevaluatedProperties: false + + components: + type: object + properties: + schemas: + type: object + additionalProperties: + $dynamicRef: '#meta' + responses: + type: object + additionalProperties: + $ref: '#/$defs/response-or-reference' + parameters: + type: object + additionalProperties: + $ref: '#/$defs/parameter-or-reference' + examples: + type: object + additionalProperties: + $ref: '#/$defs/example-or-reference' + requestBodies: + type: object + additionalProperties: + $ref: '#/$defs/request-body-or-reference' + headers: + type: object + additionalProperties: + $ref": '#/$defs/header-or-reference' + securitySchemes: + type: object + additionalProperties: + $ref: '#/$defs/security-scheme-or-reference' + links: + type: object + additionalProperties: + $ref: '#/$defs/link-or-reference' + callbacks: + type: object + additionalProperties: + $ref: '#/$defs/callbacks-or-reference' + pathItems: + type: object + additionalProperties: + $ref: '#/$defs/path-item-or-reference' + patternProperties: + '': + propertyNames: + pattern: '^[a-zA-Z0-9._-]+$' + $ref: '#/$defs/specification-extensions' + unevaluatedProperties: false + + paths: + type: object + patternProperties: + '^/': + $ref: '#/$defs/path-item' + $ref: '#/$defs/specification-extensions' + unevaluatedProperties: false + + path-item: + type: object + properties: + summary: + type: string + description: + type: string + servers: + type: array + items: + $ref: '#/$defs/server' + parameters: + type: array + items: + $ref: '#/$defs/parameter-or-reference' + patternProperties: + '^get|post|delete|options|head|patch|trace$': + $ref: '#/$defs/operation' + $ref: '#/$defs/specification-extensions' + unevaluatedProperties: false + + path-item-or-reference: + if: + required: + - $ref + then: + $ref: '#/$defs/reference' + else: + $ref: '#/$defs/path-item' + + operation: + type: object + properties: + tags: + type: array + items: + type: string + summary: + type: string + description: + type: string + externalDocs: + $ref: '#/$defs/external-documentation' + operationId: + type: string + parameters: + type: array + items: + $ref": '#/$defs/parameter-or-reference' + requestBody: + $ref: '#/$defs/request-body-or-reference' + responses: + $ref: '#/$defs/responses' + callbacks: + type: object + additionalProperties: + $ref: '#/$defs/callbacks-or-reference' + deprecated: + default: false + type: boolean + security: + type: array + items: + $ref: '#/$defs/security-requirement' + servers: + type: array + items: + $ref: '#/$defs/server' + $ref: '#/$defs/specification-extensions' + unevaluatedProperties: false + + external-documentation: + type: object + properties: + description: + type: string + url: + $ref: '#/$defs/uri' + required: + - url + $ref: '#/$defs/specification-extensions' + unevaluatedProperties: false + + parameter: + type: object + properties: + name: + type: string + in: + enum: + - query + - header + - path + - cookie + description: + type: string + required: + default: false + type: boolean + deprecated: + default: false + type: boolean + allowEmptyValue: + default: false + type: boolean + required: + - in + dependentSchemas: + schema: + properties: + style: + type: string + explode: + type: boolean + allowReserved: + default: false + type: "boolean" + schema: + $dynamicRef: '#meta' + allOf: + - $ref: '#/$defs/examples' + - $ref: '#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-path' + - $ref: '#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-header' + - $ref: '#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-query' + - $ref: '#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-cookie' + - $ref: '#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-form' + + $defs: + styles-for-path: + if: + properties: + in: + const: path + required: + - in + then: + properties: + style: + default: simple + enum: + - matrix + - label + - simple + required: + const: true + required: + - required + + styles-for-header: + if: + properties: + in: + const: header + required: + - in + then: + properties: + style: + default: simple + enum: + - simple + + styles-for-query: + if: + properties: + in: + const: query + required: + - in + then: + properties: + style: + default: form + enum: + - form + - spaceDelimited + - pipeDelimited + - deepObject + + styles-for-cookie: + if: + properties: + in: + const: cookie + required: + - in + then: + properties: + style: + default: form + enum: + - form + + styles-for-form: + if: + properties: + style: + const: form + required: + - style + then: + properties: + explode: + default: true + else: + properties: + explode: + default: false + + content: + properties: + content: + type: object + additionalProperties: + $ref: '#/$defs/media-type' + $ref: '#/$defs/specification-extensions' + unevaluatedProperties: false + + parameter-or-reference: + if: + required: + - $ref + then: + $ref: '#/$defs/reference' + else: + $ref: '#/$defs/parameter' + + request-body: + type: object + properties: + description: + type: string + content: + type: object + additionalProperties: + $ref: '#/$defs/media-type' + required: + default: false + type: boolean + required: + - content + $ref: '#/$defs/specification-extensions' + unevaluatedProperties: false + + request-body-or-reference: + if: + required: + - $ref + then: + $ref: '#/$defs/reference' + else: + $ref: '#/$defs/request-body' + + media-type: + type: object + properties: + schema: + $dynamicRef: '#meta' + encoding: + type: object + additionalProperties: + $ref: '#/$defs/encoding' + allOf: + - $ref: '#/$defs/specification-extensions' + - $ref: '#/$defs/examples' + unevaluatedProperties: false + + encoding: + type: object + properties: + contentType: + type: string + headers: + type: object + additionalProperties: + $ref: '#/$defs/header-or-reference' + style: + default: form + enum: + - form + - spaceDelimited + - pipeDelimited + - deepObject + explode: + type: boolean + allowReserved: + default: false + type: boolean + allOf: + - $ref: '#/$defs/specification-extensions' + - $ref: '#/$defs/encoding/$defs/explode-default' + unevaluatedProperties: false + + $defs: + explode-default: + if: + properties: + style: + const: form + required: + - style + then: + properties: + explode: + default: true + else: + properties: + explode: + default: false + + responses: + type: object + properties: + default: + $ref: '#/$defs/response-or-reference' + patternProperties: + '[1-5][0-9X]{2}': + $ref: '#/$defs/response-or-reference' + $ref: '#/$defs/specification-extensions' + unevaluatedProperties: false + + response: + type: object + properties: + description: + type: string + headers: + type: object + additionalProperties: + $ref: '#/$defs/header-or-reference' + content: + type: object + addtionalProperties: + $ref: '#/$defs/media-type' + links: + type: object + additionalProperties: + $ref: '#/$defs/link-or-reference' + required: + - description + $ref: '#/$defs/specification-extensions' + unevaluatedProperties: false + + response-or-reference: + if: + required: + - $ref + then: + $ref: "#/$defs/reference" + else: + $ref: "#/$defs/response" + + callbacks: + type: object + $ref: '#/$defs/specification-extensions' + additionalProperties: + $ref: '#/$defs/path-item-or-reference' + + callbacks-or-reference: + if: + required: + - $ref + then: + $ref: '#/$defs/reference' + else: + $ref: '#/$defs/callbacks' + + example: + type: object + properties: + summary: + type: string + description: + type: string + value: true + externalValue: + $ref: '#/$defs/uri' + $ref: '#/$defs/specification-extensions' + unevaluatedProperties: false + + example-or-reference: + if: + required: + - $ref + then: + $ref: '#/$defs/reference' + else: + $ref: '#/$defs/example' + + link: + type: object + properties: + operationRef: + $ref: '#/$defs/uri' + operationId: true + parameters: + $ref: '#/$defs/map-of-strings' + requestBody: true + description: + type: string + body: + $ref: '#/$defs/server' + oneOf: + - required: + - operationRef + - required: + - operationId + $ref: '#/$defs/specification-extensions' + unevaluatedProperties: false + + link-or-reference: + if: + required: + - $ref + then: + $ref: '#/$defs/reference' + else: + $ref: '#/$defs/link' + + header: + type: object + properties: + description: + type: string + required: + default: false + type: boolean + deprecated: + default: false + type: boolean + allowEmptyValue: + default: false + type: boolean + dependentSchemas: + schema: + properties: + style: + default: simple + enum: + - simple + explode: + default: false + type: boolean + allowReserved: + default: false + type: boolean + schema: + $dynamicRef: '#meta' + $ref: '#/$defs/examples' + content: + properties: + content: + type: object + additionalProperties: + $ref: '#/$defs/media-type' + $ref: '#/$defs/specification-extensions' + unevaluatedProperties: false + + header-or-reference: + if: + required: + - $ref + then: + $ref: '#/$defs/reference' + else: + $ref: '#/$defs/header' + + tag: + type: object + properties: + name: + type: string + description: + type: string + externalDocs: + $ref: '#/$defs/external-documentation' + required: + - name + $ref: '#/$defs/specification-extensions' + unevaluatedProperties: false + + reference: + type: object + properties: + $ref: + $ref: '#/$defs/uri' + summary: + type: string + description: + type: string + unevaluatedProperties: false + + schema: + $dynamicAnchor: meta + $ref: 'https://spec.openapis.org/oas/3.1/dialect/base' + + security-scheme: + type: object + properties: + type: + enum: + - apiKey + - http + - mutualTLS + - oauth2 + - openIdConnect + description: + type: string + required: + - type + allOf: + - $ref: '#/$defs/specification-extensions' + - $ref: '#/$defs/security-scheme/$defs/type-apikey' + - $ref: '#/$defs/security-scheme/$defs/type-http' + - $ref: '#/$defs/security-scheme/$defs/type-http-bearer' + - $ref: '#/$defs/security-scheme/$defs/type-oauth2' + - $ref: '#/$defs/security-scheme/$defs/type-oidc' + unevaluatedProperties: false + + $defs: + type-apikey: + if: + properties: + type: + const: apiKey + required: + - type + then: + properties: + name: + type: string + in: + enum: + - query + - header + - cookie + required: + - name + - in + + type-http: + if: + properties: + type: + const: http + required: + - type + then: + properties: + scheme: + type: string + required: + - scheme + + type-http-bearer: + if: + properties: + type: + const: http + scheme: + const: bearer + required: + - type + - scheme + then: + properties: + bearerFormat: + type: string + required: + - scheme + + type-oauth2: + if: + properties: + type: + const: oauth2 + required: + - type + then: + properties: + flows: + $ref: '#/$defs/oauth-flows' + required: + - flows + + type-oidc: + if: + properties: + type: + const: openIdConnect + required: + - type + then: + properties: + openIdConnectUrl: + $ref: '#/$defs/uri' + required: + - openIdConnectUrl + + security-scheme-or-reference: + if: + required: + - $ref + then: + $ref: '#/$defs/reference' + else: + $ref: '#/$defs/security-scheme' + + oauth-flows: + type: object + properties: + implicit: + $ref: '#/$defs/oauth-flows/$defs/implicit' + password: + $ref: '#/$defs/oauth-flows/$defs/password' + clientCredentials: + $ref": '#/$defs/oauth-flows/$defs/client-credentials' + authorizationCode: + $ref: '#/$defs/oauth-flows/$defs/authorization-code' + $ref: '#/$defs/specification-extensions' + unevaluatedProperties: false + + $defs: + implicit: + type: object + properties: + authorizationUrl: + type: string + refreshUrl: + type: string + scopes: + $ref: '#/$defs/map-of-strings' + required: + - authorizationUrl + - scopes + $ref: '#/$defs/specification-extensions' + unevaluatedProperties: false + + password: + type: object + properties: + tokenUrl: + type: string + refreshUrl: + type: string + scopes: + $ref: '#/$defs/map-of-strings' + required: + - tokenUrl + - scopes + $ref: '#/$defs/specification-extensions' + unevaluatedProperties: false + + client-credentials: + type: object + properties: + tokenUrl: + type: string + refreshUrl: + type: string + scopes: + $ref: '#/$defs/map-of-strings' + required: + - tokenUrl + - scopes + $ref: '#/$defs/specification-extensions' + unevaluatedProperties: false + + authorization-code: + type: object + properties: + authorizationUrl: + type: string + tokenUrl: + type: string + refreshUrl: + type: string + scopes: + $ref: '#/$defs/map-of-strings' + required: + - authorizationUrl + - tokenUrl + - scopes + $ref: '#/$defs/specification-extensions' + unevaluatedProperties: false + + security-requirement: + type: object + additionalProperties: + type: array + items: + type: string + + specification-extensions: + patternProperties: + '^x-': true + + examples: + properties: + example: true + examples: + type: object + additionalProperties: + $ref: '#/$defs/example-or-reference' + + uri: + type: string + format: uri + + map-of-strings: + type: object + additionalProperties: + type: string diff --git a/schemas/v3.1/test.js b/schemas/v3.1/test.js new file mode 100644 index 0000000000..ca9e820865 --- /dev/null +++ b/schemas/v3.1/test.js @@ -0,0 +1,50 @@ +const fs = require("fs"); +const yaml = require("yaml"); +const JsonSchema = require("@hyperjump/json-schema"); +const { expect } = require("chai"); +const dialect = require("./dialect/base.schema.json"); +const vocabulary = require("./meta/base.schema.json"); + + +const testSuitePath = `${__dirname}/openapi3-examples/3.1`; + +JsonSchema.setMetaOutputFormat(JsonSchema.BASIC); +//JsonSchema.setShouldMetaValidate(false); + +let metaSchema; +before(async () => { + JsonSchema.add(dialect); + JsonSchema.add(vocabulary); + JsonSchema.add(yaml.parse(fs.readFileSync(`${__dirname}/schema.yaml`, "utf8"), { prettyErrors: true })); + metaSchema = await JsonSchema.get("https://spec.openapis.org/oas/3.1/schema/2021-03-02"); +}); + +describe("Pass", () => { + fs.readdirSync(`${testSuitePath}/pass`, { withFileTypes: true }) + .filter((entry) => entry.isFile() && /\.yaml$/.test(entry.name)) + .forEach((entry) => { + const file = `${testSuitePath}/pass/${entry.name}`; + + it(entry.name, async () => { + const instance = yaml.parse(fs.readFileSync(file, "utf8")); + + const output = await JsonSchema.validate(metaSchema, instance, JsonSchema.BASIC); + expect(output.valid).to.equal(true); + }); + }); +}); + +describe("Fail", () => { + fs.readdirSync(`${testSuitePath}/fail`, { withFileTypes: true }) + .filter((entry) => entry.isFile() && /\.yaml$/.test(entry.name)) + .forEach((entry) => { + const file = `${testSuitePath}/fail/${entry.name}`; + + it(entry.name, async () => { + const instance = yaml.parse(fs.readFileSync(file, "utf8")); + + const output = await JsonSchema.validate(metaSchema, instance); + expect(output.valid).to.equal(false); + }); + }); +}); diff --git a/schemas/v3.1/validate.js b/schemas/v3.1/validate.js new file mode 100644 index 0000000000..3a25e735ff --- /dev/null +++ b/schemas/v3.1/validate.js @@ -0,0 +1,30 @@ +const fs = require("fs"); +const yaml = require("yaml"); +const JsonSchema = require("@hyperjump/json-schema"); +const dialect = require("./dialect/base.schema.json"); +const vocabulary = require("./meta/base.schema.json"); + + +JsonSchema.setMetaOutputFormat(JsonSchema.BASIC); +//JsonSchema.setShouldMetaValidate(false); + +(async function () { + try { + // Compile / meta-validate + JsonSchema.add(dialect); + JsonSchema.add(vocabulary); + JsonSchema.add(yaml.parse(fs.readFileSync(`${__dirname}/schema.yaml`, "utf8"))); + const schema = await JsonSchema.get("https://spec.openapis.org/oas/3.1/schema/2021-03-02"); + const validateSchema = await JsonSchema.validate(schema); + + // Validate instance + const instance = yaml.parse(fs.readFileSync(`${__dirname}/${process.argv[2]}`, "utf8")); + const results = validateSchema(instance, JsonSchema.BASIC); + console.log(JSON.stringify(results, null, " ")); + } catch (error) { + console.log("************* Error ***************"); + console.log(error); + console.log(JSON.stringify(error.output, null, " ")); + //console.log(error.output); + } +}()); From eccada9d15930493328158ead26d30eb03383699 Mon Sep 17 00:00:00 2001 From: Jason Desrosiers Date: Tue, 2 Mar 2021 17:24:37 -0800 Subject: [PATCH 2/7] 3.1 Schema: Extend schema to use OAS base vocabulary The main schema no longer validates schemas other than verifying that they are either a schema or an object. --- schemas/v3.1/README.md | 6 ++++++ schemas/v3.1/schema-base.json | 24 ++++++++++++++++++++++++ schemas/v3.1/schema-base.yaml | 17 +++++++++++++++++ schemas/v3.1/schema.yaml | 7 +++++-- schemas/v3.1/test.js | 3 ++- 5 files changed, 54 insertions(+), 3 deletions(-) create mode 100644 schemas/v3.1/schema-base.json create mode 100644 schemas/v3.1/schema-base.yaml diff --git a/schemas/v3.1/README.md b/schemas/v3.1/README.md index 04eba1cec1..141e3e919a 100644 --- a/schemas/v3.1/README.md +++ b/schemas/v3.1/README.md @@ -13,6 +13,12 @@ The iteration version of the JSON Schema can be found in the `$id` field. For example, the value of `$id: https://spec.openapis.org/oas/3.1/schema/2021-03-02` means this iteration was created on March 2nd, 2021. +The `schema.yaml` schema doesn't validate the JSON Schemas in your OpenAPI +document because 3.1 allows you to use any JSON Schema dialect you choose. We +have also included `schema-base.yaml` that extends the main schema to validate +that all schemas use the default OAS base vocabulary. + +## Contributing To submit improvements to the schema, modify the schema.yaml file only. The TSC will then: diff --git a/schemas/v3.1/schema-base.json b/schemas/v3.1/schema-base.json new file mode 100644 index 0000000000..078af184fe --- /dev/null +++ b/schemas/v3.1/schema-base.json @@ -0,0 +1,24 @@ +{ + "$id": "https://spec.openapis.org/oas/3.1/schema-base/2021-03-02", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "https://spec.openapis.org/oas/3.1/schema/2021-03-02", + "properties": { + "jsonSchemaDialect": { + "$ref": "#/$defs/dialect" + } + }, + "$defs": { + "dialect": { + "const": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "schema": { + "$dynamicAnchor": "meta", + "$ref\"": "https://spec.openapis.org/oas/3.1/dialect/base", + "properties": { + "$schema": { + "$ref": "#/$defs/dialect" + } + } + } + } +} diff --git a/schemas/v3.1/schema-base.yaml b/schemas/v3.1/schema-base.yaml new file mode 100644 index 0000000000..41a27be7a9 --- /dev/null +++ b/schemas/v3.1/schema-base.yaml @@ -0,0 +1,17 @@ +$id: 'https://spec.openapis.org/oas/3.1/schema-base/2021-03-02' +$schema: 'https://json-schema.org/draft/2020-12/schema' + +$ref: 'https://spec.openapis.org/oas/3.1/schema/2021-03-02' +properties: + jsonSchemaDialect: + $ref: '#/$defs/dialect' + +$defs: + dialect: + const: 'https://spec.openapis.org/oas/3.1/dialect/base' + schema: + $dynamicAnchor: meta + $ref": 'https://spec.openapis.org/oas/3.1/dialect/base' + properties: + $schema: + $ref: '#/$defs/dialect' diff --git a/schemas/v3.1/schema.yaml b/schemas/v3.1/schema.yaml index e342298091..3897e40429 100644 --- a/schemas/v3.1/schema.yaml +++ b/schemas/v3.1/schema.yaml @@ -7,7 +7,8 @@ properties: info: $ref: '#/$defs/info' jsonSchemaDialect: - type: string + $ref: '#/$defs/uri' + default: 'https://spec.openapis.org/oas/3.1/dialect/base' servers: $ref: '#/$defs/server' paths: @@ -681,7 +682,9 @@ $defs: schema: $dynamicAnchor: meta - $ref: 'https://spec.openapis.org/oas/3.1/dialect/base' + type: + - object + - boolean security-scheme: type: object diff --git a/schemas/v3.1/test.js b/schemas/v3.1/test.js index ca9e820865..2c5bb2fd1e 100644 --- a/schemas/v3.1/test.js +++ b/schemas/v3.1/test.js @@ -16,7 +16,8 @@ before(async () => { JsonSchema.add(dialect); JsonSchema.add(vocabulary); JsonSchema.add(yaml.parse(fs.readFileSync(`${__dirname}/schema.yaml`, "utf8"), { prettyErrors: true })); - metaSchema = await JsonSchema.get("https://spec.openapis.org/oas/3.1/schema/2021-03-02"); + JsonSchema.add(yaml.parse(fs.readFileSync(`${__dirname}/schema-base.yaml`, "utf8"), { prettyErrors: true })); + metaSchema = await JsonSchema.get("https://spec.openapis.org/oas/3.1/schema-base/2021-03-02"); }); describe("Pass", () => { From bd7a645620dd39be05d65ae8adaa22a12bdccfff Mon Sep 17 00:00:00 2001 From: Jason Desrosiers Date: Wed, 3 Mar 2021 13:05:58 -0800 Subject: [PATCH 3/7] 3.1 Schema: Move scripts and tests to root --- .gitmodules | 4 +- schemas/v3.1/README.md | 4 +- schemas/v3.1/validate.js | 30 ------------ scripts/validate.js | 58 +++++++++++++++++++++++ {schemas/v3.1 => tests}/openapi3-examples | 0 {schemas => tests}/v3.1/test.js | 11 ++--- 6 files changed, 67 insertions(+), 40 deletions(-) delete mode 100644 schemas/v3.1/validate.js create mode 100755 scripts/validate.js rename {schemas/v3.1 => tests}/openapi3-examples (100%) rename {schemas => tests}/v3.1/test.js (76%) diff --git a/.gitmodules b/.gitmodules index 93a5196406..ca8320435d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ -[submodule "schemas/v3.1/openapi3-examples"] - path = schemas/v3.1/openapi3-examples +[submodule "tests/v3.1/openapi3-examples"] + path = tests/openapi3-examples url = git@github.com:Mermade/openapi3-examples.git diff --git a/schemas/v3.1/README.md b/schemas/v3.1/README.md index 141e3e919a..4f305d0754 100644 --- a/schemas/v3.1/README.md +++ b/schemas/v3.1/README.md @@ -31,11 +31,11 @@ The TSC will then: The test suite is included as a git submodule of https://github.com/Mermade/openapi3-examples. ```bash -npx mocha test.js +npx mocha --recursive [repo root]/tests ``` You can also validate a document individually. ```bash -node validate.js path/to/document/to/validate.yaml +node [repo root]/scripts/validate.js path/to/document/to/validate.yaml ``` diff --git a/schemas/v3.1/validate.js b/schemas/v3.1/validate.js deleted file mode 100644 index 3a25e735ff..0000000000 --- a/schemas/v3.1/validate.js +++ /dev/null @@ -1,30 +0,0 @@ -const fs = require("fs"); -const yaml = require("yaml"); -const JsonSchema = require("@hyperjump/json-schema"); -const dialect = require("./dialect/base.schema.json"); -const vocabulary = require("./meta/base.schema.json"); - - -JsonSchema.setMetaOutputFormat(JsonSchema.BASIC); -//JsonSchema.setShouldMetaValidate(false); - -(async function () { - try { - // Compile / meta-validate - JsonSchema.add(dialect); - JsonSchema.add(vocabulary); - JsonSchema.add(yaml.parse(fs.readFileSync(`${__dirname}/schema.yaml`, "utf8"))); - const schema = await JsonSchema.get("https://spec.openapis.org/oas/3.1/schema/2021-03-02"); - const validateSchema = await JsonSchema.validate(schema); - - // Validate instance - const instance = yaml.parse(fs.readFileSync(`${__dirname}/${process.argv[2]}`, "utf8")); - const results = validateSchema(instance, JsonSchema.BASIC); - console.log(JSON.stringify(results, null, " ")); - } catch (error) { - console.log("************* Error ***************"); - console.log(error); - console.log(JSON.stringify(error.output, null, " ")); - //console.log(error.output); - } -}()); diff --git a/scripts/validate.js b/scripts/validate.js new file mode 100755 index 0000000000..cf1c2d09ca --- /dev/null +++ b/scripts/validate.js @@ -0,0 +1,58 @@ +#!/usr/bin/env node + +const fs = require("fs"); +const yaml = require("yaml"); +const JsonSchema = require("@hyperjump/json-schema"); +const dialect = require("../schemas/v3.1/dialect/base.schema.json"); +const vocabulary = require("../schemas/v3.1/meta/base.schema.json"); + + +if (process.argv.length < 3) { + console.log("Usage: validate [--schema=schema] [--version=2021-03-02] [--format=BASIC] path-to-file.yaml"); + console.log("\t--schema: (Default: schema) The name of the yaml schema file to use"); + console.log("\t--version: (Default: 2021-03-02) The version of the yaml schema file to use"); + console.log("\t--format: (Default: BASIC) The JSON Schema output format to use. Options: FLAG, BASIC, DETAILED, VERBOSE"); + process.exit(1); +} + +const args = process.argv.reduce((acc, arg) => { + if (!arg.startsWith("--")) return acc; + + const [argName, argValue] = arg.substring(2).split("=", 2); + return { ...acc, [argName]: argValue }; +}, {}); + +(async function () { + try { + // Config + JsonSchema.setMetaOutputFormat(outputFormat); + //JsonSchema.setShouldMetaValidate(false); + + const schemaType = args.schema || "schema"; + const schemaVersion = args.version || "2021-03-02"; + const ouputFormat = args.format || JsonSchema.BASIC; + + // Load schemas + JsonSchema.add(dialect); + JsonSchema.add(vocabulary); + fs.readdirSync(`${__dirname}/../schemas/v3.1`, { withFileTypes: true }) + .filter((entry) => entry.isFile() && /\.yaml$/.test(entry.name)) + .map((entry) => fs.readFileSync(`${__dirname}/../schemas/v3.1/${entry.name}`, "utf8")) + .map((schemaYaml) => yaml.parse(schemaYaml)) + .forEach((schema) => JsonSchema.add(schema)); + + // Compile / meta-validate + const schema = await JsonSchema.get(`https://spec.openapis.org/oas/3.1/${schemaType}/${schemaVersion}`); + const validateSchema = await JsonSchema.validate(schema); + + // Validate instance + const instanceYaml = fs.readFileSync(`${process.cwd()}/${process.argv[process.argv.length - 1]}`, "utf8"); + const instance = yaml.parse(instanceYaml); + const results = validateSchema(instance, outputFormat); + console.log(JSON.stringify(results, null, " ")); + } catch (error) { + console.log("************* Error ***************"); + console.log(error); + console.log(JSON.stringify(error.output, null, " ")); + } +}()); diff --git a/schemas/v3.1/openapi3-examples b/tests/openapi3-examples similarity index 100% rename from schemas/v3.1/openapi3-examples rename to tests/openapi3-examples diff --git a/schemas/v3.1/test.js b/tests/v3.1/test.js similarity index 76% rename from schemas/v3.1/test.js rename to tests/v3.1/test.js index 2c5bb2fd1e..77d62cb02f 100644 --- a/schemas/v3.1/test.js +++ b/tests/v3.1/test.js @@ -2,11 +2,11 @@ const fs = require("fs"); const yaml = require("yaml"); const JsonSchema = require("@hyperjump/json-schema"); const { expect } = require("chai"); -const dialect = require("./dialect/base.schema.json"); -const vocabulary = require("./meta/base.schema.json"); +const dialect = require("../../schemas/v3.1/dialect/base.schema.json"); +const vocabulary = require("../../schemas/v3.1/meta/base.schema.json"); -const testSuitePath = `${__dirname}/openapi3-examples/3.1`; +const testSuitePath = `${__dirname}/../openapi3-examples/3.1`; JsonSchema.setMetaOutputFormat(JsonSchema.BASIC); //JsonSchema.setShouldMetaValidate(false); @@ -15,9 +15,8 @@ let metaSchema; before(async () => { JsonSchema.add(dialect); JsonSchema.add(vocabulary); - JsonSchema.add(yaml.parse(fs.readFileSync(`${__dirname}/schema.yaml`, "utf8"), { prettyErrors: true })); - JsonSchema.add(yaml.parse(fs.readFileSync(`${__dirname}/schema-base.yaml`, "utf8"), { prettyErrors: true })); - metaSchema = await JsonSchema.get("https://spec.openapis.org/oas/3.1/schema-base/2021-03-02"); + JsonSchema.add(yaml.parse(fs.readFileSync(`${__dirname}/../../schemas/v3.1/schema.yaml`, "utf8"), { prettyErrors: true })); + metaSchema = await JsonSchema.get("https://spec.openapis.org/oas/3.1/schema/2021-03-02"); }); describe("Pass", () => { From 5cde0f01efcdf26a728f90447c8c1d32193ebeec Mon Sep 17 00:00:00 2001 From: Jason Desrosiers Date: Thu, 11 Mar 2021 11:42:15 -0800 Subject: [PATCH 4/7] 3.1 Schema: Fixes from code review during TSC meeting --- schemas/v3.1/README.md | 4 +- schemas/v3.1/dialect/base.schema.json | 8 +--- schemas/v3.1/meta/base.schema.json | 20 ++++----- schemas/v3.1/schema.json | 59 +++++++++++++++++---------- schemas/v3.1/schema.yaml | 40 ++++++++++-------- 5 files changed, 73 insertions(+), 58 deletions(-) diff --git a/schemas/v3.1/README.md b/schemas/v3.1/README.md index 4f305d0754..01da62b56d 100644 --- a/schemas/v3.1/README.md +++ b/schemas/v3.1/README.md @@ -31,11 +31,11 @@ The TSC will then: The test suite is included as a git submodule of https://github.com/Mermade/openapi3-examples. ```bash -npx mocha --recursive [repo root]/tests +npx mocha --recursive tests ``` You can also validate a document individually. ```bash -node [repo root]/scripts/validate.js path/to/document/to/validate.yaml +scripts/validate.js path/to/document/to/validate.yaml ``` diff --git a/schemas/v3.1/dialect/base.schema.json b/schemas/v3.1/dialect/base.schema.json index 1f65d64e5d..d54b0d4d7c 100644 --- a/schemas/v3.1/dialect/base.schema.json +++ b/schemas/v3.1/dialect/base.schema.json @@ -15,13 +15,7 @@ "title": "OpenAPI 3.1 Schema Object Dialect", "allOf": [ - { "$ref": "https://json-schema.org/draft/2020-12/meta/core" }, - { "$ref": "https://json-schema.org/draft/2020-12/meta/applicator" }, - { "$ref": "https://json-schema.org/draft/2020-12/meta/unevaluated" }, - { "$ref": "https://json-schema.org/draft/2020-12/meta/validation" }, - { "$ref": "https://json-schema.org/draft/2020-12/meta/meta-data" }, - { "$ref": "https://json-schema.org/draft/2020-12/meta/format-annotation" }, - { "$ref": "https://json-schema.org/draft/2020-12/meta/content" }, + { "$ref": "https://json-schema.org/draft/2020-12/schema" }, { "$ref": "https://spec.openapis.org/oas/3.1/meta/base" } ] } diff --git a/schemas/v3.1/meta/base.schema.json b/schemas/v3.1/meta/base.schema.json index 00c127dc48..f3ee03fb96 100644 --- a/schemas/v3.1/meta/base.schema.json +++ b/schemas/v3.1/meta/base.schema.json @@ -10,18 +10,18 @@ "type": ["object", "boolean"], "properties": { "example": true, - "discriminator": { "$ref": "#/$defs/Discriminator" }, - "externalDocs": { "$ref": "#/$defs/ExternalDocs" }, - "xml": { "$ref": "#/$defs/Xml" } + "discriminator": { "$ref": "#/$defs/discriminator" }, + "externalDocs": { "$ref": "#/$defs/external-docs" }, + "xml": { "$ref": "#/$defs/xml" } }, "$defs": { - "Extensible": { + "extensible": { "patternProperties": { "^x-": true } }, - "Discriminator": { - "$ref": "#/$defs/Extensible", + "discriminator": { + "$ref": "#/$defs/extensible", "type": "object", "properties": { "propertyName": { @@ -37,8 +37,8 @@ "required": ["propertyName"], "unevaluatedProperties": false }, - "ExternalDocs": { - "$ref": "#/$defs/Extensible", + "external-docs": { + "$ref": "#/$defs/extensible", "type": "object", "properties": { "url": { @@ -52,8 +52,8 @@ "required": ["url"], "unevaluatedProperties": false }, - "Xml": { - "$ref": "#/$defs/Extensible", + "xml": { + "$ref": "#/$defs/extensible", "type": "object", "properties": { "name": { diff --git a/schemas/v3.1/schema.json b/schemas/v3.1/schema.json index a6117cfa3d..7fa4dbf6fa 100644 --- a/schemas/v3.1/schema.json +++ b/schemas/v3.1/schema.json @@ -1,15 +1,18 @@ { "$id": "https://spec.openapis.org/oas/3.1/schema/2021-03-02", "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", "properties": { "openapi": { - "type": "string" + "type": "string", + "pattern": "^3\\.1\\.\\d+(-.+)?$" }, "info": { "$ref": "#/$defs/info" }, "jsonSchemaDialect": { - "type": "string" + "$ref": "#/$defs/uri", + "default": "https://spec.openapis.org/oas/3.1/dialect/base" }, "servers": { "$ref": "#/$defs/server" @@ -81,7 +84,7 @@ "termsOfService": { "type": "string" }, - "contact\"": { + "contact": { "$ref": "#/$defs/contact" }, "license": { @@ -124,7 +127,7 @@ "type": "string" }, "url": { - "$ref\"": "#/$defs/uri" + "$ref": "#/$defs/uri" } }, "required": [ @@ -226,7 +229,7 @@ "headers": { "type": "object", "additionalProperties": { - "$ref\"": "#/$defs/header-or-reference" + "$ref": "#/$defs/header-or-reference" } }, "securitySchemes": { @@ -255,7 +258,8 @@ } }, "patternProperties": { - "": { + "^schemas|responses|parameters|examples|requestBodies|headers|securitySchemes|links|callbacks|pathItems$": { + "$comment": "Enumerating all of the property names in the regex above is necessary for unevaluatedProperties to work as expected", "propertyNames": { "pattern": "^[a-zA-Z0-9._-]+$" } @@ -341,7 +345,7 @@ "parameters": { "type": "array", "items": { - "$ref\"": "#/$defs/parameter-or-reference" + "$ref": "#/$defs/parameter-or-reference" } }, "requestBody": { @@ -420,11 +424,32 @@ "allowEmptyValue": { "default": false, "type": "boolean" + }, + "schema": { + "$dynamicRef": "#meta" + }, + "content": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/media-type" + } } }, "required": [ "in" ], + "oneOf": [ + { + "required": [ + "schema" + ] + }, + { + "required": [ + "content" + ] + } + ], "dependentSchemas": { "schema": { "properties": { @@ -437,9 +462,6 @@ "allowReserved": { "default": false, "type": "boolean" - }, - "schema": { - "$dynamicRef": "#meta" } }, "allOf": [ @@ -589,16 +611,6 @@ } } } - }, - "content": { - "properties": { - "content": { - "type": "object", - "additionalProperties": { - "$ref": "#/$defs/media-type" - } - } - } } }, "$ref": "#/$defs/specification-extensions", @@ -1003,7 +1015,10 @@ }, "schema": { "$dynamicAnchor": "meta", - "$ref": "https://spec.openapis.org/oas/3.1/dialect/base" + "type": [ + "object", + "boolean" + ] }, "security-scheme": { "type": "object", @@ -1193,7 +1208,7 @@ "$ref": "#/$defs/oauth-flows/$defs/password" }, "clientCredentials": { - "$ref\"": "#/$defs/oauth-flows/$defs/client-credentials" + "$ref": "#/$defs/oauth-flows/$defs/client-credentials" }, "authorizationCode": { "$ref": "#/$defs/oauth-flows/$defs/authorization-code" diff --git a/schemas/v3.1/schema.yaml b/schemas/v3.1/schema.yaml index 3897e40429..94dcea96e5 100644 --- a/schemas/v3.1/schema.yaml +++ b/schemas/v3.1/schema.yaml @@ -1,9 +1,11 @@ $id: 'https://spec.openapis.org/oas/3.1/schema/2021-03-02' $schema: 'https://json-schema.org/draft/2020-12/schema' +type: object properties: openapi: type: string + pattern: '^3\.1\.\d+(-.+)?$' info: $ref: '#/$defs/info' jsonSchemaDialect: @@ -54,7 +56,7 @@ $defs: type: string termsOfService: type: string - contact": + contact: $ref: '#/$defs/contact' license: $ref: '#/$defs/license' @@ -86,7 +88,7 @@ $defs: identifier: type: string url: - $ref": '#/$defs/uri' + $ref: '#/$defs/uri' required: - name oneOf: @@ -156,7 +158,7 @@ $defs: headers: type: object additionalProperties: - $ref": '#/$defs/header-or-reference' + $ref: '#/$defs/header-or-reference' securitySchemes: type: object additionalProperties: @@ -174,7 +176,8 @@ $defs: additionalProperties: $ref: '#/$defs/path-item-or-reference' patternProperties: - '': + '^schemas|responses|parameters|examples|requestBodies|headers|securitySchemes|links|callbacks|pathItems$': + $comment: Enumerating all of the property names in the regex above is necessary for unevaluatedProperties to work as expected propertyNames: pattern: '^[a-zA-Z0-9._-]+$' $ref: '#/$defs/specification-extensions' @@ -236,7 +239,7 @@ $defs: parameters: type: array items: - $ref": '#/$defs/parameter-or-reference' + $ref: '#/$defs/parameter-or-reference' requestBody: $ref: '#/$defs/request-body-or-reference' responses: @@ -293,8 +296,19 @@ $defs: allowEmptyValue: default: false type: boolean + schema: + $dynamicRef: '#meta' + content: + type: object + additionalProperties: + $ref: '#/$defs/media-type' required: - in + oneOf: + - required: + - schema + - required: + - content dependentSchemas: schema: properties: @@ -304,9 +318,7 @@ $defs: type: boolean allowReserved: default: false - type: "boolean" - schema: - $dynamicRef: '#meta' + type: boolean allOf: - $ref: '#/$defs/examples' - $ref: '#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-path' @@ -397,12 +409,6 @@ $defs: explode: default: false - content: - properties: - content: - type: object - additionalProperties: - $ref: '#/$defs/media-type' $ref: '#/$defs/specification-extensions' unevaluatedProperties: false @@ -536,9 +542,9 @@ $defs: required: - $ref then: - $ref: "#/$defs/reference" + $ref: '#/$defs/reference' else: - $ref: "#/$defs/response" + $ref: '#/$defs/response' callbacks: type: object @@ -806,7 +812,7 @@ $defs: password: $ref: '#/$defs/oauth-flows/$defs/password' clientCredentials: - $ref": '#/$defs/oauth-flows/$defs/client-credentials' + $ref: '#/$defs/oauth-flows/$defs/client-credentials' authorizationCode: $ref: '#/$defs/oauth-flows/$defs/authorization-code' $ref: '#/$defs/specification-extensions' From 84ea4a0288c967e1f7355350c946199efd963fa0 Mon Sep 17 00:00:00 2001 From: Jason Desrosiers Date: Mon, 15 Mar 2021 10:40:48 -0700 Subject: [PATCH 5/7] 3.1 Schema: Fix anchoring in regexes --- schemas/v3.1/schema.json | 6 +++--- schemas/v3.1/schema.yaml | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/schemas/v3.1/schema.json b/schemas/v3.1/schema.json index 7fa4dbf6fa..92ff85b685 100644 --- a/schemas/v3.1/schema.json +++ b/schemas/v3.1/schema.json @@ -258,7 +258,7 @@ } }, "patternProperties": { - "^schemas|responses|parameters|examples|requestBodies|headers|securitySchemes|links|callbacks|pathItems$": { + "^(schemas|responses|parameters|examples|requestBodies|headers|securitySchemes|links|callbacks|pathItems)$": { "$comment": "Enumerating all of the property names in the regex above is necessary for unevaluatedProperties to work as expected", "propertyNames": { "pattern": "^[a-zA-Z0-9._-]+$" @@ -301,7 +301,7 @@ } }, "patternProperties": { - "^get|post|delete|options|head|patch|trace$": { + "^(get|post|delete|options|head|patch|trace)$": { "$ref": "#/$defs/operation" } }, @@ -763,7 +763,7 @@ } }, "patternProperties": { - "[1-5][0-9X]{2}": { + "^[1-5][0-9X]{2}$": { "$ref": "#/$defs/response-or-reference" } }, diff --git a/schemas/v3.1/schema.yaml b/schemas/v3.1/schema.yaml index 94dcea96e5..ab73adefff 100644 --- a/schemas/v3.1/schema.yaml +++ b/schemas/v3.1/schema.yaml @@ -176,7 +176,7 @@ $defs: additionalProperties: $ref: '#/$defs/path-item-or-reference' patternProperties: - '^schemas|responses|parameters|examples|requestBodies|headers|securitySchemes|links|callbacks|pathItems$': + '^(schemas|responses|parameters|examples|requestBodies|headers|securitySchemes|links|callbacks|pathItems)$': $comment: Enumerating all of the property names in the regex above is necessary for unevaluatedProperties to work as expected propertyNames: pattern: '^[a-zA-Z0-9._-]+$' @@ -207,7 +207,7 @@ $defs: items: $ref: '#/$defs/parameter-or-reference' patternProperties: - '^get|post|delete|options|head|patch|trace$': + '^(get|post|delete|options|head|patch|trace)$': $ref: '#/$defs/operation' $ref: '#/$defs/specification-extensions' unevaluatedProperties: false @@ -510,7 +510,7 @@ $defs: default: $ref: '#/$defs/response-or-reference' patternProperties: - '[1-5][0-9X]{2}': + '^[1-5][0-9X]{2}$': $ref: '#/$defs/response-or-reference' $ref: '#/$defs/specification-extensions' unevaluatedProperties: false From b5bf761d9b4e9a26294ead2a2f1a2e7d99bcd85d Mon Sep 17 00:00:00 2001 From: Jason Desrosiers Date: Fri, 19 Mar 2021 15:30:03 -0700 Subject: [PATCH 6/7] 3.1 Schema: Add media-range format for content objects --- schemas/v3.1/schema.json | 32 +++++++++++++++----------------- schemas/v3.1/schema.yaml | 24 ++++++++++++------------ 2 files changed, 27 insertions(+), 29 deletions(-) diff --git a/schemas/v3.1/schema.json b/schemas/v3.1/schema.json index 92ff85b685..fa987c12d6 100644 --- a/schemas/v3.1/schema.json +++ b/schemas/v3.1/schema.json @@ -429,10 +429,7 @@ "$dynamicRef": "#meta" }, "content": { - "type": "object", - "additionalProperties": { - "$ref": "#/$defs/media-type" - } + "$ref": "#/$defs/content" } }, "required": [ @@ -636,10 +633,7 @@ "type": "string" }, "content": { - "type": "object", - "additionalProperties": { - "$ref": "#/$defs/media-type" - } + "$ref": "#/$defs/content" }, "required": { "default": false, @@ -665,6 +659,15 @@ "$ref": "#/$defs/request-body" } }, + "content": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/media-type" + }, + "propertyNames": { + "format": "media-range" + } + }, "media-type": { "type": "object", "properties": { @@ -692,7 +695,8 @@ "type": "object", "properties": { "contentType": { - "type": "string" + "type": "string", + "format": "media-range" }, "headers": { "type": "object", @@ -783,10 +787,7 @@ } }, "content": { - "type": "object", - "addtionalProperties": { - "$ref": "#/$defs/media-type" - } + "$ref": "#/$defs/content" }, "links": { "type": "object", @@ -955,10 +956,7 @@ "content": { "properties": { "content": { - "type": "object", - "additionalProperties": { - "$ref": "#/$defs/media-type" - } + "$ref": "#/$defs/content" } } } diff --git a/schemas/v3.1/schema.yaml b/schemas/v3.1/schema.yaml index ab73adefff..9b71e11031 100644 --- a/schemas/v3.1/schema.yaml +++ b/schemas/v3.1/schema.yaml @@ -299,9 +299,7 @@ $defs: schema: $dynamicRef: '#meta' content: - type: object - additionalProperties: - $ref: '#/$defs/media-type' + $ref: '#/$defs/content' required: - in oneOf: @@ -427,9 +425,7 @@ $defs: description: type: string content: - type: object - additionalProperties: - $ref: '#/$defs/media-type' + $ref: '#/$defs/content' required: default: false type: boolean @@ -447,6 +443,13 @@ $defs: else: $ref: '#/$defs/request-body' + content: + type: object + additionalProperties: + $ref: '#/$defs/media-type' + propertyNames: + format: media-range + media-type: type: object properties: @@ -466,6 +469,7 @@ $defs: properties: contentType: type: string + format: media-range headers: type: object additionalProperties: @@ -525,9 +529,7 @@ $defs: additionalProperties: $ref: '#/$defs/header-or-reference' content: - type: object - addtionalProperties: - $ref: '#/$defs/media-type' + $ref: '#/$defs/content' links: type: object additionalProperties: @@ -646,9 +648,7 @@ $defs: content: properties: content: - type: object - additionalProperties: - $ref: '#/$defs/media-type' + $ref: '#/$defs/content' $ref: '#/$defs/specification-extensions' unevaluatedProperties: false From 1ce46a056a766aeee05b53a6a631073b7ef18e8c Mon Sep 17 00:00:00 2001 From: Jason Desrosiers Date: Fri, 19 Mar 2021 21:59:50 -0700 Subject: [PATCH 7/7] 3.1 Schema: Remove submodule --- .gitmodules | 3 -- scripts/validate.js | 8 +-- tests/openapi3-examples | 1 - tests/v3.1/fail/no_containers.yaml | 4 ++ tests/v3.1/fail/sever_enum_empty.yaml | 11 ++++ tests/v3.1/fail/sever_enum_unknown.yaml | 12 +++++ tests/v3.1/fail/unknown_container.yaml | 5 ++ tests/v3.1/pass/comp_pathitems.yaml | 6 +++ tests/v3.1/pass/info_summary.yaml | 6 +++ tests/v3.1/pass/license_identifier.yaml | 9 ++++ tests/v3.1/pass/mega.yaml | 49 +++++++++++++++++ tests/v3.1/pass/minimal_comp.yaml | 5 ++ tests/v3.1/pass/minimal_hooks.yaml | 5 ++ tests/v3.1/pass/minimal_paths.yaml | 5 ++ tests/v3.1/pass/path_no_response.yaml | 7 +++ tests/v3.1/pass/path_var_empty_pathitem.yaml | 6 +++ tests/v3.1/pass/schema.yaml | 55 ++++++++++++++++++++ tests/v3.1/test.js | 10 ++-- 18 files changed, 193 insertions(+), 14 deletions(-) delete mode 160000 tests/openapi3-examples create mode 100644 tests/v3.1/fail/no_containers.yaml create mode 100644 tests/v3.1/fail/sever_enum_empty.yaml create mode 100644 tests/v3.1/fail/sever_enum_unknown.yaml create mode 100644 tests/v3.1/fail/unknown_container.yaml create mode 100644 tests/v3.1/pass/comp_pathitems.yaml create mode 100644 tests/v3.1/pass/info_summary.yaml create mode 100644 tests/v3.1/pass/license_identifier.yaml create mode 100644 tests/v3.1/pass/mega.yaml create mode 100644 tests/v3.1/pass/minimal_comp.yaml create mode 100644 tests/v3.1/pass/minimal_hooks.yaml create mode 100644 tests/v3.1/pass/minimal_paths.yaml create mode 100644 tests/v3.1/pass/path_no_response.yaml create mode 100644 tests/v3.1/pass/path_var_empty_pathitem.yaml create mode 100644 tests/v3.1/pass/schema.yaml diff --git a/.gitmodules b/.gitmodules index ca8320435d..e69de29bb2 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +0,0 @@ -[submodule "tests/v3.1/openapi3-examples"] - path = tests/openapi3-examples - url = git@github.com:Mermade/openapi3-examples.git diff --git a/scripts/validate.js b/scripts/validate.js index cf1c2d09ca..c9c70db797 100755 --- a/scripts/validate.js +++ b/scripts/validate.js @@ -24,14 +24,14 @@ const args = process.argv.reduce((acc, arg) => { (async function () { try { + const schemaType = args.schema || "schema"; + const schemaVersion = args.version || "2021-03-02"; + const outputFormat = args.format || JsonSchema.BASIC; + // Config JsonSchema.setMetaOutputFormat(outputFormat); //JsonSchema.setShouldMetaValidate(false); - const schemaType = args.schema || "schema"; - const schemaVersion = args.version || "2021-03-02"; - const ouputFormat = args.format || JsonSchema.BASIC; - // Load schemas JsonSchema.add(dialect); JsonSchema.add(vocabulary); diff --git a/tests/openapi3-examples b/tests/openapi3-examples deleted file mode 160000 index 9c2997e1a2..0000000000 --- a/tests/openapi3-examples +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 9c2997e1a25919a8182080cc43a4db06d2dc775d diff --git a/tests/v3.1/fail/no_containers.yaml b/tests/v3.1/fail/no_containers.yaml new file mode 100644 index 0000000000..6cc86aae92 --- /dev/null +++ b/tests/v3.1/fail/no_containers.yaml @@ -0,0 +1,4 @@ +iopenapi: 3.1.0 +info: + title: API + version: 1.0.0 diff --git a/tests/v3.1/fail/sever_enum_empty.yaml b/tests/v3.1/fail/sever_enum_empty.yaml new file mode 100644 index 0000000000..62d751e171 --- /dev/null +++ b/tests/v3.1/fail/sever_enum_empty.yaml @@ -0,0 +1,11 @@ +openapi: 3.1.0 +info: + title: API + version: 1.0.0 +servers: + - url: https://example.com/{var} + variables: + var: + enum: [] + default: a +components: {} diff --git a/tests/v3.1/fail/sever_enum_unknown.yaml b/tests/v3.1/fail/sever_enum_unknown.yaml new file mode 100644 index 0000000000..eb8c8f6872 --- /dev/null +++ b/tests/v3.1/fail/sever_enum_unknown.yaml @@ -0,0 +1,12 @@ +openapi: 3.1.0 +info: + title: API + version: 1.0.0 +servers: + - url: https://example.com/{var} + variables: + var: + enum: + - a + default: b +components: {} diff --git a/tests/v3.1/fail/unknown_container.yaml b/tests/v3.1/fail/unknown_container.yaml new file mode 100644 index 0000000000..e0565f4a5a --- /dev/null +++ b/tests/v3.1/fail/unknown_container.yaml @@ -0,0 +1,5 @@ +openapi: 3.1.0 +info: + title: API + version: 1.0.0 +overlays: {} diff --git a/tests/v3.1/pass/comp_pathitems.yaml b/tests/v3.1/pass/comp_pathitems.yaml new file mode 100644 index 0000000000..502ca1fca2 --- /dev/null +++ b/tests/v3.1/pass/comp_pathitems.yaml @@ -0,0 +1,6 @@ +openapi: 3.1.0 +info: + title: API + version: 1.0.0 +components: + pathItems: {} diff --git a/tests/v3.1/pass/info_summary.yaml b/tests/v3.1/pass/info_summary.yaml new file mode 100644 index 0000000000..30d224afc2 --- /dev/null +++ b/tests/v3.1/pass/info_summary.yaml @@ -0,0 +1,6 @@ +openapi: 3.1.0 +info: + title: API + summary: My lovely API + version: 1.0.0 +components: {} diff --git a/tests/v3.1/pass/license_identifier.yaml b/tests/v3.1/pass/license_identifier.yaml new file mode 100644 index 0000000000..fbdba5efbe --- /dev/null +++ b/tests/v3.1/pass/license_identifier.yaml @@ -0,0 +1,9 @@ +openapi: 3.1.0 +info: + title: API + summary: My lovely API + version: 1.0.0 + license: + name: Apache + identifier: Apache-2.0 +components: {} diff --git a/tests/v3.1/pass/mega.yaml b/tests/v3.1/pass/mega.yaml new file mode 100644 index 0000000000..8838c03a6d --- /dev/null +++ b/tests/v3.1/pass/mega.yaml @@ -0,0 +1,49 @@ +openapi: 3.1.0 +info: + summary: My API's summary + title: My API + version: 1.0.0 + license: + name: Apache 2.0 + identifier: Apache-2.0 +jsonSchemaDialect: https://spec.openapis.org/oas/3.1/dialect/base +paths: + /: + get: + parameters: [] + /{pathTest}: {} +webhooks: + myWebhook: + $ref: '#/components/pathItems/myPathItem' + description: Overriding description +components: + securitySchemes: + mtls: + type: mutualTLS + pathItems: + myPathItem: + post: + requestBody: + required: true + content: + 'application/json': + schema: + type: object + properties: + type: + type: string + int: + type: integer + exclusiveMaximum: 100 + exclusiveMinimum: 0 + none: + type: 'null' + arr: + type: array + $comment: Array without items keyword + either: + type: ['string','null'] + discriminator: + propertyName: type + x-extension: true + myArbitraryKeyword: true diff --git a/tests/v3.1/pass/minimal_comp.yaml b/tests/v3.1/pass/minimal_comp.yaml new file mode 100644 index 0000000000..4553689ab4 --- /dev/null +++ b/tests/v3.1/pass/minimal_comp.yaml @@ -0,0 +1,5 @@ +openapi: 3.1.0 +info: + title: API + version: 1.0.0 +components: {} diff --git a/tests/v3.1/pass/minimal_hooks.yaml b/tests/v3.1/pass/minimal_hooks.yaml new file mode 100644 index 0000000000..e67b2889de --- /dev/null +++ b/tests/v3.1/pass/minimal_hooks.yaml @@ -0,0 +1,5 @@ +openapi: 3.1.0 +info: + title: API + version: 1.0.0 +webhooks: {} diff --git a/tests/v3.1/pass/minimal_paths.yaml b/tests/v3.1/pass/minimal_paths.yaml new file mode 100644 index 0000000000..016e86796f --- /dev/null +++ b/tests/v3.1/pass/minimal_paths.yaml @@ -0,0 +1,5 @@ +openapi: 3.1.0 +info: + title: API + version: 1.0.0 +paths: {} diff --git a/tests/v3.1/pass/path_no_response.yaml b/tests/v3.1/pass/path_no_response.yaml new file mode 100644 index 0000000000..334608f111 --- /dev/null +++ b/tests/v3.1/pass/path_no_response.yaml @@ -0,0 +1,7 @@ +openapi: 3.1.0 +info: + title: API + version: 1.0.0 +paths: + /: + get: {} diff --git a/tests/v3.1/pass/path_var_empty_pathitem.yaml b/tests/v3.1/pass/path_var_empty_pathitem.yaml new file mode 100644 index 0000000000..ba92742f10 --- /dev/null +++ b/tests/v3.1/pass/path_var_empty_pathitem.yaml @@ -0,0 +1,6 @@ +openapi: 3.1.0 +info: + title: API + version: 1.0.0 +paths: + /{var}: {} diff --git a/tests/v3.1/pass/schema.yaml b/tests/v3.1/pass/schema.yaml new file mode 100644 index 0000000000..e192529a68 --- /dev/null +++ b/tests/v3.1/pass/schema.yaml @@ -0,0 +1,55 @@ +openapi: 3.1.0 +info: + title: API + version: 1.0.0 +paths: {} +components: + schemas: + model: + type: object + properties: + one: + description: type array + type: + - integer + - string + two: + description: type 'null' + type: "null" + three: + description: type array including 'null' + type: + - string + - "null" + four: + description: array with no items + type: array + five: + description: singular example + type: string + examples: + - exampleValue + six: + description: exclusiveMinimum true + exclusiveMinimum: 10 + seven: + description: exclusiveMinimum false + minimum: 10 + eight: + description: exclusiveMaximum true + exclusiveMaximum: 20 + nine: + description: exclusiveMaximum false + maximum: 20 + ten: + description: nullable string + type: + - string + - "null" + eleven: + description: x-nullable string + type: + - string + - "null" + twelve: + description: file/binary diff --git a/tests/v3.1/test.js b/tests/v3.1/test.js index 77d62cb02f..43f7e80588 100644 --- a/tests/v3.1/test.js +++ b/tests/v3.1/test.js @@ -6,8 +6,6 @@ const dialect = require("../../schemas/v3.1/dialect/base.schema.json"); const vocabulary = require("../../schemas/v3.1/meta/base.schema.json"); -const testSuitePath = `${__dirname}/../openapi3-examples/3.1`; - JsonSchema.setMetaOutputFormat(JsonSchema.BASIC); //JsonSchema.setShouldMetaValidate(false); @@ -20,10 +18,10 @@ before(async () => { }); describe("Pass", () => { - fs.readdirSync(`${testSuitePath}/pass`, { withFileTypes: true }) + fs.readdirSync(`${__dirname}/pass`, { withFileTypes: true }) .filter((entry) => entry.isFile() && /\.yaml$/.test(entry.name)) .forEach((entry) => { - const file = `${testSuitePath}/pass/${entry.name}`; + const file = `${__dirname}/pass/${entry.name}`; it(entry.name, async () => { const instance = yaml.parse(fs.readFileSync(file, "utf8")); @@ -35,10 +33,10 @@ describe("Pass", () => { }); describe("Fail", () => { - fs.readdirSync(`${testSuitePath}/fail`, { withFileTypes: true }) + fs.readdirSync(`${__dirname}/fail`, { withFileTypes: true }) .filter((entry) => entry.isFile() && /\.yaml$/.test(entry.name)) .forEach((entry) => { - const file = `${testSuitePath}/fail/${entry.name}`; + const file = `${__dirname}/fail/${entry.name}`; it(entry.name, async () => { const instance = yaml.parse(fs.readFileSync(file, "utf8"));