rename to rswag plus major refactor - almost a rewrite

This commit is contained in:
richie
2016-10-11 18:31:12 -07:00
parent f8d993356f
commit c558098c39
453 changed files with 52410 additions and 35793 deletions

View File

@@ -0,0 +1,80 @@
module Rswag
module Specs
module ExampleGroupHelpers
def path(path, &block)
api_metadata = { path: path}
describe(path, api_metadata, &block)
end
[ :get, :post, :patch, :put, :delete, :head ].each do |verb|
define_method(verb) do |summary, &block|
api_metadata = { operation: { verb: verb, summary: summary } }
describe(verb, api_metadata, &block)
end
end
[ :operationId, :deprecated, :security ].each do |attr_name|
define_method(attr_name) do |value|
metadata[:operation][attr_name] = value
end
end
# NOTE: 'description' requires special treatment because ExampleGroup already
# defines a method with that name. Provide an override that supports the existing
# functionality while also setting the appropriate metadata if applicable
def description(value=nil)
return super() if value.nil?
metadata[:operation][:description] = value
end
# These are array properties - note the splat operator
[ :tags, :consumes, :produces, :schemes ].each do |attr_name|
define_method(attr_name) do |*value|
metadata[:operation][attr_name] = value
end
end
def parameter(attributes)
attributes[:required] = true if attributes[:in].to_sym == :path
metadata[:operation][:parameters] ||= []
metadata[:operation][:parameters] << attributes
end
def response(code, description, &block)
api_metadata = { response: { code: code, description: description } }
context(description, api_metadata, &block)
end
def schema(value)
metadata[:response][:schema] = value
end
def header(name, attributes)
metadata[:response][:headers] ||= {}
metadata[:response][:headers][name] = attributes
end
def run_test!
# NOTE: rspec 2.x support
if RSPEC_VERSION < 3
before do
submit_request(example.metadata)
end
it "returns a #{metadata[:response][:code]} response" do
assert_response_matches_metadata(example.metadata)
end
else
before do |example|
submit_request(example.metadata)
end
it "returns a #{metadata[:response][:code]} response" do |example|
assert_response_matches_metadata(example.metadata)
end
end
end
end
end
end

View File

@@ -0,0 +1,43 @@
require 'rswag/specs/request_factory'
require 'rswag/specs/response_validator'
module Rswag
module Specs
module ExampleHelpers
def submit_request(api_metadata)
factory = RequestFactory.new(api_metadata, global_metadata(api_metadata[:swagger_doc]))
if RAILS_VERSION < 5
send(
api_metadata[:operation][:verb],
factory.build_fullpath(self),
factory.build_body(self),
factory.build_headers(self)
)
else
send(
api_metadata[:operation][:verb],
factory.build_fullpath(self),
{
params: factory.build_body(self),
headers: factory.build_headers(self)
}
)
end
end
def assert_response_matches_metadata(api_metadata)
validator = ResponseValidator.new(api_metadata, global_metadata(api_metadata[:swagger_doc]))
validator.validate!(response)
end
private
def global_metadata(swagger_doc)
swagger_docs = ::RSpec.configuration.swagger_docs
swagger_doc.nil? ? swagger_docs.values.first : swagger_docs[swagger_doc]
end
end
end
end

View File

@@ -0,0 +1,10 @@
module Rswag
module Specs
class Railtie < ::Rails::Railtie
rake_tasks do
load File.expand_path('../../../tasks/rswag-specs_tasks.rake', __FILE__)
end
end
end
end

View File

