diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000..e69de29bb2 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..01da62b56d --- /dev/null +++ b/schemas/v3.1/README.md @@ -0,0 +1,41 @@ +# 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. + +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: +- 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 --recursive tests +``` + +You can also validate a document individually. + +```bash +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 new file mode 100644 index 0000000000..d54b0d4d7c --- /dev/null +++ b/schemas/v3.1/dialect/base.schema.json @@ -0,0 +1,21 @@ +{ + "$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/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 new file mode 100644 index 0000000000..f3ee03fb96 --- /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/external-docs" }, + "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 + }, + "external-docs": { + "$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/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.json b/schemas/v3.1/schema.json new file mode 100644 index 0000000000..fa987c12d6 --- /dev/null +++ b/schemas/v3.1/schema.json @@ -0,0 +1,1340 @@ +{ + "$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": { + "$ref": "#/$defs/uri", + "default": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "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": { + "^(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", + "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" + }, + "schema": { + "$dynamicRef": "#meta" + }, + "content": { + "$ref": "#/$defs/content" + } + }, + "required": [ + "in" + ], + "oneOf": [ + { + "required": [ + "schema" + ] + }, + { + "required": [ + "content" + ] + } + ], + "dependentSchemas": { + "schema": { + "properties": { + "style": { + "type": "string" + }, + "explode": { + "type": "boolean" + }, + "allowReserved": { + "default": false, + "type": "boolean" + } + }, + "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 + } + } + } + } + } + } + }, + "$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": { + "$ref": "#/$defs/content" + }, + "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" + } + }, + "content": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/media-type" + }, + "propertyNames": { + "format": "media-range" + } + }, + "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", + "format": "media-range" + }, + "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": { + "$ref": "#/$defs/content" + }, + "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": { + "$ref": "#/$defs/content" + } + } + } + }, + "$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", + "type": [ + "object", + "boolean" + ] + }, + "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..9b71e11031 --- /dev/null +++ b/schemas/v3.1/schema.yaml @@ -0,0 +1,911 @@ +$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: + $ref: '#/$defs/uri' + default: 'https://spec.openapis.org/oas/3.1/dialect/base' + 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: + '^(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' + 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 + schema: + $dynamicRef: '#meta' + content: + $ref: '#/$defs/content' + required: + - in + oneOf: + - required: + - schema + - required: + - content + dependentSchemas: + schema: + properties: + style: + type: string + explode: + type: boolean + allowReserved: + default: false + type: boolean + 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 + + $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: + $ref: '#/$defs/content' + 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' + + content: + type: object + additionalProperties: + $ref: '#/$defs/media-type' + propertyNames: + format: media-range + + 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 + format: media-range + 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: + $ref: '#/$defs/content' + 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: + $ref: '#/$defs/content' + $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 + type: + - object + - boolean + + 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/scripts/validate.js b/scripts/validate.js new file mode 100755 index 0000000000..c9c70db797 --- /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 { + 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); + + // 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/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 new file mode 100644 index 0000000000..43f7e80588 --- /dev/null +++ b/tests/v3.1/test.js @@ -0,0 +1,48 @@ +const fs = require("fs"); +const yaml = require("yaml"); +const JsonSchema = require("@hyperjump/json-schema"); +const { expect } = require("chai"); +const dialect = require("../../schemas/v3.1/dialect/base.schema.json"); +const vocabulary = require("../../schemas/v3.1/meta/base.schema.json"); + + +JsonSchema.setMetaOutputFormat(JsonSchema.BASIC); +//JsonSchema.setShouldMetaValidate(false); + +let metaSchema; +before(async () => { + JsonSchema.add(dialect); + JsonSchema.add(vocabulary); + 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", () => { + fs.readdirSync(`${__dirname}/pass`, { withFileTypes: true }) + .filter((entry) => entry.isFile() && /\.yaml$/.test(entry.name)) + .forEach((entry) => { + const file = `${__dirname}/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(`${__dirname}/fail`, { withFileTypes: true }) + .filter((entry) => entry.isFile() && /\.yaml$/.test(entry.name)) + .forEach((entry) => { + const file = `${__dirname}/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); + }); + }); +});