mirror of
https://github.com/ditkrg/rswag.git
synced 2026-01-25 15:22:56 +00:00
rename to rswag plus major refactor - almost a rewrite
This commit is contained in:
80
rswag-specs/lib/rswag/specs/example_group_helpers.rb
Normal file
80
rswag-specs/lib/rswag/specs/example_group_helpers.rb
Normal 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
|
||||
43
rswag-specs/lib/rswag/specs/example_helpers.rb
Normal file
43
rswag-specs/lib/rswag/specs/example_helpers.rb
Normal 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
|
||||
10
rswag-specs/lib/rswag/specs/railtie.rb
Normal file
10
rswag-specs/lib/rswag/specs/railtie.rb
Normal 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
|
||||
70
rswag-specs/lib/rswag/specs/request_factory.rb
Normal file
70
rswag-specs/lib/rswag/specs/request_factory.rb
Normal 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
|
||||
38
rswag-specs/lib/rswag/specs/response_validator.rb
Normal file
38
rswag-specs/lib/rswag/specs/response_validator.rb
Normal 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
|
||||
79
rswag-specs/lib/rswag/specs/swagger_formatter.rb
Normal file
79
rswag-specs/lib/rswag/specs/swagger_formatter.rb
Normal 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
|
||||
5
rswag-specs/lib/rswag/specs/version.rb
Normal file
5
rswag-specs/lib/rswag/specs/version.rb
Normal file
@@ -0,0 +1,5 @@
|
||||
module Rswag
|
||||
module Specs
|
||||
VERSION = '1.0.0'
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user