@@ -0,0 +1,70 @@
require 'active_support/core_ext/hash/slice'
require 'json'
module Rswag
module Specs
class RequestFactory
def initialize(api_metadata, global_metadata)
@api_metadata = api_metadata
@global_metadata = global_metadata
end
def build_fullpath(example)
@api_metadata[:path].dup.tap do |t|
parameters_in(:path).each { |p| t.gsub!("{#{p[:name]}}", example.send(p[:name]).to_s) }
t.concat(build_query(example))
t.prepend(@global_metadata[:basePath] || '')
end
end
def build_query(example)
query_string = parameters_in(:query)
.map { |p| "#{p[:name]}=#{example.send(p[:name])}" }
.join('&')
query_string.empty? ? '' : "?#{query_string}"
end
def build_body(example)
body_parameter = parameters_in(:body).first
body_parameter.nil? ? '' : example.send(body_parameter[:name]).to_json
end
def build_headers(example)
headers = Hash[ parameters_in(:header).map { |p| [ p[:name], example.send(p[:name]).to_s ] } ]
headers.tap do |h|
produces = @api_metadata[:operation][:produces] || @global_metadata[:produces]
consumes = @api_metadata[:operation][:consumes] || @global_metadata[:consumes]
h['ACCEPT'] = produces.join(';') unless produces.nil?
h['CONTENT_TYPE'] = consumes.join(';') unless consumes.nil?
end
end
private
def parameters_in(location)
(@api_metadata[:operation][:parameters] || [])
.map { |p| p['$ref'] ? resolve_parameter(p['$ref']) : p } # resolve any references
.concat(resolve_api_key_parameters)
.select { |p| p[:in] == location }
end
def resolve_parameter(ref)
defined_params = @global_metadata[:parameters]
key = ref.sub('#/parameters/', '')
raise "Referenced parameter '#{ref}' must be defined" unless defined_params && defined_params[key]
defined_params[key]
end
def resolve_api_key_parameters
@api_key_params ||= begin
global_requirements = (@global_metadata[:security] || {})
requirements = global_requirements.merge(@api_metadata[:operation][:security] || {})
definitions = (@global_metadata[:securityDefinitions] || {}).slice(*requirements.keys)
definitions.values.select { |d| d[:type] == :apiKey }
end
end
end
end
end

View File

@@ -0,0 +1,38 @@
require 'json-schema'
module Rswag
module Specs
class ResponseValidator
def initialize(api_metadata, global_metadata)
@api_metadata = api_metadata
@global_metadata = global_metadata
end
def validate!(response)
validate_code!(response.code)
validate_body!(response.body)
end
private
def validate_code!(code)
if code.to_s != @api_metadata[:response][:code].to_s
raise UnexpectedResponse, "Expected response code '#{code}' to match '#{@api_metadata[:response][:code]}'"
end
end
def validate_body!(body)
schema = @api_metadata[:response][:schema]
return if schema.nil?
begin
JSON::Validator.validate!(schema.merge(@global_metadata), body)
rescue JSON::Schema::ValidationError => ex
raise UnexpectedResponse, "Expected response body to match schema: #{ex.message}"
end
end
end
class UnexpectedResponse < StandardError; end
end
end

View File

@@ -0,0 +1,79 @@
require 'active_support/core_ext/hash/deep_merge'
require 'rspec/core/formatters/base_text_formatter'
require 'swagger_helper'
module Rswag
module Specs
class SwaggerFormatter
# NOTE: rspec 2.x support
if RSPEC_VERSION > 2
::RSpec::Core::Formatters.register self, :example_group_finished, :stop
end
def initialize(output)
@output = output
@swagger_root = ::RSpec.configuration.swagger_root
raise ConfigurationError, 'Missing swagger_root. See swagger_helper.rb' if @swagger_root.nil?
@swagger_docs = ::RSpec.configuration.swagger_docs || []
raise ConfigurationError, 'Missing swagger_docs. See swagger_helper.rb' if @swagger_docs.empty?
@output.puts 'Generating Swagger docs ...'
end
def example_group_finished(notification)
# NOTE: rspec 2.x support
if RSPEC_VERSION > 2
metadata = notification.group.metadata
else
metadata = notification.metadata
end
return unless metadata.has_key?(:response)
swagger_doc = get_swagger_doc(metadata[:swagger_doc])
swagger_doc.deep_merge!(metadata_to_swagger(metadata))
end
def stop(notification=nil)
@swagger_docs.each do |url_path, doc|
file_path = File.join(@swagger_root, url_path)
dirname = File.dirname(file_path)
FileUtils.mkdir_p dirname unless File.exists?(dirname)
File.open(file_path, 'w') do |file|
file.write(JSON.pretty_generate(doc))
end
@output.puts "Swagger doc generated at #{file_path}"
end
end
private
def get_swagger_doc(tag)
return @swagger_docs.values.first if tag.nil?
raise ConfigurationError, "Unknown swagger_doc '#{tag}'" unless @swagger_docs.has_key?(tag)
@swagger_docs[tag]
end
def metadata_to_swagger(metadata)
response_code = metadata[:response][:code]
response = metadata[:response].reject { |k,v| k == :code }
verb = metadata[:operation][:verb]
operation = metadata[:operation]
.reject { |k,v| k == :verb }
.merge(responses: { response_code => response })
{
paths: {
metadata[:path] => {
verb => operation
}
}
}
end
end
class ConfigurationError < StandardError; end
end
end

View File

@@ -0,0 +1,5 @@
module Rswag
module Specs
VERSION = '1.0.0'
end
end