Initial commit for trying to produce and consume v3 swagger

This commit is contained in:
Jay Danielian 2019-06-29 18:12:21 -04:00
parent 10bb732148
commit 768a1a1d43
10 changed files with 130 additions and 91 deletions

1
.gitignore vendored
View File

@ -6,3 +6,4 @@
**/*/node_modules **/*/node_modules
*.swp *.swp
Gemfile.lock Gemfile.lock
/.idea/

View File

@ -1 +1 @@
2.3.1 2.5.1

View File

@ -13,7 +13,7 @@ when '4', '5'
gem 'responders' gem 'responders'
end end
gem 'sqlite3' gem 'sqlite3', '~> 1.3.6'
gem 'rswag-api', path: './rswag-api' gem 'rswag-api', path: './rswag-api'
gem 'rswag-ui', path: './rswag-ui' gem 'rswag-ui', path: './rswag-ui'

View File

@ -35,6 +35,14 @@ module Rswag
end end
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) def parameter(attributes)
if attributes[:in] && attributes[:in].to_sym == :path if attributes[:in] && attributes[:in].to_sym == :path
attributes[:required] = true attributes[:required] = true

View File

@ -15,7 +15,7 @@ module Rswag
class ExtendedTypeAttribute < JSON::Schema::TypeV4Attribute class ExtendedTypeAttribute < JSON::Schema::TypeV4Attribute
def self.validate(current_schema, data, fragments, processor, validator, options={}) 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 super
end end
end end

View File

@ -39,7 +39,7 @@ module Rswag
def derive_security_params(metadata, swagger_doc) def derive_security_params(metadata, swagger_doc)
requirements = metadata[:operation][:security] || swagger_doc[:security] || [] requirements = metadata[:operation][:security] || swagger_doc[:security] || []
scheme_names = requirements.flat_map { |r| r.keys } 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| schemes.map do |scheme|
param = (scheme[:type] == :apiKey) ? scheme.slice(:name, :in) : { name: 'Authorization', in: :header } param = (scheme[:type] == :apiKey) ? scheme.slice(:name, :in) : { name: 'Authorization', in: :header }

View File

