diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index b60478a..bb7456a 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -2,9 +2,9 @@ name: CI on: push: - branches: [ master ] + branches: [ master, openapi-31 ] pull_request: - branches: [ master ] + branches: [ master, openapi-31 ] jobs: phpunit: diff --git a/Makefile b/Makefile index ee99b2a..3662f26 100644 --- a/Makefile +++ b/Makefile @@ -63,10 +63,13 @@ stan: $(DOCKER_PHP) php $(PHPARGS) vendor/bin/phpstan analyse -l 5 src # copy openapi3 json schema -schemas/openapi-v3.0.json: vendor/oai/openapi-specification/schemas/v3.0/schema.json +schemas/openapi-v3.0.json: vendor/oai/openapi-specification-3.0/schemas/v3.0/schema.json cp $< $@ - -schemas/openapi-v3.0.yaml: vendor/oai/openapi-specification/schemas/v3.0/schema.yaml +schemas/openapi-v3.0.yaml: vendor/oai/openapi-specification-3.0/schemas/v3.0/schema.yaml + cp $< $@ +schemas/openapi-v3.1.json: vendor/oai/openapi-specification-3.1/schemas/v3.1/schema.json + cp $< $@ +schemas/openapi-v3.1.yaml: vendor/oai/openapi-specification-3.1/schemas/v3.1/schema.yaml cp $< $@ php-cs-fixer.phar: diff --git a/README.md b/README.md index 3d25df0..8d94a09 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,13 @@ # php-openapi -Read and write [OpenAPI](https://www.openapis.org/) 3.0.x YAML and JSON files and make the content accessible in PHP objects. +Read and write [OpenAPI](https://www.openapis.org/) 3.x YAML and JSON files and make the content accessible in PHP objects. -It also provides a CLI tool for validating and converting OpenAPI 3.0.x Description files. +It also provides a CLI tool for validating and converting OpenAPI 3.x Description files. + +Supported OpenAPI versions: + +- 3.0.x +- 3.1.x [![Latest Stable Version](https://poser.pugx.org/cebe/php-openapi/v/stable)](https://packagist.org/packages/cebe/php-openapi) [![Total Downloads](https://poser.pugx.org/cebe/php-openapi/downloads)](https://packagist.org/packages/cebe/php-openapi) @@ -222,7 +227,7 @@ $openapi->resolveReferences( The library provides simple validation operations, that check basic OpenAPI spec requirements. This is the same as "structural errors found while reading the API Description file" from the CLI tool. -This validation does not include checking against the OpenAPI v3.0 JSON schema, this is only implemented in the CLI. +This validation does not include checking against the OpenAPI v3.0/v3.1 JSON schemas, this is only implemented in the CLI. ``` // return `true` in case no errors have been found, `false` in case of errors. @@ -235,48 +240,6 @@ $errors = $openapi->getErrors(); > but the list of errors given may not be complete. Also a passing validation does not necessarily indicate a completely > valid spec. - -## Completeness - -This library is currently work in progress, the following list tracks completeness: - -- [x] read OpenAPI 3.0 JSON -- [x] read OpenAPI 3.0 YAML -- [ ] OpenAPI 3.0 Schema - - [x] OpenAPI Object - - [x] Info Object - - [x] Contact Object - - [x] License Object - - [x] Server Object - - [x] Server Variable Object - - [x] Components Object - - [x] Paths Object - - [x] Path Item Object - - [x] Operation Object - - [x] External Documentation Object - - [x] Parameter Object - - [x] Request Body Object - - [x] Media Type Object - - [x] Encoding Object - - [x] Responses Object - - [x] Response Object - - [x] Callback Object - - [x] Example Object - - [x] Link Object - - [ ] [Runtime Expressions](https://github.com/OAI/OpenAPI-Specification/blob/3.0.2/versions/3.0.2.md#runtime-expressions) - - [x] Header Object - - [x] Tag Object - - [x] Reference Object - - [x] Schema Object - - [x] load/read - - [ ] validation - - [x] Discriminator Object - - [x] XML Object - - [x] Security Scheme Object - - [x] OAuth Flows Object - - [x] OAuth Flow Object - - [x] Security Requirement Object - # Development You may use the docker environment for local development: @@ -286,7 +249,6 @@ You may use the docker environment for local development: make IN_DOCKER=1 test ... - # Support **Need help with your API project?** diff --git a/bin/php-openapi b/bin/php-openapi index ae2ffdc..53e5196 100755 --- a/bin/php-openapi +++ b/bin/php-openapi @@ -131,16 +131,22 @@ switch ($command) { // Validate + // OpenAPI version check + $openApiVersion = $openApi->getMajorVersion(); + if ($openApiVersion === \cebe\openapi\spec\OpenApi::VERSION_UNSUPPORTED) { + error("Unsupported OpenAPI version: " . $openApi->openapi); + } + $openApi->validate(); $errors = array_merge($errors, $openApi->getErrors()); $validator = new JsonSchema\Validator; $openApiData = $openApi->getSerializableData(); - $validator->validate($openApiData, (object)['$ref' => 'file://' . dirname(__DIR__) . '/schemas/openapi-v3.0.json']); + $validator->validate($openApiData, (object)['$ref' => 'file://' . dirname(__DIR__) . "/schemas/openapi-v{$openApiVersion}.json"]); if ($validator->isValid() && empty($errors)) { if(!$silentMode) { - print_formatted("The supplied API Description \B\Gvalidates\C against the OpenAPI v3.0 schema.\n", STDERR); + print_formatted("The supplied API Description \B\Gvalidates\C against the OpenAPI v{$openApiVersion} schema.\n", STDERR); } exit(0); } @@ -163,7 +169,7 @@ switch ($command) { } } if (!$validator->isValid()) { - print_formatted("\BOpenAPI v3.0 schema violations:\C\n", STDERR); + print_formatted("\BOpenAPI v{$openApiVersion} schema violations:\C\n", STDERR); $errors = $validator->getErrors(); foreach ($errors as $error) { // hide some errors triggered by other errors further down the path diff --git a/composer.json b/composer.json index 46cb26f..19fbf7b 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,10 @@ "require-dev": { "cebe/indent": "*", "phpunit/phpunit": "^6.5 || ^7.5 || ^8.5 || ^9.4", - "oai/openapi-specification": "3.0.3", + + "oai/openapi-specification-3.0": "3.0.3", + "oai/openapi-specification-3.1": "3.1.0", + "mermade/openapi3-examples": "1.0.0", "apis-guru/openapi-directory": "1.0.0", "nexmo/api-specification": "1.0.0", @@ -52,7 +55,7 @@ { "type": "package", "package": { - "name": "oai/openapi-specification", + "name": "oai/openapi-specification-3.0", "version": "3.0.3", "source": { "url": "https://github.com/OAI/OpenAPI-Specification", @@ -61,6 +64,18 @@ } } }, + { + "type": "package", + "package": { + "name": "oai/openapi-specification-3.1", + "version": "3.1.0", + "source": { + "url": "https://github.com/OAI/OpenAPI-Specification", + "type": "git", + "reference": "v3.1.1-dev" + } + } + }, { "type": "package", "package": { @@ -69,7 +84,7 @@ "source": { "url": "https://github.com/Mermade/openapi3-examples", "type": "git", - "reference": "3e8740c4994310a5d6a35d9b19e405862326f149" + "reference": "9c2997e1a25919a8182080cc43a4db06d2dc775d" } } }, diff --git a/schemas/openapi-v3.1.json b/schemas/openapi-v3.1.json new file mode 100644 index 0000000..295029f --- /dev/null +++ b/schemas/openapi-v3.1.json @@ -0,0 +1,1383 @@ +{ + "$id": "https://spec.openapis.org/oas/3.1/schema/2021-09-28", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "openapi": { + "type": "string", + "pattern": "^3\\.1\\.\\d+(-.+)?$" + }, + "info": { + "$ref": "#/$defs/info" + }, + "jsonSchemaDialect": { + "type": "string", + "format": "uri", + "default": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "servers": { + "type": "array", + "items": { + "$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": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#info-object", + "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": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#contact-object", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "email": { + "type": "string" + } + }, + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "license": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#license-object", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "identifier": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri" + } + }, + "required": [ + "name" + ], + "oneOf": [ + { + "required": [ + "identifier" + ] + }, + { + "required": [ + "url" + ] + } + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "server": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#server-object", + "type": "object", + "properties": { + "url": { + "type": "string", + "format": "uri-reference" + }, + "description": { + "type": "string" + }, + "variables": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/server-variable" + } + } + }, + "required": [ + "url" + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "server-variable": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#server-variable-object", + "type": "object", + "properties": { + "enum": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1 + }, + "default": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "required": [ + "default" + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "components": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#components-object", + "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": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#paths-object", + "type": "object", + "patternProperties": { + "^/": { + "$ref": "#/$defs/path-item" + } + }, + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "path-item": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#path-item-object", + "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|put|post|delete|options|head|patch|trace)$": { + "$ref": "#/$defs/operation" + } + }, + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "path-item-or-reference": { + "if": { + "type": "object", + "required": [ + "$ref" + ] + }, + "then": { + "$ref": "#/$defs/reference" + }, + "else": { + "$ref": "#/$defs/path-item" + } + }, + "operation": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#operation-object", + "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": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#external-documentation-object", + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri" + } + }, + "required": [ + "url" + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "parameter": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#parameter-object", + "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", + "minProperties": 1, + "maxProperties": 1 + } + }, + "required": [ + "name", + "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": { + "name": { + "pattern": "[^/#?]+$" + }, + "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", + "const": "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", + "const": "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": { + "type": "object", + "required": [ + "$ref" + ] + }, + "then": { + "$ref": "#/$defs/reference" + }, + "else": { + "$ref": "#/$defs/parameter" + } + }, + "request-body": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#request-body-object", + "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": { + "type": "object", + "required": [ + "$ref" + ] + }, + "then": { + "$ref": "#/$defs/reference" + }, + "else": { + "$ref": "#/$defs/request-body" + } + }, + "content": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#fixed-fields-10", + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/media-type" + }, + "propertyNames": { + "format": "media-range" + } + }, + "media-type": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#media-type-object", + "type": "object", + "properties": { + "schema": { + "$dynamicRef": "#meta" + }, + "encoding": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/encoding" + } + } + }, + "allOf": [ + { + "$ref": "#/$defs/specification-extensions" + }, + { + "$ref": "#/$defs/examples" + } + ], + "unevaluatedProperties": false + }, + "encoding": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#encoding-object", + "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": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#responses-object", + "type": "object", + "properties": { + "default": { + "$ref": "#/$defs/response-or-reference" + } + }, + "patternProperties": { + "^[1-5](?:[0-9]{2}|XX)$": { + "$ref": "#/$defs/response-or-reference" + } + }, + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "response": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#response-object", + "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": { + "type": "object", + "required": [ + "$ref" + ] + }, + "then": { + "$ref": "#/$defs/reference" + }, + "else": { + "$ref": "#/$defs/response" + } + }, + "callbacks": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#callback-object", + "type": "object", + "$ref": "#/$defs/specification-extensions", + "additionalProperties": { + "$ref": "#/$defs/path-item-or-reference" + } + }, + "callbacks-or-reference": { + "if": { + "type": "object", + "required": [ + "$ref" + ] + }, + "then": { + "$ref": "#/$defs/reference" + }, + "else": { + "$ref": "#/$defs/callbacks" + } + }, + "example": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#example-object", + "type": "object", + "properties": { + "summary": { + "type": "string" + }, + "description": { + "type": "string" + }, + "value": true, + "externalValue": { + "type": "string", + "format": "uri" + } + }, + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "example-or-reference": { + "if": { + "type": "object", + "required": [ + "$ref" + ] + }, + "then": { + "$ref": "#/$defs/reference" + }, + "else": { + "$ref": "#/$defs/example" + } + }, + "link": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#link-object", + "type": "object", + "properties": { + "operationRef": { + "type": "string", + "format": "uri-reference" + }, + "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": { + "type": "object", + "required": [ + "$ref" + ] + }, + "then": { + "$ref": "#/$defs/reference" + }, + "else": { + "$ref": "#/$defs/link" + } + }, + "header": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#header-object", + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "required": { + "default": false, + "type": "boolean" + }, + "deprecated": { + "default": false, + "type": "boolean" + }, + "schema": { + "$dynamicRef": "#meta" + }, + "content": { + "$ref": "#/$defs/content", + "minProperties": 1, + "maxProperties": 1 + } + }, + "oneOf": [ + { + "required": [ + "schema" + ] + }, + { + "required": [ + "content" + ] + } + ], + "dependentSchemas": { + "schema": { + "properties": { + "style": { + "default": "simple", + "const": "simple" + }, + "explode": { + "default": false, + "type": "boolean" + } + }, + "$ref": "#/$defs/examples" + } + }, + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "header-or-reference": { + "if": { + "type": "object", + "required": [ + "$ref" + ] + }, + "then": { + "$ref": "#/$defs/reference" + }, + "else": { + "$ref": "#/$defs/header" + } + }, + "tag": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#tag-object", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "externalDocs": { + "$ref": "#/$defs/external-documentation" + } + }, + "required": [ + "name" + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "reference": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#reference-object", + "type": "object", + "properties": { + "$ref": { + "type": "string", + "format": "uri-reference" + }, + "summary": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "unevaluatedProperties": false + }, + "schema": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#schema-object", + "$dynamicAnchor": "meta", + "type": [ + "object", + "boolean" + ] + }, + "security-scheme": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#security-scheme-object", + "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": { + "type": "string", + "pattern": "^[Bb][Ee][Aa][Rr][Ee][Rr]$" + } + }, + "required": [ + "type", + "scheme" + ] + }, + "then": { + "properties": { + "bearerFormat": { + "type": "string" + } + } + } + }, + "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": { + "type": "string", + "format": "uri" + } + }, + "required": [ + "openIdConnectUrl" + ] + } + } + } + }, + "security-scheme-or-reference": { + "if": { + "type": "object", + "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": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#security-requirement-object", + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "specification-extensions": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#specification-extensions", + "patternProperties": { + "^x-": true + } + }, + "examples": { + "properties": { + "example": true, + "examples": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/example-or-reference" + } + } + } + }, + "map-of-strings": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } +} diff --git a/schemas/openapi-v3.1.yaml b/schemas/openapi-v3.1.yaml new file mode 100644 index 0000000..49c7a52 --- /dev/null +++ b/schemas/openapi-v3.1.yaml @@ -0,0 +1,953 @@ +$id: 'https://spec.openapis.org/oas/3.1/schema/2021-09-28' +$schema: 'https://json-schema.org/draft/2020-12/schema' + +type: object +properties: + openapi: + type: string + pattern: '^3\.1\.\d+(-.+)?$' + info: + $ref: '#/$defs/info' + jsonSchemaDialect: + type: string + format: uri + default: 'https://spec.openapis.org/oas/3.1/dialect/base' + servers: + type: array + items: + $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: + $comment: https://spec.openapis.org/oas/v3.1.0#info-object + 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: + $comment: https://spec.openapis.org/oas/v3.1.0#contact-object + type: object + properties: + name: + type: string + url: + type: string + email: + type: string + $ref: '#/$defs/specification-extensions' + unevaluatedProperties: false + + license: + $comment: https://spec.openapis.org/oas/v3.1.0#license-object + type: object + properties: + name: + type: string + identifier: + type: string + url: + type: string + format: uri + required: + - name + oneOf: + - required: + - identifier + - required: + - url + $ref: '#/$defs/specification-extensions' + unevaluatedProperties: false + + server: + $comment: https://spec.openapis.org/oas/v3.1.0#server-object + type: object + properties: + url: + type: string + format: uri-reference + description: + type: string + variables: + type: object + additionalProperties: + $ref: '#/$defs/server-variable' + required: + - url + $ref: '#/$defs/specification-extensions' + unevaluatedProperties: false + + server-variable: + $comment: https://spec.openapis.org/oas/v3.1.0#server-variable-object + type: object + properties: + enum: + type: array + items: + type: string + minItems: 1 + default: + type: string + description: + type: string + required: + - default + $ref: '#/$defs/specification-extensions' + unevaluatedProperties: false + + components: + $comment: https://spec.openapis.org/oas/v3.1.0#components-object + 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: + $comment: https://spec.openapis.org/oas/v3.1.0#paths-object + type: object + patternProperties: + '^/': + $ref: '#/$defs/path-item' + $ref: '#/$defs/specification-extensions' + unevaluatedProperties: false + + path-item: + $comment: https://spec.openapis.org/oas/v3.1.0#path-item-object + 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|put|post|delete|options|head|patch|trace)$': + $ref: '#/$defs/operation' + $ref: '#/$defs/specification-extensions' + unevaluatedProperties: false + + path-item-or-reference: + if: + type: object + required: + - $ref + then: + $ref: '#/$defs/reference' + else: + $ref: '#/$defs/path-item' + + operation: + $comment: https://spec.openapis.org/oas/v3.1.0#operation-object + 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: + $comment: https://spec.openapis.org/oas/v3.1.0#external-documentation-object + type: object + properties: + description: + type: string + url: + type: string + format: uri + required: + - url + $ref: '#/$defs/specification-extensions' + unevaluatedProperties: false + + parameter: + $comment: https://spec.openapis.org/oas/v3.1.0#parameter-object + 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' + minProperties: 1 + maxProperties: 1 + required: + - name + - 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: + name: + pattern: '[^/#?]+$' + 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 + const: 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 + const: 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: + type: object + required: + - $ref + then: + $ref: '#/$defs/reference' + else: + $ref: '#/$defs/parameter' + + request-body: + $comment: https://spec.openapis.org/oas/v3.1.0#request-body-object + 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: + type: object + required: + - $ref + then: + $ref: '#/$defs/reference' + else: + $ref: '#/$defs/request-body' + + content: + $comment: https://spec.openapis.org/oas/v3.1.0#fixed-fields-10 + type: object + additionalProperties: + $ref: '#/$defs/media-type' + propertyNames: + format: media-range + + media-type: + $comment: https://spec.openapis.org/oas/v3.1.0#media-type-object + type: object + properties: + schema: + $dynamicRef: '#meta' + encoding: + type: object + additionalProperties: + $ref: '#/$defs/encoding' + allOf: + - $ref: '#/$defs/specification-extensions' + - $ref: '#/$defs/examples' + unevaluatedProperties: false + + encoding: + $comment: https://spec.openapis.org/oas/v3.1.0#encoding-object + 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: + $comment: https://spec.openapis.org/oas/v3.1.0#responses-object + type: object + properties: + default: + $ref: '#/$defs/response-or-reference' + patternProperties: + '^[1-5](?:[0-9]{2}|XX)$': + $ref: '#/$defs/response-or-reference' + $ref: '#/$defs/specification-extensions' + unevaluatedProperties: false + + response: + $comment: https://spec.openapis.org/oas/v3.1.0#response-object + 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: + type: object + required: + - $ref + then: + $ref: '#/$defs/reference' + else: + $ref: '#/$defs/response' + + callbacks: + $comment: https://spec.openapis.org/oas/v3.1.0#callback-object + type: object + $ref: '#/$defs/specification-extensions' + additionalProperties: + $ref: '#/$defs/path-item-or-reference' + + callbacks-or-reference: + if: + type: object + required: + - $ref + then: + $ref: '#/$defs/reference' + else: + $ref: '#/$defs/callbacks' + + example: + $comment: https://spec.openapis.org/oas/v3.1.0#example-object + type: object + properties: + summary: + type: string + description: + type: string + value: true + externalValue: + type: string + format: uri + $ref: '#/$defs/specification-extensions' + unevaluatedProperties: false + + example-or-reference: + if: + type: object + required: + - $ref + then: + $ref: '#/$defs/reference' + else: + $ref: '#/$defs/example' + + link: + $comment: https://spec.openapis.org/oas/v3.1.0#link-object + type: object + properties: + operationRef: + type: string + format: uri-reference + 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: + type: object + required: + - $ref + then: + $ref: '#/$defs/reference' + else: + $ref: '#/$defs/link' + + header: + $comment: https://spec.openapis.org/oas/v3.1.0#header-object + type: object + properties: + description: + type: string + required: + default: false + type: boolean + deprecated: + default: false + type: boolean + schema: + $dynamicRef: '#meta' + content: + $ref: '#/$defs/content' + minProperties: 1 + maxProperties: 1 + oneOf: + - required: + - schema + - required: + - content + dependentSchemas: + schema: + properties: + style: + default: simple + const: simple + explode: + default: false + type: boolean + $ref: '#/$defs/examples' + $ref: '#/$defs/specification-extensions' + unevaluatedProperties: false + + header-or-reference: + if: + type: object + required: + - $ref + then: + $ref: '#/$defs/reference' + else: + $ref: '#/$defs/header' + + tag: + $comment: https://spec.openapis.org/oas/v3.1.0#tag-object + type: object + properties: + name: + type: string + description: + type: string + externalDocs: + $ref: '#/$defs/external-documentation' + required: + - name + $ref: '#/$defs/specification-extensions' + unevaluatedProperties: false + + reference: + $comment: https://spec.openapis.org/oas/v3.1.0#reference-object + type: object + properties: + $ref: + type: string + format: uri-reference + summary: + type: string + description: + type: string + unevaluatedProperties: false + + schema: + $comment: https://spec.openapis.org/oas/v3.1.0#schema-object + $dynamicAnchor: meta + type: + - object + - boolean + + security-scheme: + $comment: https://spec.openapis.org/oas/v3.1.0#security-scheme-object + 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: + type: string + pattern: ^[Bb][Ee][Aa][Rr][Ee][Rr]$ + required: + - type + - scheme + then: + properties: + bearerFormat: + type: string + + 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: + type: string + format: uri + required: + - openIdConnectUrl + + security-scheme-or-reference: + if: + type: object + 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: + $comment: https://spec.openapis.org/oas/v3.1.0#security-requirement-object + type: object + additionalProperties: + type: array + items: + type: string + + specification-extensions: + $comment: https://spec.openapis.org/oas/v3.1.0#specification-extensions + patternProperties: + '^x-': true + + examples: + properties: + example: true + examples: + type: object + additionalProperties: + $ref: '#/$defs/example-or-reference' + + map-of-strings: + type: object + additionalProperties: + type: string diff --git a/src/SpecBaseObject.php b/src/SpecBaseObject.php index aa3367a..4e57a5e 100644 --- a/src/SpecBaseObject.php +++ b/src/SpecBaseObject.php @@ -324,13 +324,23 @@ protected function hasPropertyValue(string $name): bool return isset($this->_properties[$name]); } - protected function requireProperties(array $names) + protected function requireProperties(array $names, array $atLeastOne = []) { foreach ($names as $name) { if (!isset($this->_properties[$name])) { $this->addError(" is missing required property: $name", get_class($this)); } } + + if (count($atLeastOne) > 0) { + foreach ($atLeastOne as $name) { + if (array_key_exists($name, $this->_properties)) { + return; + } + } + + $this->addError(" is missing at least one of the following required properties: " . implode(', ', $atLeastOne), get_class($this)); + } } protected function validateEmail(string $property) diff --git a/src/spec/OpenApi.php b/src/spec/OpenApi.php index 29d38b3..7476ce8 100644 --- a/src/spec/OpenApi.php +++ b/src/spec/OpenApi.php @@ -20,6 +20,7 @@ * @property Server[] $servers * @property Paths|PathItem[] $paths * @property Components|null $components + * @property PathItem[]|null $webhooks * @property SecurityRequirement[] $security * @property Tag[] $tags * @property ExternalDocumentation|null $externalDocs @@ -27,6 +28,15 @@ */ class OpenApi extends SpecBaseObject { + const VERSION_3_0 = '3.0'; + const VERSION_3_1 = '3.1'; + const VERSION_UNSUPPORTED = 'unsupported'; + + /** + * Pattern used to validate OpenAPI versions. + */ + const PATTERN_VERSION = '/^(3\.(0|1))\.\d+(-rc\d)?$/i'; + /** * @return array array of attributes available in this object. */ @@ -37,6 +47,7 @@ protected function attributes(): array 'info' => Info::class, 'servers' => [Server::class], 'paths' => Paths::class, + 'webhooks' => [PathItem::class], 'components' => Components::class, 'security' => [SecurityRequirement::class], 'tags' => [Tag::class], @@ -74,9 +85,37 @@ public function __get($name) */ public function performValidation() { - $this->requireProperties(['openapi', 'info', 'paths']); - if (!empty($this->openapi) && !preg_match('/^3\.0\.\d+(-rc\d)?$/i', $this->openapi)) { + if ($this->getMajorVersion() === static::VERSION_3_0) { + $this->requireProperties(['openapi', 'info', 'paths']); + } else { + $this->requireProperties(['openapi', 'info'], ['paths', 'webhooks', 'components']); + } + + if (!empty($this->openapi) && !preg_match(static::PATTERN_VERSION, $this->openapi)) { $this->addError('Unsupported openapi version: ' . $this->openapi); } } + + /** + * Returns the OpenAPI major version of the loaded OpenAPI description. + * @return string This returns a value of one of the `VERSION_*`-constants. Currently supported versions are: + * + * - `VERSION_3_0 = '3.0'` + * - `VERSION_3_1 = '3.1'` + * + * For unsupported version, this function will return `VERSION_UNSUPPORTED = 'unsupported'` + */ + public function getMajorVersion() + { + if (preg_match(static::PATTERN_VERSION, $this->openapi, $matches)) { + switch ($matches[1]) { + case '3.0': + return static::VERSION_3_0; + case '3.1': + return static::VERSION_3_1; + } + } + + return self::VERSION_UNSUPPORTED; + } } diff --git a/src/spec/Schema.php b/src/spec/Schema.php index 6c17379..864a90e 100644 --- a/src/spec/Schema.php +++ b/src/spec/Schema.php @@ -40,7 +40,7 @@ * @property string[] $required list of required properties * @property array $enum * - * @property string $type + * @property string|string[] $type type can only be `string` in OpenAPI 3.0, but can be an array of strings since OpenAPI 3.1 * @property Schema[]|Reference[] $allOf * @property Schema[]|Reference[] $oneOf * @property Schema[]|Reference[] $anyOf diff --git a/src/spec/Type.php b/src/spec/Type.php index 9861000..b4c8123 100644 --- a/src/spec/Type.php +++ b/src/spec/Type.php @@ -21,6 +21,7 @@ class Type const BOOLEAN = 'boolean'; const OBJECT = 'object'; const ARRAY = 'array'; + const NULL = 'null'; // Since OpenAPI 3.1 /** * Indicate whether a type is a scalar type, i.e. not an array or object. @@ -38,6 +39,7 @@ public static function isScalar(string $type): bool self::NUMBER, self::STRING, self::BOOLEAN, + self::NULL, ]); } } diff --git a/tests/ReaderTest.php b/tests/ReaderTest.php index 435a8e0..ae69d92 100644 --- a/tests/ReaderTest.php +++ b/tests/ReaderTest.php @@ -91,7 +91,7 @@ private function assertApiContent(\cebe\openapi\spec\OpenApi $openapi) public function testSymfonyYamlBugHunt() { - $openApiFile = __DIR__ . '/../vendor/oai/openapi-specification/examples/v3.0/uspto.yaml'; + $openApiFile = __DIR__ . '/../vendor/oai/openapi-specification-3.0/examples/v3.0/uspto.yaml'; $openapi = \cebe\openapi\Reader::readFromYamlFile($openApiFile); $inlineYamlExample = $openapi->paths['/']->get->responses['200']->content['application/json']->example; diff --git a/tests/spec/OpenApiTest.php b/tests/spec/OpenApiTest.php index 20b568e..2424a12 100644 --- a/tests/spec/OpenApiTest.php +++ b/tests/spec/OpenApiTest.php @@ -1,6 +1,7 @@ assertEquals([ 'OpenApi is missing required property: openapi', 'OpenApi is missing required property: info', - 'OpenApi is missing required property: paths', + 'OpenApi is missing at least one of the following required properties: paths, webhooks, components', ], $openapi->getErrors()); // check default value of servers @@ -28,7 +29,7 @@ public function testEmpty() public function testReadPetStore() { - $openApiFile = __DIR__ . '/../../vendor/oai/openapi-specification/examples/v3.0/petstore.yaml'; + $openApiFile = __DIR__ . '/../../vendor/oai/openapi-specification-3.0/examples/v3.0/petstore.yaml'; $yaml = Yaml::parse(file_get_contents($openApiFile)); $openapi = new OpenApi($yaml); @@ -93,53 +94,57 @@ public function specProvider() // examples from https://github.com/OAI/OpenAPI-Specification/tree/master/examples/v3.0 $oaiExamples = [ // TODO symfony/yaml can not read this file!? -// __DIR__ . '/../../vendor/oai/openapi-specification/examples/v3.0/api-with-examples.yaml', - __DIR__ . '/../../vendor/oai/openapi-specification/examples/v3.0/callback-example.yaml', - __DIR__ . '/../../vendor/oai/openapi-specification/examples/v3.0/link-example.yaml', - __DIR__ . '/../../vendor/oai/openapi-specification/examples/v3.0/petstore.yaml', - __DIR__ . '/../../vendor/oai/openapi-specification/examples/v3.0/petstore-expanded.yaml', - __DIR__ . '/../../vendor/oai/openapi-specification/examples/v3.0/uspto.yaml', +// __DIR__ . '/../../vendor/oai/openapi-specification-3.0/examples/v3.0/api-with-examples.yaml', + __DIR__ . '/../../vendor/oai/openapi-specification-3.0/examples/v3.0/callback-example.yaml', + __DIR__ . '/../../vendor/oai/openapi-specification-3.0/examples/v3.0/link-example.yaml', + __DIR__ . '/../../vendor/oai/openapi-specification-3.0/examples/v3.0/petstore.yaml', + __DIR__ . '/../../vendor/oai/openapi-specification-3.0/examples/v3.0/petstore-expanded.yaml', + __DIR__ . '/../../vendor/oai/openapi-specification-3.0/examples/v3.0/uspto.yaml', ]; // examples from https://github.com/Mermade/openapi3-examples $mermadeExamples = [ - __DIR__ . '/../../vendor/mermade/openapi3-examples/pass/externalPathItemRef.yaml', - __DIR__ . '/../../vendor/mermade/openapi3-examples/pass/deprecated.yaml', - __DIR__ . '/../../vendor/mermade/openapi3-examples/pass/swagger2openapi/openapi.json', - __DIR__ . '/../../vendor/mermade/openapi3-examples/pass/gluecon/example1_from_._Different_parameters.md.yaml', - __DIR__ . '/../../vendor/mermade/openapi3-examples/pass/gluecon/example1_from_._Fixed_file.md.yaml', - __DIR__ . '/../../vendor/mermade/openapi3-examples/pass/gluecon/example1_from_._Different_parameters.md.yaml', - __DIR__ . '/../../vendor/mermade/openapi3-examples/pass/gluecon/example1_from_._Fixed_file.md.yaml', - __DIR__ . '/../../vendor/mermade/openapi3-examples/pass/gluecon/example1_from_._Fixed_multipart.md.yaml', - __DIR__ . '/../../vendor/mermade/openapi3-examples/pass/gluecon/example1_from_._Improved_examples.md.yaml', - __DIR__ . '/../../vendor/mermade/openapi3-examples/pass/gluecon/example1_from_._Improved_pathdescriptions.md.yaml', - __DIR__ . '/../../vendor/mermade/openapi3-examples/pass/gluecon/example1_from_._Improved_securityschemes.md.yaml', - __DIR__ . '/../../vendor/mermade/openapi3-examples/pass/gluecon/example1_from_._Improved_serverseverywhere.md.yaml', - __DIR__ . '/../../vendor/mermade/openapi3-examples/pass/gluecon/example1_from_._New_callbacks.md.yaml', - __DIR__ . '/../../vendor/mermade/openapi3-examples/pass/gluecon/example1_from_._New_links.md.yaml', - __DIR__ . '/../../vendor/mermade/openapi3-examples/pass/gluecon/example2_from_._Different_parameters.md.yaml', - __DIR__ . '/../../vendor/mermade/openapi3-examples/pass/gluecon/example2_from_._Different_requestbody.md.yaml', - __DIR__ . '/../../vendor/mermade/openapi3-examples/pass/gluecon/example2_from_._Different_servers.md.yaml', - __DIR__ . '/../../vendor/mermade/openapi3-examples/pass/gluecon/example2_from_._Fixed_multipart.md.yaml', - __DIR__ . '/../../vendor/mermade/openapi3-examples/pass/gluecon/example2_from_._Improved_securityschemes.md.yaml', - __DIR__ . '/../../vendor/mermade/openapi3-examples/pass/gluecon/example2_from_._New_callbacks.md.yaml', - __DIR__ . '/../../vendor/mermade/openapi3-examples/pass/gluecon/example2_from_._New_links.md.yaml', - __DIR__ . '/../../vendor/mermade/openapi3-examples/pass/gluecon/example3_from_._Different_parameters.md.yaml', - __DIR__ . '/../../vendor/mermade/openapi3-examples/pass/gluecon/example3_from_._Different_servers.md.yaml', - __DIR__ . '/../../vendor/mermade/openapi3-examples/pass/gluecon/example4_from_._Different_parameters.md.yaml', - __DIR__ . '/../../vendor/mermade/openapi3-examples/pass/gluecon/example5_from_._Different_parameters.md.yaml', + __DIR__ . '/../../vendor/mermade/openapi3-examples/3.0/pass/externalPathItemRef.yaml', + __DIR__ . '/../../vendor/mermade/openapi3-examples/3.0/pass/deprecated.yaml', + __DIR__ . '/../../vendor/mermade/openapi3-examples/3.0/pass/swagger2openapi/openapi.json', + __DIR__ . '/../../vendor/mermade/openapi3-examples/3.0/pass/gluecon/example1_from_._Different_parameters.md.yaml', + __DIR__ . '/../../vendor/mermade/openapi3-examples/3.0/pass/gluecon/example1_from_._Fixed_file.md.yaml', + __DIR__ . '/../../vendor/mermade/openapi3-examples/3.0/pass/gluecon/example1_from_._Fixed_multipart.md.yaml', + __DIR__ . '/../../vendor/mermade/openapi3-examples/3.0/pass/gluecon/example1_from_._Improved_examples.md.yaml', + __DIR__ . '/../../vendor/mermade/openapi3-examples/3.0/pass/gluecon/example1_from_._Improved_pathdescriptions.md.yaml', + __DIR__ . '/../../vendor/mermade/openapi3-examples/3.0/pass/gluecon/example1_from_._Improved_securityschemes.md.yaml', + __DIR__ . '/../../vendor/mermade/openapi3-examples/3.0/pass/gluecon/example1_from_._Improved_serverseverywhere.md.yaml', + __DIR__ . '/../../vendor/mermade/openapi3-examples/3.0/pass/gluecon/example1_from_._New_callbacks.md.yaml', + __DIR__ . '/../../vendor/mermade/openapi3-examples/3.0/pass/gluecon/example1_from_._New_links.md.yaml', + __DIR__ . '/../../vendor/mermade/openapi3-examples/3.0/pass/gluecon/example2_from_._Different_parameters.md.yaml', + __DIR__ . '/../../vendor/mermade/openapi3-examples/3.0/pass/gluecon/example2_from_._Different_requestbody.md.yaml', + __DIR__ . '/../../vendor/mermade/openapi3-examples/3.0/pass/gluecon/example2_from_._Different_servers.md.yaml', + __DIR__ . '/../../vendor/mermade/openapi3-examples/3.0/pass/gluecon/example2_from_._Fixed_multipart.md.yaml', + __DIR__ . '/../../vendor/mermade/openapi3-examples/3.0/pass/gluecon/example2_from_._Improved_securityschemes.md.yaml', + __DIR__ . '/../../vendor/mermade/openapi3-examples/3.0/pass/gluecon/example2_from_._New_callbacks.md.yaml', + __DIR__ . '/../../vendor/mermade/openapi3-examples/3.0/pass/gluecon/example2_from_._New_links.md.yaml', + __DIR__ . '/../../vendor/mermade/openapi3-examples/3.0/pass/gluecon/example3_from_._Different_parameters.md.yaml', + __DIR__ . '/../../vendor/mermade/openapi3-examples/3.0/pass/gluecon/example3_from_._Different_servers.md.yaml', + __DIR__ . '/../../vendor/mermade/openapi3-examples/3.0/pass/gluecon/example4_from_._Different_parameters.md.yaml', + __DIR__ . '/../../vendor/mermade/openapi3-examples/3.0/pass/gluecon/example5_from_._Different_parameters.md.yaml', // TODO symfony/yaml can not read this file!? -// __DIR__ . '/../../vendor/mermade/openapi3-examples/pass/OAI/api-with-examples.yaml', - __DIR__ . '/../../vendor/mermade/openapi3-examples/pass/OAI/petstore-expanded.yaml', - __DIR__ . '/../../vendor/mermade/openapi3-examples/pass/OAI/petstore.yaml', - __DIR__ . '/../../vendor/mermade/openapi3-examples/pass/OAI/uber.yaml', - - __DIR__ . '/../../vendor/mermade/openapi3-examples/malicious/rapid7-html.json', - __DIR__ . '/../../vendor/mermade/openapi3-examples/malicious/rapid7-java.json', - __DIR__ . '/../../vendor/mermade/openapi3-examples/malicious/rapid7-js.json', - __DIR__ . '/../../vendor/mermade/openapi3-examples/malicious/rapid7-php.json', - __DIR__ . '/../../vendor/mermade/openapi3-examples/malicious/rapid7-ruby.json', -// __DIR__ . '/../../vendor/mermade/openapi3-examples/malicious/yamlbomb.yaml', +// __DIR__ . '/../../vendor/mermade/openapi3-examples/3.0/pass/OAI/api-with-examples.yaml', + __DIR__ . '/../../vendor/mermade/openapi3-examples/3.0/pass/OAI/petstore-expanded.yaml', + __DIR__ . '/../../vendor/mermade/openapi3-examples/3.0/pass/OAI/petstore.yaml', + __DIR__ . '/../../vendor/mermade/openapi3-examples/3.0/pass/OAI/uber.yaml', + + __DIR__ . '/../../vendor/mermade/openapi3-examples/3.0/malicious/rapid7-html.json', + __DIR__ . '/../../vendor/mermade/openapi3-examples/3.0/malicious/rapid7-java.json', + __DIR__ . '/../../vendor/mermade/openapi3-examples/3.0/malicious/rapid7-js.json', + __DIR__ . '/../../vendor/mermade/openapi3-examples/3.0/malicious/rapid7-php.json', + __DIR__ . '/../../vendor/mermade/openapi3-examples/3.0/malicious/rapid7-ruby.json', +// __DIR__ . '/../../vendor/mermade/openapi3-examples/3.0/malicious/yamlbomb.yaml', + + // OpenAPI 3.1 examples + __DIR__ . '/../../vendor/mermade/openapi3-examples/3.1/pass/minimal_comp.yaml', + __DIR__ . '/../../vendor/mermade/openapi3-examples/3.1/pass/minimal_hooks.yaml', + __DIR__ . '/../../vendor/mermade/openapi3-examples/3.1/pass/minimal_paths.yaml', + __DIR__ . '/../../vendor/mermade/openapi3-examples/3.1/pass/path_var_empty_pathitem.yaml', ]; // examples from https://github.com/APIs-guru/openapi-directory/tree/openapi3.0.0/APIs @@ -176,9 +181,9 @@ public function specProvider() $nexmoExamples ); foreach($all as $path) { - yield [ - substr($path, strlen(__DIR__ . '/../../vendor/')), - basename(dirname($path, 2)) . DIRECTORY_SEPARATOR . basename(dirname($path, 1)) . DIRECTORY_SEPARATOR . basename($path) + $pathWithoutVendorPrefix = substr($path, strlen(__DIR__ . '/../../vendor/')); + yield $pathWithoutVendorPrefix => [ + $pathWithoutVendorPrefix ]; } } @@ -202,7 +207,7 @@ public function testSpecs($openApiFile) $this->assertTrue($result); // openapi - $this->assertStringStartsWith('3.0.', $openapi->openapi); + $this->assertNotSame(OpenApi::VERSION_UNSUPPORTED, $openapi->getMajorVersion()); // info $this->assertInstanceOf(\cebe\openapi\spec\Info::class, $openapi->info); @@ -211,10 +216,15 @@ public function testSpecs($openApiFile) $this->assertAllInstanceOf(\cebe\openapi\spec\Server::class, $openapi->servers); // paths - if ($openapi->components !== null) { + if ($openapi->paths !== null) { $this->assertInstanceOf(\cebe\openapi\spec\Paths::class, $openapi->paths); } + // webhooks + if ($openapi->webhooks !== null) { + $this->assertAllInstanceOf(\cebe\openapi\spec\PathItem::class, $openapi->webhooks); + } + // components if ($openapi->components !== null) { $this->assertInstanceOf(\cebe\openapi\spec\Components::class, $openapi->components); @@ -232,4 +242,31 @@ public function testSpecs($openApiFile) } } + + public function testVersions() + { + $yaml = <<assertTrue($openapi->validate(), print_r($openapi->getErrors(), true)); + $this->assertEquals('3.0', $openapi->getMajorVersion()); + + $yaml = <<assertTrue($openapi->validate(), print_r($openapi->getErrors(), true)); + $this->assertEquals('3.1', $openapi->getMajorVersion()); + + + } } diff --git a/tests/spec/PathTest.php b/tests/spec/PathTest.php index 6c46e4b..d028258 100644 --- a/tests/spec/PathTest.php +++ b/tests/spec/PathTest.php @@ -66,7 +66,7 @@ public function testRead() } } - public function testCreateionFromObjects() + public function testCreationFromObjects() { $paths = new Paths([ '/pets' => new PathItem([