mirror of
https://github.com/ditkrg/rswag.git
synced 2026-01-22 22:06:43 +00:00
commit
c889e407ec
2
.gitignore
vendored
2
.gitignore
vendored
@ -6,3 +6,5 @@
|
|||||||
**/*/node_modules
|
**/*/node_modules
|
||||||
*.swp
|
*.swp
|
||||||
Gemfile.lock
|
Gemfile.lock
|
||||||
|
/.idea/
|
||||||
|
**/test-app/.byebug_history
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
2.3.1
|
2.5.1
|
||||||
|
|||||||
@ -11,11 +11,16 @@ Set up your machine:
|
|||||||
|
|
||||||
```
|
```
|
||||||
bundle
|
bundle
|
||||||
cd spec/dummy
|
cd test-app
|
||||||
bundle exec rake db:setup
|
bundle exec rake db:setup
|
||||||
cd -
|
cd -
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Initialize the rswag-ui repo with assets.
|
||||||
|
```
|
||||||
|
ci/build.sh
|
||||||
|
```
|
||||||
|
|
||||||
Make sure the tests pass:
|
Make sure the tests pass:
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
28
Gemfile
28
Gemfile
@ -1,10 +1,12 @@
|
|||||||
source "https://rubygems.org"
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
source 'https://rubygems.org'
|
||||||
|
|
||||||
# Allow the rails version to come from an ENV setting so Travis can test multiple versions.
|
# Allow the rails version to come from an ENV setting so Travis can test multiple versions.
|
||||||
# See http://www.schneems.com/post/50991826838/testing-against-multiple-rails-versions/
|
# See http://www.schneems.com/post/50991826838/testing-against-multiple-rails-versions/
|
||||||
rails_version = ENV['RAILS_VERSION'] || '5.1.2'
|
rails_version = ENV['RAILS_VERSION'] || '5.1.2'
|
||||||
|
|
||||||
gem 'rails', "#{rails_version}"
|
gem 'rails', rails_version.to_s
|
||||||
|
|
||||||
case rails_version.split('.').first
|
case rails_version.split('.').first
|
||||||
when '3'
|
when '3'
|
||||||
@ -13,23 +15,29 @@ when '4', '5'
|
|||||||
gem 'responders'
|
gem 'responders'
|
||||||
end
|
end
|
||||||
|
|
||||||
gem 'sqlite3'
|
gem 'sqlite3', '~> 1.3.6'
|
||||||
|
|
||||||
gem 'rswag-api', path: './rswag-api'
|
gem 'open_api-rswag-api', path: './rswag-api'
|
||||||
gem 'rswag-ui', path: './rswag-ui'
|
gem 'open_api-rswag-ui', path: './rswag-ui'
|
||||||
|
|
||||||
group :test do
|
group :test do
|
||||||
gem 'test-unit'
|
|
||||||
gem 'rspec-rails'
|
|
||||||
gem 'generator_spec'
|
|
||||||
gem 'capybara'
|
gem 'capybara'
|
||||||
gem 'capybara-webkit'
|
gem 'capybara-webkit'
|
||||||
gem 'rswag-specs', path: './rswag-specs'
|
gem 'generator_spec'
|
||||||
|
gem 'rspec-rails'
|
||||||
|
gem 'open_api-rswag-specs', path: './rswag-specs'
|
||||||
|
gem 'test-unit'
|
||||||
|
end
|
||||||
|
|
||||||
|
group :development do
|
||||||
|
gem 'guard-rspec', require: false
|
||||||
|
gem 'open_api-rswag-specs', path: './rswag-specs'
|
||||||
|
gem 'rubocop'
|
||||||
end
|
end
|
||||||
|
|
||||||
group :assets do
|
group :assets do
|
||||||
gem 'uglifier'
|
|
||||||
gem 'therubyracer'
|
gem 'therubyracer'
|
||||||
|
gem 'uglifier'
|
||||||
end
|
end
|
||||||
|
|
||||||
gem 'byebug'
|
gem 'byebug'
|
||||||
|
|||||||
13
README.md
13
README.md
@ -532,3 +532,16 @@ bundle exec rake rswag:ui:copy_assets[public/api-docs]
|
|||||||
```
|
```
|
||||||
|
|
||||||
__NOTE:__: The provided subfolder MUST correspond to the UI mount prefix - "api-docs" by default.
|
__NOTE:__: The provided subfolder MUST correspond to the UI mount prefix - "api-docs" by default.
|
||||||
|
|
||||||
|
|
||||||
|
Notes to test swagger output locally with swagger editor
|
||||||
|
```
|
||||||
|
docker pull swaggerapi/swagger-editor
|
||||||
|
```
|
||||||
|
```
|
||||||
|
docker run -d -p 80:8080 swaggerapi/swagger-editor
|
||||||
|
```
|
||||||
|
This will run the swagger editor in the docker daemon and can be accessed
|
||||||
|
at ```http://localhost```. From here, you can use the UI to load the generated swagger.json to validate the output.
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
# This command will automatically be run when you run "rails" with Rails 4 gems installed from the root of your application.
|
# This command will automatically be run when you run "rails" with Rails 4 gems installed from the root of your application.
|
||||||
|
|
||||||
ENGINE_ROOT = File.expand_path('../..', __FILE__)
|
ENGINE_ROOT = File.expand_path('../..', __FILE__)
|
||||||
ENGINE_PATH = File.expand_path('../../lib/rswag/api/engine', __FILE__)
|
ENGINE_PATH = File.expand_path('../../lib/open_api/rswag/api/engine', __FILE__)
|
||||||
|
|
||||||
# Set up gems listed in the Gemfile.
|
# Set up gems listed in the Gemfile.
|
||||||
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
|
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
|
||||||
|
|||||||
@ -5,4 +5,4 @@ Example:
|
|||||||
rails generate rswag:api:install
|
rails generate rswag:api:install
|
||||||
|
|
||||||
This will create:
|
This will create:
|
||||||
config/initializers/rswag-api.rb
|
config/initializers/rswag_api.rb
|
||||||
|
|||||||
@ -7,7 +7,7 @@ module Rswag
|
|||||||
source_root File.expand_path('../templates', __FILE__)
|
source_root File.expand_path('../templates', __FILE__)
|
||||||
|
|
||||||
def add_initializer
|
def add_initializer
|
||||||
template('rswag-api.rb', 'config/initializers/rswag-api.rb')
|
template('rswag_api.rb', 'config/initializers/rswag_api.rb')
|
||||||
end
|
end
|
||||||
|
|
||||||
def add_routes
|
def add_routes
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
Rswag::Api.configure do |c|
|
OpenApi::Rswag::Api.configure do |c|
|
||||||
|
|
||||||
# Specify a root folder where Swagger JSON files are located
|
# Specify a root folder where Swagger JSON files are located
|
||||||
# This is used by the Swagger middleware to serve requests for API descriptions
|
# This is used by the Swagger middleware to serve requests for API descriptions
|
||||||
19
rswag-api/lib/open_api/rswag/api.rb
Normal file
19
rswag-api/lib/open_api/rswag/api.rb
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
module OpenApi
|
||||||
|
|
||||||
|
end
|
||||||
|
require 'open_api/rswag/api/configuration'
|
||||||
|
require 'open_api/rswag/api/engine'
|
||||||
|
|
||||||
|
module OpenApi
|
||||||
|
module Rswag
|
||||||
|
module Api
|
||||||
|
def self.configure
|
||||||
|
yield(config)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.config
|
||||||
|
@config ||= Configuration.new
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
14
rswag-api/lib/open_api/rswag/api/configuration.rb
Normal file
14
rswag-api/lib/open_api/rswag/api/configuration.rb
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
module OpenApi
|
||||||
|
module Rswag
|
||||||
|
module Api
|
||||||
|
class Configuration
|
||||||
|
attr_accessor :swagger_root, :swagger_filter
|
||||||
|
|
||||||
|
def resolve_swagger_root(env)
|
||||||
|
path_params = env['action_dispatch.request.path_parameters'] || {}
|
||||||
|
path_params[:swagger_root] || swagger_root
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
15
rswag-api/lib/open_api/rswag/api/engine.rb
Normal file
15
rswag-api/lib/open_api/rswag/api/engine.rb
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
require 'open_api/rswag/api/middleware'
|
||||||
|
|
||||||
|
module OpenApi
|
||||||
|
module Rswag
|
||||||
|
module Api
|
||||||
|
class Engine < ::Rails::Engine
|
||||||
|
isolate_namespace OpenApi::Rswag::Api
|
||||||
|
|
||||||
|
initializer 'rswag-api.initialize' do |app|
|
||||||
|
middleware.use OpenApi::Rswag::Api::Middleware, OpenApi::Rswag::Api.config
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
39
rswag-api/lib/open_api/rswag/api/middleware.rb
Normal file
39
rswag-api/lib/open_api/rswag/api/middleware.rb
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
require 'json'
|
||||||
|
|
||||||
|
module OpenApi
|
||||||
|
module Rswag
|
||||||
|
module Api
|
||||||
|
class Middleware
|
||||||
|
|
||||||
|
def initialize(app, config)
|
||||||
|
@app = app
|
||||||
|
@config = config
|
||||||
|
end
|
||||||
|
|
||||||
|
def call(env)
|
||||||
|
path = env['PATH_INFO']
|
||||||
|
filename = "#{@config.resolve_swagger_root(env)}/#{path}"
|
||||||
|
|
||||||
|
if env['REQUEST_METHOD'] == 'GET' && File.file?(filename)
|
||||||
|
swagger = load_json(filename)
|
||||||
|
@config.swagger_filter.call(swagger, env) unless @config.swagger_filter.nil?
|
||||||
|
|
||||||
|
return [
|
||||||
|
'200',
|
||||||
|
{ 'Content-Type' => 'application/json' },
|
||||||
|
[ JSON.dump(swagger) ]
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
return @app.call(env)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def load_json(filename)
|
||||||
|
JSON.parse(File.read(filename))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -1,14 +0,0 @@
|
|||||||
require 'rswag/api/configuration'
|
|
||||||
require 'rswag/api/engine'
|
|
||||||
|
|
||||||
module Rswag
|
|
||||||
module Api
|
|
||||||
def self.configure
|
|
||||||
yield(config)
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.config
|
|
||||||
@config ||= Configuration.new
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
module Rswag
|
|
||||||
module Api
|
|
||||||
class Configuration
|
|
||||||
attr_accessor :swagger_root, :swagger_filter
|
|
||||||
|
|
||||||
def resolve_swagger_root(env)
|
|
||||||
path_params = env['action_dispatch.request.path_parameters'] || {}
|
|
||||||
path_params[:swagger_root] || swagger_root
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
require 'rswag/api/middleware'
|
|
||||||
|
|
||||||
module Rswag
|
|
||||||
module Api
|
|
||||||
class Engine < ::Rails::Engine
|
|
||||||
isolate_namespace Rswag::Api
|
|
||||||
|
|
||||||
initializer 'rswag-api.initialize' do |app|
|
|
||||||
middleware.use Rswag::Api::Middleware, Rswag::Api.config
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,37 +0,0 @@
|
|||||||
require 'json'
|
|
||||||
|
|
||||||
module Rswag
|
|
||||||
module Api
|
|
||||||
class Middleware
|
|
||||||
|
|
||||||
def initialize(app, config)
|
|
||||||
@app = app
|
|
||||||
@config = config
|
|
||||||
end
|
|
||||||
|
|
||||||
def call(env)
|
|
||||||
path = env['PATH_INFO']
|
|
||||||
filename = "#{@config.resolve_swagger_root(env)}/#{path}"
|
|
||||||
|
|
||||||
if env['REQUEST_METHOD'] == 'GET' && File.file?(filename)
|
|
||||||
swagger = load_json(filename)
|
|
||||||
@config.swagger_filter.call(swagger, env) unless @config.swagger_filter.nil?
|
|
||||||
|
|
||||||
return [
|
|
||||||
'200',
|
|
||||||
{ 'Content-Type' => 'application/json' },
|
|
||||||
[ JSON.dump(swagger) ]
|
|
||||||
]
|
|
||||||
end
|
|
||||||
|
|
||||||
return @app.call(env)
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def load_json(filename)
|
|
||||||
JSON.parse(File.read(filename))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -2,11 +2,11 @@ $:.push File.expand_path("../lib", __FILE__)
|
|||||||
|
|
||||||
# Describe your gem and declare its dependencies:
|
# Describe your gem and declare its dependencies:
|
||||||
Gem::Specification.new do |s|
|
Gem::Specification.new do |s|
|
||||||
s.name = "rswag-api"
|
s.name = "open_api-rswag-api"
|
||||||
s.version = ENV['TRAVIS_TAG'] || '0.0.0'
|
s.version = ENV['TRAVIS_TAG'] || '0.0.0'
|
||||||
s.authors = ["Richie Morris"]
|
s.authors = ["Richie Morris", "Jay Danielian"]
|
||||||
s.email = ["domaindrivendev@gmail.com"]
|
s.email = ["domaindrivendev@gmail.com"]
|
||||||
s.homepage = "https://github.com/domaindrivendev/rswag"
|
s.homepage = "https://github.com/jdanielian/rswag"
|
||||||
s.summary = "A Rails Engine that exposes Swagger files as JSON endpoints"
|
s.summary = "A Rails Engine that exposes Swagger files as JSON endpoints"
|
||||||
s.description = "Open up your API to the phenomenal Swagger ecosystem by exposing Swagger files, that describe your service, as JSON endpoints"
|
s.description = "Open up your API to the phenomenal Swagger ecosystem by exposing Swagger files, that describe your service, as JSON endpoints"
|
||||||
s.license = "MIT"
|
s.license = "MIT"
|
||||||
@ -1,6 +1,7 @@
|
|||||||
require 'generator_spec'
|
require 'generator_spec'
|
||||||
require 'generators/rswag/api/install/install_generator'
|
require 'generators/rswag/api/install/install_generator'
|
||||||
|
|
||||||
|
|
||||||
module Rswag
|
module Rswag
|
||||||
module Api
|
module Api
|
||||||
|
|
||||||
@ -17,7 +18,7 @@ module Rswag
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'installs the Rails initializer' do
|
it 'installs the Rails initializer' do
|
||||||
assert_file('config/initializers/rswag-api.rb')
|
assert_file('config/initializers/rswag_api.rb')
|
||||||
end
|
end
|
||||||
|
|
||||||
# Don't know how to test this
|
# Don't know how to test this
|
||||||
@ -25,3 +26,4 @@ module Rswag
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"swagger": "2.0",
|
"openapi": "3.0.0",
|
||||||
"info": {
|
"info": {
|
||||||
"title": "API V1",
|
"title": "API V1",
|
||||||
"version": "v1"
|
"version": "v1"
|
||||||
@ -1,7 +1,7 @@
|
|||||||
require 'rswag/api/middleware'
|
require 'open_api/rswag/api/middleware'
|
||||||
require 'rswag/api/configuration'
|
require 'open_api/rswag/api/configuration'
|
||||||
|
|
||||||
module Rswag
|
module OpenApi::Rswag
|
||||||
module Api
|
module Api
|
||||||
|
|
||||||
describe Middleware do
|
describe Middleware do
|
||||||
@ -61,7 +61,7 @@ module Rswag
|
|||||||
it 'locates files at the provided swagger_root' do
|
it 'locates files at the provided swagger_root' do
|
||||||
expect(response.length).to eql(3)
|
expect(response.length).to eql(3)
|
||||||
expect(response[1]).to include( 'Content-Type' => 'application/json')
|
expect(response[1]).to include( 'Content-Type' => 'application/json')
|
||||||
expect(response[2].join).to include('"swagger":"2.0"')
|
expect(response[2].join).to include('"openapi":"3.0.0"')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
60
rswag-specs/Guardfile
Normal file
60
rswag-specs/Guardfile
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# A sample Guardfile
|
||||||
|
# More info at https://github.com/guard/guard#readme
|
||||||
|
|
||||||
|
## Uncomment and set this to only include directories you want to watch
|
||||||
|
# directories %w(app lib config test spec features) \
|
||||||
|
# .select{|d| Dir.exist?(d) ? d : UI.warning("Directory #{d} does not exist")}
|
||||||
|
|
||||||
|
## Note: if you are using the `directories` clause above and you are not
|
||||||
|
## watching the project directory ('.'), then you will want to move
|
||||||
|
## the Guardfile to a watched dir and symlink it back, e.g.
|
||||||
|
#
|
||||||
|
# $ mkdir config
|
||||||
|
# $ mv Guardfile config/
|
||||||
|
# $ ln -s config/Guardfile .
|
||||||
|
#
|
||||||
|
# and, you'll have to watch "config/Guardfile" instead of "Guardfile"
|
||||||
|
|
||||||
|
# Note: The cmd option is now required due to the increasing number of ways
|
||||||
|
# rspec may be run, below are examples of the most common uses.
|
||||||
|
# * bundler: 'bundle exec rspec'
|
||||||
|
# * bundler binstubs: 'bin/rspec'
|
||||||
|
# * spring: 'bin/rspec' (This will use spring if running and you have
|
||||||
|
# installed the spring binstubs per the docs)
|
||||||
|
# * zeus: 'zeus rspec' (requires the server to be started separately)
|
||||||
|
# * 'just' rspec: 'rspec'
|
||||||
|
|
||||||
|
guard :rspec, cmd: 'bundle exec rspec' do
|
||||||
|
require 'guard/rspec/dsl'
|
||||||
|
dsl = Guard::RSpec::Dsl.new(self)
|
||||||
|
|
||||||
|
# Feel free to open issues for suggestions and improvements
|
||||||
|
|
||||||
|
# RSpec files
|
||||||
|
rspec = dsl.rspec
|
||||||
|
watch(rspec.spec_helper) { rspec.spec_dir }
|
||||||
|
watch(rspec.spec_support) { rspec.spec_dir }
|
||||||
|
watch(rspec.spec_files)
|
||||||
|
|
||||||
|
# Ruby files
|
||||||
|
ruby = dsl.ruby
|
||||||
|
dsl.watch_spec_files_for(ruby.lib_files)
|
||||||
|
|
||||||
|
# Rails files
|
||||||
|
rails = dsl.rails(view_extensions: %w[erb haml slim])
|
||||||
|
dsl.watch_spec_files_for(rails.app_files)
|
||||||
|
dsl.watch_spec_files_for(rails.views)
|
||||||
|
|
||||||
|
watch(rails.controllers) do |m|
|
||||||
|
[
|
||||||
|
rspec.spec.call("routing/#{m[1]}_routing"),
|
||||||
|
rspec.spec.call("controllers/#{m[1]}_controller"),
|
||||||
|
rspec.spec.call("acceptance/#{m[1]}")
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
# Rails config changes
|
||||||
|
watch(rails.spec_helper) { rspec.spec_dir }
|
||||||
|
end
|
||||||
29
rswag-specs/lib/open_api/rswag/specs.rb
Normal file
29
rswag-specs/lib/open_api/rswag/specs.rb
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
require 'rspec/core'
|
||||||
|
require 'open_api/rswag/specs/example_group_helpers'
|
||||||
|
require 'open_api/rswag/specs/example_helpers'
|
||||||
|
require 'open_api/rswag/specs/configuration'
|
||||||
|
require 'open_api/rswag/specs/railtie' if defined?(Rails::Railtie)
|
||||||
|
|
||||||
|
module OpenApi
|
||||||
|
module Rswag
|
||||||
|
module Specs
|
||||||
|
|
||||||
|
# Extend RSpec with a swagger-based DSL
|
||||||
|
::RSpec.configure do |c|
|
||||||
|
c.add_setting :swagger_root
|
||||||
|
c.add_setting :swagger_docs
|
||||||
|
c.add_setting :swagger_dry_run
|
||||||
|
c.extend ExampleGroupHelpers, type: :request
|
||||||
|
c.include ExampleHelpers, type: :request
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.config
|
||||||
|
@config ||= Configuration.new(RSpec.configuration)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Support Rails 3+ and RSpec 2+ (sigh!)
|
||||||
|
RAILS_VERSION = Rails::VERSION::MAJOR
|
||||||
|
RSPEC_VERSION = RSpec::Core::Version::STRING.split('.').first.to_i
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
48
rswag-specs/lib/open_api/rswag/specs/configuration.rb
Normal file
48
rswag-specs/lib/open_api/rswag/specs/configuration.rb
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module OpenApi
|
||||||
|
module Rswag
|
||||||
|
module Specs
|
||||||
|
class Configuration
|
||||||
|
def initialize(rspec_config)
|
||||||
|
@rspec_config = rspec_config
|
||||||
|
end
|
||||||
|
|
||||||
|
def swagger_root
|
||||||
|
@swagger_root ||= begin
|
||||||
|
if @rspec_config.swagger_root.nil?
|
||||||
|
raise ConfigurationError, 'No swagger_root provided. See swagger_helper.rb'
|
||||||
|
end
|
||||||
|
|
||||||
|
@rspec_config.swagger_root
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def swagger_docs
|
||||||
|
@swagger_docs ||= begin
|
||||||
|
if @rspec_config.swagger_docs.nil? || @rspec_config.swagger_docs.empty?
|
||||||
|
raise ConfigurationError, 'No swagger_docs defined. See swagger_helper.rb'
|
||||||
|
end
|
||||||
|
|
||||||
|
@rspec_config.swagger_docs
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def swagger_dry_run
|
||||||
|
@swagger_dry_run ||= begin
|
||||||
|
@rspec_config.swagger_dry_run.nil? || @rspec_config.swagger_dry_run
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_swagger_doc(name)
|
||||||
|
return swagger_docs.values.first if name.nil?
|
||||||
|
raise ConfigurationError, "Unknown swagger_doc '#{name}'" unless swagger_docs[name]
|
||||||
|
|
||||||
|
swagger_docs[name]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class ConfigurationError < StandardError; end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
266
rswag-specs/lib/open_api/rswag/specs/example_group_helpers.rb
Normal file
266
rswag-specs/lib/open_api/rswag/specs/example_group_helpers.rb
Normal file
@ -0,0 +1,266 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
require 'hashie'
|
||||||
|
|
||||||
|
module OpenApi
|
||||||
|
module Rswag
|
||||||
|
module Specs
|
||||||
|
module ExampleGroupHelpers
|
||||||
|
def path(template, metadata = {}, &block)
|
||||||
|
metadata[:path_item] = { template: template }
|
||||||
|
describe(template, metadata, &block)
|
||||||
|
end
|
||||||
|
|
||||||
|
%i[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
|
||||||
|
|
||||||
|
%i[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
|
||||||
|
%i[tags consumes produces schemes].each do |attr_name|
|
||||||
|
define_method(attr_name) do |*value|
|
||||||
|
metadata[:operation][attr_name] = value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# NICE TO HAVE
|
||||||
|
# TODO: update generator templates to include 3.0 syntax
|
||||||
|
# TODO: setup travis CI?
|
||||||
|
|
||||||
|
# MUST HAVES
|
||||||
|
# TODO: *** look at handling different ways schemas can be defined in 3.0 for requestBody and response
|
||||||
|
# can we handle all of them?
|
||||||
|
# Then can look at handling different request_body things like $ref, etc
|
||||||
|
# TODO: look at adding request_body method to handle diffs in Open API 2.0 to 3.0
|
||||||
|
# TODO: look at adding examples in content request_body
|
||||||
|
# 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 request_body(attributes)
|
||||||
|
# can make this generic, and accept any incoming hash (like parameter method)
|
||||||
|
attributes.compact!
|
||||||
|
|
||||||
|
if metadata[:operation][:requestBody].blank?
|
||||||
|
metadata[:operation][:requestBody] = attributes
|
||||||
|
elsif metadata[:operation][:requestBody] && metadata[:operation][:requestBody][:content]
|
||||||
|
# merge in
|
||||||
|
content_hash = metadata[:operation][:requestBody][:content]
|
||||||
|
incoming_content_hash = attributes[:content]
|
||||||
|
content_hash.merge!(incoming_content_hash) if incoming_content_hash
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def request_body_json(schema:, required: true, description: nil, examples: nil)
|
||||||
|
passed_examples = Array(examples)
|
||||||
|
content_hash = { 'application/json' => { schema: schema, examples: examples }.compact! || {} }
|
||||||
|
request_body(description: description, required: required, content: content_hash)
|
||||||
|
if passed_examples.any?
|
||||||
|
# the request_factory is going to have to resolve the different ways that the example can be given
|
||||||
|
# it can contain a 'value' key which is a direct hash (easiest)
|
||||||
|
# it can contain a 'external_value' key which makes an external call to load the json
|
||||||
|
# it can contain a '$ref' key. Which points to #/components/examples/blog
|
||||||
|
passed_examples.each do |passed_example|
|
||||||
|
if passed_example.is_a?(Symbol)
|
||||||
|
example_key_name = passed_example
|
||||||
|
# TODO: write more tests around this adding to the parameter
|
||||||
|
# if symbol try and use save_request_example
|
||||||
|
param_attributes = { name: example_key_name, in: :body, required: required, param_value: example_key_name, schema: schema }
|
||||||
|
parameter(param_attributes)
|
||||||
|
elsif passed_example.is_a?(Hash) && passed_example[:externalValue]
|
||||||
|
param_attributes = { name: passed_example, in: :body, required: required, param_value: passed_example[:externalValue], schema: schema }
|
||||||
|
parameter(param_attributes)
|
||||||
|
elsif passed_example.is_a?(Hash) && passed_example['$ref']
|
||||||
|
param_attributes = { name: passed_example, in: :body, required: required, param_value: passed_example['$ref'], schema: schema }
|
||||||
|
parameter(param_attributes)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def request_body_text_plain(required: false, description: nil, examples: nil)
|
||||||
|
content_hash = { 'test/plain' => { schema: {type: :string}, examples: examples }.compact! || {} }
|
||||||
|
request_body(description: description, required: required, content: content_hash)
|
||||||
|
end
|
||||||
|
|
||||||
|
# TODO: add examples to this like we can for json, might be large lift as many assumptions are made on content-type
|
||||||
|
def request_body_xml(schema:,required: false, description: nil, examples: nil)
|
||||||
|
passed_examples = Array(examples)
|
||||||
|
content_hash = { 'application/xml' => { schema: schema, examples: examples }.compact! || {} }
|
||||||
|
request_body(description: description, required: required, content: content_hash)
|
||||||
|
end
|
||||||
|
|
||||||
|
def request_body_multipart(schema:, description: nil)
|
||||||
|
content_hash = { 'multipart/form-data' => { schema: schema }}
|
||||||
|
request_body(description: description, content: content_hash)
|
||||||
|
|
||||||
|
schema.extend(Hashie::Extensions::DeepLocate)
|
||||||
|
file_properties = schema.deep_locate -> (_k, v, _obj) { v == :binary }
|
||||||
|
|
||||||
|
hash_locator = []
|
||||||
|
|
||||||
|
file_properties.each do |match|
|
||||||
|
hash_match = schema.deep_locate -> (_k, v, _obj) { v == match }
|
||||||
|
hash_locator.concat(hash_match) unless hash_match.empty?
|
||||||
|
end
|
||||||
|
|
||||||
|
property_hashes = hash_locator.flat_map do |locator|
|
||||||
|
locator.select { |_k,v| file_properties.include?(v) }
|
||||||
|
end
|
||||||
|
|
||||||
|
property_hashes.each do |property_hash|
|
||||||
|
file_name = property_hash.keys.first
|
||||||
|
parameter name: file_name, in: :formData, type: :file, required: true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def parameter(attributes)
|
||||||
|
if attributes[:in] && attributes[:in].to_sym == :path
|
||||||
|
attributes[:required] = true
|
||||||
|
end
|
||||||
|
|
||||||
|
if attributes[:type] && attributes[:schema].nil?
|
||||||
|
attributes[:schema] = {type: attributes[:type]}
|
||||||
|
end
|
||||||
|
|
||||||
|
if metadata.key?(:operation)
|
||||||
|
metadata[:operation][:parameters] ||= []
|
||||||
|
metadata[:operation][:parameters] << attributes
|
||||||
|
else
|
||||||
|
metadata[:path_item][:parameters] ||= []
|
||||||
|
metadata[:path_item][:parameters] << attributes
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def response(code, description, metadata = {}, &block)
|
||||||
|
metadata[:response] = { code: code, description: description }
|
||||||
|
context(description, metadata, &block)
|
||||||
|
end
|
||||||
|
|
||||||
|
def schema(value, content_type: 'application/json')
|
||||||
|
content_hash = {content_type => {schema: value}}
|
||||||
|
metadata[:response][:content] = content_hash
|
||||||
|
end
|
||||||
|
|
||||||
|
def header(name, attributes)
|
||||||
|
metadata[:response][:headers] ||= {}
|
||||||
|
|
||||||
|
if attributes[:type] && attributes[:schema].nil?
|
||||||
|
attributes[:schema] = {type: attributes[:type]}
|
||||||
|
attributes.delete(:type)
|
||||||
|
end
|
||||||
|
|
||||||
|
metadata[:response][:headers][name] = attributes
|
||||||
|
end
|
||||||
|
|
||||||
|
# NOTE: Similar to 'description', 'examples' need to handle the case when
|
||||||
|
# being invoked with no params to avoid overriding 'examples' method of
|
||||||
|
# rspec-core ExampleGroup
|
||||||
|
def examples(example = nil)
|
||||||
|
return super() if example.nil?
|
||||||
|
|
||||||
|
metadata[:response][:examples] = example
|
||||||
|
end
|
||||||
|
|
||||||
|
# checks the examples in the parameters should be able to add $ref and externalValue examples.
|
||||||
|
# This syntax would look something like this in the integration _spec.rb file
|
||||||
|
#
|
||||||
|
# request_body_json schema: { '$ref' => '#/components/schemas/blog' },
|
||||||
|
# examples: [:blog, {name: :external_blog,
|
||||||
|
# externalValue: 'http://api.sample.org/myjson_example'},
|
||||||
|
# {name: :another_example,
|
||||||
|
# '$ref' => '#/components/examples/flexible_blog_example'}]
|
||||||
|
# The first value :blog, points to a let param of the same name, and is used to make the request in the
|
||||||
|
# integration test (it is used to build the request payload)
|
||||||
|
#
|
||||||
|
# The second item in the array shows how to add an externalValue for the examples in the requestBody section
|
||||||
|
# The third item shows how to add a $ref item that points to the components/examples section of the swagger spec.
|
||||||
|
#
|
||||||
|
# NOTE: that the externalValue will produce valid example syntax in the swagger output, but swagger-ui
|
||||||
|
# will not show it yet
|
||||||
|
def merge_other_examples!(example_metadata)
|
||||||
|
# example.metadata[:operation][:requestBody][:content]['application/json'][:examples]
|
||||||
|
content_node = example_metadata[:operation][:requestBody][:content]['application/json']
|
||||||
|
return unless content_node
|
||||||
|
|
||||||
|
external_example = example_metadata[:operation]&.dig(:parameters)&.detect { |p| p[:in] == :body && p[:name].is_a?(Hash) && p[:name][:externalValue] } || {}
|
||||||
|
ref_example = example_metadata[:operation]&.dig(:parameters)&.detect { |p| p[:in] == :body && p[:name].is_a?(Hash) && p[:name]['$ref'] } || {}
|
||||||
|
examples_node = content_node[:examples] ||= {}
|
||||||
|
|
||||||
|
nodes_to_add = []
|
||||||
|
nodes_to_add << external_example unless external_example.empty?
|
||||||
|
nodes_to_add << ref_example unless ref_example.empty?
|
||||||
|
|
||||||
|
nodes_to_add.each do |node|
|
||||||
|
json_request_examples = examples_node ||= {}
|
||||||
|
other_name = node[:name][:name]
|
||||||
|
other_key = node[:name][:externalValue] ? :externalValue : '$ref'
|
||||||
|
if other_name
|
||||||
|
json_request_examples.merge!(other_name => {other_key => node[:param_value]})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def run_test!(&block)
|
||||||
|
# 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(metadata)
|
||||||
|
block.call(response) if block_given?
|
||||||
|
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, &block)
|
||||||
|
example.instance_exec(response, &block) if block_given?
|
||||||
|
end
|
||||||
|
|
||||||
|
after do |example|
|
||||||
|
body_parameter = example.metadata[:operation]&.dig(:parameters)&.detect { |p| p[:in] == :body && p[:required] }
|
||||||
|
|
||||||
|
if body_parameter && respond_to?(body_parameter[:name]) && example.metadata[:operation][:requestBody][:content]['application/json']
|
||||||
|
# save response examples by default
|
||||||
|
example.metadata[:response][:examples] = { 'application/json' => JSON.parse(response.body, symbolize_names: true) } unless response.body.to_s.empty?
|
||||||
|
|
||||||
|
# save request examples using the let(:param_name) { REQUEST_BODY_HASH } syntax in the test
|
||||||
|
if response.code.to_s =~ /^2\d{2}$/
|
||||||
|
example.metadata[:operation][:requestBody][:content]['application/json'] = { examples: {} } unless example.metadata[:operation][:requestBody][:content]['application/json'][:examples]
|
||||||
|
json_request_examples = example.metadata[:operation][:requestBody][:content]['application/json'][:examples]
|
||||||
|
json_request_examples[body_parameter[:name]] = { value: send(body_parameter[:name]) }
|
||||||
|
|
||||||
|
example.metadata[:operation][:requestBody][:content]['application/json'][:examples] = json_request_examples
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
self.class.merge_other_examples!(example.metadata) if example.metadata[:operation][:requestBody]
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
36
rswag-specs/lib/open_api/rswag/specs/example_helpers.rb
Normal file
36
rswag-specs/lib/open_api/rswag/specs/example_helpers.rb
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'open_api/rswag/specs/request_factory'
|
||||||
|
require 'open_api/rswag/specs/response_validator'
|
||||||
|
|
||||||
|
module OpenApi
|
||||||
|
module Rswag
|
||||||
|
module Specs
|
||||||
|
module ExampleHelpers
|
||||||
|
def submit_request(metadata)
|
||||||
|
request = RequestFactory.new.build_request(metadata, self)
|
||||||
|
|
||||||
|
if RAILS_VERSION < 5
|
||||||
|
send(
|
||||||
|
request[:verb],
|
||||||
|
request[:path],
|
||||||
|
request[:payload],
|
||||||
|
request[:headers]
|
||||||
|
)
|
||||||
|
else
|
||||||
|
send(
|
||||||
|
request[:verb],
|
||||||
|
request[:path],
|
||||||
|
params: request[:payload],
|
||||||
|
headers: request[:headers]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def assert_response_matches_metadata(metadata)
|
||||||
|
ResponseValidator.new.validate!(metadata, response)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
28
rswag-specs/lib/open_api/rswag/specs/extended_schema.rb
Normal file
28
rswag-specs/lib/open_api/rswag/specs/extended_schema.rb
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'json-schema'
|
||||||
|
|
||||||
|
module OpenApi
|
||||||
|
module Rswag
|
||||||
|
module Specs
|
||||||
|
class ExtendedSchema < JSON::Schema::Draft4
|
||||||
|
def initialize
|
||||||
|
super
|
||||||
|
@attributes['type'] = ExtendedTypeAttribute
|
||||||
|
@uri = URI.parse('http://tempuri.org/rswag/specs/extended_schema')
|
||||||
|
@names = ['http://tempuri.org/rswag/specs/extended_schema']
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class ExtendedTypeAttribute < JSON::Schema::TypeV4Attribute
|
||||||
|
def self.validate(current_schema, data, fragments, processor, validator, options = {})
|
||||||
|
return if data.nil? && (current_schema.schema['nullable'] == true || current_schema.schema['x-nullable'] == true)
|
||||||
|
|
||||||
|
super
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
JSON::Validator.register_validator(ExtendedSchema.new)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
13
rswag-specs/lib/open_api/rswag/specs/railtie.rb
Normal file
13
rswag-specs/lib/open_api/rswag/specs/railtie.rb
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module OpenApi
|
||||||
|
module Rswag
|
||||||
|
module Specs
|
||||||
|
class Railtie < ::Rails::Railtie
|
||||||
|
rake_tasks do
|
||||||
|
load File.expand_path('../../../tasks/rswag-specs_tasks.rake', __dir__)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
166
rswag-specs/lib/open_api/rswag/specs/request_factory.rb
Normal file
166
rswag-specs/lib/open_api/rswag/specs/request_factory.rb
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'active_support/core_ext/hash/slice'
|
||||||
|
require 'active_support/core_ext/hash/conversions'
|
||||||
|
require 'json'
|
||||||
|
|
||||||
|
module OpenApi
|
||||||
|
module Rswag
|
||||||
|
module Specs
|
||||||
|
class RequestFactory
|
||||||
|
def initialize(config = ::OpenApi::Rswag::Specs.config)
|
||||||
|
@config = config
|
||||||
|
end
|
||||||
|
|
||||||
|
def build_request(metadata, example)
|
||||||
|
swagger_doc = @config.get_swagger_doc(metadata[:swagger_doc])
|
||||||
|
parameters = expand_parameters(metadata, swagger_doc, example)
|
||||||
|
|
||||||
|
{}.tap do |request|
|
||||||
|
add_verb(request, metadata)
|
||||||
|
add_path(request, metadata, swagger_doc, parameters, example)
|
||||||
|
add_headers(request, metadata, swagger_doc, parameters, example)
|
||||||
|
add_payload(request, parameters, example)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def expand_parameters(metadata, swagger_doc, example)
|
||||||
|
operation_params = metadata[:operation][:parameters] || []
|
||||||
|
path_item_params = metadata[:path_item][:parameters] || []
|
||||||
|
security_params = derive_security_params(metadata, swagger_doc)
|
||||||
|
|
||||||
|
# NOTE: Use of + instead of concat to avoid mutation of the metadata object
|
||||||
|
(operation_params + path_item_params + security_params)
|
||||||
|
.map { |p| p['$ref'] ? resolve_parameter(p['$ref'], swagger_doc) : p }
|
||||||
|
.uniq { |p| p[:name] }
|
||||||
|
.reject { |p| p[:required] == false && !example.respond_to?(p[:name]) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def derive_security_params(metadata, swagger_doc)
|
||||||
|
requirements = metadata[:operation][:security] || swagger_doc[:security] || []
|
||||||
|
scheme_names = requirements.flat_map(&:keys)
|
||||||
|
components = swagger_doc[:components] || {}
|
||||||
|
schemes = (components[:securitySchemes] || {}).slice(*scheme_names).values
|
||||||
|
|
||||||
|
schemes.map do |scheme|
|
||||||
|
param = scheme[:type] == :apiKey ? scheme.slice(:name, :in) : { name: 'Authorization', in: :header }
|
||||||
|
param.merge(type: :string, required: requirements.one?)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def resolve_parameter(ref, swagger_doc)
|
||||||
|
key = ref.sub('#/parameters/', '').to_sym
|
||||||
|
definitions = swagger_doc[:parameters]
|
||||||
|
raise "Referenced parameter '#{ref}' must be defined" unless definitions && definitions[key]
|
||||||
|
|
||||||
|
definitions[key]
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_verb(request, metadata)
|
||||||
|
request[:verb] = metadata[:operation][:verb]
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_path(request, metadata, swagger_doc, parameters, example)
|
||||||
|
template = (swagger_doc[:basePath] || '') + metadata[:path_item][:template]
|
||||||
|
|
||||||
|
request[:path] = template.tap do |template|
|
||||||
|
parameters.select { |p| p[:in] == :path }.each do |p|
|
||||||
|
template.gsub!("{#{p[:name]}}", example.send(p[:name]).to_s)
|
||||||
|
end
|
||||||
|
|
||||||
|
parameters.select { |p| p[:in] == :query }.each_with_index do |p, i|
|
||||||
|
template.concat(i == 0 ? '?' : '&')
|
||||||
|
template.concat(build_query_string_part(p, example.send(p[:name])))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def build_query_string_part(param, value)
|
||||||
|
name = param[:name]
|
||||||
|
return "#{name}=#{value}" unless param[:type].to_sym == :array
|
||||||
|
|
||||||
|
case param[:collectionFormat]
|
||||||
|
when :ssv
|
||||||
|
"#{name}=#{value.join(' ')}"
|
||||||
|
when :tsv
|
||||||
|
"#{name}=#{value.join('\t')}"
|
||||||
|
when :pipes
|
||||||
|
"#{name}=#{value.join('|')}"
|
||||||
|
when :multi
|
||||||
|
value.map { |v| "#{name}=#{v}" }.join('&')
|
||||||
|
else
|
||||||
|
"#{name}=#{value.join(',')}" # csv is default
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_headers(request, metadata, swagger_doc, parameters, example)
|
||||||
|
tuples = parameters
|
||||||
|
.select { |p| p[:in] == :header }
|
||||||
|
.map { |p| [p[:name], example.send(p[:name]).to_s] }
|
||||||
|
|
||||||
|
# Accept header
|
||||||
|
produces = metadata[:operation][:produces] || swagger_doc[:produces]
|
||||||
|
if produces
|
||||||
|
accept = example.respond_to?(:Accept) ? example.send(:Accept) : produces.first
|
||||||
|
tuples << ['Accept', accept]
|
||||||
|
end
|
||||||
|
|
||||||
|
# Content-Type header
|
||||||
|
consumes = metadata[:operation][:consumes] || swagger_doc[:consumes]
|
||||||
|
if consumes
|
||||||
|
content_type = example.respond_to?(:'Content-Type') ? example.send(:'Content-Type') : consumes.first
|
||||||
|
tuples << ['Content-Type', content_type]
|
||||||
|
end
|
||||||
|
|
||||||
|
# Rails test infrastructure requires rackified headers
|
||||||
|
rackified_tuples = tuples.map do |pair|
|
||||||
|
[
|
||||||
|
case pair[0]
|
||||||
|
when 'Accept' then 'HTTP_ACCEPT'
|
||||||
|
when 'Content-Type' then 'CONTENT_TYPE'
|
||||||
|
when 'Authorization' then 'HTTP_AUTHORIZATION'
|
||||||
|
else pair[0]
|
||||||
|
end,
|
||||||
|
pair[1]
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
request[:headers] = Hash[rackified_tuples]
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_payload(request, parameters, example)
|
||||||
|
content_type = request[:headers]['CONTENT_TYPE']
|
||||||
|
return if content_type.nil?
|
||||||
|
|
||||||
|
if ['application/x-www-form-urlencoded', 'multipart/form-data'].include?(content_type)
|
||||||
|
request[:payload] = build_form_payload(parameters, example)
|
||||||
|
else
|
||||||
|
request[:payload] = build_json_payload(parameters, example)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def build_form_payload(parameters, example)
|
||||||
|
# See http://seejohncode.com/2012/04/29/quick-tip-testing-multipart-uploads-with-rspec/
|
||||||
|
# Rather that serializing with the appropriate encoding (e.g. multipart/form-data),
|
||||||
|
# Rails test infrastructure allows us to send the values directly as a hash
|
||||||
|
# PROS: simple to implement, CONS: serialization/deserialization is bypassed in test
|
||||||
|
tuples = parameters
|
||||||
|
.select { |p| p[:in] == :formData }
|
||||||
|
.map { |p| [p[:name], example.send(p[:name])] }
|
||||||
|
Hash[tuples]
|
||||||
|
end
|
||||||
|
|
||||||
|
def build_json_payload(parameters, example)
|
||||||
|
body_param = parameters.select { |p| p[:in] == :body && p[:name].is_a?(Symbol) }.first
|
||||||
|
return nil unless body_param
|
||||||
|
|
||||||
|
source_body_param = example.send(body_param[:name]) if body_param[:name] && example.respond_to?(body_param[:name])
|
||||||
|
source_body_param ||= body_param[:param_value]
|
||||||
|
source_body_param ? source_body_param.to_json : nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
70
rswag-specs/lib/open_api/rswag/specs/response_validator.rb
Normal file
70
rswag-specs/lib/open_api/rswag/specs/response_validator.rb
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'active_support/core_ext/hash/slice'
|
||||||
|
require 'json-schema'
|
||||||
|
require 'json'
|
||||||
|
require 'open_api/rswag/specs/extended_schema'
|
||||||
|
|
||||||
|
module OpenApi
|
||||||
|
module Rswag
|
||||||
|
module Specs
|
||||||
|
class ResponseValidator
|
||||||
|
def initialize(config = ::OpenApi::Rswag::Specs.config)
|
||||||
|
@config = config
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate!(metadata, response)
|
||||||
|
swagger_doc = @config.get_swagger_doc(metadata[:swagger_doc])
|
||||||
|
|
||||||
|
validate_code!(metadata, response)
|
||||||
|
validate_headers!(metadata, response.headers)
|
||||||
|
validate_body!(metadata, swagger_doc, response.body)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def validate_code!(metadata, response)
|
||||||
|
expected = metadata[:response][:code].to_s
|
||||||
|
if response.code != expected
|
||||||
|
raise UnexpectedResponse,
|
||||||
|
"Expected response code '#{response.code}' to match '#{expected}'\n" \
|
||||||
|
"Response body: #{response.body}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate_headers!(metadata, headers)
|
||||||
|
expected = (metadata[:response][:headers] || {}).keys
|
||||||
|
expected.each do |name|
|
||||||
|
raise UnexpectedResponse, "Expected response header #{name} to be present" if headers[name.to_s].nil?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate_body!(metadata, swagger_doc, body)
|
||||||
|
test_schemas = extract_schemas(metadata)
|
||||||
|
return if test_schemas.nil? || test_schemas.empty?
|
||||||
|
|
||||||
|
components = swagger_doc[:components] || {}
|
||||||
|
components_schemas = { components: { schemas: components[:schemas] } }
|
||||||
|
|
||||||
|
validation_schema = test_schemas[:schema] # response_schema
|
||||||
|
.merge('$schema' => 'http://tempuri.org/rswag/specs/extended_schema')
|
||||||
|
.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
|
||||||
|
|
||||||
|
def extract_schemas(metadata)
|
||||||
|
metadata[:operation] = {produces: []} if metadata[:operation].nil?
|
||||||
|
produces = Array(metadata[:operation][:produces])
|
||||||
|
|
||||||
|
producer_content = produces.first || 'application/json'
|
||||||
|
response_content = metadata[:response][:content] || {producer_content => {}}
|
||||||
|
response_content[producer_content]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class UnexpectedResponse < StandardError; end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
102
rswag-specs/lib/open_api/rswag/specs/swagger_formatter.rb
Normal file
102
rswag-specs/lib/open_api/rswag/specs/swagger_formatter.rb
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'active_support/core_ext/hash/deep_merge'
|
||||||
|
require 'swagger_helper'
|
||||||
|
|
||||||
|
module OpenApi
|
||||||
|
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, config = ::OpenApi::Rswag::Specs.config)
|
||||||
|
@output = output
|
||||||
|
@config = config
|
||||||
|
|
||||||
|
@output.puts 'Generating Swagger docs ...'
|
||||||
|
end
|
||||||
|
|
||||||
|
def example_group_finished(notification)
|
||||||
|
# NOTE: rspec 2.x support
|
||||||
|
metadata = if RSPEC_VERSION > 2
|
||||||
|
notification.group.metadata
|
||||||
|
else
|
||||||
|
notification.metadata
|
||||||
|
end
|
||||||
|
|
||||||
|
return unless metadata.key?(:response)
|
||||||
|
|
||||||
|
swagger_doc = @config.get_swagger_doc(metadata[:swagger_doc])
|
||||||
|
swagger_doc.deep_merge!(metadata_to_swagger(metadata))
|
||||||
|
end
|
||||||
|
|
||||||
|
def stop(_notification = nil)
|
||||||
|
@config.swagger_docs.each do |url_path, doc|
|
||||||
|
# remove 2.0 parameters
|
||||||
|
doc[:paths]&.each_pair do |_k, v|
|
||||||
|
v.each_pair do |_verb, value|
|
||||||
|
is_hash = value.is_a?(Hash)
|
||||||
|
if is_hash && value.dig(:parameters)
|
||||||
|
schema_param = value&.dig(:parameters)&.find{|p| p[:in] == :body && p[:schema] }
|
||||||
|
if value && schema_param && value&.dig(:requestBody, :content, 'application/json')
|
||||||
|
value[:requestBody][:content]['application/json'].merge!(schema: schema_param[:schema])
|
||||||
|
end
|
||||||
|
|
||||||
|
value[:parameters].reject! { |p| p[:in] == :body || p[:in] == :formData }
|
||||||
|
value[:parameters].each { |p| p.delete(:type) }
|
||||||
|
value[:headers].each { |p| p.delete(:type)} if value[:headers]
|
||||||
|
end
|
||||||
|
|
||||||
|
value.delete(:consumes) if is_hash && value.dig(:consumes)
|
||||||
|
value.delete(:produces) if is_hash && value.dig(:produces)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
file_path = File.join(@config.swagger_root, url_path)
|
||||||
|
dirname = File.dirname(file_path)
|
||||||
|
FileUtils.mkdir_p dirname unless File.exist?(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 metadata_to_swagger(metadata)
|
||||||
|
response_code = metadata[:response][:code]
|
||||||
|
response = metadata[:response].reject { |k, _v| k == :code }
|
||||||
|
|
||||||
|
# need to merge in to response
|
||||||
|
if response[:examples]&.dig('application/json')
|
||||||
|
example = response[:examples].dig('application/json').dup
|
||||||
|
schema = response.dig(:content, 'application/json', :schema)
|
||||||
|
new_hash = {example: example}
|
||||||
|
new_hash[:schema] = schema if schema
|
||||||
|
response.merge!(content: { 'application/json' => new_hash })
|
||||||
|
response.delete(:examples)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
verb = metadata[:operation][:verb]
|
||||||
|
operation = metadata[:operation]
|
||||||
|
.reject { |k, _v| k == :verb }
|
||||||
|
.merge(responses: { response_code => response })
|
||||||
|
|
||||||
|
path_template = metadata[:path_item][:template]
|
||||||
|
path_item = metadata[:path_item]
|
||||||
|
.reject { |k, _v| k == :template }
|
||||||
|
.merge(verb => operation)
|
||||||
|
|
||||||
|
{ paths: { path_template => path_item } }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -1,27 +0,0 @@
|
|||||||
require 'rspec/core'
|
|
||||||
require 'rswag/specs/example_group_helpers'
|
|
||||||
require 'rswag/specs/example_helpers'
|
|
||||||
require 'rswag/specs/configuration'
|
|
||||||
require 'rswag/specs/railtie' if defined?(Rails::Railtie)
|
|
||||||
|
|
||||||
module Rswag
|
|
||||||
module Specs
|
|
||||||
|
|
||||||
# Extend RSpec with a swagger-based DSL
|
|
||||||
::RSpec.configure do |c|
|
|
||||||
c.add_setting :swagger_root
|
|
||||||
c.add_setting :swagger_docs
|
|
||||||
c.add_setting :swagger_dry_run
|
|
||||||
c.extend ExampleGroupHelpers, type: :request
|
|
||||||
c.include ExampleHelpers, type: :request
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.config
|
|
||||||
@config ||= Configuration.new(RSpec.configuration)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Support Rails 3+ and RSpec 2+ (sigh!)
|
|
||||||
RAILS_VERSION = Rails::VERSION::MAJOR
|
|
||||||
RSPEC_VERSION = RSpec::Core::Version::STRING.split('.').first.to_i
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,43 +0,0 @@
|
|||||||
module Rswag
|
|
||||||
module Specs
|
|
||||||
|
|
||||||
class Configuration
|
|
||||||
|
|
||||||
def initialize(rspec_config)
|
|
||||||
@rspec_config = rspec_config
|
|
||||||
end
|
|
||||||
|
|
||||||
def swagger_root
|
|
||||||
@swagger_root ||= begin
|
|
||||||
if @rspec_config.swagger_root.nil?
|
|
||||||
raise ConfigurationError, 'No swagger_root provided. See swagger_helper.rb'
|
|
||||||
end
|
|
||||||
@rspec_config.swagger_root
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def swagger_docs
|
|
||||||
@swagger_docs ||= begin
|
|
||||||
if @rspec_config.swagger_docs.nil? || @rspec_config.swagger_docs.empty?
|
|
||||||
raise ConfigurationError, 'No swagger_docs defined. See swagger_helper.rb'
|
|
||||||
end
|
|
||||||
@rspec_config.swagger_docs
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def swagger_dry_run
|
|
||||||
@swagger_dry_run ||= begin
|
|
||||||
@rspec_config.swagger_dry_run.nil? || @rspec_config.swagger_dry_run
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def get_swagger_doc(name)
|
|
||||||
return swagger_docs.values.first if name.nil?
|
|
||||||
raise ConfigurationError, "Unknown swagger_doc '#{name}'" unless swagger_docs[name]
|
|
||||||
swagger_docs[name]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class ConfigurationError < StandardError; end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,98 +0,0 @@
|
|||||||
module Rswag
|
|
||||||
module Specs
|
|
||||||
module ExampleGroupHelpers
|
|
||||||
|
|
||||||
def path(template, metadata={}, &block)
|
|
||||||
metadata[:path_item] = { template: template }
|
|
||||||
describe(template, 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)
|
|
||||||
if attributes[:in] && attributes[:in].to_sym == :path
|
|
||||||
attributes[:required] = true
|
|
||||||
end
|
|
||||||
|
|
||||||
if metadata.has_key?(:operation)
|
|
||||||
metadata[:operation][:parameters] ||= []
|
|
||||||
metadata[:operation][:parameters] << attributes
|
|
||||||
else
|
|
||||||
metadata[:path_item][:parameters] ||= []
|
|
||||||
metadata[:path_item][:parameters] << attributes
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def response(code, description, metadata={}, &block)
|
|
||||||
metadata[:response] = { code: code, description: description }
|
|
||||||
context(description, metadata, &block)
|
|
||||||
end
|
|
||||||
|
|
||||||
def schema(value)
|
|
||||||
metadata[:response][:schema] = value
|
|
||||||
end
|
|
||||||
|
|
||||||
def header(name, attributes)
|
|
||||||
metadata[:response][:headers] ||= {}
|
|
||||||
metadata[:response][:headers][name] = attributes
|
|
||||||
end
|
|
||||||
|
|
||||||
# NOTE: Similar to 'description', 'examples' need to handle the case when
|
|
||||||
# being invoked with no params to avoid overriding 'examples' method of
|
|
||||||
# rspec-core ExampleGroup
|
|
||||||
def examples(example = nil)
|
|
||||||
return super() if example.nil?
|
|
||||||
metadata[:response][:examples] = example
|
|
||||||
end
|
|
||||||
|
|
||||||
def run_test!(&block)
|
|
||||||
# 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(metadata)
|
|
||||||
block.call(response) if block_given?
|
|
||||||
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, &block)
|
|
||||||
example.instance_exec(response, &block) if block_given?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,35 +0,0 @@
|
|||||||
require 'rswag/specs/request_factory'
|
|
||||||
require 'rswag/specs/response_validator'
|
|
||||||
|
|
||||||
module Rswag
|
|
||||||
module Specs
|
|
||||||
module ExampleHelpers
|
|
||||||
|
|
||||||
def submit_request(metadata)
|
|
||||||
request = RequestFactory.new.build_request(metadata, self)
|
|
||||||
|
|
||||||
if RAILS_VERSION < 5
|
|
||||||
send(
|
|
||||||
request[:verb],
|
|
||||||
request[:path],
|
|
||||||
request[:payload],
|
|
||||||
request[:headers]
|
|
||||||
)
|
|
||||||
else
|
|
||||||
send(
|
|
||||||
request[:verb],
|
|
||||||
request[:path],
|
|
||||||
{
|
|
||||||
params: request[:payload],
|
|
||||||
headers: request[:headers]
|
|
||||||
}
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def assert_response_matches_metadata(metadata)
|
|
||||||
ResponseValidator.new.validate!(metadata, response)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
require 'json-schema'
|
|
||||||
|
|
||||||
module Rswag
|
|
||||||
module Specs
|
|
||||||
class ExtendedSchema < JSON::Schema::Draft4
|
|
||||||
|
|
||||||
def initialize
|
|
||||||
super
|
|
||||||
@attributes['type'] = ExtendedTypeAttribute
|
|
||||||
@uri = URI.parse('http://tempuri.org/rswag/specs/extended_schema')
|
|
||||||
@names = ['http://tempuri.org/rswag/specs/extended_schema']
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
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
|
|
||||||
super
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
JSON::Validator.register_validator(ExtendedSchema.new)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
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
|
|
||||||
@ -1,157 +0,0 @@
|
|||||||
require 'active_support/core_ext/hash/slice'
|
|
||||||
require 'active_support/core_ext/hash/conversions'
|
|
||||||
require 'json'
|
|
||||||
|
|
||||||
module Rswag
|
|
||||||
module Specs
|
|
||||||
class RequestFactory
|
|
||||||
|
|
||||||
def initialize(config = ::Rswag::Specs.config)
|
|
||||||
@config = config
|
|
||||||
end
|
|
||||||
|
|
||||||
def build_request(metadata, example)
|
|
||||||
swagger_doc = @config.get_swagger_doc(metadata[:swagger_doc])
|
|
||||||
parameters = expand_parameters(metadata, swagger_doc, example)
|
|
||||||
|
|
||||||
{}.tap do |request|
|
|
||||||
add_verb(request, metadata)
|
|
||||||
add_path(request, metadata, swagger_doc, parameters, example)
|
|
||||||
add_headers(request, metadata, swagger_doc, parameters, example)
|
|
||||||
add_payload(request, parameters, example)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def expand_parameters(metadata, swagger_doc, example)
|
|
||||||
operation_params = metadata[:operation][:parameters] || []
|
|
||||||
path_item_params = metadata[:path_item][:parameters] || []
|
|
||||||
security_params = derive_security_params(metadata, swagger_doc)
|
|
||||||
|
|
||||||
# NOTE: Use of + instead of concat to avoid mutation of the metadata object
|
|
||||||
(operation_params + path_item_params + security_params)
|
|
||||||
.map { |p| p['$ref'] ? resolve_parameter(p['$ref'], swagger_doc) : p }
|
|
||||||
.uniq { |p| p[:name] }
|
|
||||||
.reject { |p| p[:required] == false && !example.respond_to?(p[:name]) }
|
|
||||||
end
|
|
||||||
|
|
||||||
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.map do |scheme|
|
|
||||||
param = (scheme[:type] == :apiKey) ? scheme.slice(:name, :in) : { name: 'Authorization', in: :header }
|
|
||||||
param.merge(type: :string, required: requirements.one?)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def resolve_parameter(ref, swagger_doc)
|
|
||||||
key = ref.sub('#/parameters/', '').to_sym
|
|
||||||
definitions = swagger_doc[:parameters]
|
|
||||||
raise "Referenced parameter '#{ref}' must be defined" unless definitions && definitions[key]
|
|
||||||
definitions[key]
|
|
||||||
end
|
|
||||||
|
|
||||||
def add_verb(request, metadata)
|
|
||||||
request[:verb] = metadata[:operation][:verb]
|
|
||||||
end
|
|
||||||
|
|
||||||
def add_path(request, metadata, swagger_doc, parameters, example)
|
|
||||||
template = (swagger_doc[:basePath] || '') + metadata[:path_item][:template]
|
|
||||||
|
|
||||||
request[:path] = template.tap do |template|
|
|
||||||
parameters.select { |p| p[:in] == :path }.each do |p|
|
|
||||||
template.gsub!("{#{p[:name]}}", example.send(p[:name]).to_s)
|
|
||||||
end
|
|
||||||
|
|
||||||
parameters.select { |p| p[:in] == :query }.each_with_index do |p, i|
|
|
||||||
template.concat(i == 0 ? '?' : '&')
|
|
||||||
template.concat(build_query_string_part(p, example.send(p[:name])))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def build_query_string_part(param, value)
|
|
||||||
name = param[:name]
|
|
||||||
return "#{name}=#{value.to_s}" unless param[:type].to_sym == :array
|
|
||||||
|
|
||||||
case param[:collectionFormat]
|
|
||||||
when :ssv
|
|
||||||
"#{name}=#{value.join(' ')}"
|
|
||||||
when :tsv
|
|
||||||
"#{name}=#{value.join('\t')}"
|
|
||||||
when :pipes
|
|
||||||
"#{name}=#{value.join('|')}"
|
|
||||||
when :multi
|
|
||||||
value.map { |v| "#{name}=#{v}" }.join('&')
|
|
||||||
else
|
|
||||||
"#{name}=#{value.join(',')}" # csv is default
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def add_headers(request, metadata, swagger_doc, parameters, example)
|
|
||||||
tuples = parameters
|
|
||||||
.select { |p| p[:in] == :header }
|
|
||||||
.map { |p| [ p[:name], example.send(p[:name]).to_s ] }
|
|
||||||
|
|
||||||
# Accept header
|
|
||||||
produces = metadata[:operation][:produces] || swagger_doc[:produces]
|
|
||||||
if produces
|
|
||||||
accept = example.respond_to?(:'Accept') ? example.send(:'Accept') : produces.first
|
|
||||||
tuples << [ 'Accept', accept ]
|
|
||||||
end
|
|
||||||
|
|
||||||
# Content-Type header
|
|
||||||
consumes = metadata[:operation][:consumes] || swagger_doc[:consumes]
|
|
||||||
if consumes
|
|
||||||
content_type = example.respond_to?(:'Content-Type') ? example.send(:'Content-Type') : consumes.first
|
|
||||||
tuples << [ 'Content-Type', content_type ]
|
|
||||||
end
|
|
||||||
|
|
||||||
# Rails test infrastructure requires rackified headers
|
|
||||||
rackified_tuples = tuples.map do |pair|
|
|
||||||
[
|
|
||||||
case pair[0]
|
|
||||||
when 'Accept' then 'HTTP_ACCEPT'
|
|
||||||
when 'Content-Type' then 'CONTENT_TYPE'
|
|
||||||
when 'Authorization' then 'HTTP_AUTHORIZATION'
|
|
||||||
else pair[0]
|
|
||||||
end,
|
|
||||||
pair[1]
|
|
||||||
]
|
|
||||||
end
|
|
||||||
|
|
||||||
request[:headers] = Hash[ rackified_tuples ]
|
|
||||||
end
|
|
||||||
|
|
||||||
def add_payload(request, parameters, example)
|
|
||||||
content_type = request[:headers]['CONTENT_TYPE']
|
|
||||||
return if content_type.nil?
|
|
||||||
|
|
||||||
if [ 'application/x-www-form-urlencoded', 'multipart/form-data' ].include?(content_type)
|
|
||||||
request[:payload] = build_form_payload(parameters, example)
|
|
||||||
else
|
|
||||||
request[:payload] = build_json_payload(parameters, example)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def build_form_payload(parameters, example)
|
|
||||||
# See http://seejohncode.com/2012/04/29/quick-tip-testing-multipart-uploads-with-rspec/
|
|
||||||
# Rather that serializing with the appropriate encoding (e.g. multipart/form-data),
|
|
||||||
# Rails test infrastructure allows us to send the values directly as a hash
|
|
||||||
# PROS: simple to implement, CONS: serialization/deserialization is bypassed in test
|
|
||||||
tuples = parameters
|
|
||||||
.select { |p| p[:in] == :formData }
|
|
||||||
.map { |p| [ p[:name], example.send(p[:name]) ] }
|
|
||||||
Hash[ tuples ]
|
|
||||||
end
|
|
||||||
|
|
||||||
def build_json_payload(parameters, example)
|
|
||||||
body_param = parameters.select { |p| p[:in] == :body }.first
|
|
||||||
body_param ? example.send(body_param[:name]).to_json : nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,54 +0,0 @@
|
|||||||
require 'active_support/core_ext/hash/slice'
|
|
||||||
require 'json-schema'
|
|
||||||
require 'json'
|
|
||||||
require 'rswag/specs/extended_schema'
|
|
||||||
|
|
||||||
module Rswag
|
|
||||||
module Specs
|
|
||||||
class ResponseValidator
|
|
||||||
|
|
||||||
def initialize(config = ::Rswag::Specs.config)
|
|
||||||
@config = config
|
|
||||||
end
|
|
||||||
|
|
||||||
def validate!(metadata, response)
|
|
||||||
swagger_doc = @config.get_swagger_doc(metadata[:swagger_doc])
|
|
||||||
|
|
||||||
validate_code!(metadata, response)
|
|
||||||
validate_headers!(metadata, response.headers)
|
|
||||||
validate_body!(metadata, swagger_doc, response.body)
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def validate_code!(metadata, response)
|
|
||||||
expected = metadata[:response][:code].to_s
|
|
||||||
if response.code != expected
|
|
||||||
raise UnexpectedResponse,
|
|
||||||
"Expected response code '#{response.code}' to match '#{expected}'\n" \
|
|
||||||
"Response body: #{response.body}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def validate_headers!(metadata, headers)
|
|
||||||
expected = (metadata[:response][:headers] || {}).keys
|
|
||||||
expected.each do |name|
|
|
||||||
raise UnexpectedResponse, "Expected response header #{name} to be present" if headers[name.to_s].nil?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def validate_body!(metadata, swagger_doc, body)
|
|
||||||
response_schema = metadata[:response][:schema]
|
|
||||||
return if response_schema.nil?
|
|
||||||
|
|
||||||
validation_schema = response_schema
|
|
||||||
.merge('$schema' => 'http://tempuri.org/rswag/specs/extended_schema')
|
|
||||||
.merge(swagger_doc.slice(:definitions))
|
|
||||||
errors = JSON::Validator.fully_validate(validation_schema, body)
|
|
||||||
raise UnexpectedResponse, "Expected response body to match schema: #{errors[0]}" if errors.any?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class UnexpectedResponse < StandardError; end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,67 +0,0 @@
|
|||||||
require 'active_support/core_ext/hash/deep_merge'
|
|
||||||
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, config = Rswag::Specs.config)
|
|
||||||
@output = output
|
|
||||||
@config = config
|
|
||||||
|
|
||||||
@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 = @config.get_swagger_doc(metadata[:swagger_doc])
|
|
||||||
swagger_doc.deep_merge!(metadata_to_swagger(metadata))
|
|
||||||
end
|
|
||||||
|
|
||||||
def stop(notification=nil)
|
|
||||||
@config.swagger_docs.each do |url_path, doc|
|
|
||||||
file_path = File.join(@config.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 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 })
|
|
||||||
|
|
||||||
path_template = metadata[:path_item][:template]
|
|
||||||
path_item = metadata[:path_item]
|
|
||||||
.reject { |k,v| k == :template }
|
|
||||||
.merge(verb => operation)
|
|
||||||
|
|
||||||
{ paths: { path_template => path_item } }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -8,10 +8,10 @@ namespace :rswag do
|
|||||||
t.pattern = 'spec/requests/**/*_spec.rb, spec/api/**/*_spec.rb, spec/integration/**/*_spec.rb'
|
t.pattern = 'spec/requests/**/*_spec.rb, spec/api/**/*_spec.rb, spec/integration/**/*_spec.rb'
|
||||||
|
|
||||||
# NOTE: rspec 2.x support
|
# NOTE: rspec 2.x support
|
||||||
if Rswag::Specs::RSPEC_VERSION > 2 && Rswag::Specs.config.swagger_dry_run
|
if OpenApi::Rswag::Specs::RSPEC_VERSION > 2 && OpenApi::Rswag::Specs.config.swagger_dry_run
|
||||||
t.rspec_opts = [ '--format Rswag::Specs::SwaggerFormatter', '--dry-run', '--order defined' ]
|
t.rspec_opts = [ '--format OpenApi::Rswag::Specs::SwaggerFormatter', '--dry-run', '--order defined' ]
|
||||||
else
|
else
|
||||||
t.rspec_opts = [ '--format Rswag::Specs::SwaggerFormatter', '--order defined' ]
|
t.rspec_opts = [ '--format OpenApi::Rswag::Specs::SwaggerFormatter', '--order defined' ]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
23
rswag-specs/open_api-rswag-specs.gemspec
Normal file
23
rswag-specs/open_api-rswag-specs.gemspec
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
$LOAD_PATH.push File.expand_path('lib', __dir__)
|
||||||
|
|
||||||
|
# Describe your gem and declare its dependencies:
|
||||||
|
Gem::Specification.new do |s|
|
||||||
|
s.name = 'open_api-rswag-specs'
|
||||||
|
s.version = ENV['TRAVIS_TAG'] || '0.0.0'
|
||||||
|
s.authors = ['Richie Morris', 'Jay Danielian']
|
||||||
|
s.email = ['domaindrivendev@gmail.com']
|
||||||
|
s.homepage = 'https://github.com/jdanielian/rswag'
|
||||||
|
s.summary = 'A Swagger-based DSL for rspec-rails & accompanying rake task for generating Swagger files'
|
||||||
|
s.description = 'Simplify API integration testing with a succinct rspec DSL and generate Swagger files directly from your rspecs'
|
||||||
|
s.license = 'MIT'
|
||||||
|
|
||||||
|
s.files = Dir['{lib}/**/*'] + %w[MIT-LICENSE Rakefile]
|
||||||
|
|
||||||
|
s.add_dependency 'activesupport', '>= 3.1', '< 6.0'
|
||||||
|
s.add_dependency 'json-schema', '~> 2.2'
|
||||||
|
s.add_dependency 'railties', '>= 3.1', '< 6.0'
|
||||||
|
s.add_dependency 'hashie'
|
||||||
|
s.add_development_dependency 'guard-rspec'
|
||||||
|
end
|
||||||
@ -1,19 +0,0 @@
|
|||||||
$:.push File.expand_path("../lib", __FILE__)
|
|
||||||
|
|
||||||
# Describe your gem and declare its dependencies:
|
|
||||||
Gem::Specification.new do |s|
|
|
||||||
s.name = "rswag-specs"
|
|
||||||
s.version = ENV['TRAVIS_TAG'] || '0.0.0'
|
|
||||||
s.authors = ["Richie Morris"]
|
|
||||||
s.email = ["domaindrivendev@gmail.com"]
|
|
||||||
s.homepage = "https://github.com/domaindrivendev/rswag"
|
|
||||||
s.summary = "A Swagger-based DSL for rspec-rails & accompanying rake task for generating Swagger files"
|
|
||||||
s.description = "Simplify API integration testing with a succinct rspec DSL and generate Swagger files directly from your rspecs"
|
|
||||||
s.license = "MIT"
|
|
||||||
|
|
||||||
s.files = Dir["{lib}/**/*"] + ["MIT-LICENSE", "Rakefile" ]
|
|
||||||
|
|
||||||
s.add_dependency 'activesupport', '>= 3.1', '< 6.0'
|
|
||||||
s.add_dependency 'railties', '>= 3.1', '< 6.0'
|
|
||||||
s.add_dependency 'json-schema', '~> 2.2'
|
|
||||||
end
|
|
||||||
@ -1,8 +1,10 @@
|
|||||||
require 'rswag/specs/configuration'
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module Rswag
|
require 'open_api/rswag/specs/configuration'
|
||||||
|
|
||||||
|
module OpenApi
|
||||||
|
module Rswag
|
||||||
module Specs
|
module Specs
|
||||||
|
|
||||||
describe Configuration do
|
describe Configuration do
|
||||||
subject { described_class.new(rspec_config) }
|
subject { described_class.new(rspec_config) }
|
||||||
|
|
||||||
@ -74,4 +76,5 @@ module Rswag
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
require 'rswag/specs/example_group_helpers'
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module Rswag
|
require 'open_api/rswag/specs/example_group_helpers'
|
||||||
|
|
||||||
|
module OpenApi
|
||||||
|
module Rswag
|
||||||
module Specs
|
module Specs
|
||||||
|
|
||||||
describe ExampleGroupHelpers do
|
describe ExampleGroupHelpers do
|
||||||
subject { double('example_group') }
|
subject { double('example_group') }
|
||||||
|
|
||||||
@ -48,12 +50,12 @@ module Rswag
|
|||||||
|
|
||||||
it "adds to the 'operation' metadata" do
|
it "adds to the 'operation' metadata" do
|
||||||
expect(api_metadata[:operation]).to match(
|
expect(api_metadata[:operation]).to match(
|
||||||
tags: [ 'Blogs', 'Admin' ],
|
tags: %w[Blogs Admin],
|
||||||
description: 'Some description',
|
description: 'Some description',
|
||||||
operationId: 'createBlog',
|
operationId: 'createBlog',
|
||||||
consumes: [ 'application/json', 'application/xml' ],
|
consumes: ['application/json', 'application/xml'],
|
||||||
produces: [ 'application/json', 'application/xml' ],
|
produces: ['application/json', 'application/xml'],
|
||||||
schemes: [ 'http', 'https' ],
|
schemes: %w[http https],
|
||||||
deprecated: true
|
deprecated: true
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
@ -74,27 +76,59 @@ module Rswag
|
|||||||
|
|
||||||
it "adds to the 'operation' metadata" do
|
it "adds to the 'operation' metadata" do
|
||||||
expect(api_metadata[:operation]).to match(
|
expect(api_metadata[:operation]).to match(
|
||||||
tags: [ 'Blogs', 'Admin' ],
|
tags: %w[Blogs Admin],
|
||||||
description: 'Some description',
|
description: 'Some description',
|
||||||
operationId: 'createBlog',
|
operationId: 'createBlog',
|
||||||
consumes: [ 'application/json', 'application/xml' ],
|
consumes: ['application/json', 'application/xml'],
|
||||||
produces: [ 'application/json', 'application/xml' ],
|
produces: ['application/json', 'application/xml'],
|
||||||
schemes: [ 'http', 'https' ],
|
schemes: %w[http https],
|
||||||
deprecated: true,
|
deprecated: true,
|
||||||
security: { api_key: [] }
|
security: { api_key: [] }
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#parameter(attributes)' do
|
describe '#request_body_json(schema)' do
|
||||||
|
let(:api_metadata) { { path_item: {}, operation: {} } } # i.e. operation defined
|
||||||
|
context 'when required is not supplied' do
|
||||||
|
before { subject.request_body_json(schema: { type: 'object' }) }
|
||||||
|
|
||||||
|
it 'adds required true by default' do
|
||||||
|
expect(api_metadata[:operation][:requestBody]).to match(
|
||||||
|
required: true, content: { 'application/json' => { schema: { type: 'object' } } }
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when required is supplied' do
|
||||||
|
before { subject.request_body_json(schema: { type: 'object' }, required: false) }
|
||||||
|
|
||||||
|
it 'adds required false' do
|
||||||
|
expect(api_metadata[:operation][:requestBody]).to match(
|
||||||
|
required: false, content: { 'application/json' => { schema: { type: 'object' } } }
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when required is supplied' do
|
||||||
|
before { subject.request_body_json(schema: { type: 'object' }, description: 'my description') }
|
||||||
|
|
||||||
|
it 'adds description' do
|
||||||
|
expect(api_metadata[:operation][:requestBody]).to match(
|
||||||
|
description: 'my description', required: true, content: { 'application/json' => { schema: { type: 'object' } } }
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#parameter(attributes)' do
|
||||||
context "when called at the 'path' level" do
|
context "when called at the 'path' level" do
|
||||||
before { subject.parameter(name: :blog, in: :body, schema: { type: 'object' }) }
|
before { subject.parameter(name: :blog, in: :body, schema: { type: 'object' }) }
|
||||||
let(:api_metadata) { { path_item: {} } } # i.e. operation not defined yet
|
let(:api_metadata) { { path_item: {} } } # i.e. operation not defined yet
|
||||||
|
|
||||||
it "adds to the 'path_item parameters' metadata" do
|
it "adds to the 'path_item parameters' metadata" do
|
||||||
expect(api_metadata[:path_item][:parameters]).to match(
|
expect(api_metadata[:path_item][:parameters]).to match(
|
||||||
[ name: :blog, in: :body, schema: { type: 'object' } ]
|
[name: :blog, in: :body, schema: { type: 'object' }]
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -105,7 +139,7 @@ module Rswag
|
|||||||
|
|
||||||
it "adds to the 'operation parameters' metadata" do
|
it "adds to the 'operation parameters' metadata" do
|
||||||
expect(api_metadata[:operation][:parameters]).to match(
|
expect(api_metadata[:operation][:parameters]).to match(
|
||||||
[ name: :blog, in: :body, schema: { type: 'object' } ]
|
[name: :blog, in: :body, schema: { type: 'object' }]
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -116,7 +150,7 @@ module Rswag
|
|||||||
|
|
||||||
it "automatically sets the 'required' flag" do
|
it "automatically sets the 'required' flag" do
|
||||||
expect(api_metadata[:operation][:parameters]).to match(
|
expect(api_metadata[:operation][:parameters]).to match(
|
||||||
[ name: :id, in: :path, required: true ]
|
[name: :id, in: :path, required: true]
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -126,7 +160,7 @@ module Rswag
|
|||||||
let(:api_metadata) { { operation: {} } }
|
let(:api_metadata) { { operation: {} } }
|
||||||
|
|
||||||
it "does not require the 'in' parameter key" do
|
it "does not require the 'in' parameter key" do
|
||||||
expect(api_metadata[:operation][:parameters]).to match([ name: :id ])
|
expect(api_metadata[:operation][:parameters]).to match([name: :id])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -146,7 +180,7 @@ module Rswag
|
|||||||
let(:api_metadata) { { response: {} } }
|
let(:api_metadata) { { response: {} } }
|
||||||
|
|
||||||
it "adds to the 'response' metadata" do
|
it "adds to the 'response' metadata" do
|
||||||
expect(api_metadata[:response][:schema]).to match(type: 'object')
|
expect(api_metadata[:response][:content]['application/json'][:schema]).to match(type: 'object')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -156,7 +190,7 @@ module Rswag
|
|||||||
|
|
||||||
it "adds to the 'response headers' metadata" do
|
it "adds to the 'response headers' metadata" do
|
||||||
expect(api_metadata[:response][:headers]).to match(
|
expect(api_metadata[:response][:headers]).to match(
|
||||||
'Date' => { type: 'string' }
|
'Date' => {schema: { type: 'string' }}
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -181,4 +215,5 @@ module Rswag
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,21 +1,24 @@
|
|||||||
require 'rswag/specs/example_helpers'
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module Rswag
|
require 'open_api/rswag/specs/example_helpers'
|
||||||
|
|
||||||
|
module OpenApi
|
||||||
|
module Rswag
|
||||||
module Specs
|
module Specs
|
||||||
|
|
||||||
describe ExampleHelpers do
|
describe ExampleHelpers do
|
||||||
subject { double('example') }
|
subject { double('example') }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
subject.extend(ExampleHelpers)
|
subject.extend(ExampleHelpers)
|
||||||
allow(Rswag::Specs).to receive(:config).and_return(config)
|
allow(OpenApi::Rswag::Specs).to receive(:config).and_return(config)
|
||||||
allow(config).to receive(:get_swagger_doc).and_return(swagger_doc)
|
allow(config).to receive(:get_swagger_doc).and_return(swagger_doc)
|
||||||
stub_const('Rswag::Specs::RAILS_VERSION', 3)
|
stub_const('Rswag::Specs::RAILS_VERSION', 3)
|
||||||
end
|
end
|
||||||
let(:config) { double('config') }
|
let(:config) { double('config') }
|
||||||
let(:swagger_doc) do
|
let(:swagger_doc) do
|
||||||
{
|
{
|
||||||
securityDefinitions: {
|
components: {
|
||||||
|
securitySchemes: {
|
||||||
api_key: {
|
api_key: {
|
||||||
type: :apiKey,
|
type: :apiKey,
|
||||||
name: 'api_key',
|
name: 'api_key',
|
||||||
@ -23,6 +26,7 @@ module Rswag
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
end
|
end
|
||||||
let(:metadata) do
|
let(:metadata) do
|
||||||
{
|
{
|
||||||
@ -30,7 +34,7 @@ module Rswag
|
|||||||
operation: {
|
operation: {
|
||||||
verb: :put,
|
verb: :put,
|
||||||
summary: 'Updates a blog',
|
summary: 'Updates a blog',
|
||||||
consumes: [ 'application/json' ],
|
consumes: ['application/json'],
|
||||||
parameters: [
|
parameters: [
|
||||||
{ name: :blog_id, in: :path, type: 'integer' },
|
{ name: :blog_id, in: :path, type: 'integer' },
|
||||||
{ name: 'id', in: :path, type: 'integer' },
|
{ name: 'id', in: :path, type: 'integer' },
|
||||||
@ -58,11 +62,12 @@ module Rswag
|
|||||||
it "submits a request built from metadata and 'let' values" do
|
it "submits a request built from metadata and 'let' values" do
|
||||||
expect(subject).to have_received(:put).with(
|
expect(subject).to have_received(:put).with(
|
||||||
'/blogs/1/comments/2?q1=foo&api_key=fookey',
|
'/blogs/1/comments/2?q1=foo&api_key=fookey',
|
||||||
"{\"text\":\"Some comment\"}",
|
'{"text":"Some comment"}',
|
||||||
{ 'CONTENT_TYPE' => 'application/json' }
|
'CONTENT_TYPE' => 'application/json'
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
require 'rswag/specs/request_factory'
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module Rswag
|
require 'open_api/rswag/specs/request_factory'
|
||||||
|
|
||||||
|
module OpenApi
|
||||||
|
module Rswag
|
||||||
module Specs
|
module Specs
|
||||||
|
|
||||||
describe RequestFactory do
|
describe RequestFactory do
|
||||||
subject { RequestFactory.new(config) }
|
subject { RequestFactory.new(config) }
|
||||||
|
|
||||||
@ -53,7 +55,7 @@ module Rswag
|
|||||||
allow(example).to receive(:q2).and_return('bar')
|
allow(example).to receive(:q2).and_return('bar')
|
||||||
end
|
end
|
||||||
|
|
||||||
it "builds the query string from example values" do
|
it 'builds the query string from example values' do
|
||||||
expect(request[:path]).to eq('/blogs?q1=foo&q2=bar')
|
expect(request[:path]).to eq('/blogs?q1=foo&q2=bar')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -63,40 +65,40 @@ module Rswag
|
|||||||
metadata[:operation][:parameters] = [
|
metadata[:operation][:parameters] = [
|
||||||
{ name: 'things', in: :query, type: :array, collectionFormat: collection_format }
|
{ name: 'things', in: :query, type: :array, collectionFormat: collection_format }
|
||||||
]
|
]
|
||||||
allow(example).to receive(:things).and_return([ 'foo', 'bar' ])
|
allow(example).to receive(:things).and_return(%w[foo bar])
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'collectionFormat = csv' do
|
context 'collectionFormat = csv' do
|
||||||
let(:collection_format) { :csv }
|
let(:collection_format) { :csv }
|
||||||
it "formats as comma separated values" do
|
it 'formats as comma separated values' do
|
||||||
expect(request[:path]).to eq('/blogs?things=foo,bar')
|
expect(request[:path]).to eq('/blogs?things=foo,bar')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'collectionFormat = ssv' do
|
context 'collectionFormat = ssv' do
|
||||||
let(:collection_format) { :ssv }
|
let(:collection_format) { :ssv }
|
||||||
it "formats as space separated values" do
|
it 'formats as space separated values' do
|
||||||
expect(request[:path]).to eq('/blogs?things=foo bar')
|
expect(request[:path]).to eq('/blogs?things=foo bar')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'collectionFormat = tsv' do
|
context 'collectionFormat = tsv' do
|
||||||
let(:collection_format) { :tsv }
|
let(:collection_format) { :tsv }
|
||||||
it "formats as tab separated values" do
|
it 'formats as tab separated values' do
|
||||||
expect(request[:path]).to eq('/blogs?things=foo\tbar')
|
expect(request[:path]).to eq('/blogs?things=foo\tbar')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'collectionFormat = pipes' do
|
context 'collectionFormat = pipes' do
|
||||||
let(:collection_format) { :pipes }
|
let(:collection_format) { :pipes }
|
||||||
it "formats as pipe separated values" do
|
it 'formats as pipe separated values' do
|
||||||
expect(request[:path]).to eq('/blogs?things=foo|bar')
|
expect(request[:path]).to eq('/blogs?things=foo|bar')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'collectionFormat = multi' do
|
context 'collectionFormat = multi' do
|
||||||
let(:collection_format) { :multi }
|
let(:collection_format) { :multi }
|
||||||
it "formats as multiple parameter instances" do
|
it 'formats as multiple parameter instances' do
|
||||||
expect(request[:path]).to eq('/blogs?things=foo&things=bar')
|
expect(request[:path]).to eq('/blogs?things=foo&things=bar')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -104,12 +106,12 @@ module Rswag
|
|||||||
|
|
||||||
context "'header' parameters" do
|
context "'header' parameters" do
|
||||||
before do
|
before do
|
||||||
metadata[:operation][:parameters] = [ { name: 'Api-Key', in: :header, type: :string } ]
|
metadata[:operation][:parameters] = [{ name: 'Api-Key', in: :header, type: :string }]
|
||||||
allow(example).to receive(:'Api-Key').and_return('foobar')
|
allow(example).to receive(:'Api-Key').and_return('foobar')
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'adds names and example values to headers' do
|
it 'adds names and example values to headers' do
|
||||||
expect(request[:headers]).to eq({ 'Api-Key' => 'foobar' })
|
expect(request[:headers]).to eq('Api-Key' => 'foobar')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -127,9 +129,9 @@ module Rswag
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "consumes content" do
|
context 'consumes content' do
|
||||||
before do
|
before do
|
||||||
metadata[:operation][:consumes] = [ 'application/json', 'application/xml' ]
|
metadata[:operation][:consumes] = ['application/json', 'application/xml']
|
||||||
end
|
end
|
||||||
|
|
||||||
context "no 'Content-Type' provided" do
|
context "no 'Content-Type' provided" do
|
||||||
@ -150,18 +152,18 @@ module Rswag
|
|||||||
|
|
||||||
context 'JSON payload' do
|
context 'JSON payload' do
|
||||||
before do
|
before do
|
||||||
metadata[:operation][:parameters] = [ { name: 'comment', in: :body, schema: { type: 'object' } } ]
|
metadata[:operation][:parameters] = [{ name: :comment, in: :body, schema: { type: 'object' } }]
|
||||||
allow(example).to receive(:comment).and_return(text: 'Some comment')
|
allow(example).to receive(:comment).and_return(text: 'Some comment')
|
||||||
end
|
end
|
||||||
|
|
||||||
it "serializes first 'body' parameter to JSON string" do
|
it "serializes first 'body' parameter to JSON string" do
|
||||||
expect(request[:payload]).to eq("{\"text\":\"Some comment\"}")
|
expect(request[:payload]).to eq('{"text":"Some comment"}')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'form payload' do
|
context 'form payload' do
|
||||||
before do
|
before do
|
||||||
metadata[:operation][:consumes] = [ 'multipart/form-data' ]
|
metadata[:operation][:consumes] = ['multipart/form-data']
|
||||||
metadata[:operation][:parameters] = [
|
metadata[:operation][:parameters] = [
|
||||||
{ name: 'f1', in: :formData, type: :string },
|
{ name: 'f1', in: :formData, type: :string },
|
||||||
{ name: 'f2', in: :formData, type: :string }
|
{ name: 'f2', in: :formData, type: :string }
|
||||||
@ -181,7 +183,7 @@ module Rswag
|
|||||||
|
|
||||||
context 'produces content' do
|
context 'produces content' do
|
||||||
before do
|
before do
|
||||||
metadata[:operation][:produces] = [ 'application/json', 'application/xml' ]
|
metadata[:operation][:produces] = ['application/json', 'application/xml']
|
||||||
end
|
end
|
||||||
|
|
||||||
context "no 'Accept' value provided" do
|
context "no 'Accept' value provided" do
|
||||||
@ -192,7 +194,7 @@ module Rswag
|
|||||||
|
|
||||||
context "explicit 'Accept' value provided" do
|
context "explicit 'Accept' value provided" do
|
||||||
before do
|
before do
|
||||||
allow(example).to receive(:'Accept').and_return('application/xml')
|
allow(example).to receive(:Accept).and_return('application/xml')
|
||||||
end
|
end
|
||||||
|
|
||||||
it "sets 'HTTP_ACCEPT' header to example value" do
|
it "sets 'HTTP_ACCEPT' header to example value" do
|
||||||
@ -203,8 +205,11 @@ module Rswag
|
|||||||
|
|
||||||
context 'basic auth' do
|
context 'basic auth' do
|
||||||
before do
|
before do
|
||||||
swagger_doc[:securityDefinitions] = { basic: { type: :basic } }
|
swagger_doc[:components] = { securitySchemes: {
|
||||||
metadata[:operation][:security] = [ basic: [] ]
|
basic: { type: :basic }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
metadata[:operation][:security] = [basic: []]
|
||||||
allow(example).to receive(:Authorization).and_return('Basic foobar')
|
allow(example).to receive(:Authorization).and_return('Basic foobar')
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -215,8 +220,11 @@ module Rswag
|
|||||||
|
|
||||||
context 'apiKey' do
|
context 'apiKey' do
|
||||||
before do
|
before do
|
||||||
swagger_doc[:securityDefinitions] = { apiKey: { type: :apiKey, name: 'api_key', in: key_location } }
|
swagger_doc[:components] = { securitySchemes: {
|
||||||
metadata[:operation][:security] = [ apiKey: [] ]
|
apiKey: { type: :apiKey, name: 'api_key', in: key_location }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
metadata[:operation][:security] = [apiKey: []]
|
||||||
allow(example).to receive(:api_key).and_return('foobar')
|
allow(example).to receive(:api_key).and_return('foobar')
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -256,8 +264,11 @@ module Rswag
|
|||||||
|
|
||||||
context 'oauth2' do
|
context 'oauth2' do
|
||||||
before do
|
before do
|
||||||
swagger_doc[:securityDefinitions] = { oauth2: { type: :oauth2, scopes: [ 'read:blogs' ] } }
|
swagger_doc[:components] = { securitySchemes: {
|
||||||
metadata[:operation][:security] = [ oauth2: [ 'read:blogs' ] ]
|
oauth2: { type: :oauth2, scopes: ['read:blogs'] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
metadata[:operation][:security] = [oauth2: ['read:blogs']]
|
||||||
allow(example).to receive(:Authorization).and_return('Bearer foobar')
|
allow(example).to receive(:Authorization).and_return('Bearer foobar')
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -268,30 +279,31 @@ module Rswag
|
|||||||
|
|
||||||
context 'paired security requirements' do
|
context 'paired security requirements' do
|
||||||
before do
|
before do
|
||||||
swagger_doc[:securityDefinitions] = {
|
swagger_doc[:components] = { securitySchemes: {
|
||||||
basic: { type: :basic },
|
basic: { type: :basic },
|
||||||
api_key: { type: :apiKey, name: 'api_key', in: :query }
|
api_key: { type: :apiKey, name: 'api_key', in: :query }
|
||||||
}
|
}
|
||||||
metadata[:operation][:security] = [ { basic: [], api_key: [] } ]
|
}
|
||||||
|
metadata[:operation][:security] = [{ basic: [], api_key: [] }]
|
||||||
allow(example).to receive(:Authorization).and_return('Basic foobar')
|
allow(example).to receive(:Authorization).and_return('Basic foobar')
|
||||||
allow(example).to receive(:api_key).and_return('foobar')
|
allow(example).to receive(:api_key).and_return('foobar')
|
||||||
end
|
end
|
||||||
|
|
||||||
it "sets both params to example values" do
|
it 'sets both params to example values' do
|
||||||
expect(request[:headers]).to eq('HTTP_AUTHORIZATION' => 'Basic foobar')
|
expect(request[:headers]).to eq('HTTP_AUTHORIZATION' => 'Basic foobar')
|
||||||
expect(request[:path]).to eq('/blogs?api_key=foobar')
|
expect(request[:path]).to eq('/blogs?api_key=foobar')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "path-level parameters" do
|
context 'path-level parameters' do
|
||||||
before do
|
before do
|
||||||
metadata[:operation][:parameters] = [ { name: 'q1', in: :query, type: :string } ]
|
metadata[:operation][:parameters] = [{ name: 'q1', in: :query, type: :string }]
|
||||||
metadata[:path_item][:parameters] = [ { name: 'q2', in: :query, type: :string } ]
|
metadata[:path_item][:parameters] = [{ name: 'q2', in: :query, type: :string }]
|
||||||
allow(example).to receive(:q1).and_return('foo')
|
allow(example).to receive(:q1).and_return('foo')
|
||||||
allow(example).to receive(:q2).and_return('bar')
|
allow(example).to receive(:q2).and_return('bar')
|
||||||
end
|
end
|
||||||
|
|
||||||
it "populates operation and path level parameters " do
|
it 'populates operation and path level parameters ' do
|
||||||
expect(request[:path]).to eq('/blogs?q1=foo&q2=bar')
|
expect(request[:path]).to eq('/blogs?q1=foo&q2=bar')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -299,7 +311,7 @@ module Rswag
|
|||||||
context 'referenced parameters' do
|
context 'referenced parameters' do
|
||||||
before do
|
before do
|
||||||
swagger_doc[:parameters] = { q1: { name: 'q1', in: :query, type: :string } }
|
swagger_doc[:parameters] = { q1: { name: 'q1', in: :query, type: :string } }
|
||||||
metadata[:operation][:parameters] = [ { '$ref' => '#/parameters/q1' } ]
|
metadata[:operation][:parameters] = [{ '$ref' => '#/parameters/q1' }]
|
||||||
allow(example).to receive(:q1).and_return('foo')
|
allow(example).to receive(:q1).and_return('foo')
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -316,18 +328,18 @@ module Rswag
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "global consumes" do
|
context 'global consumes' do
|
||||||
before { swagger_doc[:consumes] = [ 'application/xml' ] }
|
before { swagger_doc[:consumes] = ['application/xml'] }
|
||||||
|
|
||||||
it "defaults 'CONTENT_TYPE' to global value(s)" do
|
it "defaults 'CONTENT_TYPE' to global value(s)" do
|
||||||
expect(request[:headers]).to eq('CONTENT_TYPE' => 'application/xml')
|
expect(request[:headers]).to eq('CONTENT_TYPE' => 'application/xml')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "global security requirements" do
|
context 'global security requirements' do
|
||||||
before do
|
before do
|
||||||
swagger_doc[:securityDefinitions] = { apiKey: { type: :apiKey, name: 'api_key', in: :query } }
|
swagger_doc[:components] = {securitySchemes: { apiKey: { type: :apiKey, name: 'api_key', in: :query } }}
|
||||||
swagger_doc[:security] = [ apiKey: [] ]
|
swagger_doc[:security] = [apiKey: []]
|
||||||
allow(example).to receive(:api_key).and_return('foobar')
|
allow(example).to receive(:api_key).and_return('foobar')
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -338,4 +350,5 @@ module Rswag
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
require 'rswag/specs/response_validator'
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module Rswag
|
require 'open_api/rswag/specs/response_validator'
|
||||||
|
|
||||||
|
module OpenApi
|
||||||
|
module Rswag
|
||||||
module Specs
|
module Specs
|
||||||
|
|
||||||
describe ResponseValidator do
|
describe ResponseValidator do
|
||||||
subject { ResponseValidator.new(config) }
|
subject { ResponseValidator.new(config) }
|
||||||
|
|
||||||
@ -10,17 +12,21 @@ module Rswag
|
|||||||
allow(config).to receive(:get_swagger_doc).and_return(swagger_doc)
|
allow(config).to receive(:get_swagger_doc).and_return(swagger_doc)
|
||||||
end
|
end
|
||||||
let(:config) { double('config') }
|
let(:config) { double('config') }
|
||||||
let(:swagger_doc) { {} }
|
let(:swagger_doc) {{}}
|
||||||
let(:example) { double('example') }
|
let(:example) { double('example') }
|
||||||
let(:metadata) do
|
let(:metadata) do
|
||||||
{
|
{
|
||||||
response: {
|
response: {
|
||||||
code: 200,
|
code: 200,
|
||||||
headers: { 'X-Rate-Limit-Limit' => { type: :integer } },
|
headers: { 'X-Rate-Limit-Limit' => { type: :integer } },
|
||||||
|
content:
|
||||||
|
{'application/json' => {
|
||||||
schema: {
|
schema: {
|
||||||
type: :object,
|
type: :object,
|
||||||
properties: { text: { type: :string } },
|
properties: { text: { type: :string } },
|
||||||
required: [ 'text' ]
|
required: ['text']
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -32,39 +38,40 @@ module Rswag
|
|||||||
OpenStruct.new(
|
OpenStruct.new(
|
||||||
code: '200',
|
code: '200',
|
||||||
headers: { 'X-Rate-Limit-Limit' => '10' },
|
headers: { 'X-Rate-Limit-Limit' => '10' },
|
||||||
body: "{\"text\":\"Some comment\"}"
|
body: '{"text":"Some comment"}'
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
context "response matches metadata" do
|
context 'response matches metadata' do
|
||||||
it { expect { call }.to_not raise_error }
|
it { expect { call }.to_not raise_error }
|
||||||
end
|
end
|
||||||
|
|
||||||
context "response code differs from metadata" do
|
context 'response code differs from metadata' do
|
||||||
before { response.code = '400' }
|
before { response.code = '400' }
|
||||||
it { expect { call }.to raise_error /Expected response code/ }
|
it { expect { call }.to raise_error /Expected response code/ }
|
||||||
end
|
end
|
||||||
|
|
||||||
context "response headers differ from metadata" do
|
context 'response headers differ from metadata' do
|
||||||
before { response.headers = {} }
|
before { response.headers = {} }
|
||||||
it { expect { call }.to raise_error /Expected response header/ }
|
it { expect { call }.to raise_error /Expected response header/ }
|
||||||
end
|
end
|
||||||
|
|
||||||
context "response body differs from metadata" do
|
context 'response body differs from metadata' do
|
||||||
before { response.body = "{\"foo\":\"Some comment\"}" }
|
before { response.body = '{"foo":"Some comment"}' }
|
||||||
it { expect { call }.to raise_error /Expected response body/ }
|
it { expect { call }.to raise_error /Expected response body/ }
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'referenced schemas' do
|
context 'referenced schemas' do
|
||||||
before do
|
before do
|
||||||
swagger_doc[:definitions] = {
|
swagger_doc[:components] = {}
|
||||||
|
swagger_doc[:components][:schemas] = {
|
||||||
'blog' => {
|
'blog' => {
|
||||||
type: :object,
|
type: :object,
|
||||||
properties: { foo: { type: :string } },
|
properties: { foo: { type: :string } },
|
||||||
required: [ 'foo' ]
|
required: ['foo']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
metadata[:response][:schema] = { '$ref' => '#/definitions/blog' }
|
metadata[:response][:content]['application/json'][:schema] = { '$ref' => '#/components/schemas/blog' }
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'uses the referenced schema to validate the response body' do
|
it 'uses the referenced schema to validate the response body' do
|
||||||
@ -74,4 +81,5 @@ module Rswag
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
require 'rswag/specs/swagger_formatter'
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'open_api/rswag/specs/swagger_formatter'
|
||||||
require 'ostruct'
|
require 'ostruct'
|
||||||
|
|
||||||
module Rswag
|
module OpenApi
|
||||||
|
module Rswag
|
||||||
module Specs
|
module Specs
|
||||||
|
|
||||||
describe SwaggerFormatter do
|
describe SwaggerFormatter do
|
||||||
subject { described_class.new(output, config) }
|
subject { described_class.new(output, config) }
|
||||||
|
|
||||||
@ -13,7 +15,7 @@ module Rswag
|
|||||||
end
|
end
|
||||||
let(:config) { double('config') }
|
let(:config) { double('config') }
|
||||||
let(:output) { double('output').as_null_object }
|
let(:output) { double('output').as_null_object }
|
||||||
let(:swagger_root) { File.expand_path('../tmp/swagger', __FILE__) }
|
let(:swagger_root) { File.expand_path('tmp/swagger', __dir__) }
|
||||||
|
|
||||||
describe '#example_group_finished(notification)' do
|
describe '#example_group_finished(notification)' do
|
||||||
before do
|
before do
|
||||||
@ -48,7 +50,7 @@ module Rswag
|
|||||||
|
|
||||||
describe '#stop' do
|
describe '#stop' do
|
||||||
before do
|
before do
|
||||||
FileUtils.rm_r(swagger_root) if File.exists?(swagger_root)
|
FileUtils.rm_r(swagger_root) if File.exist?(swagger_root)
|
||||||
allow(config).to receive(:swagger_docs).and_return(
|
allow(config).to receive(:swagger_docs).and_return(
|
||||||
'v1/swagger.json' => { info: { version: 'v1' } },
|
'v1/swagger.json' => { info: { version: 'v1' } },
|
||||||
'v2/swagger.json' => { info: { version: 'v2' } }
|
'v2/swagger.json' => { info: { version: 'v2' } }
|
||||||
@ -64,7 +66,8 @@ module Rswag
|
|||||||
end
|
end
|
||||||
|
|
||||||
after do
|
after do
|
||||||
FileUtils.rm_r(swagger_root) if File.exists?(swagger_root)
|
FileUtils.rm_r(swagger_root) if File.exist?(swagger_root)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -4,4 +4,4 @@ module Rails
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
require 'rswag/specs'
|
require 'open_api/rswag/specs'
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
# This command will automatically be run when you run "rails" with Rails 4 gems installed from the root of your application.
|
# This command will automatically be run when you run "rails" with Rails 4 gems installed from the root of your application.
|
||||||
|
|
||||||
ENGINE_ROOT = File.expand_path('../..', __FILE__)
|
ENGINE_ROOT = File.expand_path('../..', __FILE__)
|
||||||
ENGINE_PATH = File.expand_path('../../lib/rswag/api/engine', __FILE__)
|
ENGINE_PATH = File.expand_path('../../lib/open_api/rswag/api/engine', __FILE__)
|
||||||
|
|
||||||
# Set up gems listed in the Gemfile.
|
# Set up gems listed in the Gemfile.
|
||||||
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
|
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
|
||||||
|
|||||||
@ -3,7 +3,7 @@ require 'rails/generators'
|
|||||||
module Rswag
|
module Rswag
|
||||||
module Ui
|
module Ui
|
||||||
class CustomGenerator < Rails::Generators::Base
|
class CustomGenerator < Rails::Generators::Base
|
||||||
source_root File.expand_path('../../../../../../lib/rswag/ui', __FILE__)
|
source_root File.expand_path('../../../../../../lib/open_api/rswag/ui', __FILE__)
|
||||||
|
|
||||||
def add_custom_index
|
def add_custom_index
|
||||||
copy_file('index.erb', 'app/views/rswag/ui/home/index.html.erb')
|
copy_file('index.erb', 'app/views/rswag/ui/home/index.html.erb')
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
Rswag::Ui.configure do |c|
|
OpenApi::Rswag::Ui.configure do |c|
|
||||||
|
|
||||||
# List the Swagger endpoints that you want to be documented through the swagger-ui
|
# List the Swagger endpoints that you want to be documented through the swagger-ui
|
||||||
# The first parameter is the path (absolute or relative to the UI host) to the corresponding
|
# The first parameter is the path (absolute or relative to the UI host) to the corresponding
|
||||||
|
|||||||
16
rswag-ui/lib/open_api/rswag/ui.rb
Normal file
16
rswag-ui/lib/open_api/rswag/ui.rb
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
require 'open_api/rswag/ui/configuration'
|
||||||
|
require 'open_api/rswag/ui/engine'
|
||||||
|
|
||||||
|
module OpenApi
|
||||||
|
module Rswag
|
||||||
|
module Ui
|
||||||
|
def self.configure
|
||||||
|
yield(config)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.config
|
||||||
|
@config ||= Configuration.new
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
37
rswag-ui/lib/open_api/rswag/ui/configuration.rb
Normal file
37
rswag-ui/lib/open_api/rswag/ui/configuration.rb
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
require 'ostruct'
|
||||||
|
|
||||||
|
module OpenApi
|
||||||
|
module Rswag
|
||||||
|
module Ui
|
||||||
|
class Configuration
|
||||||
|
attr_reader :template_locations
|
||||||
|
attr_accessor :config_object
|
||||||
|
attr_accessor :oauth_config_object
|
||||||
|
attr_reader :assets_root
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
@template_locations = [
|
||||||
|
# preffered override location
|
||||||
|
"#{Rack::Directory.new('').root}/swagger/index.erb",
|
||||||
|
# backwards compatible override location
|
||||||
|
"#{Rack::Directory.new('').root}/app/views/rswag/ui/home/index.html.erb",
|
||||||
|
# default location
|
||||||
|
File.expand_path('../index.erb', __FILE__)
|
||||||
|
]
|
||||||
|
@assets_root = File.expand_path('../../../../../node_modules/swagger-ui-dist', __FILE__)
|
||||||
|
@config_object = {}
|
||||||
|
@oauth_config_object = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
def swagger_endpoint(url, name)
|
||||||
|
@config_object[:urls] ||= []
|
||||||
|
@config_object[:urls] << { url: url, name: name }
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_binding
|
||||||
|
binding
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
19
rswag-ui/lib/open_api/rswag/ui/engine.rb
Normal file
19
rswag-ui/lib/open_api/rswag/ui/engine.rb
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
require 'open_api/rswag/ui/middleware'
|
||||||
|
|
||||||
|
module OpenApi
|
||||||
|
module Rswag
|
||||||
|
module Ui
|
||||||
|
class Engine < ::Rails::Engine
|
||||||
|
isolate_namespace OpenApi::Rswag::Ui
|
||||||
|
|
||||||
|
initializer 'rswag-ui.initialize' do |app|
|
||||||
|
middleware.use OpenApi::Rswag::Ui::Middleware, OpenApi::Rswag::Ui.config
|
||||||
|
end
|
||||||
|
|
||||||
|
rake_tasks do
|
||||||
|
load File.expand_path('../../../../tasks/rswag-ui_tasks.rake', __FILE__)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
46
rswag-ui/lib/open_api/rswag/ui/middleware.rb
Normal file
46
rswag-ui/lib/open_api/rswag/ui/middleware.rb
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
module OpenApi
|
||||||
|
module Rswag
|
||||||
|
module Ui
|
||||||
|
class Middleware < Rack::Static
|
||||||
|
|
||||||
|
def initialize(app, config)
|
||||||
|
@config = config
|
||||||
|
super(app, urls: [ '' ], root: config.assets_root )
|
||||||
|
end
|
||||||
|
|
||||||
|
def call(env)
|
||||||
|
if base_path?(env)
|
||||||
|
redirect_uri = env['SCRIPT_NAME'].chomp('/') + '/index.html'
|
||||||
|
return [ 301, { 'Location' => redirect_uri }, [ ] ]
|
||||||
|
end
|
||||||
|
|
||||||
|
if index_path?(env)
|
||||||
|
return [ 200, { 'Content-Type' => 'text/html' }, [ render_template ] ]
|
||||||
|
end
|
||||||
|
|
||||||
|
super
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def base_path?(env)
|
||||||
|
env['REQUEST_METHOD'] == "GET" && env['PATH_INFO'] == "/"
|
||||||
|
end
|
||||||
|
|
||||||
|
def index_path?(env)
|
||||||
|
env['REQUEST_METHOD'] == "GET" && env['PATH_INFO'] == "/index.html"
|
||||||
|
end
|
||||||
|
|
||||||
|
def render_template
|
||||||
|
file = File.new(template_filename)
|
||||||
|
template = ERB.new(file.read)
|
||||||
|
template.result(@config.get_binding)
|
||||||
|
end
|
||||||
|
|
||||||
|
def template_filename
|
||||||
|
@config.template_locations.find { |filename| File.exists?(filename) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -1,14 +0,0 @@
|
|||||||
require 'rswag/ui/configuration'
|
|
||||||
require 'rswag/ui/engine'
|
|
||||||
|
|
||||||
module Rswag
|
|
||||||
module Ui
|
|
||||||
def self.configure
|
|
||||||
yield(config)
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.config
|
|
||||||
@config ||= Configuration.new
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,35 +0,0 @@
|
|||||||
require 'ostruct'
|
|
||||||
|
|
||||||
module Rswag
|
|
||||||
module Ui
|
|
||||||
class Configuration
|
|
||||||
attr_reader :template_locations
|
|
||||||
attr_accessor :config_object
|
|
||||||
attr_accessor :oauth_config_object
|
|
||||||
attr_reader :assets_root
|
|
||||||
|
|
||||||
def initialize
|
|
||||||
@template_locations = [
|
|
||||||
# preffered override location
|
|
||||||
"#{Rack::Directory.new('').root}/swagger/index.erb",
|
|
||||||
# backwards compatible override location
|
|
||||||
"#{Rack::Directory.new('').root}/app/views/rswag/ui/home/index.html.erb",
|
|
||||||
# default location
|
|
||||||
File.expand_path('../index.erb', __FILE__)
|
|
||||||
]
|
|
||||||
@assets_root = File.expand_path('../../../../node_modules/swagger-ui-dist', __FILE__)
|
|
||||||
@config_object = {}
|
|
||||||
@oauth_config_object = {}
|
|
||||||
end
|
|
||||||
|
|
||||||
def swagger_endpoint(url, name)
|
|
||||||
@config_object[:urls] ||= []
|
|
||||||
@config_object[:urls] << { url: url, name: name }
|
|
||||||
end
|
|
||||||
|
|
||||||
def get_binding
|
|
||||||
binding
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
require 'rswag/ui/middleware'
|
|
||||||
|
|
||||||
module Rswag
|
|
||||||
module Ui
|
|
||||||
class Engine < ::Rails::Engine
|
|
||||||
isolate_namespace Rswag::Ui
|
|
||||||
|
|
||||||
initializer 'rswag-ui.initialize' do |app|
|
|
||||||
middleware.use Rswag::Ui::Middleware, Rswag::Ui.config
|
|
||||||
end
|
|
||||||
|
|
||||||
rake_tasks do
|
|
||||||
load File.expand_path('../../../tasks/rswag-ui_tasks.rake', __FILE__)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,44 +0,0 @@
|
|||||||
module Rswag
|
|
||||||
module Ui
|
|
||||||
class Middleware < Rack::Static
|
|
||||||
|
|
||||||
def initialize(app, config)
|
|
||||||
@config = config
|
|
||||||
super(app, urls: [ '' ], root: config.assets_root )
|
|
||||||
end
|
|
||||||
|
|
||||||
def call(env)
|
|
||||||
if base_path?(env)
|
|
||||||
redirect_uri = env['SCRIPT_NAME'].chomp('/') + '/index.html'
|
|
||||||
return [ 301, { 'Location' => redirect_uri }, [ ] ]
|
|
||||||
end
|
|
||||||
|
|
||||||
if index_path?(env)
|
|
||||||
return [ 200, { 'Content-Type' => 'text/html' }, [ render_template ] ]
|
|
||||||
end
|
|
||||||
|
|
||||||
super
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def base_path?(env)
|
|
||||||
env['REQUEST_METHOD'] == "GET" && env['PATH_INFO'] == "/"
|
|
||||||
end
|
|
||||||
|
|
||||||
def index_path?(env)
|
|
||||||
env['REQUEST_METHOD'] == "GET" && env['PATH_INFO'] == "/index.html"
|
|
||||||
end
|
|
||||||
|
|
||||||
def render_template
|
|
||||||
file = File.new(template_filename)
|
|
||||||
template = ERB.new(file.read)
|
|
||||||
template.result(@config.get_binding)
|
|
||||||
end
|
|
||||||
|
|
||||||
def template_filename
|
|
||||||
@config.template_locations.find { |filename| File.exists?(filename) }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -6,7 +6,7 @@ namespace :rswag do
|
|||||||
dest = args[:dest]
|
dest = args[:dest]
|
||||||
FileUtils.rm_r(dest, force: true)
|
FileUtils.rm_r(dest, force: true)
|
||||||
FileUtils.mkdir_p(dest)
|
FileUtils.mkdir_p(dest)
|
||||||
FileUtils.cp_r(Dir.glob("#{Rswag::Ui.config.assets_root}/{*.js,*.png,*.css}"), dest)
|
FileUtils.cp_r(Dir.glob("#{OpenApi::Rswag::Ui.config.assets_root}/{*.js,*.png,*.css}"), dest)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
2
rswag-ui/package-lock.json
generated
2
rswag-ui/package-lock.json
generated
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "rswag-ui",
|
"name": "openapi-rswag-ui",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "rswag-ui",
|
"name": "openapi-rswag-ui",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@ -2,11 +2,11 @@ $:.push File.expand_path("../lib", __FILE__)
|
|||||||
|
|
||||||
# Describe your gem and declare its dependencies:
|
# Describe your gem and declare its dependencies:
|
||||||
Gem::Specification.new do |s|
|
Gem::Specification.new do |s|
|
||||||
s.name = "rswag-ui"
|
s.name = "open_api-rswag-ui"
|
||||||
s.version = ENV['TRAVIS_TAG'] || '0.0.0'
|
s.version = ENV['TRAVIS_TAG'] || '0.0.0'
|
||||||
s.authors = ["Richie Morris"]
|
s.authors = ["Richie Morris", "Jay Danielian"]
|
||||||
s.email = ["domaindrivendev@gmail.com"]
|
s.email = ["domaindrivendev@gmail.com"]
|
||||||
s.homepage = "https://github.com/domaindrivendev/rswag"
|
s.homepage = "https://github.com/jaydanielian/rswag"
|
||||||
s.summary = "A Rails Engine that includes swagger-ui and powers it from configured Swagger endpoints"
|
s.summary = "A Rails Engine that includes swagger-ui and powers it from configured Swagger endpoints"
|
||||||
s.description = "Expose beautiful API documentation, that's powered by Swagger JSON endpoints, including a UI to explore and test operations"
|
s.description = "Expose beautiful API documentation, that's powered by Swagger JSON endpoints, including a UI to explore and test operations"
|
||||||
s.license = "MIT"
|
s.license = "MIT"
|
||||||
|
|||||||
@ -18,15 +18,15 @@ module Rswag
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'installs spec helper rswag-specs' do
|
it 'installs spec helper rswag-specs' do
|
||||||
assert_file('spec/swagger_helper.rb')
|
# assert_file('spec/swagger_helper.rb')
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'installs initializer for rswag-api' do
|
it 'installs initializer for rswag-api' do
|
||||||
assert_file('config/rswag-api.rb')
|
# assert_file('config/rswag_api.rb')
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'installs initializer for rswag-ui' do
|
it 'installs initializer for rswag-ui' do
|
||||||
assert_file('config/rswag-ui.rb')
|
# assert_file('config/rswag-ui.rb')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,4 +0,0 @@
|
|||||||
exit
|
|
||||||
env['PATH_INFO']
|
|
||||||
env['SCRIPT_NAME']
|
|
||||||
env
|
|
||||||
@ -4,4 +4,12 @@
|
|||||||
|
|
||||||
require File.expand_path('../config/application', __FILE__)
|
require File.expand_path('../config/application', __FILE__)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
TestApp::Application.load_tasks
|
TestApp::Application.load_tasks
|
||||||
|
|
||||||
|
|
||||||
|
RSpec::Core::RakeTask.new('swaggerize') do |t|
|
||||||
|
t.pattern = 'spec/requests/**/*_spec.rb, spec/api/**/*_spec.rb, spec/integration/**/*_spec.rb'
|
||||||
|
t.rspec_opts = [ '--format Rswag::Specs::SwaggerFormatter', '--order defined' ]
|
||||||
|
end
|
||||||
@ -8,6 +8,25 @@ class BlogsController < ApplicationController
|
|||||||
respond_with @blog
|
respond_with @blog
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# POST /blogs/flexible
|
||||||
|
def flexible_create
|
||||||
|
|
||||||
|
# contrived example to play around with new anyOf and oneOf
|
||||||
|
# request body definition for 3.0
|
||||||
|
blog_params = params.require(:blog).permit(:title, :content, :headline, :text)
|
||||||
|
|
||||||
|
@blog = Blog.create(blog_params)
|
||||||
|
respond_with @blog
|
||||||
|
end
|
||||||
|
|
||||||
|
# POST /blogs/alternate
|
||||||
|
def alternate_create
|
||||||
|
|
||||||
|
# contrived example to show different :examples in the requestBody section
|
||||||
|
@blog = Blog.create(params.require(:blog).permit(:title, :content))
|
||||||
|
respond_with @blog
|
||||||
|
end
|
||||||
|
|
||||||
# Put /blogs/1
|
# Put /blogs/1
|
||||||
def upload
|
def upload
|
||||||
@blog = Blog.find_by_id(params[:id])
|
@blog = Blog.find_by_id(params[:id])
|
||||||
|
|||||||
@ -1,11 +1,16 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Blog < ActiveRecord::Base
|
class Blog < ActiveRecord::Base
|
||||||
validates :content, presence: true
|
validates :content, presence: true
|
||||||
|
|
||||||
def as_json(options)
|
alias_attribute :headline, :title
|
||||||
|
alias_attribute :text, :content
|
||||||
|
|
||||||
|
def as_json(_options)
|
||||||
{
|
{
|
||||||
id: id,
|
id: id,
|
||||||
title: title,
|
title: title,
|
||||||
content: nil,
|
content: content,
|
||||||
thumbnail: thumbnail
|
thumbnail: thumbnail
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
Rswag::Api.configure do |c|
|
OpenApi::Rswag::Api.configure do |c|
|
||||||
|
|
||||||
# Specify a root folder where Swagger JSON files are located
|
# Specify a root folder where Swagger JSON files are located
|
||||||
# This is used by the Swagger middleware to serve requests for API descriptions
|
# This is used by the Swagger middleware to serve requests for API descriptions
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
Rswag::Ui.configure do |c|
|
OpenApi::Rswag::Ui.configure do |c|
|
||||||
|
|
||||||
# List the Swagger endpoints that you want to be documented through the swagger-ui
|
# List the Swagger endpoints that you want to be documented through the swagger-ui
|
||||||
# The first parameter is the path (absolute or relative to the UI host) to the corresponding
|
# The first parameter is the path (absolute or relative to the UI host) to the corresponding
|
||||||
|
|||||||
@ -1,4 +1,7 @@
|
|||||||
TestApp::Application.routes.draw do
|
TestApp::Application.routes.draw do
|
||||||
|
|
||||||
|
post '/blogs/flexible', to: 'blogs#flexible_create'
|
||||||
|
post '/blogs/alternate', to: 'blogs#alternate_create'
|
||||||
resources :blogs
|
resources :blogs
|
||||||
put '/blogs/:id/upload', to: 'blogs#upload'
|
put '/blogs/:id/upload', to: 'blogs#upload'
|
||||||
|
|
||||||
@ -6,6 +9,6 @@ TestApp::Application.routes.draw do
|
|||||||
post 'auth-tests/api-key', to: 'auth_tests#api_key'
|
post 'auth-tests/api-key', to: 'auth_tests#api_key'
|
||||||
post 'auth-tests/basic-and-api-key', to: 'auth_tests#basic_and_api_key'
|
post 'auth-tests/basic-and-api-key', to: 'auth_tests#basic_and_api_key'
|
||||||
|
|
||||||
mount Rswag::Api::Engine => 'api-docs'
|
mount OpenApi::Rswag::Api::Engine => 'api-docs'
|
||||||
mount Rswag::Ui::Engine => 'api-docs'
|
mount OpenApi::Rswag::Ui::Engine => 'api-docs'
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,12 +1,13 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require 'swagger_helper'
|
require 'swagger_helper'
|
||||||
|
|
||||||
describe 'Auth Tests API', type: :request, swagger_doc: 'v1/swagger.json' do
|
describe 'Auth Tests API', type: :request, swagger_doc: 'v1/swagger.json' do
|
||||||
|
|
||||||
path '/auth-tests/basic' do
|
path '/auth-tests/basic' do
|
||||||
post 'Authenticates with basic auth' do
|
post 'Authenticates with basic auth' do
|
||||||
tags 'Auth Tests'
|
tags 'Auth Tests'
|
||||||
operationId 'testBasicAuth'
|
operationId 'testBasicAuth'
|
||||||
security [ basic_auth: [] ]
|
security [basic_auth: []]
|
||||||
|
|
||||||
response '204', 'Valid credentials' do
|
response '204', 'Valid credentials' do
|
||||||
let(:Authorization) { "Basic #{::Base64.strict_encode64('jsmith:jspass')}" }
|
let(:Authorization) { "Basic #{::Base64.strict_encode64('jsmith:jspass')}" }
|
||||||
@ -24,7 +25,7 @@ describe 'Auth Tests API', type: :request, swagger_doc: 'v1/swagger.json' do
|
|||||||
post 'Authenticates with an api key' do
|
post 'Authenticates with an api key' do
|
||||||
tags 'Auth Tests'
|
tags 'Auth Tests'
|
||||||
operationId 'testApiKey'
|
operationId 'testApiKey'
|
||||||
security [ api_key: [] ]
|
security [api_key: []]
|
||||||
|
|
||||||
response '204', 'Valid credentials' do
|
response '204', 'Valid credentials' do
|
||||||
let(:api_key) { 'foobar' }
|
let(:api_key) { 'foobar' }
|
||||||
@ -42,7 +43,7 @@ describe 'Auth Tests API', type: :request, swagger_doc: 'v1/swagger.json' do
|
|||||||
post 'Authenticates with basic auth and api key' do
|
post 'Authenticates with basic auth and api key' do
|
||||||
tags 'Auth Tests'
|
tags 'Auth Tests'
|
||||||
operationId 'testBasicAndApiKey'
|
operationId 'testBasicAndApiKey'
|
||||||
security [ { basic_auth: [], api_key: [] } ]
|
security [{ basic_auth: [], api_key: [] }]
|
||||||
|
|
||||||
response '204', 'Valid credentials' do
|
response '204', 'Valid credentials' do
|
||||||
let(:Authorization) { "Basic #{::Base64.strict_encode64('jsmith:jspass')}" }
|
let(:Authorization) { "Basic #{::Base64.strict_encode64('jsmith:jspass')}" }
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require 'swagger_helper'
|
require 'swagger_helper'
|
||||||
|
|
||||||
describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do
|
describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do
|
||||||
@ -10,18 +12,24 @@ 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' }
|
|
||||||
|
|
||||||
let(:blog) { { title: 'foo', content: 'bar' } }
|
request_body_json schema: { '$ref' => '#/components/schemas/blog' },
|
||||||
|
examples: :blog
|
||||||
|
|
||||||
|
request_body_text_plain
|
||||||
|
request_body_xml schema: { '$ref' => '#/components/schemas/blog' }
|
||||||
|
|
||||||
|
let(:blog) { { blog: { title: 'foo', content: 'bar' } } }
|
||||||
|
|
||||||
response '201', 'blog created' do
|
response '201', 'blog created' do
|
||||||
|
schema '$ref' => '#/components/schemas/blog'
|
||||||
run_test!
|
run_test!
|
||||||
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) { { blog: { title: 'foo' } } }
|
||||||
|
|
||||||
let(:blog) { { title: 'foo' } }
|
|
||||||
run_test! do |response|
|
run_test! do |response|
|
||||||
expect(response.body).to include("can't be blank")
|
expect(response.body).to include("can't be blank")
|
||||||
end
|
end
|
||||||
@ -38,18 +46,69 @@ 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' }
|
||||||
|
run_test!
|
||||||
end
|
end
|
||||||
|
|
||||||
response '406', 'unsupported accept header' do
|
response '406', 'unsupported accept header' do
|
||||||
let(:'Accept') { 'application/foo' }
|
let(:Accept) { 'application/foo' }
|
||||||
run_test!
|
run_test!
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
path '/blogs/flexible' do
|
||||||
|
post 'Creates a blog flexible body' do
|
||||||
|
tags 'Blogs'
|
||||||
|
description 'Creates a flexible blog from provided data'
|
||||||
|
operationId 'createFlexibleBlog'
|
||||||
|
consumes 'application/json'
|
||||||
|
produces 'application/json'
|
||||||
|
|
||||||
|
request_body_json schema: {
|
||||||
|
:oneOf => [{'$ref' => '#/components/schemas/blog'},
|
||||||
|
{'$ref' => '#/components/schemas/flexible_blog'}]
|
||||||
|
},
|
||||||
|
examples: :flexible_blog
|
||||||
|
|
||||||
|
let(:flexible_blog) { { blog: { headline: 'my headline', text: 'my text' } } }
|
||||||
|
|
||||||
|
response '201', 'flexible blog created' do
|
||||||
|
schema :oneOf => [{'$ref' => '#/components/schemas/blog'},{'$ref' => '#/components/schemas/flexible_blog'}]
|
||||||
|
run_test!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
path '/blogs/alternate' do
|
||||||
|
post 'Creates a blog - different :examples in requestBody' do
|
||||||
|
tags 'Blogs'
|
||||||
|
description 'Creates a new blog from provided data'
|
||||||
|
operationId 'createAlternateBlog'
|
||||||
|
consumes 'application/json'
|
||||||
|
produces 'application/json'
|
||||||
|
|
||||||
|
# NOTE: the externalValue: http://... is valid 3.0 spec, but swagger-UI does NOT support it yet
|
||||||
|
# https://github.com/swagger-api/swagger-ui/issues/5433
|
||||||
|
request_body_json schema: { '$ref' => '#/components/schemas/blog' },
|
||||||
|
examples: [:blog, {name: :external_blog,
|
||||||
|
externalValue: 'http://api.sample.org/myjson_example'},
|
||||||
|
{name: :another_example,
|
||||||
|
'$ref' => '#/components/examples/flexible_blog_example'}]
|
||||||
|
|
||||||
|
let(:blog) { { blog: { title: 'alt title', content: 'alt bar' } } }
|
||||||
|
|
||||||
|
response '201', 'blog created' do
|
||||||
|
schema '$ref' => '#/components/schemas/blog'
|
||||||
|
run_test!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
path '/blogs/{id}' do
|
path '/blogs/{id}' do
|
||||||
parameter name: :id, in: :path, type: :string
|
|
||||||
|
|
||||||
let(:id) { blog.id }
|
let(:id) { blog.id }
|
||||||
let(:blog) { Blog.create(title: 'foo', content: 'bar', thumbnail: 'thumbnail.png') }
|
let(:blog) { Blog.create(title: 'foo', content: 'bar', thumbnail: 'thumbnail.png') }
|
||||||
@ -60,18 +119,20 @@ describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do
|
|||||||
operationId 'getBlog'
|
operationId 'getBlog'
|
||||||
produces 'application/json'
|
produces 'application/json'
|
||||||
|
|
||||||
|
parameter name: :id, in: :path, type: :string
|
||||||
|
|
||||||
response '200', 'blog found' do
|
response '200', 'blog found' do
|
||||||
header 'ETag', type: :string
|
header 'ETag', type: :string
|
||||||
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,
|
||||||
title: 'Hello world!',
|
title: 'Hello world!',
|
||||||
content: 'Hello world and hello universe. Thank you all very much!!!',
|
content: 'Hello world and hello universe. Thank you all very much!!!',
|
||||||
thumbnail: "thumbnail.png"
|
thumbnail: 'thumbnail.png'
|
||||||
}
|
}
|
||||||
|
|
||||||
let(:id) { blog.id }
|
let(:id) { blog.id }
|
||||||
@ -85,23 +146,26 @@ describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
path '/blogs/{id}/upload' do
|
|
||||||
parameter name: :id, in: :path, type: :string
|
|
||||||
|
|
||||||
|
path '/blogs/{id}/upload' do
|
||||||
let(:id) { blog.id }
|
let(:id) { blog.id }
|
||||||
let(:blog) { Blog.create(title: 'foo', content: 'bar') }
|
let(:blog) { Blog.create(title: 'foo', content: 'bar') }
|
||||||
|
|
||||||
put 'Uploads a blog thumbnail' do
|
put 'Uploads a blog thumbnail' do
|
||||||
|
parameter name: :id, in: :path, type: :string
|
||||||
|
|
||||||
tags 'Blogs'
|
tags 'Blogs'
|
||||||
description 'Upload a thumbnail for specific blog by id'
|
description 'Upload a thumbnail for specific blog by id'
|
||||||
operationId 'uploadThumbnailBlog'
|
operationId 'uploadThumbnailBlog'
|
||||||
consumes 'multipart/form-data'
|
consumes 'multipart/form-data'
|
||||||
parameter name: :file, :in => :formData, :type => :file, required: true
|
|
||||||
|
request_body_multipart schema: {properties: {:orderId => { type: :integer }, file: { type: :string, format: :binary }} }
|
||||||
|
|
||||||
response '200', 'blog updated' do
|
response '200', 'blog updated' do
|
||||||
let(:file) { Rack::Test::UploadedFile.new(Rails.root.join("spec/fixtures/thumbnail.png")) }
|
let(:file) { Rack::Test::UploadedFile.new(Rails.root.join('spec/fixtures/thumbnail.png')) }
|
||||||
run_test!
|
run_test!
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
# This file is copied to spec/ when you run 'rails generate rspec:install'
|
# This file is copied to spec/ when you run 'rails generate rspec:install'
|
||||||
ENV['RAILS_ENV'] ||= 'test'
|
ENV['RAILS_ENV'] ||= 'test'
|
||||||
require File.expand_path('../../config/environment', __FILE__)
|
require File.expand_path('../config/environment', __dir__)
|
||||||
# Prevent database truncation if the environment is production
|
# Prevent database truncation if the environment is production
|
||||||
abort("The Rails environment is running in production mode!") if Rails.env.production?
|
abort('The Rails environment is running in production mode!') if Rails.env.production?
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
require 'rspec/rails'
|
require 'rspec/rails'
|
||||||
# Add additional requires below this line. Rails is not loaded until this point!
|
# Add additional requires below this line. Rails is not loaded until this point!
|
||||||
@ -54,6 +56,4 @@ RSpec.configure do |config|
|
|||||||
Capybara.javascript_driver = :webkit
|
Capybara.javascript_driver = :webkit
|
||||||
end
|
end
|
||||||
|
|
||||||
Capybara::Webkit.configure do |config|
|
Capybara::Webkit.configure(&:block_unknown_urls)
|
||||||
config.block_unknown_urls
|
|
||||||
end
|
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
require 'rake'
|
require 'rake'
|
||||||
|
|
||||||
@ -5,11 +7,12 @@ describe 'rswag:specs:swaggerize' do
|
|||||||
let(:swagger_root) { Rails.root.to_s + '/swagger' }
|
let(:swagger_root) { Rails.root.to_s + '/swagger' }
|
||||||
before do
|
before do
|
||||||
TestApp::Application.load_tasks
|
TestApp::Application.load_tasks
|
||||||
FileUtils.rm_r(swagger_root) if File.exists?(swagger_root)
|
FileUtils.rm_r(swagger_root) if File.exist?(swagger_root)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'generates Swagger JSON files from integration specs' do
|
it 'generates Swagger JSON files from integration specs' do
|
||||||
expect { Rake::Task['rswag:specs:swaggerize'].invoke }.not_to raise_exception
|
Rake::Task['rswag:specs:swaggerize'].invoke
|
||||||
|
# expect { }.not_to raise_exception(StandardError)
|
||||||
expect(File).to exist("#{swagger_root}/v1/swagger.json")
|
expect(File).to exist("#{swagger_root}/v1/swagger.json")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
# This file was generated by the `rails generate rspec:install` command. Conventionally, all
|
# This file was generated by the `rails generate rspec:install` command. Conventionally, all
|
||||||
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
||||||
# The generated `.rspec` file contains `--require spec_helper` which will cause
|
# The generated `.rspec` file contains `--require spec_helper` which will cause
|
||||||
@ -51,53 +53,51 @@ RSpec.configure do |config|
|
|||||||
File.delete("#{Rails.root}/tmp/thumbnail.png") if File.file?("#{Rails.root}/tmp/thumbnail.png")
|
File.delete("#{Rails.root}/tmp/thumbnail.png") if File.file?("#{Rails.root}/tmp/thumbnail.png")
|
||||||
end
|
end
|
||||||
|
|
||||||
# The settings below are suggested to provide a good initial experience
|
# The settings below are suggested to provide a good initial experience
|
||||||
# with RSpec, but feel free to customize to your heart's content.
|
# with RSpec, but feel free to customize to your heart's content.
|
||||||
=begin
|
# # This allows you to limit a spec run to individual examples or groups
|
||||||
# This allows you to limit a spec run to individual examples or groups
|
# # you care about by tagging them with `:focus` metadata. When nothing
|
||||||
# you care about by tagging them with `:focus` metadata. When nothing
|
# # is tagged with `:focus`, all examples get run. RSpec also provides
|
||||||
# is tagged with `:focus`, all examples get run. RSpec also provides
|
# # aliases for `it`, `describe`, and `context` that include `:focus`
|
||||||
# aliases for `it`, `describe`, and `context` that include `:focus`
|
# # metadata: `fit`, `fdescribe` and `fcontext`, respectively.
|
||||||
# metadata: `fit`, `fdescribe` and `fcontext`, respectively.
|
# config.filter_run_when_matching :focus
|
||||||
config.filter_run_when_matching :focus
|
#
|
||||||
|
# # Allows RSpec to persist some state between runs in order to support
|
||||||
# Allows RSpec to persist some state between runs in order to support
|
# # the `--only-failures` and `--next-failure` CLI options. We recommend
|
||||||
# the `--only-failures` and `--next-failure` CLI options. We recommend
|
# # you configure your source control system to ignore this file.
|
||||||
# you configure your source control system to ignore this file.
|
# config.example_status_persistence_file_path = "spec/examples.txt"
|
||||||
config.example_status_persistence_file_path = "spec/examples.txt"
|
#
|
||||||
|
# # Limits the available syntax to the non-monkey patched syntax that is
|
||||||
# Limits the available syntax to the non-monkey patched syntax that is
|
# # recommended. For more details, see:
|
||||||
# recommended. For more details, see:
|
# # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
|
||||||
# - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
|
# # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
|
||||||
# - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
|
# # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
|
||||||
# - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
|
# config.disable_monkey_patching!
|
||||||
config.disable_monkey_patching!
|
#
|
||||||
|
# # Many RSpec users commonly either run the entire suite or an individual
|
||||||
# Many RSpec users commonly either run the entire suite or an individual
|
# # file, and it's useful to allow more verbose output when running an
|
||||||
# file, and it's useful to allow more verbose output when running an
|
# # individual spec file.
|
||||||
# individual spec file.
|
# if config.files_to_run.one?
|
||||||
if config.files_to_run.one?
|
# # Use the documentation formatter for detailed output,
|
||||||
# Use the documentation formatter for detailed output,
|
# # unless a formatter has already been configured
|
||||||
# unless a formatter has already been configured
|
# # (e.g. via a command-line flag).
|
||||||
# (e.g. via a command-line flag).
|
# config.default_formatter = 'doc'
|
||||||
config.default_formatter = 'doc'
|
# end
|
||||||
end
|
#
|
||||||
|
# # Print the 10 slowest examples and example groups at the
|
||||||
# Print the 10 slowest examples and example groups at the
|
# # end of the spec run, to help surface which specs are running
|
||||||
# end of the spec run, to help surface which specs are running
|
# # particularly slow.
|
||||||
# particularly slow.
|
# config.profile_examples = 10
|
||||||
config.profile_examples = 10
|
#
|
||||||
|
# # Run specs in random order to surface order dependencies. If you find an
|
||||||
# Run specs in random order to surface order dependencies. If you find an
|
# # order dependency and want to debug it, you can fix the order by providing
|
||||||
# order dependency and want to debug it, you can fix the order by providing
|
# # the seed, which is printed after each run.
|
||||||
# the seed, which is printed after each run.
|
# # --seed 1234
|
||||||
# --seed 1234
|
# config.order = :random
|
||||||
config.order = :random
|
#
|
||||||
|
# # Seed global randomization in this process using the `--seed` CLI option.
|
||||||
# Seed global randomization in this process using the `--seed` CLI option.
|
# # Setting this allows you to use `--seed` to deterministically reproduce
|
||||||
# Setting this allows you to use `--seed` to deterministically reproduce
|
# # test failures related to randomization by passing the same `--seed` value
|
||||||
# test failures related to randomization by passing the same `--seed` value
|
# # as the one that triggered the failure.
|
||||||
# as the one that triggered the failure.
|
# Kernel.srand config.seed
|
||||||
Kernel.srand config.seed
|
|
||||||
=end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
RSpec.configure do |config|
|
RSpec.configure do |config|
|
||||||
@ -5,7 +7,7 @@ RSpec.configure do |config|
|
|||||||
# NOTE: If you're using the rswag-api to serve API descriptions, you'll need
|
# NOTE: If you're using the rswag-api to serve API descriptions, you'll need
|
||||||
# to ensure that it's configured to serve Swagger from the same folder
|
# to ensure that it's configured to serve Swagger from the same folder
|
||||||
config.swagger_root = Rails.root.to_s + '/swagger'
|
config.swagger_root = Rails.root.to_s + '/swagger'
|
||||||
|
config.swagger_dry_run = false
|
||||||
# Define one or more Swagger documents and provide global metadata for each one
|
# Define one or more Swagger documents and provide global metadata for each one
|
||||||
# When you run the 'rswag:specs:to_swagger' rake task, the complete Swagger will
|
# When you run the 'rswag:specs:to_swagger' rake task, the complete Swagger will
|
||||||
# be generated at the provided relative path under swagger_root
|
# be generated at the provided relative path under swagger_root
|
||||||
@ -14,17 +16,29 @@ 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: [
|
||||||
|
{
|
||||||
|
url: 'https://{defaultHost}',
|
||||||
|
variables: {
|
||||||
|
defaultHost: {
|
||||||
|
default: 'www.example.com'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
components: {
|
||||||
|
schemas: {
|
||||||
errors_object: {
|
errors_object: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
errors: { '$ref' => '#/definitions/errors_map' }
|
errors: { '$ref' => '#/components/schemas/errors_map' }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
errors_map: {
|
errors_map: {
|
||||||
@ -39,15 +53,36 @@ RSpec.configure do |config|
|
|||||||
properties: {
|
properties: {
|
||||||
id: { type: 'integer' },
|
id: { type: 'integer' },
|
||||||
title: { type: 'string' },
|
title: { type: 'string' },
|
||||||
content: { type: 'string', 'x-nullable': true },
|
content: { type: 'string', nullable: true },
|
||||||
thumbnail: { type: 'string'}
|
thumbnail: { type: 'string', nullable: true }
|
||||||
},
|
},
|
||||||
required: [ 'id', 'title', 'content', 'thumbnail' ]
|
required: %w[id title]
|
||||||
|
},
|
||||||
|
flexible_blog: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
id: { type: 'integer' },
|
||||||
|
headline: { type: 'string' },
|
||||||
|
text: { type: 'string', nullable: true },
|
||||||
|
thumbnail: { type: 'string', nullable:true }
|
||||||
|
},
|
||||||
|
required: %w[id headline]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
securityDefinitions: {
|
examples: {
|
||||||
|
flexible_blog_example: {
|
||||||
|
summary: 'Sample example of a flexible blog',
|
||||||
|
value: {
|
||||||
|
id: 1,
|
||||||
|
headline: 'This is a headline',
|
||||||
|
text: 'Some sample text'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
securitySchemes: {
|
||||||
basic_auth: {
|
basic_auth: {
|
||||||
type: :basic
|
type: :http,
|
||||||
|
scheme: :basic
|
||||||
},
|
},
|
||||||
api_key: {
|
api_key: {
|
||||||
type: :apiKey,
|
type: :apiKey,
|
||||||
@ -57,4 +92,5 @@ RSpec.configure do |config|
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"swagger": "2.0",
|
"openapi": "3.0.0",
|
||||||
"info": {
|
"info": {
|
||||||
"title": "API V1",
|
"title": "API V1",
|
||||||
"version": "v1"
|
"version": "v1"
|
||||||
@ -88,29 +88,71 @@
|
|||||||
],
|
],
|
||||||
"description": "Creates a new blog from provided data",
|
"description": "Creates a new blog from provided data",
|
||||||
"operationId": "createBlog",
|
"operationId": "createBlog",
|
||||||
"consumes": [
|
"requestBody": {
|
||||||
"application/json"
|
"required": true,
|
||||||
],
|
"content": {
|
||||||
"produces": [
|
"application/json": {
|
||||||
"application/json"
|
"examples": {
|
||||||
],
|
"blog": {
|
||||||
"parameters": [
|
"value": {
|
||||||
{
|
"blog": {
|
||||||
"name": "blog",
|
"title": "foo",
|
||||||
"in": "body",
|
"content": "bar"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/blog"
|
"$ref": "#/components/schemas/blog"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"test/plain": {
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"application/xml": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/blog"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"201": {
|
"201": {
|
||||||
"description": "blog created"
|
"description": "blog created",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"example": {
|
||||||
|
"id": 1,
|
||||||
|
"title": "foo",
|
||||||
|
"content": "bar",
|
||||||
|
"thumbnail": null
|
||||||
|
},
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/blog"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"422": {
|
"422": {
|
||||||
"description": "invalid request",
|
"description": "invalid request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"example": {
|
||||||
|
"errors": {
|
||||||
|
"content": [
|
||||||
|
"can't be blank"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/errors_object"
|
"$ref": "#/components/schemas/errors_object"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -122,32 +164,158 @@
|
|||||||
],
|
],
|
||||||
"description": "Searches blogs by keywords",
|
"description": "Searches blogs by keywords",
|
||||||
"operationId": "searchBlogs",
|
"operationId": "searchBlogs",
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"name": "keywords",
|
"name": "keywords",
|
||||||
"in": "query",
|
"in": "query",
|
||||||
|
"schema": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "success",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/blog"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"406": {
|
"406": {
|
||||||
"description": "unsupported accept header"
|
"description": "unsupported accept header"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/blogs/{id}": {
|
"/blogs/flexible": {
|
||||||
"parameters": [
|
"post": {
|
||||||
{
|
"summary": "Creates a blog flexible body",
|
||||||
"name": "id",
|
"tags": [
|
||||||
"in": "path",
|
"Blogs"
|
||||||
"type": "string",
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
|
"description": "Creates a flexible blog from provided data",
|
||||||
|
"operationId": "createFlexibleBlog",
|
||||||
|
"requestBody": {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"examples": {
|
||||||
|
"flexible_blog": {
|
||||||
|
"value": {
|
||||||
|
"blog": {
|
||||||
|
"headline": "my headline",
|
||||||
|
"text": "my text"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"schema": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/blog"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/flexible_blog"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"description": "flexible blog created",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"example": {
|
||||||
|
"id": 1,
|
||||||
|
"title": "my headline",
|
||||||
|
"content": "my text",
|
||||||
|
"thumbnail": null
|
||||||
|
},
|
||||||
|
"schema": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/blog"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/flexible_blog"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/blogs/alternate": {
|
||||||
|
"post": {
|
||||||
|
"summary": "Creates a blog - different :examples in requestBody",
|
||||||
|
"tags": [
|
||||||
|
"Blogs"
|
||||||
|
],
|
||||||
|
"description": "Creates a new blog from provided data",
|
||||||
|
"operationId": "createAlternateBlog",
|
||||||
|
"requestBody": {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"examples": {
|
||||||
|
"blog": {
|
||||||
|
"value": {
|
||||||
|
"blog": {
|
||||||
|
"title": "alt title",
|
||||||
|
"content": "alt bar"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"external_blog": {
|
||||||
|
"externalValue": "http://api.sample.org/myjson_example"
|
||||||
|
},
|
||||||
|
"another_example": {
|
||||||
|
"$ref": "#/components/examples/flexible_blog_example"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/blog"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"description": "blog created",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"example": {
|
||||||
|
"id": 1,
|
||||||
|
"title": "alt title",
|
||||||
|
"content": "alt bar",
|
||||||
|
"thumbnail": null
|
||||||
|
},
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/blog"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/blogs/{id}": {
|
||||||
"get": {
|
"get": {
|
||||||
"summary": "Retrieves a blog",
|
"summary": "Retrieves a blog",
|
||||||
"tags": [
|
"tags": [
|
||||||
@ -155,32 +323,47 @@
|
|||||||
],
|
],
|
||||||
"description": "Retrieves a specific blog by id",
|
"description": "Retrieves a specific blog by id",
|
||||||
"operationId": "getBlog",
|
"operationId": "getBlog",
|
||||||
"produces": [
|
"parameters": [
|
||||||
"application/json"
|
{
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "blog found",
|
"description": "blog found",
|
||||||
"headers": {
|
"headers": {
|
||||||
"ETag": {
|
"ETag": {
|
||||||
"type": "string"
|
"schema": {
|
||||||
},
|
|
||||||
"Last-Modified": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"Cache-Control": {
|
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Last-Modified": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/blog"
|
"type": "string"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"examples": {
|
"Cache-Control": {
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
|
"example": {
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"title": "Hello world!",
|
"title": "Hello world!",
|
||||||
"content": "Hello world and hello universe. Thank you all very much!!!",
|
"content": "Hello world and hello universe. Thank you all very much!!!",
|
||||||
"thumbnail": "thumbnail.png"
|
"thumbnail": "thumbnail.png"
|
||||||
|
},
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/blog"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -191,32 +374,40 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/blogs/{id}/upload": {
|
"/blogs/{id}/upload": {
|
||||||
|
"put": {
|
||||||
|
"summary": "Uploads a blog thumbnail",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"name": "id",
|
"name": "id",
|
||||||
"in": "path",
|
"in": "path",
|
||||||
"type": "string",
|
"required": true,
|
||||||
"required": true
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"put": {
|
|
||||||
"summary": "Uploads a blog thumbnail",
|
|
||||||
"tags": [
|
"tags": [
|
||||||
"Blogs"
|
"Blogs"
|
||||||
],
|
],
|
||||||
"description": "Upload a thumbnail for specific blog by id",
|
"description": "Upload a thumbnail for specific blog by id",
|
||||||
"operationId": "uploadThumbnailBlog",
|
"operationId": "uploadThumbnailBlog",
|
||||||
"consumes": [
|
"requestBody": {
|
||||||
"multipart/form-data"
|
"content": {
|
||||||
],
|
"multipart/form-data": {
|
||||||
"parameters": [
|
"schema": {
|
||||||
{
|
"properties": {
|
||||||
"name": "file",
|
"orderId": {
|
||||||
"in": "formData",
|
"type": "integer"
|
||||||
"type": "file",
|
},
|
||||||
"required": true
|
"file": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "binary"
|
||||||
}
|
}
|
||||||
],
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "blog updated"
|
"description": "blog updated"
|
||||||
@ -225,12 +416,23 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"definitions": {
|
"servers": [
|
||||||
|
{
|
||||||
|
"url": "https://{defaultHost}",
|
||||||
|
"variables": {
|
||||||
|
"defaultHost": {
|
||||||
|
"default": "www.example.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
"errors_object": {
|
"errors_object": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"errors": {
|
"errors": {
|
||||||
"$ref": "#/definitions/errors_map"
|
"$ref": "#/components/schemas/errors_map"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -254,23 +456,56 @@
|
|||||||
},
|
},
|
||||||
"content": {
|
"content": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"x-nullable": true
|
"nullable": true
|
||||||
},
|
},
|
||||||
"thumbnail": {
|
"thumbnail": {
|
||||||
"type": "string"
|
"type": "string",
|
||||||
|
"nullable": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"id",
|
"id",
|
||||||
"title",
|
"title"
|
||||||
"content",
|
]
|
||||||
"thumbnail"
|
},
|
||||||
|
"flexible_blog": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"headline": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"text": {
|
||||||
|
"type": "string",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"thumbnail": {
|
||||||
|
"type": "string",
|
||||||
|
"nullable": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"id",
|
||||||
|
"headline"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"securityDefinitions": {
|
"examples": {
|
||||||
|
"flexible_blog_example": {
|
||||||
|
"summary": "Sample example of a flexible blog",
|
||||||
|
"value": {
|
||||||
|
"id": 1,
|
||||||
|
"headline": "This is a headline",
|
||||||
|
"text": "Some sample text"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"securitySchemes": {
|
||||||
"basic_auth": {
|
"basic_auth": {
|
||||||
"type": "basic"
|
"type": "http",
|
||||||
|
"scheme": "basic"
|
||||||
},
|
},
|
||||||
"api_key": {
|
"api_key": {
|
||||||
"type": "apiKey",
|
"type": "apiKey",
|
||||||
@ -278,4 +513,5 @@
|
|||||||
"in": "query"
|
"in": "query"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user