diff --git a/.gitignore b/.gitignore index d7a22a8..0c1b96a 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ **/*/node_modules *.swp Gemfile.lock +/.idea/ diff --git a/.ruby-version b/.ruby-version index 2bf1c1c..73462a5 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.3.1 +2.5.1 diff --git a/Gemfile b/Gemfile index bbd3d73..1abf797 100644 --- a/Gemfile +++ b/Gemfile @@ -13,7 +13,7 @@ when '4', '5' gem 'responders' end -gem 'sqlite3' +gem 'sqlite3', '~> 1.3.6' gem 'rswag-api', path: './rswag-api' gem 'rswag-ui', path: './rswag-ui' diff --git a/rswag-specs/lib/rswag/specs/example_group_helpers.rb b/rswag-specs/lib/rswag/specs/example_group_helpers.rb index c293c63..2e2fd7d 100644 --- a/rswag-specs/lib/rswag/specs/example_group_helpers.rb +++ b/rswag-specs/lib/rswag/specs/example_group_helpers.rb @@ -35,6 +35,14 @@ module Rswag end end + #TODO: look at adding request_body method to handle diffs in Open API 2.0 to 3.0 + # https://swagger.io/docs/specification/describing-request-body/ + # need to make sure we output requestBody in the swagger generator .json + # also need to make sure that it can handle content: , required: true/false, schema: ref + + + + def parameter(attributes) if attributes[:in] && attributes[:in].to_sym == :path attributes[:required] = true diff --git a/rswag-specs/lib/rswag/specs/extended_schema.rb b/rswag-specs/lib/rswag/specs/extended_schema.rb index 62eb4ee..847cd72 100644 --- a/rswag-specs/lib/rswag/specs/extended_schema.rb +++ b/rswag-specs/lib/rswag/specs/extended_schema.rb @@ -15,7 +15,7 @@ module Rswag class ExtendedTypeAttribute < JSON::Schema::TypeV4Attribute def self.validate(current_schema, data, fragments, processor, validator, options={}) - return if data.nil? && current_schema.schema['x-nullable'] == true + return if data.nil? && (current_schema.schema['nullable'] == true || current_schema.schema['x-nullable'] == true) super end end diff --git a/rswag-specs/lib/rswag/specs/request_factory.rb b/rswag-specs/lib/rswag/specs/request_factory.rb index 7106015..0bb7a33 100644 --- a/rswag-specs/lib/rswag/specs/request_factory.rb +++ b/rswag-specs/lib/rswag/specs/request_factory.rb @@ -39,7 +39,7 @@ module Rswag def derive_security_params(metadata, swagger_doc) requirements = metadata[:operation][:security] || swagger_doc[:security] || [] scheme_names = requirements.flat_map { |r| r.keys } - schemes = (swagger_doc[:securityDefinitions] || {}).slice(*scheme_names).values + schemes = (swagger_doc[:components][:securitySchemes] || {}).slice(*scheme_names).values schemes.map do |scheme| param = (scheme[:type] == :apiKey) ? scheme.slice(:name, :in) : { name: 'Authorization', in: :header } diff --git a/rswag-specs/lib/rswag/specs/response_validator.rb b/rswag-specs/lib/rswag/specs/response_validator.rb index c3e363f..d2b71ad 100644 --- a/rswag-specs/lib/rswag/specs/response_validator.rb +++ b/rswag-specs/lib/rswag/specs/response_validator.rb @@ -41,9 +41,12 @@ module Rswag response_schema = metadata[:response][:schema] return if response_schema.nil? + components_schemas = {components: {schemas: swagger_doc[:components][:schemas]}} + validation_schema = response_schema .merge('$schema' => 'http://tempuri.org/rswag/specs/extended_schema') - .merge(swagger_doc.slice(:definitions)) + .merge(components_schemas) + errors = JSON::Validator.fully_validate(validation_schema, body) raise UnexpectedResponse, "Expected response body to match schema: #{errors[0]}" if errors.any? end diff --git a/test-app/spec/integration/blogs_spec.rb b/test-app/spec/integration/blogs_spec.rb index abca570..08297f8 100644 --- a/test-app/spec/integration/blogs_spec.rb +++ b/test-app/spec/integration/blogs_spec.rb @@ -10,7 +10,7 @@ describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do operationId 'createBlog' consumes 'application/json' produces 'application/json' - parameter name: :blog, in: :body, schema: { '$ref' => '#/definitions/blog' } + parameter name: :blog, in: :body, schema: { '$ref' => '#/components/schemas/blog' } let(:blog) { { title: 'foo', content: 'bar' } } @@ -19,7 +19,7 @@ describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do end response '422', 'invalid request' do - schema '$ref' => '#/definitions/errors_object' + schema '$ref' => '#/components/schemas/errors_object' let(:blog) { { title: 'foo' } } run_test! do |response| @@ -38,7 +38,7 @@ describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do let(:keywords) { 'foo bar' } response '200', 'success' do - schema type: 'array', items: { '$ref' => '#/definitions/blog' } + schema type: 'array', items: { '$ref' => '#/components/schemas/blog' } end response '406', 'unsupported accept header' do @@ -65,7 +65,7 @@ describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do header 'Last-Modified', type: :string header 'Cache-Control', type: :string - schema '$ref' => '#/definitions/blog' + schema '$ref' => '#/components/schemas/blog' examples 'application/json' => { id: 1, diff --git a/test-app/spec/swagger_helper.rb b/test-app/spec/swagger_helper.rb index fa1162a..e206999 100644 --- a/test-app/spec/swagger_helper.rb +++ b/test-app/spec/swagger_helper.rb @@ -14,47 +14,61 @@ RSpec.configure do |config| # the root example_group in your specs, e.g. describe '...', swagger_doc: 'v2/swagger.json' config.swagger_docs = { 'v1/swagger.json' => { - swagger: '2.0', + openapi: '3.0.0', info: { title: 'API V1', version: 'v1' }, paths: {}, - definitions: { - errors_object: { - type: 'object', - properties: { - errors: { '$ref' => '#/definitions/errors_map' } + servers: [ + { + url: "https://{defaultHost}", + variables: { + defaultHost: { + default: "www.example.com" + } + } } + ], + + components: { + schemas: { + errors_object: { + type: 'object', + properties: { + errors: { '$ref' => '#/components/schemas/errors_map' } + } + }, + errors_map: { + type: 'object', + additionalProperties: { + type: 'array', + items: { type: 'string' } + } + }, + blog: { + type: 'object', + properties: { + id: { type: 'integer' }, + title: { type: 'string' }, + content: { type: 'string', nullable: true }, + thumbnail: { type: 'string'} + }, + required: [ 'id', 'title', 'content', 'thumbnail' ] + } }, - errors_map: { - type: 'object', - additionalProperties: { - type: 'array', - items: { type: 'string' } - } - }, - blog: { - type: 'object', - properties: { - id: { type: 'integer' }, - title: { type: 'string' }, - content: { type: 'string', 'x-nullable': true }, - thumbnail: { type: 'string'} - }, - required: [ 'id', 'title', 'content', 'thumbnail' ] + securitySchemes: { + basic_auth: { + type: :http, + scheme: :basic + }, + api_key: { + type: :apiKey, + name: 'api_key', + in: :query + } } }, - securityDefinitions: { - basic_auth: { - type: :basic - }, - api_key: { - type: :apiKey, - name: 'api_key', - in: :query - } - } } } end diff --git a/test-app/swagger/v1/swagger.json b/test-app/swagger/v1/swagger.json index 8531c7a..6e24e14 100644 --- a/test-app/swagger/v1/swagger.json +++ b/test-app/swagger/v1/swagger.json @@ -1,5 +1,5 @@ { - "swagger": "2.0", + "openapi": "3.0.0", "info": { "title": "API V1", "version": "v1" @@ -99,7 +99,7 @@ "name": "blog", "in": "body", "schema": { - "$ref": "#/definitions/blog" + "$ref": "#/components/schemas/blog" } } ], @@ -110,7 +110,7 @@ "422": { "description": "invalid request", "schema": { - "$ref": "#/definitions/errors_object" + "$ref": "#/components/schemas/errors_object" } } } @@ -173,7 +173,7 @@ } }, "schema": { - "$ref": "#/definitions/blog" + "$ref": "#/components/schemas/blog" }, "examples": { "application/json": { @@ -225,57 +225,70 @@ } } }, - "definitions": { - "errors_object": { - "type": "object", - "properties": { - "errors": { - "$ref": "#/definitions/errors_map" + "servers": [ + { + "url": "https://{defaultHost}", + "variables": { + "defaultHost": { + "default": "www.example.com" } } - }, - "errors_map": { - "type": "object", - "additionalProperties": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "blog": { - "type": "object", - "properties": { - "id": { - "type": "integer" - }, - "title": { - "type": "string" - }, - "content": { - "type": "string", - "x-nullable": true - }, - "thumbnail": { - "type": "string" + } + ], + "components": { + "schemas": { + "errors_object": { + "type": "object", + "properties": { + "errors": { + "$ref": "#/components/schemas/errors_map" + } } }, - "required": [ - "id", - "title", - "content", - "thumbnail" - ] - } - }, - "securityDefinitions": { - "basic_auth": { - "type": "basic" + "errors_map": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "blog": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "title": { + "type": "string" + }, + "content": { + "type": "string", + "nullable": true + }, + "thumbnail": { + "type": "string" + } + }, + "required": [ + "id", + "title", + "content", + "thumbnail" + ] + } }, - "api_key": { - "type": "apiKey", - "name": "api_key", - "in": "query" + "securitySchemes": { + "basic_auth": { + "type": "http", + "scheme": "basic" + }, + "api_key": { + "type": "apiKey", + "name": "api_key", + "in": "query" + } } } } \ No newline at end of file