@ -41,9 +41,12 @@ module Rswag
response_schema = metadata[:response][:schema] response_schema = metadata[:response][:schema]
return if response_schema.nil? return if response_schema.nil?
components_schemas = {components: {schemas: swagger_doc[:components][:schemas]}}
validation_schema = response_schema validation_schema = response_schema
.merge('$schema' => 'http://tempuri.org/rswag/specs/extended_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) errors = JSON::Validator.fully_validate(validation_schema, body)
raise UnexpectedResponse, "Expected response body to match schema: #{errors[0]}" if errors.any? raise UnexpectedResponse, "Expected response body to match schema: #{errors[0]}" if errors.any?
end end

View File

@ -10,7 +10,7 @@ describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do
operationId 'createBlog' operationId 'createBlog'
consumes 'application/json' consumes 'application/json'
produces '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' } } let(:blog) { { title: 'foo', content: 'bar' } }
@ -19,7 +19,7 @@ describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do
end end
response '422', 'invalid request' do response '422', 'invalid request' do
schema '$ref' => '#/definitions/errors_object' schema '$ref' => '#/components/schemas/errors_object'
let(:blog) { { title: 'foo' } } let(:blog) { { title: 'foo' } }
run_test! do |response| run_test! do |response|
@ -38,7 +38,7 @@ describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do
let(:keywords) { 'foo bar' } let(:keywords) { 'foo bar' }
response '200', 'success' do response '200', 'success' do
schema type: 'array', items: { '$ref' => '#/definitions/blog' } schema type: 'array', items: { '$ref' => '#/components/schemas/blog' }
end end
response '406', 'unsupported accept header' do 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 'Last-Modified', type: :string
header 'Cache-Control', type: :string header 'Cache-Control', type: :string
schema '$ref' => '#/definitions/blog' schema '$ref' => '#/components/schemas/blog'
examples 'application/json' => { examples 'application/json' => {
id: 1, id: 1,

View File

@ -14,47 +14,61 @@ RSpec.configure do |config|
# the root example_group in your specs, e.g. describe '...', swagger_doc: 'v2/swagger.json' # the root example_group in your specs, e.g. describe '...', swagger_doc: 'v2/swagger.json'
config.swagger_docs = { config.swagger_docs = {
'v1/swagger.json' => { 'v1/swagger.json' => {
swagger: '2.0', openapi: '3.0.0',
info: { info: {
title: 'API V1', title: 'API V1',
version: 'v1' version: 'v1'
}, },
paths: {}, paths: {},
definitions: { servers: [
errors_object: { {
type: 'object', url: "https://{defaultHost}",
properties: { variables: {
errors: { '$ref' => '#/definitions/errors_map' } 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: { securitySchemes: {
type: 'object', basic_auth: {
additionalProperties: { type: :http,
type: 'array', scheme: :basic
items: { type: 'string' } },
} api_key: {
}, type: :apiKey,
blog: { name: 'api_key',
type: 'object', in: :query
properties: { }
id: { type: 'integer' },
title: { type: 'string' },
content: { type: 'string', 'x-nullable': true },
thumbnail: { type: 'string'}
},
required: [ 'id', 'title', 'content', 'thumbnail' ]
} }
}, },
securityDefinitions: {
basic_auth: {
type: :basic
},
api_key: {
type: :apiKey,
name: 'api_key',
in: :query
}
}
} }
} }
end end

View File

@ -1,5 +1,5 @@
{ {
"swagger": "2.0", "openapi": "3.0.0",
"info": { "info": {
"title": "API V1", "title": "API V1",
"version": "v1" "version": "v1"
@ -99,7 +99,7 @@
"name": "blog", "name": "blog",
"in": "body", "in": "body",
"schema": { "schema": {
"$ref": "#/definitions/blog" "$ref": "#/components/schemas/blog"
} }
} }
], ],
@ -110,7 +110,7 @@
"422": { "422": {
"description": "invalid request", "description": "invalid request",
"schema": { "schema": {
"$ref": "#/definitions/errors_object" "$ref": "#/components/schemas/errors_object"
} }
} }
} }
@ -173,7 +173,7 @@
} }
}, },
"schema": { "schema": {
"$ref": "#/definitions/blog" "$ref": "#/components/schemas/blog"
}, },
"examples": { "examples": {
"application/json": { "application/json": {
@ -225,57 +225,70 @@
} }
} }
}, },
"definitions": { "servers": [
"errors_object": { {
"type": "object", "url": "https://{defaultHost}",
"properties": { "variables": {
"errors": { "defaultHost": {
"$ref": "#/definitions/errors_map" "default": "www.example.com"
} }
} }
}, }
"errors_map": { ],
"type": "object", "components": {
"additionalProperties": { "schemas": {
"type": "array", "errors_object": {
"items": { "type": "object",
"type": "string" "properties": {
} "errors": {
} "$ref": "#/components/schemas/errors_map"
}, }
"blog": {
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"title": {
"type": "string"
},
"content": {
"type": "string",
"x-nullable": true
},
"thumbnail": {
"type": "string"
} }
}, },
"required": [ "errors_map": {
"id", "type": "object",
"title", "additionalProperties": {
"content", "type": "array",
"thumbnail" "items": {
] "type": "string"
} }
}, }
"securityDefinitions": { },
"basic_auth": { "blog": {
"type": "basic" "type": "object",
"properties": {
"id": {
"type": "integer"
},
"title": {
"type": "string"
},
"content": {
"type": "string",
"nullable": true
},
"thumbnail": {
"type": "string"
}
},
"required": [
"id",
"title",
"content",
"thumbnail"
]
}
}, },
"api_key": { "securitySchemes": {
"type": "apiKey", "basic_auth": {
"name": "api_key", "type": "http",
"in": "query" "scheme": "basic"
},
"api_key": {
"type": "apiKey",
"name": "api_key",
"in": "query"
}
} }
} }
} }