diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c191cd4..4f84784 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,4 +25,4 @@ jobs: bundle exec rspec spec/ - name: Lint with rubocop run: | - bundle exec rubocop --verbose && bundle exec rubocop + bundle exec rubocop diff --git a/lib/open_api_parser/document.rb b/lib/open_api_parser/document.rb index 984b3c9..9649d2e 100644 --- a/lib/open_api_parser/document.rb +++ b/lib/open_api_parser/document.rb @@ -4,7 +4,7 @@ module OpenApiParser class Document def self.resolve(path, file_cache = OpenApiParser::FileCache.new) file_cache.get(path) do - content = YAML.load_file(path) + content = YAML.safe_load(ERB.new(File.read(path)).result) Document.new(path, content, file_cache).resolve end end diff --git a/lib/open_api_parser/specification.rb b/lib/open_api_parser/specification.rb index aeb5a58..b2dcfe3 100644 --- a/lib/open_api_parser/specification.rb +++ b/lib/open_api_parser/specification.rb @@ -2,17 +2,26 @@ module OpenApiParser module Specification - META_SCHEMA_PATH = File.expand_path('../../resources/swagger_meta_schema.json', __dir__) + META_SCHEMA_PATH_2 = File.expand_path('../../resources/swagger_meta_schema_2.0.json', __dir__) + META_SCHEMA_PATH_3 = File.expand_path('../../resources/swagger_meta_schema_3.0.json', __dir__) def self.resolve(path, validate_meta_schema: true) raw_specification = Document.resolve(path) if validate_meta_schema - meta_schema = JSON.parse(File.read(META_SCHEMA_PATH)) + meta_schema = JSON.parse(File.read(meta_schema_path(raw_specification))) JSON::Validator.validate!(meta_schema, raw_specification) end OpenApiParser::Specification::Root.new(raw_specification) end + + def self.meta_schema_path(raw_specification) + if raw_specification.key?('openapi') + META_SCHEMA_PATH_3 + else + META_SCHEMA_PATH_2 + end + end end end diff --git a/resources/swagger_meta_schema.json b/resources/swagger_meta_schema_2.0.json similarity index 100% rename from resources/swagger_meta_schema.json rename to resources/swagger_meta_schema_2.0.json diff --git a/resources/swagger_meta_schema_3.0.json b/resources/swagger_meta_schema_3.0.json new file mode 100644 index 0000000..cc828d1 --- /dev/null +++ b/resources/swagger_meta_schema_3.0.json @@ -0,0 +1,2297 @@ +{ + "type": "object", + "required": [ + "openapi", + "info", + "paths" + ], + "properties": { + "openapi": { + "type": "string", + "pattern": "^3\\.0\\.\\d(-.+)?$" + }, + "info": { + "$ref": "#/definitions/Info" + }, + "externalDocs": { + "$ref": "#/definitions/ExternalDocumentation" + }, + "servers": { + "type": "array", + "items": { + "$ref": "#/definitions/Server" + } + }, + "security": { + "type": "array", + "items": { + "$ref": "#/definitions/SecurityRequirement" + } + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/definitions/Tag" + } + }, + "paths": { + "$ref": "#/definitions/Paths" + }, + "components": { + "$ref": "#/definitions/Components" + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false, + "definitions": { + "Reference": { + "type": "object", + "required": [ + "$ref" + ], + "properties": { + "$ref": { + "type": "string", + "format": "uriref" + } + } + }, + "Info": { + "type": "object", + "required": [ + "title", + "version" + ], + "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "termsOfService": { + "type": "string", + "format": "uriref" + }, + "contact": { + "$ref": "#/definitions/Contact" + }, + "license": { + "$ref": "#/definitions/License" + }, + "version": { + "type": "string" + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "Contact": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uriref" + }, + "email": { + "type": "string", + "format": "email" + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "License": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uriref" + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "Server": { + "type": "object", + "required": [ + "url" + ], + "properties": { + "url": { + "type": "string" + }, + "description": { + "type": "string" + }, + "variables": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/ServerVariable" + } + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "ServerVariable": { + "type": "object", + "required": [ + "default" + ], + "properties": { + "enum": { + "type": "array", + "items": { + "type": "string" + } + }, + "default": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "Components": { + "type": "object", + "properties": { + "schemas": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "#/definitions/Reference" + }, + { + "$ref": "#/definitions/Schema" + } + ] + } + } + }, + "responses": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "#/definitions/Reference" + }, + { + "$ref": "#/definitions/Response" + } + ] + } + } + }, + "parameters": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "#/definitions/Reference" + }, + { + "$ref": "#/definitions/Parameter" + } + ] + } + } + }, + "examples": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "#/definitions/Reference" + }, + { + "$ref": "#/definitions/Example" + } + ] + } + } + }, + "requestBodies": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "#/definitions/Reference" + }, + { + "$ref": "#/definitions/RequestBody" + } + ] + } + } + }, + "headers": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "#/definitions/Reference" + }, + { + "$ref": "#/definitions/Header" + } + ] + } + } + }, + "securitySchemes": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "#/definitions/Reference" + }, + { + "$ref": "#/definitions/SecurityScheme" + } + ] + } + } + }, + "links": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "#/definitions/Reference" + }, + { + "$ref": "#/definitions/Link" + } + ] + } + } + }, + "callbacks": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "#/definitions/Reference" + }, + { + "$ref": "#/definitions/Callback" + } + ] + } + } + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "Schema": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "multipleOf": { + "type": "number", + "minimum": 0, + "exclusiveMinimum": true + }, + "maximum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "boolean", + "default": false + }, + "minimum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "boolean", + "default": false + }, + "maxLength": { + "type": "integer", + "minimum": 0 + }, + "minLength": { + "type": "integer", + "minimum": 0, + "default": 0 + }, + "pattern": { + "type": "string", + "format": "regex" + }, + "maxItems": { + "type": "integer", + "minimum": 0 + }, + "minItems": { + "type": "integer", + "minimum": 0, + "default": 0 + }, + "uniqueItems": { + "type": "boolean", + "default": false + }, + "maxProperties": { + "type": "integer", + "minimum": 0 + }, + "minProperties": { + "type": "integer", + "minimum": 0, + "default": 0 + }, + "required": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1, + "uniqueItems": true + }, + "enum": { + "type": "array", + "items": {}, + "minItems": 1, + "uniqueItems": true + }, + "type": { + "type": "string", + "enum": [ + "array", + "boolean", + "integer", + "number", + "object", + "string" + ] + }, + "not": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "allOf": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "oneOf": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "anyOf": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "items": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "properties": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + }, + { + "type": "boolean" + } + ], + "default": true + }, + "description": { + "type": "string" + }, + "format": { + "type": "string" + }, + "default": {}, + "nullable": { + "type": "boolean", + "default": false + }, + "discriminator": { + "$ref": "#/definitions/Discriminator" + }, + "readOnly": { + "type": "boolean", + "default": false + }, + "writeOnly": { + "type": "boolean", + "default": false + }, + "example": {}, + "externalDocs": { + "$ref": "#/definitions/ExternalDocumentation" + }, + "deprecated": { + "type": "boolean", + "default": false + }, + "xml": { + "$ref": "#/definitions/XML" + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "Discriminator": { + "type": "object", + "required": [ + "propertyName" + ], + "properties": { + "propertyName": { + "type": "string" + }, + "mapping": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + }, + "XML": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "namespace": { + "type": "string", + "format": "url" + }, + "prefix": { + "type": "string" + }, + "attribute": { + "type": "boolean", + "default": false + }, + "wrapped": { + "type": "boolean", + "default": false + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "Response": { + "type": "object", + "required": [ + "description" + ], + "properties": { + "description": { + "type": "string" + }, + "headers": { + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/definitions/Header" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "content": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/MediaType" + } + }, + "links": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/definitions/Link" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "MediaType": { + "oneOf": [ + { + "$ref": "#/definitions/MediaTypeWithExample" + }, + { + "$ref": "#/definitions/MediaTypeWithExamples" + } + ] + }, + "MediaTypeWithExample": { + "type": "object", + "properties": { + "schema": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "example": {}, + "encoding": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Encoding" + } + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "MediaTypeWithExamples": { + "type": "object", + "required": [ + "examples" + ], + "properties": { + "schema": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "examples": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/definitions/Example" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "encoding": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Encoding" + } + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "Example": { + "type": "object", + "properties": { + "summary": { + "type": "string" + }, + "description": { + "type": "string" + }, + "value": {}, + "externalValue": { + "type": "string", + "format": "uriref" + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "Header": { + "oneOf": [ + { + "$ref": "#/definitions/HeaderWithSchema" + }, + { + "$ref": "#/definitions/HeaderWithContent" + } + ] + }, + "HeaderWithSchema": { + "oneOf": [ + { + "$ref": "#/definitions/HeaderWithSchemaWithExample" + }, + { + "$ref": "#/definitions/HeaderWithSchemaWithExamples" + } + ] + }, + "HeaderWithSchemaWithExample": { + "type": "object", + "required": [ + "schema" + ], + "properties": { + "description": { + "type": "string" + }, + "required": { + "type": "boolean", + "default": false + }, + "deprecated": { + "type": "boolean", + "default": false + }, + "allowEmptyValue": { + "type": "boolean", + "default": false + }, + "style": { + "type": "string", + "enum": [ + "simple" + ], + "default": "simple" + }, + "explode": { + "type": "boolean" + }, + "allowReserved": { + "type": "boolean", + "default": false + }, + "schema": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "example": {} + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "HeaderWithSchemaWithExamples": { + "type": "object", + "required": [ + "schema", + "examples" + ], + "properties": { + "description": { + "type": "string" + }, + "required": { + "type": "boolean", + "default": false + }, + "deprecated": { + "type": "boolean", + "default": false + }, + "allowEmptyValue": { + "type": "boolean", + "default": false + }, + "style": { + "type": "string", + "enum": [ + "simple" + ], + "default": "simple" + }, + "explode": { + "type": "boolean" + }, + "allowReserved": { + "type": "boolean", + "default": false + }, + "schema": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "examples": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/definitions/Example" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "HeaderWithContent": { + "type": "object", + "required": [ + "content" + ], + "properties": { + "description": { + "type": "string" + }, + "required": { + "type": "boolean", + "default": false + }, + "deprecated": { + "type": "boolean", + "default": false + }, + "allowEmptyValue": { + "type": "boolean", + "default": false + }, + "content": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/MediaType" + }, + "minProperties": 1, + "maxProperties": 1 + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "Paths": { + "type": "object", + "patternProperties": { + "^\\/": { + "$ref": "#/definitions/PathItem" + }, + "^x-": {} + }, + "additionalProperties": false + }, + "PathItem": { + "type": "object", + "properties": { + "$ref": { + "type": "string" + }, + "summary": { + "type": "string" + }, + "description": { + "type": "string" + }, + "get": { + "$ref": "#/definitions/Operation" + }, + "put": { + "$ref": "#/definitions/Operation" + }, + "post": { + "$ref": "#/definitions/Operation" + }, + "delete": { + "$ref": "#/definitions/Operation" + }, + "options": { + "$ref": "#/definitions/Operation" + }, + "head": { + "$ref": "#/definitions/Operation" + }, + "patch": { + "$ref": "#/definitions/Operation" + }, + "trace": { + "$ref": "#/definitions/Operation" + }, + "servers": { + "type": "array", + "items": { + "$ref": "#/definitions/Server" + } + }, + "parameters": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/definitions/Parameter" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "Operation": { + "type": "object", + "required": [ + "responses" + ], + "properties": { + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "summary": { + "type": "string" + }, + "description": { + "type": "string" + }, + "externalDocs": { + "$ref": "#/definitions/ExternalDocumentation" + }, + "operationId": { + "type": "string" + }, + "parameters": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/definitions/Parameter" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "requestBody": { + "oneOf": [ + { + "$ref": "#/definitions/RequestBody" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "responses": { + "$ref": "#/definitions/Responses" + }, + "callbacks": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/definitions/Callback" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "deprecated": { + "type": "boolean", + "default": false + }, + "security": { + "type": "array", + "items": { + "$ref": "#/definitions/SecurityRequirement" + } + }, + "servers": { + "type": "array", + "items": { + "$ref": "#/definitions/Server" + } + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "Responses": { + "type": "object", + "properties": { + "default": { + "oneOf": [ + { + "$ref": "#/definitions/Response" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "patternProperties": { + "[1-5](?:\\d{2}|XX)": { + "oneOf": [ + { + "$ref": "#/definitions/Response" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "^x-": {} + }, + "minProperties": 1, + "additionalProperties": false, + "not": { + "type": "object", + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + } + }, + "SecurityRequirement": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "Tag": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "externalDocs": { + "$ref": "#/definitions/ExternalDocumentation" + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "ExternalDocumentation": { + "type": "object", + "required": [ + "url" + ], + "properties": { + "description": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uriref" + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "Parameter": { + "oneOf": [ + { + "$ref": "#/definitions/ParameterWithSchema" + }, + { + "$ref": "#/definitions/ParameterWithContent" + } + ] + }, + "ParameterWithSchema": { + "oneOf": [ + { + "$ref": "#/definitions/ParameterWithSchemaWithExample" + }, + { + "$ref": "#/definitions/ParameterWithSchemaWithExamples" + } + ] + }, + "ParameterWithSchemaWithExample": { + "oneOf": [ + { + "$ref": "#/definitions/ParameterWithSchemaWithExampleInPath" + }, + { + "$ref": "#/definitions/ParameterWithSchemaWithExampleInQuery" + }, + { + "$ref": "#/definitions/ParameterWithSchemaWithExampleInHeader" + }, + { + "$ref": "#/definitions/ParameterWithSchemaWithExampleInCookie" + } + ] + }, + "ParameterWithSchemaWithExampleInPath": { + "type": "object", + "required": [ + "name", + "in", + "schema", + "required" + ], + "properties": { + "name": { + "type": "string" + }, + "in": { + "type": "string", + "enum": [ + "path" + ] + }, + "description": { + "type": "string" + }, + "required": { + "type": "boolean", + "enum": [ + true + ] + }, + "deprecated": { + "type": "boolean", + "default": false + }, + "allowEmptyValue": { + "type": "boolean", + "default": false + }, + "style": { + "type": "string", + "enum": [ + "matrix", + "label", + "simple" + ], + "default": "simple" + }, + "explode": { + "type": "boolean" + }, + "allowReserved": { + "type": "boolean", + "default": false + }, + "schema": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "example": {} + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "ParameterWithSchemaWithExampleInQuery": { + "type": "object", + "required": [ + "name", + "in", + "schema" + ], + "properties": { + "name": { + "type": "string" + }, + "in": { + "type": "string", + "enum": [ + "query" + ] + }, + "description": { + "type": "string" + }, + "required": { + "type": "boolean", + "default": false + }, + "deprecated": { + "type": "boolean", + "default": false + }, + "allowEmptyValue": { + "type": "boolean", + "default": false + }, + "style": { + "type": "string", + "enum": [ + "form", + "spaceDelimited", + "pipeDelimited", + "deepObject" + ], + "default": "form" + }, + "explode": { + "type": "boolean" + }, + "allowReserved": { + "type": "boolean", + "default": false + }, + "schema": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "example": {} + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "ParameterWithSchemaWithExampleInHeader": { + "type": "object", + "required": [ + "name", + "in", + "schema" + ], + "properties": { + "name": { + "type": "string" + }, + "in": { + "type": "string", + "enum": [ + "header" + ] + }, + "description": { + "type": "string" + }, + "required": { + "type": "boolean", + "default": false + }, + "deprecated": { + "type": "boolean", + "default": false + }, + "allowEmptyValue": { + "type": "boolean", + "default": false + }, + "style": { + "type": "string", + "enum": [ + "simple" + ], + "default": "simple" + }, + "explode": { + "type": "boolean" + }, + "allowReserved": { + "type": "boolean", + "default": false + }, + "schema": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "example": {} + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "ParameterWithSchemaWithExampleInCookie": { + "type": "object", + "required": [ + "name", + "in", + "schema" + ], + "properties": { + "name": { + "type": "string" + }, + "in": { + "type": "string", + "enum": [ + "cookie" + ] + }, + "description": { + "type": "string" + }, + "required": { + "type": "boolean", + "default": false + }, + "deprecated": { + "type": "boolean", + "default": false + }, + "allowEmptyValue": { + "type": "boolean", + "default": false + }, + "style": { + "type": "string", + "enum": [ + "form" + ], + "default": "form" + }, + "explode": { + "type": "boolean" + }, + "allowReserved": { + "type": "boolean", + "default": false + }, + "schema": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "example": {} + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "ParameterWithSchemaWithExamples": { + "oneOf": [ + { + "$ref": "#/definitions/ParameterWithSchemaWithExamplesInPath" + }, + { + "$ref": "#/definitions/ParameterWithSchemaWithExamplesInQuery" + }, + { + "$ref": "#/definitions/ParameterWithSchemaWithExamplesInHeader" + }, + { + "$ref": "#/definitions/ParameterWithSchemaWithExamplesInCookie" + } + ] + }, + "ParameterWithSchemaWithExamplesInPath": { + "type": "object", + "required": [ + "name", + "in", + "schema", + "required", + "examples" + ], + "properties": { + "name": { + "type": "string" + }, + "in": { + "type": "string", + "enum": [ + "path" + ] + }, + "description": { + "type": "string" + }, + "required": { + "type": "boolean", + "enum": [ + true + ] + }, + "deprecated": { + "type": "boolean", + "default": false + }, + "allowEmptyValue": { + "type": "boolean", + "default": false + }, + "style": { + "type": "string", + "enum": [ + "matrix", + "label", + "simple" + ], + "default": "simple" + }, + "explode": { + "type": "boolean" + }, + "allowReserved": { + "type": "boolean", + "default": false + }, + "schema": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "examples": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/definitions/Example" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "ParameterWithSchemaWithExamplesInQuery": { + "type": "object", + "required": [ + "name", + "in", + "schema", + "examples" + ], + "properties": { + "name": { + "type": "string" + }, + "in": { + "type": "string", + "enum": [ + "query" + ] + }, + "description": { + "type": "string" + }, + "required": { + "type": "boolean", + "default": false + }, + "deprecated": { + "type": "boolean", + "default": false + }, + "allowEmptyValue": { + "type": "boolean", + "default": false + }, + "style": { + "type": "string", + "enum": [ + "form", + "spaceDelimited", + "pipeDelimited", + "deepObject" + ], + "default": "form" + }, + "explode": { + "type": "boolean" + }, + "allowReserved": { + "type": "boolean", + "default": false + }, + "schema": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "examples": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/definitions/Example" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "ParameterWithSchemaWithExamplesInHeader": { + "type": "object", + "required": [ + "name", + "in", + "schema", + "examples" + ], + "properties": { + "name": { + "type": "string" + }, + "in": { + "type": "string", + "enum": [ + "header" + ] + }, + "description": { + "type": "string" + }, + "required": { + "type": "boolean", + "default": false + }, + "deprecated": { + "type": "boolean", + "default": false + }, + "allowEmptyValue": { + "type": "boolean", + "default": false + }, + "style": { + "type": "string", + "enum": [ + "simple" + ], + "default": "simple" + }, + "explode": { + "type": "boolean" + }, + "allowReserved": { + "type": "boolean", + "default": false + }, + "schema": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "examples": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/definitions/Example" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "ParameterWithSchemaWithExamplesInCookie": { + "type": "object", + "required": [ + "name", + "in", + "schema", + "examples" + ], + "properties": { + "name": { + "type": "string" + }, + "in": { + "type": "string", + "enum": [ + "cookie" + ] + }, + "description": { + "type": "string" + }, + "required": { + "type": "boolean", + "default": false + }, + "deprecated": { + "type": "boolean", + "default": false + }, + "allowEmptyValue": { + "type": "boolean", + "default": false + }, + "style": { + "type": "string", + "enum": [ + "form" + ], + "default": "form" + }, + "explode": { + "type": "boolean" + }, + "allowReserved": { + "type": "boolean", + "default": false + }, + "schema": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "examples": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/definitions/Example" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "ParameterWithContent": { + "oneOf": [ + { + "$ref": "#/definitions/ParameterWithContentInPath" + }, + { + "$ref": "#/definitions/ParameterWithContentNotInPath" + } + ] + }, + "ParameterWithContentInPath": { + "type": "object", + "required": [ + "name", + "in", + "content" + ], + "properties": { + "name": { + "type": "string" + }, + "in": { + "type": "string", + "enum": [ + "path" + ] + }, + "description": { + "type": "string" + }, + "required": { + "type": "boolean", + "enum": [ + true + ] + }, + "deprecated": { + "type": "boolean", + "default": false + }, + "allowEmptyValue": { + "type": "boolean", + "default": false + }, + "content": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/MediaType" + }, + "minProperties": 1, + "maxProperties": 1 + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "ParameterWithContentNotInPath": { + "type": "object", + "required": [ + "name", + "in", + "content" + ], + "properties": { + "name": { + "type": "string" + }, + "in": { + "type": "string", + "enum": [ + "query", + "header", + "cookie" + ] + }, + "description": { + "type": "string" + }, + "required": { + "type": "boolean", + "default": false + }, + "deprecated": { + "type": "boolean", + "default": false + }, + "allowEmptyValue": { + "type": "boolean", + "default": false + }, + "content": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/MediaType" + }, + "minProperties": 1, + "maxProperties": 1 + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "RequestBody": { + "type": "object", + "required": [ + "content" + ], + "properties": { + "description": { + "type": "string" + }, + "content": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/MediaType" + } + }, + "required": { + "type": "boolean", + "default": false + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "SecurityScheme": { + "oneOf": [ + { + "$ref": "#/definitions/APIKeySecurityScheme" + }, + { + "$ref": "#/definitions/HTTPSecurityScheme" + }, + { + "$ref": "#/definitions/OAuth2SecurityScheme" + }, + { + "$ref": "#/definitions/OpenIdConnectSecurityScheme" + } + ] + }, + "APIKeySecurityScheme": { + "type": "object", + "required": [ + "type", + "name", + "in" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "apiKey" + ] + }, + "name": { + "type": "string" + }, + "in": { + "type": "string", + "enum": [ + "header", + "query", + "cookie" + ] + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "HTTPSecurityScheme": { + "oneOf": [ + { + "$ref": "#/definitions/NonBearerHTTPSecurityScheme" + }, + { + "$ref": "#/definitions/BearerHTTPSecurityScheme" + } + ] + }, + "NonBearerHTTPSecurityScheme": { + "not": { + "type": "object", + "properties": { + "scheme": { + "type": "string", + "enum": [ + "bearer" + ] + } + } + }, + "type": "object", + "required": [ + "scheme", + "type" + ], + "properties": { + "scheme": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "http" + ] + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "BearerHTTPSecurityScheme": { + "type": "object", + "required": [ + "type", + "scheme" + ], + "properties": { + "scheme": { + "type": "string", + "enum": [ + "bearer" + ] + }, + "bearerFormat": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "http" + ] + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "OAuth2SecurityScheme": { + "type": "object", + "required": [ + "type", + "flows" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "oauth2" + ] + }, + "flows": { + "$ref": "#/definitions/OAuthFlows" + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "OpenIdConnectSecurityScheme": { + "type": "object", + "required": [ + "type", + "openIdConnectUrl" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "openIdConnect" + ] + }, + "openIdConnectUrl": { + "type": "string", + "format": "url" + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "OAuthFlows": { + "type": "object", + "properties": { + "implicit": { + "$ref": "#/definitions/ImplicitOAuthFlow" + }, + "password": { + "$ref": "#/definitions/PasswordOAuthFlow" + }, + "clientCredentials": { + "$ref": "#/definitions/ClientCredentialsFlow" + }, + "authorizationCode": { + "$ref": "#/definitions/AuthorizationCodeOAuthFlow" + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "ImplicitOAuthFlow": { + "type": "object", + "required": [ + "authorizationUrl", + "scopes" + ], + "properties": { + "authorizationUrl": { + "type": "string", + "format": "uriref" + }, + "refreshUrl": { + "type": "string", + "format": "uriref" + }, + "scopes": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "PasswordOAuthFlow": { + "type": "object", + "required": [ + "tokenUrl" + ], + "properties": { + "tokenUrl": { + "type": "string", + "format": "uriref" + }, + "refreshUrl": { + "type": "string", + "format": "uriref" + }, + "scopes": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "ClientCredentialsFlow": { + "type": "object", + "required": [ + "tokenUrl" + ], + "properties": { + "tokenUrl": { + "type": "string", + "format": "uriref" + }, + "refreshUrl": { + "type": "string", + "format": "uriref" + }, + "scopes": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "AuthorizationCodeOAuthFlow": { + "type": "object", + "required": [ + "authorizationUrl", + "tokenUrl" + ], + "properties": { + "authorizationUrl": { + "type": "string", + "format": "uriref" + }, + "tokenUrl": { + "type": "string", + "format": "uriref" + }, + "refreshUrl": { + "type": "string", + "format": "uriref" + }, + "scopes": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "Link": { + "oneOf": [ + { + "$ref": "#/definitions/LinkWithOperationRef" + }, + { + "$ref": "#/definitions/LinkWithOperationId" + } + ] + }, + "LinkWithOperationRef": { + "type": "object", + "properties": { + "operationRef": { + "type": "string", + "format": "uriref" + }, + "parameters": { + "type": "object", + "additionalProperties": {} + }, + "requestBody": {}, + "description": { + "type": "string" + }, + "server": { + "$ref": "#/definitions/Server" + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "LinkWithOperationId": { + "type": "object", + "properties": { + "operationId": { + "type": "string" + }, + "parameters": { + "type": "object", + "additionalProperties": {} + }, + "requestBody": {}, + "description": { + "type": "string" + }, + "server": { + "$ref": "#/definitions/Server" + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "Callback": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/PathItem" + }, + "patternProperties": { + "^x-": {} + } + }, + "Encoding": { + "type": "object", + "properties": { + "contentType": { + "type": "string" + }, + "headers": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Header" + } + }, + "style": { + "type": "string", + "enum": [ + "form", + "spaceDelimited", + "pipeDelimited", + "deepObject" + ] + }, + "explode": { + "type": "boolean" + }, + "allowReserved": { + "type": "boolean", + "default": false + } + }, + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/spec/open_api_parser/document_spec.rb b/spec/open_api_parser/document_spec.rb index 3e0470f..1e3d2d7 100644 --- a/spec/open_api_parser/document_spec.rb +++ b/spec/open_api_parser/document_spec.rb @@ -41,6 +41,16 @@ expect(json['person_greeting']).to eq('Drew') end + + it 'resolves mix of pointers and erb files' do + path = File.expand_path('../resources/valid_spec_v3.yaml', __dir__) + json = OpenApiParser::Document.resolve(path) + + erb_result = [1, 2, 3].sum + + expect(json.dig('components', 'schemas', 'createAnimal', 'properties', 'legs', 'maximum')) + .to eq erb_result + end end context 'json' do diff --git a/spec/open_api_parser/reference_spec.rb b/spec/open_api_parser/reference_spec.rb index 4ac03ae..22c44c6 100644 --- a/spec/open_api_parser/reference_spec.rb +++ b/spec/open_api_parser/reference_spec.rb @@ -108,7 +108,7 @@ def project_root context 'given a $ref path the same as the base path' do it 'reuses the current document' do - expect(YAML).to_not receive(:load) + expect(YAML).to_not receive(:safe_load) document = { 'current' => true } ref = OpenApiParser::Reference.new('person.yaml') _, referrent_doc, = ref.resolve('person.yaml', '', document, file_cache) @@ -186,8 +186,8 @@ def project_root let(:ref_path) { 'standard.yaml' } before do - expect(YAML).to_not( - receive(:load_file).with(base_path) + expect(File).to_not( + receive(:read).with(base_path) ) end @@ -216,8 +216,8 @@ def project_root let(:ref_path) { 'another_standard.yaml' } before do - expect(YAML).to( - receive(:load_file).with(cwd_relative('spec/resources/another_standard.yaml')).and_return(document) + expect(File).to( + receive(:read).with(cwd_relative('spec/resources/another_standard.yaml')).and_return(document.to_yaml) ) end [ @@ -288,8 +288,8 @@ def project_root let(:ref_path) { 'another_standard.yaml' } before do - expect(YAML).to( - receive(:load_file).with(cwd_relative('spec/resources/another_standard.yaml')).and_return(document) + expect(File).to( + receive(:read).with(cwd_relative('spec/resources/another_standard.yaml')).and_return(document.to_yaml) ) end [ diff --git a/spec/open_api_parser/specification_spec.rb b/spec/open_api_parser/specification_spec.rb index f4df302..894b127 100644 --- a/spec/open_api_parser/specification_spec.rb +++ b/spec/open_api_parser/specification_spec.rb @@ -4,6 +4,33 @@ RSpec.describe OpenApiParser::Specification do describe 'self.resolve' do + context 'valid 3.0 specification' do + let(:path) { File.expand_path('../resources/valid_spec_v3.yaml', __dir__) } + let(:specification) { OpenApiParser::Specification.resolve(path) } + + it 'resolves successfully' do + expect(specification.raw.fetch('openapi')).to eq('3.0.0') + end + + it 'properly resolves matching substring references' do + expanded_info_response = { + 'type' => 'object', + 'properties' => { + 'info' => { + 'type' => 'object', + 'properties' => { + 'name' => { + 'type' => 'string' + } + } + } + } + } + + expect(specification.raw.dig('components', 'schemas', 'personInfoResponse')).to eq(expanded_info_response) + end + end + context 'valid specification' do let(:path) { File.expand_path('../resources/valid_spec.yaml', __dir__) } let(:specification) { OpenApiParser::Specification.resolve(path) } diff --git a/spec/resources/valid_erb.yaml.erb b/spec/resources/valid_erb.yaml.erb new file mode 100644 index 0000000..82e2e19 --- /dev/null +++ b/spec/resources/valid_erb.yaml.erb @@ -0,0 +1,3 @@ +legsSchema: + type: integer + maximum: <%= [1,2,3].sum %> \ No newline at end of file diff --git a/spec/resources/valid_spec_v3.yaml b/spec/resources/valid_spec_v3.yaml new file mode 100644 index 0000000..2bbf7b3 --- /dev/null +++ b/spec/resources/valid_spec_v3.yaml @@ -0,0 +1,231 @@ +openapi: 3.0.0 + +info: + title: Simple API overview + version: "1" + +# produces: +# - application/json +# consumes: +# - application/json + +components: + schemas: + createAnimal: + type: object + required: + - name + - legs + properties: + name: + type: string + legs: + $ref: 'valid_erb.yaml.erb#/legsSchema' + + validResponse: + type: object + required: + - status + properties: + status: + type: string + enum: + - ok + + createSearch: + type: object + required: + - name + properties: + name: + type: string + + personInfo: + type: object + properties: + name: + type: string + + personInfoResponse: + type: object + properties: + info: + $ref: '#/components/schemas/personInfo' + + searchResponse: + type: object + required: + - name + - legs + properties: + name: + type: string + legs: + type: integer + + errorResponse: + type: object + required: + - status + properties: + status: + type: string + enum: + - bad + +paths: + /animals: + post: + requestBody: + description: Create an animal + content: + application/json: + schema: + type: object + required: + - body + properties: + body: + $ref: "#/components/schemas/createAnimal" + responses: + 201: + description: Valid create + content: + application/json: + schema: + $ref: "#/components/schemas/validResponse" + default: + description: Error response + content: + application/json: + schema: + $ref: "#/components/schemas/errorResponse" + + /animals/{id}: + get: + operationId: getAnimal + parameters: + - name: id + in: path + required: true + schema: + type: integer + - name: size + in: query + required: false + schema: + type: string + enum: + - small + - large + - name: age + in: query + required: false + schema: + type: integer + - name: tag + in: query + required: false + schema: + type: string + - name: User-Id + in: header + required: false + schema: + type: integer + responses: + 200: + description: Valid get animal + content: + application/json: + schema: + $ref: "#/components/schemas/validResponse" + default: + description: Error response + content: + application/json: + schema: + $ref: "#/components/schemas/errorResponse" + delete: + operationId: deleteAnimal + parameters: + - name: id + in: path + required: true + schema: + type: integer + responses: + 204: + description: Valid delete + default: + description: Error response + content: + application/json: + schema: + $ref: "#/components/schemas/errorResponse" + + /animals/search: + post: + summary: Create a search query for animals + operationId: searchAnimals + requestBody: + content: + application/json: + schema: + type: object + properties: + body: + $ref: "#/components/schemas/createSearch" + responses: + 200: + description: Search results + content: + application/json: + schema: + $ref: "#/components/schemas/searchResponse" + default: + description: Error response + content: + application/json: + schema: + $ref: "#/components/schemas/errorResponse" + + /people/{id}: + get: + operationId: getPerson + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + 200: + description: Valid get person + content: + application/json: + schema: + $ref: "#/components/schemas/personInfoResponse" + default: + description: Error response + content: + application/json: + schema: + $ref: "#/components/schemas/errorResponse" + + /headers: + get: + operationId: getHeaders + responses: + 200: + description: Valid get headers + content: + application/json: + schema: + $ref: "#/components/schemas/validResponse" + headers: + X-My-Header: + schema: + type: string + enum: + - "my value"