mirror of
https://github.com/ditkrg/rswag.git
synced 2026-01-25 15:22:56 +00:00
Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
77d4cbe0ea | ||
|
|
32a7ab8234 | ||
|
|
95b009a72f | ||
|
|
471dff5e34 | ||
|
|
99be8135f7 | ||
|
|
8315eda8b2 | ||
|
|
e1fe9f3239 | ||
|
|
64b0de494f | ||
|
|
9d4069bcfe | ||
|
|
17a6cd13c4 | ||
|
|
0b0acfe4c7 | ||
|
|
5ea97a4278 | ||
|
|
5cf376891a | ||
|
|
10dd37896f | ||
|
|
3506fee3d0 | ||
|
|
5df130922f | ||
|
|
5a19cd2373 | ||
|
|
7025ec0063 | ||
|
|
23f4120fe3 | ||
|
|
312f68ae72 | ||
|
|
63e0e53104 | ||
|
|
4d675056c1 | ||
|
|
de09df59e1 | ||
|
|
b81b2927be | ||
|
|
f1850bc6d0 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,3 +2,4 @@
|
||||
**/*/log
|
||||
**/*/*.gem
|
||||
**/*/*.sqlite3
|
||||
**/*/public/assets
|
||||
|
||||
@@ -7,4 +7,8 @@ env:
|
||||
- "RAILS_VERSION=5.0.0"
|
||||
cache: bundler
|
||||
install: bundle update
|
||||
before_script:
|
||||
- "export DISPLAY=:99.0"
|
||||
- "sh -e /etc/init.d/xvfb start"
|
||||
- sleep 3 # give xvfb some time to start
|
||||
script: ./ci/test.sh
|
||||
|
||||
7
Gemfile
7
Gemfile
@@ -25,5 +25,12 @@ group :test do
|
||||
gem 'test-unit'
|
||||
gem 'rspec-rails'
|
||||
gem 'generator_spec'
|
||||
gem 'capybara'
|
||||
gem 'capybara-webkit'
|
||||
gem 'rswag-specs', path: './rswag-specs'
|
||||
end
|
||||
|
||||
group :assets do
|
||||
gem 'uglifier'
|
||||
gem 'therubyracer'
|
||||
end
|
||||
|
||||
33
Gemfile.lock
33
Gemfile.lock
@@ -1,13 +1,13 @@
|
||||
PATH
|
||||
remote: ./rswag-api
|
||||
specs:
|
||||
rswag-api (1.0.0)
|
||||
rswag-api (1.2.0)
|
||||
rails (>= 3.1, < 5.1)
|
||||
|
||||
PATH
|
||||
remote: ./rswag-specs
|
||||
specs:
|
||||
rswag-specs (1.0.0)
|
||||
rswag-specs (1.2.0)
|
||||
json-schema (~> 2.2)
|
||||
rails (>= 3.1, < 5.1)
|
||||
rspec-rails (>= 2.14, < 4)
|
||||
@@ -15,7 +15,7 @@ PATH
|
||||
PATH
|
||||
remote: ./rswag-ui
|
||||
specs:
|
||||
rswag-ui (1.0.0)
|
||||
rswag-ui (1.2.0)
|
||||
rails (>= 3.1, < 5.1)
|
||||
|
||||
GEM
|
||||
@@ -51,8 +51,19 @@ GEM
|
||||
addressable (2.4.0)
|
||||
arel (3.0.3)
|
||||
builder (3.0.4)
|
||||
capybara (2.10.1)
|
||||
addressable
|
||||
mime-types (>= 1.16)
|
||||
nokogiri (>= 1.3.3)
|
||||
rack (>= 1.0.0)
|
||||
rack-test (>= 0.5.4)
|
||||
xpath (~> 2.0)
|
||||
capybara-webkit (1.1.0)
|
||||
capybara (~> 2.0, >= 2.0.2)
|
||||
json
|
||||
diff-lcs (1.2.5)
|
||||
erubis (2.7.0)
|
||||
execjs (2.7.0)
|
||||
generator_spec (0.9.3)
|
||||
activesupport (>= 3.0.0)
|
||||
railties (>= 3.0.0)
|
||||
@@ -62,11 +73,15 @@ GEM
|
||||
json (1.8.3)
|
||||
json-schema (2.7.0)
|
||||
addressable (>= 2.4)
|
||||
libv8 (3.16.14.15)
|
||||
mail (2.5.4)
|
||||
mime-types (~> 1.16)
|
||||
treetop (~> 1.4.8)
|
||||
mime-types (1.25.1)
|
||||
mini_portile2 (2.1.0)
|
||||
multi_json (1.12.1)
|
||||
nokogiri (1.6.8.1)
|
||||
mini_portile2 (~> 2.1.0)
|
||||
polyglot (0.3.5)
|
||||
power_assert (0.3.1)
|
||||
rack (1.4.7)
|
||||
@@ -94,6 +109,7 @@ GEM
|
||||
rake (11.3.0)
|
||||
rdoc (3.12.2)
|
||||
json (~> 1.4)
|
||||
ref (2.0.0)
|
||||
rspec-core (3.5.4)
|
||||
rspec-support (~> 3.5.0)
|
||||
rspec-expectations (3.5.0)
|
||||
@@ -124,17 +140,26 @@ GEM
|
||||
railties (~> 3.0)
|
||||
test-unit (3.2.1)
|
||||
power_assert
|
||||
therubyracer (0.12.2)
|
||||
libv8 (~> 3.16.14.0)
|
||||
ref
|
||||
thor (0.19.1)
|
||||
tilt (1.4.1)
|
||||
treetop (1.4.15)
|
||||
polyglot
|
||||
polyglot (>= 0.3.1)
|
||||
tzinfo (0.3.51)
|
||||
uglifier (3.0.2)
|
||||
execjs (>= 0.3.0, < 3)
|
||||
xpath (2.0.0)
|
||||
nokogiri (~> 1.3)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
capybara
|
||||
capybara-webkit
|
||||
generator_spec
|
||||
rails (= 3.2.22)
|
||||
rspec-rails
|
||||
@@ -144,3 +169,5 @@ DEPENDENCIES
|
||||
sqlite3
|
||||
strong_parameters
|
||||
test-unit
|
||||
therubyracer
|
||||
uglifier
|
||||
|
||||
57
README.md
57
README.md
@@ -127,7 +127,7 @@ In addition to paths, operations and responses, Swagger also supports global API
|
||||
# spec/swagger_helper.rb
|
||||
RSpec.configure do |config|
|
||||
config.swagger_root = Rails.root.to_s + '/swagger'
|
||||
|
||||
|
||||
config.swagger_docs = {
|
||||
'v1/swagger.json' => {
|
||||
swagger: '2.0',
|
||||
@@ -171,8 +171,8 @@ The steps described above will get you up and running with minimal setup. Howeve
|
||||
|Gem|Description|Added/Updated|
|
||||
|---------|-----------|-------------|
|
||||
|__rswag-specs__|Swagger-based DSL for rspec & accompanying rake task for generating Swagger files|_spec/swagger_helper.rb_|
|
||||
|__rswag-api__ |Rails Engine that exposes the Swagger files as JSON endpoints|_config/initializers/rswag-api.rb, config/routes.rb_|
|
||||
|__rswag-ui__ |Rails Engine that includes [swagger-ui](https://github.com/swagger-api/swagger-ui) and powers it from the Swagger endpoints|_config/initializers/rswag-ui.rb, config/routes.rb_|
|
||||
|__rswag-api__ |Rails Engine that exposes your Swagger files as JSON endpoints|_config/initializers/rswag-api.rb, config/routes.rb_|
|
||||
|__rswag-ui__ |Rails Engine that includes [swagger-ui](https://github.com/swagger-api/swagger-ui) and powers it from your Swagger endpoints|_config/initializers/rswag-ui.rb, config/routes.rb_|
|
||||
|
||||
### Output Location for Generated Swagger Files ###
|
||||
|
||||
@@ -224,25 +224,66 @@ describe 'Blogs API' do
|
||||
path '/blogs' do
|
||||
|
||||
post 'Creates a blog' do
|
||||
|
||||
|
||||
response 422, 'invalid request' do
|
||||
schema '$ref' => '#/definitions/errors_object'
|
||||
...
|
||||
end
|
||||
|
||||
|
||||
# spec/integration/comments_spec.rb
|
||||
describe 'Blogs API' do
|
||||
|
||||
path '/blogs/{blog_id}/comments' do
|
||||
|
||||
post 'Creates a comment' do
|
||||
|
||||
|
||||
response 422, 'invalid request' do
|
||||
schema '$ref' => '#/definitions/errors_object'
|
||||
...
|
||||
end
|
||||
```
|
||||
|
||||
### Response headers ###
|
||||
|
||||
In Rswag, you could use `header` method inside the response block to specify header objects for this response. Rswag will validate your response headers with those header objects and inject them into the generated swagger file:
|
||||
|
||||
```ruby
|
||||
# spec/integration/comments_spec.rb
|
||||
describe 'Blogs API' do
|
||||
|
||||
path '/blogs/{blog_id}/comments' do
|
||||
|
||||
post 'Creates a comment' do
|
||||
|
||||
response 422, 'invalid request' do
|
||||
header 'X-Rate-Limit-Limit', type: :integer, description: 'The number of allowed requests in the current period'
|
||||
header 'X-Rate-Limit-Remaining', type: :integer, description: 'The number of remaining requests in the current period'
|
||||
...
|
||||
end
|
||||
```
|
||||
|
||||
### Response examples ###
|
||||
|
||||
You can provide custom response examples to the generated swagger file by calling the method `examples` inside the response block:
|
||||
|
||||
```ruby
|
||||
# spec/integration/blogs_spec.rb
|
||||
describe 'Blogs API' do
|
||||
|
||||
path '/blogs/{blog_id}' do
|
||||
|
||||
get 'Retrieves a blog' do
|
||||
|
||||
response 200, 'blog found' do
|
||||
examples 'application/json' => {
|
||||
id: 1,
|
||||
title: 'Hello world!',
|
||||
content: '...'
|
||||
}
|
||||
...
|
||||
end
|
||||
```
|
||||
|
||||
### Route Prefix for Swagger JSON Endpoints ###
|
||||
|
||||
The functionality to expose Swagger files, such as those generated by rswag-specs, as JSON endpoints is implemented as a Rails Engine. As with any Engine, you can change it's mount prefix in _routes.rb_:
|
||||
@@ -273,7 +314,7 @@ end
|
||||
```
|
||||
|
||||
__NOTE__: If you're using rswag-specs to generate Swagger files, you'll want to ensure they both use the same <swagger_root>. The reason for separate settings is to maintain independence between the two gems. For example, you could install rswag-api independently and create your Swagger files manually.
|
||||
|
||||
|
||||
### Dynamic Values for Swagger JSON ##
|
||||
|
||||
There may be cases where you need to add dynamic values to the Swagger JSON that's returned by rswag-api. For example, you may want to provide an explicit host name. Rather than hardcoding it, you can configure a filter that's executed prior to serializing every Swagger document:
|
||||
@@ -287,7 +328,7 @@ end
|
||||
```
|
||||
|
||||
Note how the filter is passed the rack env for the current request. This provides a lot of flexibilty. For example, you can assign the "host" property (as shown) or you could inspect session information or an Authoriation header and remove operations based on user permissions.
|
||||
|
||||
|
||||
### Enable Swagger Endpoints for swagger-ui ###
|
||||
|
||||
You can update the _rswag-ui.rb_ initializer, installed with rswag-ui, to specify which Swagger endpoints should be available to power the documentation UI. If you're using rswag-api, these should correspond to the Swagger endpoints it exposes. When the UI is rendered, you'll see these listed in a drop-down to the top right of the page:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
module Rswag
|
||||
module Api
|
||||
VERSION = '1.0.1'
|
||||
VERSION = '1.2.0'
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,6 +2,7 @@ require 'rspec/core'
|
||||
require 'rswag/specs/version'
|
||||
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
|
||||
@@ -15,6 +16,10 @@ module Rswag
|
||||
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
|
||||
|
||||
37
rswag-specs/lib/rswag/specs/configuration.rb
Normal file
37
rswag-specs/lib/rswag/specs/configuration.rb
Normal file
@@ -0,0 +1,37 @@
|
||||
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 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
|
||||
@@ -2,9 +2,9 @@ module Rswag
|
||||
module Specs
|
||||
module ExampleGroupHelpers
|
||||
|
||||
def path(path, &block)
|
||||
api_metadata = { path: path}
|
||||
describe(path, api_metadata, &block)
|
||||
def path(template, &block)
|
||||
api_metadata = { path_item: { template: template } }
|
||||
describe(template, api_metadata, &block)
|
||||
end
|
||||
|
||||
[ :get, :post, :patch, :put, :delete, :head ].each do |verb|
|
||||
@@ -25,7 +25,7 @@ module Rswag
|
||||
# functionality while also setting the appropriate metadata if applicable
|
||||
def description(value=nil)
|
||||
return super() if value.nil?
|
||||
metadata[:operation][:description] = value
|
||||
metadata[:operation][:description] = value
|
||||
end
|
||||
|
||||
# These are array properties - note the splat operator
|
||||
@@ -37,8 +37,14 @@ module Rswag
|
||||
|
||||
def parameter(attributes)
|
||||
attributes[:required] = true if attributes[:in].to_sym == :path
|
||||
metadata[:operation][:parameters] ||= []
|
||||
metadata[:operation][:parameters] << attributes
|
||||
|
||||
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, &block)
|
||||
@@ -55,6 +61,14 @@ module Rswag
|
||||
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!
|
||||
# NOTE: rspec 2.x support
|
||||
if RSPEC_VERSION < 3
|
||||
|
||||
@@ -6,7 +6,8 @@ module Rswag
|
||||
module ExampleHelpers
|
||||
|
||||
def submit_request(api_metadata)
|
||||
factory = RequestFactory.new(api_metadata, global_metadata(api_metadata[:swagger_doc]))
|
||||
global_metadata = rswag_config.get_swagger_doc(api_metadata[:swagger_doc])
|
||||
factory = RequestFactory.new(api_metadata, global_metadata)
|
||||
|
||||
if RAILS_VERSION < 5
|
||||
send(
|
||||
@@ -28,15 +29,15 @@ module Rswag
|
||||
end
|
||||
|
||||
def assert_response_matches_metadata(api_metadata)
|
||||
validator = ResponseValidator.new(api_metadata, global_metadata(api_metadata[:swagger_doc]))
|
||||
global_metadata = rswag_config.get_swagger_doc(api_metadata[:swagger_doc])
|
||||
validator = ResponseValidator.new(api_metadata, global_metadata)
|
||||
validator.validate!(response)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def global_metadata(swagger_doc)
|
||||
swagger_docs = ::RSpec.configuration.swagger_docs
|
||||
swagger_doc.nil? ? swagger_docs.values.first : swagger_docs[swagger_doc]
|
||||
def rswag_config
|
||||
::Rswag::Specs.config
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
25
rswag-specs/lib/rswag/specs/extended_schema.rb
Normal file
25
rswag-specs/lib/rswag/specs/extended_schema.rb
Normal file
@@ -0,0 +1,25 @@
|
||||
require 'json-schema'
|
||||
|
||||
module Rswag
|
||||
module Specs
|
||||
class ExtendedSchema < JSON::Schema::Validator
|
||||
|
||||
def initialize
|
||||
super
|
||||
extend_schema_definition("http://json-schema.org/draft-04/schema#")
|
||||
@attributes['type'] = ExtendedTypeAttribute
|
||||
@uri = URI.parse('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
|
||||
@@ -11,7 +11,7 @@ module Rswag
|
||||
end
|
||||
|
||||
def build_fullpath(example)
|
||||
@api_metadata[:path].dup.tap do |t|
|
||||
@api_metadata[:path_item][:template].dup.tap do |t|
|
||||
t.prepend(@global_metadata[:basePath] || '')
|
||||
parameters_in(:path).each { |p| t.gsub!("{#{p[:name]}}", example.send(p[:name]).to_s) }
|
||||
t.concat(build_query_string(example))
|
||||
@@ -44,7 +44,13 @@ module Rswag
|
||||
private
|
||||
|
||||
def parameters_in(location)
|
||||
(@api_metadata[:operation][:parameters] || [])
|
||||
path_item_params = @api_metadata[:path_item][:parameters] || []
|
||||
operation_params = @api_metadata[:operation][:parameters] || []
|
||||
applicable_params = operation_params
|
||||
.concat(path_item_params)
|
||||
.uniq { |p| p[:name] } # operation params should override path_item params
|
||||
|
||||
applicable_params
|
||||
.map { |p| p['$ref'] ? resolve_parameter(p['$ref']) : p } # resolve any references
|
||||
.concat(resolve_api_key_parameters)
|
||||
.select { |p| p[:in] == location }
|
||||
@@ -59,9 +65,13 @@ module Rswag
|
||||
|
||||
def resolve_api_key_parameters
|
||||
@api_key_params ||= begin
|
||||
global_requirements = (@global_metadata[:security] || {})
|
||||
requirements = global_requirements.merge(@api_metadata[:operation][:security] || {})
|
||||
definitions = (@global_metadata[:securityDefinitions] || {}).slice(*requirements.keys)
|
||||
# First figure out the security requirement applicable to the operation
|
||||
global_requirements = (@global_metadata[:security] || [] ).map { |r| r.keys.first }
|
||||
operation_requirements = (@api_metadata[:operation][:security] || [] ).map { |r| r.keys.first }
|
||||
requirements = global_requirements | operation_requirements
|
||||
|
||||
# Then obtain the scheme definitions for those requirements
|
||||
definitions = (@global_metadata[:securityDefinitions] || {}).slice(*requirements)
|
||||
definitions.values.select { |d| d[:type] == :apiKey }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
require 'active_support/core_ext/hash/slice'
|
||||
require 'json-schema'
|
||||
require 'rswag/specs/extended_schema'
|
||||
|
||||
module Rswag
|
||||
module Specs
|
||||
@@ -11,6 +13,7 @@ module Rswag
|
||||
|
||||
def validate!(response)
|
||||
validate_code!(response.code)
|
||||
validate_headers!(response.headers)
|
||||
validate_body!(response.body)
|
||||
end
|
||||
|
||||
@@ -22,13 +25,26 @@ module Rswag
|
||||
end
|
||||
end
|
||||
|
||||
def validate_headers!(headers)
|
||||
header_schema = @api_metadata[:response][:headers]
|
||||
return if header_schema.nil?
|
||||
|
||||
header_schema.keys.each do |header_name|
|
||||
raise UnexpectedResponse, "Expected response header #{header_name} to be present" if headers[header_name.to_s].nil?
|
||||
end
|
||||
end
|
||||
|
||||
def validate_body!(body)
|
||||
schema = @api_metadata[:response][:schema]
|
||||
return if schema.nil?
|
||||
response_schema = @api_metadata[:response][:schema]
|
||||
return if response_schema.nil?
|
||||
|
||||
begin
|
||||
JSON::Validator.validate!(schema.merge(@global_metadata), body)
|
||||
validation_schema = response_schema
|
||||
.merge('$schema' => 'http://tempuri.org/rswag/specs/extended_schema')
|
||||
.merge(@global_metadata.slice(:definitions))
|
||||
JSON::Validator.validate!(validation_schema, body)
|
||||
rescue JSON::Schema::ValidationError => ex
|
||||
raise UnexpectedResponse, "Expected response body to match schema: #{ex.message}"
|
||||
raise UnexpectedResponse, "Expected response body to match schema: #{ex.message}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
require 'active_support/core_ext/hash/deep_merge'
|
||||
require 'rspec/core/formatters/base_text_formatter'
|
||||
require 'swagger_helper'
|
||||
|
||||
module Rswag
|
||||
@@ -11,12 +10,9 @@ module Rswag
|
||||
::RSpec::Core::Formatters.register self, :example_group_finished, :stop
|
||||
end
|
||||
|
||||
def initialize(output)
|
||||
def initialize(output, config = Rswag::Specs.config)
|
||||
@output = output
|
||||
@swagger_root = ::RSpec.configuration.swagger_root
|
||||
raise ConfigurationError, 'Missing swagger_root. See swagger_helper.rb' if @swagger_root.nil?
|
||||
@swagger_docs = ::RSpec.configuration.swagger_docs || []
|
||||
raise ConfigurationError, 'Missing swagger_docs. See swagger_helper.rb' if @swagger_docs.empty?
|
||||
@config = config
|
||||
|
||||
@output.puts 'Generating Swagger docs ...'
|
||||
end
|
||||
@@ -30,13 +26,13 @@ module Rswag
|
||||
end
|
||||
|
||||
return unless metadata.has_key?(:response)
|
||||
swagger_doc = get_swagger_doc(metadata[:swagger_doc])
|
||||
swagger_doc = @config.get_swagger_doc(metadata[:swagger_doc])
|
||||
swagger_doc.deep_merge!(metadata_to_swagger(metadata))
|
||||
end
|
||||
|
||||
def stop(notification=nil)
|
||||
@swagger_docs.each do |url_path, doc|
|
||||
file_path = File.join(@swagger_root, url_path)
|
||||
@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)
|
||||
|
||||
@@ -50,30 +46,22 @@ module Rswag
|
||||
|
||||
private
|
||||
|
||||
def get_swagger_doc(tag)
|
||||
return @swagger_docs.values.first if tag.nil?
|
||||
raise ConfigurationError, "Unknown swagger_doc '#{tag}'" unless @swagger_docs.has_key?(tag)
|
||||
@swagger_docs[tag]
|
||||
end
|
||||
|
||||
def metadata_to_swagger(metadata)
|
||||
response_code = metadata[:response][:code]
|
||||
response = metadata[:response].reject { |k,v| k == :code }
|
||||
|
||||
verb = metadata[:operation][:verb]
|
||||
operation = metadata[:operation]
|
||||
.reject { |k,v| k == :verb }
|
||||
.merge(responses: { response_code => response })
|
||||
|
||||
{
|
||||
paths: {
|
||||
metadata[:path] => {
|
||||
verb => operation
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
|
||||
class ConfigurationError < StandardError; end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
module Rswag
|
||||
module Specs
|
||||
VERSION = '1.0.1'
|
||||
VERSION = '1.2.0'
|
||||
end
|
||||
end
|
||||
|
||||
77
rswag-specs/spec/rswag/specs/configuration_spec.rb
Normal file
77
rswag-specs/spec/rswag/specs/configuration_spec.rb
Normal file
@@ -0,0 +1,77 @@
|
||||
require 'rswag/specs/configuration'
|
||||
|
||||
module Rswag
|
||||
module Specs
|
||||
|
||||
describe Configuration do
|
||||
subject { described_class.new(rspec_config) }
|
||||
|
||||
let(:rspec_config) { OpenStruct.new(swagger_root: swagger_root, swagger_docs: swagger_docs) }
|
||||
let(:swagger_root) { 'foobar' }
|
||||
let(:swagger_docs) do
|
||||
{
|
||||
'v1/swagger.json' => { info: { title: 'v1' } },
|
||||
'v2/swagger.json' => { info: { title: 'v2' } }
|
||||
}
|
||||
end
|
||||
|
||||
describe '#swagger_root' do
|
||||
let(:response) { subject.swagger_root }
|
||||
|
||||
context 'provided in rspec config' do
|
||||
it { expect(response).to eq('foobar') }
|
||||
end
|
||||
|
||||
context 'not provided' do
|
||||
let(:swagger_root) { nil }
|
||||
it { expect { response }.to raise_error ConfigurationError }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#swagger_docs' do
|
||||
let(:response) { subject.swagger_docs }
|
||||
|
||||
context 'provided in rspec config' do
|
||||
it { expect(response).to be_an_instance_of(Hash) }
|
||||
end
|
||||
|
||||
context 'not provided' do
|
||||
let(:swagger_docs) { nil }
|
||||
it { expect { response }.to raise_error ConfigurationError }
|
||||
end
|
||||
|
||||
context 'provided but empty' do
|
||||
let(:swagger_docs) { {} }
|
||||
it { expect { response }.to raise_error ConfigurationError }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#get_swagger_doc(tag=nil)' do
|
||||
let(:swagger_doc) { subject.get_swagger_doc(tag) }
|
||||
|
||||
context 'no tag provided' do
|
||||
let(:tag) { nil }
|
||||
|
||||
it 'returns the first doc in rspec config' do
|
||||
expect(swagger_doc).to eq(info: { title: 'v1' })
|
||||
end
|
||||
end
|
||||
|
||||
context 'tag provided' do
|
||||
context 'matching doc' do
|
||||
let(:tag) { 'v2/swagger.json' }
|
||||
|
||||
it 'returns the matching doc in rspec config' do
|
||||
expect(swagger_doc).to eq(info: { title: 'v2' })
|
||||
end
|
||||
end
|
||||
|
||||
context 'no matching doc' do
|
||||
let(:tag) { 'foobar' }
|
||||
it { expect { swagger_doc }.to raise_error ConfigurationError }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -5,20 +5,21 @@ module Rswag
|
||||
|
||||
describe ExampleGroupHelpers do
|
||||
subject { double('example_group') }
|
||||
let(:api_metadata) { {} }
|
||||
|
||||
before do
|
||||
subject.extend ExampleGroupHelpers
|
||||
allow(subject).to receive(:describe)
|
||||
allow(subject).to receive(:context)
|
||||
allow(subject).to receive(:metadata).and_return(api_metadata)
|
||||
end
|
||||
let(:api_metadata) { {} }
|
||||
|
||||
describe '#path(path)' do
|
||||
before { subject.path('/blogs') }
|
||||
|
||||
it "delegates to 'describe' with 'path' metadata" do
|
||||
expect(subject).to have_received(:describe).with(
|
||||
'/blogs', path: '/blogs'
|
||||
'/blogs', path_item: { template: '/blogs' }
|
||||
)
|
||||
end
|
||||
end
|
||||
@@ -34,7 +35,6 @@ module Rswag
|
||||
end
|
||||
|
||||
describe '#tags|description|operationId|consumes|produces|schemes|deprecated(value)' do
|
||||
let(:api_metadata) { { operation: {} } }
|
||||
before do
|
||||
subject.tags('Blogs', 'Admin')
|
||||
subject.description('Some description')
|
||||
@@ -44,6 +44,7 @@ module Rswag
|
||||
subject.schemes('http', 'https')
|
||||
subject.deprecated(true)
|
||||
end
|
||||
let(:api_metadata) { { operation: {} } }
|
||||
|
||||
it "adds to the 'operation' metadata" do
|
||||
expect(api_metadata[:operation]).to match(
|
||||
@@ -59,7 +60,6 @@ module Rswag
|
||||
end
|
||||
|
||||
describe '#tags|description|operationId|consumes|produces|schemes|deprecated|security(value)' do
|
||||
let(:api_metadata) { { operation: {} } }
|
||||
before do
|
||||
subject.tags('Blogs', 'Admin')
|
||||
subject.description('Some description')
|
||||
@@ -70,6 +70,7 @@ module Rswag
|
||||
subject.deprecated(true)
|
||||
subject.security(api_key: [])
|
||||
end
|
||||
let(:api_metadata) { { operation: {} } }
|
||||
|
||||
it "adds to the 'operation' metadata" do
|
||||
expect(api_metadata[:operation]).to match(
|
||||
@@ -86,10 +87,21 @@ module Rswag
|
||||
end
|
||||
|
||||
describe '#parameter(attributes)' do
|
||||
let(:api_metadata) { { operation: {} } }
|
||||
|
||||
context 'always' do
|
||||
context "when called at the 'path' level" do
|
||||
before { subject.parameter(name: :blog, in: :body, schema: { type: 'object' }) }
|
||||
let(:api_metadata) { { path_item: {} } } # i.e. operation not defined yet
|
||||
|
||||
it "adds to the 'path_item parameters' metadata" do
|
||||
expect(api_metadata[:path_item][:parameters]).to match(
|
||||
[ name: :blog, in: :body, schema: { type: 'object' } ]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context "when called at the 'operation' level" do
|
||||
before { subject.parameter(name: :blog, in: :body, schema: { type: 'object' }) }
|
||||
let(:api_metadata) { { path_item: {}, operation: {} } } # i.e. operation defined
|
||||
|
||||
it "adds to the 'operation parameters' metadata" do
|
||||
expect(api_metadata[:operation][:parameters]).to match(
|
||||
@@ -100,7 +112,8 @@ module Rswag
|
||||
|
||||
context "'path' parameter" do
|
||||
before { subject.parameter(name: :id, in: :path) }
|
||||
|
||||
let(:api_metadata) { { operation: {} } }
|
||||
|
||||
it "automatically sets the 'required' flag" do
|
||||
expect(api_metadata[:operation][:parameters]).to match(
|
||||
[ name: :id, in: :path, required: true ]
|
||||
@@ -120,8 +133,8 @@ module Rswag
|
||||
end
|
||||
|
||||
describe '#schema(value)' do
|
||||
let(:api_metadata) { { response: {} } }
|
||||
before { subject.schema(type: 'object') }
|
||||
let(:api_metadata) { { response: {} } }
|
||||
|
||||
it "adds to the 'response' metadata" do
|
||||
expect(api_metadata[:response][:schema]).to match(type: 'object')
|
||||
@@ -129,8 +142,8 @@ module Rswag
|
||||
end
|
||||
|
||||
describe '#header(name, attributes)' do
|
||||
let(:api_metadata) { { response: {} } }
|
||||
before { subject.header('Date', type: 'string') }
|
||||
let(:api_metadata) { { response: {} } }
|
||||
|
||||
it "adds to the 'response headers' metadata" do
|
||||
expect(api_metadata[:response][:headers]).to match(
|
||||
@@ -138,6 +151,25 @@ module Rswag
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#examples(example)' do
|
||||
let(:json_example) do
|
||||
{
|
||||
'application/json' => {
|
||||
foo: 'bar'
|
||||
}
|
||||
}
|
||||
end
|
||||
let(:api_metadata) { { response: {} } }
|
||||
|
||||
before do
|
||||
subject.examples(json_example)
|
||||
end
|
||||
|
||||
it "adds to the 'response examples' metadata" do
|
||||
expect(api_metadata[:response][:examples]).to eq(json_example)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,9 +4,19 @@ module Rswag
|
||||
module Specs
|
||||
|
||||
describe ExampleHelpers do
|
||||
subject { double('example') }
|
||||
|
||||
before do
|
||||
subject.extend ExampleHelpers
|
||||
# Mock out some infrastructure
|
||||
stub_const('Rails::VERSION::MAJOR', 3)
|
||||
rswag_config = double('rswag_config')
|
||||
allow(rswag_config).to receive(:get_swagger_doc).and_return(global_metadata)
|
||||
allow(subject).to receive(:rswag_config).and_return(rswag_config)
|
||||
end
|
||||
let(:api_metadata) do
|
||||
{
|
||||
path: '/blogs/{blog_id}/comments/{id}',
|
||||
path_item: { template: '/blogs/{blog_id}/comments/{id}' },
|
||||
operation: {
|
||||
verb: :put,
|
||||
summary: 'Updates a blog',
|
||||
@@ -16,9 +26,9 @@ module Rswag
|
||||
{ name: 'q1', in: :query, type: 'string' },
|
||||
{ name: :blog, in: :body, schema: { type: 'object' } }
|
||||
],
|
||||
security: {
|
||||
api_key: []
|
||||
}
|
||||
security: [
|
||||
{ api_key: [] }
|
||||
]
|
||||
}
|
||||
}
|
||||
end
|
||||
@@ -34,22 +44,14 @@ module Rswag
|
||||
}
|
||||
end
|
||||
|
||||
subject { double('example') }
|
||||
|
||||
before do
|
||||
subject.extend ExampleHelpers
|
||||
allow(subject).to receive(:blog_id).and_return(1)
|
||||
allow(subject).to receive(:id).and_return(2)
|
||||
allow(subject).to receive(:q1).and_return('foo')
|
||||
allow(subject).to receive(:api_key).and_return('fookey')
|
||||
allow(subject).to receive(:blog).and_return(text: 'Some comment')
|
||||
allow(subject).to receive(:global_metadata).and_return(global_metadata)
|
||||
allow(subject).to receive(:put)
|
||||
end
|
||||
|
||||
describe '#submit_request(api_metadata)' do
|
||||
before do
|
||||
stub_const('Rails::VERSION::MAJOR', 3)
|
||||
allow(subject).to receive(:blog_id).and_return(1)
|
||||
allow(subject).to receive(:id).and_return(2)
|
||||
allow(subject).to receive(:q1).and_return('foo')
|
||||
allow(subject).to receive(:api_key).and_return('fookey')
|
||||
allow(subject).to receive(:blog).and_return(text: 'Some comment')
|
||||
allow(subject).to receive(:put)
|
||||
subject.submit_request(api_metadata)
|
||||
end
|
||||
|
||||
|
||||
@@ -4,9 +4,15 @@ module Rswag
|
||||
module Specs
|
||||
|
||||
describe RequestFactory do
|
||||
subject { RequestFactory.new(api_metadata, global_metadata) }
|
||||
|
||||
before do
|
||||
allow(example).to receive(:blog_id).and_return(1)
|
||||
allow(example).to receive(:id).and_return('2')
|
||||
end
|
||||
let(:api_metadata) do
|
||||
{
|
||||
path: '/blogs/{blog_id}/comments/{id}',
|
||||
path_item: { template: '/blogs/{blog_id}/comments/{id}' },
|
||||
operation: {
|
||||
verb: :put,
|
||||
summary: 'Updates a blog',
|
||||
@@ -18,14 +24,7 @@ module Rswag
|
||||
}
|
||||
end
|
||||
let(:global_metadata) { {} }
|
||||
|
||||
subject { RequestFactory.new(api_metadata, global_metadata) }
|
||||
|
||||
let(:example) { double('example') }
|
||||
before do
|
||||
allow(example).to receive(:blog_id).and_return(1)
|
||||
allow(example).to receive(:id).and_return('2')
|
||||
end
|
||||
|
||||
describe '#build_fullpath(example)' do
|
||||
let(:path) { subject.build_fullpath(example) }
|
||||
@@ -103,7 +102,7 @@ module Rswag
|
||||
end
|
||||
|
||||
context 'global requirement' do
|
||||
before { global_metadata[:security] = { api_key: [] } }
|
||||
before { global_metadata[:security] = [ { api_key: [] } ] }
|
||||
|
||||
it "appends the api_key using metadata and example value" do
|
||||
expect(path).to eq('/blogs/1/comments/2?api_key=fookey')
|
||||
@@ -111,7 +110,7 @@ module Rswag
|
||||
end
|
||||
|
||||
context 'operation-specific requirement' do
|
||||
before { api_metadata[:operation][:security] = { api_key: [] } }
|
||||
before { api_metadata[:operation][:security] = [ { api_key: [] } ] }
|
||||
|
||||
it "appends the api_key using metadata and example value" do
|
||||
expect(path).to eq('/blogs/1/comments/2?api_key=fookey')
|
||||
@@ -126,6 +125,17 @@ module Rswag
|
||||
expect(path).to eq('/foobar/blogs/1/comments/2')
|
||||
end
|
||||
end
|
||||
|
||||
context "defined at the 'path' level" do
|
||||
before do
|
||||
api_metadata[:path_item][:parameters] = [ { name: :blog_id, in: :path } ]
|
||||
api_metadata[:operation][:parameters] = [ { name: :id, in: :path } ]
|
||||
end
|
||||
|
||||
it "builds path from parameters defined at path and operation levels" do
|
||||
expect(path).to eq('/blogs/1/comments/2')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#build_body(example)' do
|
||||
|
||||
@@ -4,11 +4,11 @@ module Rswag
|
||||
module Specs
|
||||
|
||||
describe ResponseValidator do
|
||||
subject { ResponseValidator.new(api_metadata, global_metadata) }
|
||||
|
||||
let(:api_metadata) { { response: { code: 200 } } }
|
||||
let(:global_metadata) { {} }
|
||||
|
||||
subject { ResponseValidator.new(api_metadata, global_metadata) }
|
||||
|
||||
describe '#validate!(response)' do
|
||||
let(:call) { subject.validate!(response) }
|
||||
|
||||
@@ -66,6 +66,42 @@ module Rswag
|
||||
it { expect { call }.to raise_error UnexpectedResponse }
|
||||
end
|
||||
end
|
||||
|
||||
context "'headers' provided" do
|
||||
before do
|
||||
api_metadata[:response][:headers] = {
|
||||
'X-Rate-Limit-Limit' => {
|
||||
description: 'The number of allowed requests in the current period',
|
||||
type: 'integer'
|
||||
},
|
||||
'X-Rate-Limit-Remaining' => {
|
||||
description: 'The number of remaining requests in the current period',
|
||||
type: 'integer'
|
||||
},
|
||||
'X-Rate-Limit-Reset' => {
|
||||
description: 'The number of seconds left in the current period',
|
||||
type: 'integer'
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
context 'response code & body matches' do
|
||||
let(:response) { OpenStruct.new(code: 200, body: '{}', headers: {
|
||||
'X-Rate-Limit-Limit' => 1,
|
||||
'X-Rate-Limit-Remaining' => 1,
|
||||
'X-Rate-Limit-Reset' => 1
|
||||
}) }
|
||||
it { expect { call }.to_not raise_error }
|
||||
end
|
||||
|
||||
context 'response code matches & body does not' do
|
||||
let(:response) { OpenStruct.new(code: 200, body: '{}', headers: {
|
||||
'X-Rate-Limit-Limit' => 1,
|
||||
'X-Rate-Limit-Remaining' => 1
|
||||
}) }
|
||||
it { expect { call }.to raise_error UnexpectedResponse }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,112 +5,59 @@ module Rswag
|
||||
module Specs
|
||||
|
||||
describe SwaggerFormatter do
|
||||
# Mock infrastructure - output, RSpec.configuration etc.
|
||||
subject { described_class.new(output, config) }
|
||||
|
||||
# Mock out some infrastructure
|
||||
before do
|
||||
allow(config).to receive(:swagger_root).and_return(swagger_root)
|
||||
end
|
||||
let(:config) { double('config') }
|
||||
let(:output) { double('output').as_null_object }
|
||||
let(:swagger_root) { File.expand_path('../tmp', __FILE__) }
|
||||
let(:swagger_docs) do
|
||||
{
|
||||
'v1/swagger.json' => { info: { version: 'v1' } }
|
||||
}
|
||||
end
|
||||
let(:config) { OpenStruct.new(swagger_root: swagger_root, swagger_docs: swagger_docs) }
|
||||
before { allow(RSpec).to receive(:configuration).and_return(config) }
|
||||
|
||||
subject { described_class.new(output) }
|
||||
|
||||
describe '::new(output)' do
|
||||
context 'swagger_root not configured' do
|
||||
let(:swagger_root) { nil }
|
||||
it { expect { subject }.to raise_error ConfigurationError }
|
||||
end
|
||||
|
||||
context 'swagger_docs not configured' do
|
||||
let(:swagger_docs) { nil }
|
||||
it { expect { subject }.to raise_error ConfigurationError }
|
||||
end
|
||||
end
|
||||
let(:swagger_root) { File.expand_path('../tmp/swagger', __FILE__) }
|
||||
|
||||
describe '#example_group_finished(notification)' do
|
||||
# Mock notification parameter
|
||||
before do
|
||||
allow(config).to receive(:get_swagger_doc).and_return(swagger_doc)
|
||||
subject.example_group_finished(notification)
|
||||
end
|
||||
let(:swagger_doc) { {} }
|
||||
let(:notification) { OpenStruct.new(group: OpenStruct.new(metadata: api_metadata)) }
|
||||
let(:api_metadata) do
|
||||
{
|
||||
path: '/blogs',
|
||||
path_item: { template: '/blogs' },
|
||||
operation: { verb: :post, summary: 'Creates a blog' },
|
||||
response: { code: '201', description: 'blog created' }
|
||||
}
|
||||
end
|
||||
let(:notification) { OpenStruct.new(group: OpenStruct.new(metadata: api_metadata)) }
|
||||
|
||||
let(:call) { subject.example_group_finished(notification) }
|
||||
|
||||
context 'single swagger_doc' do
|
||||
before { call }
|
||||
|
||||
it 'converts metadata to swagger and merges into the doc' do
|
||||
expect(swagger_docs.values.first).to match(
|
||||
info: { version: 'v1' },
|
||||
paths: {
|
||||
'/blogs' => {
|
||||
post: {
|
||||
summary: 'Creates a blog',
|
||||
responses: {
|
||||
'201' => { description: 'blog created' }
|
||||
}
|
||||
it 'converts to swagger and merges into the corresponding swagger doc' do
|
||||
expect(swagger_doc).to match(
|
||||
paths: {
|
||||
'/blogs' => {
|
||||
post: {
|
||||
summary: 'Creates a blog',
|
||||
responses: {
|
||||
'201' => { description: 'blog created' }
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'multiple swagger_docs' do
|
||||
let(:swagger_docs) do
|
||||
{
|
||||
'v1/swagger.json' => {},
|
||||
'v2/swagger.json' => {}
|
||||
}
|
||||
end
|
||||
|
||||
context "no 'swagger_doc' tag" do
|
||||
before { call }
|
||||
|
||||
it 'merges into the first doc' do
|
||||
expect(swagger_docs.values.first).to have_key(:paths)
|
||||
end
|
||||
end
|
||||
|
||||
context "matching 'swagger_doc' tag" do
|
||||
before do
|
||||
api_metadata[:swagger_doc] = 'v2/swagger.json'
|
||||
call
|
||||
end
|
||||
|
||||
it 'merges into the matched doc' do
|
||||
expect(swagger_docs.values.last).to have_key(:paths)
|
||||
end
|
||||
end
|
||||
|
||||
context "non matching 'swagger_doc' tag" do
|
||||
before { api_metadata[:swagger_doc] = 'foobar' }
|
||||
it { expect { call }.to raise_error ConfigurationError }
|
||||
end
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#stop' do
|
||||
let(:notification) { double('notification') }
|
||||
let(:swagger_docs) do
|
||||
{
|
||||
'v1/swagger.json' => { info: { version: 'v1' } },
|
||||
'v2/swagger.json' => { info: { version: 'v2' } },
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
FileUtils.rm_r(swagger_root) if File.exists?(swagger_root)
|
||||
allow(config).to receive(:swagger_docs).and_return(
|
||||
'v1/swagger.json' => { info: { version: 'v1' } },
|
||||
'v2/swagger.json' => { info: { version: 'v2' } }
|
||||
)
|
||||
subject.stop(notification)
|
||||
end
|
||||
|
||||
let(:notification) { double('notification') }
|
||||
|
||||
it 'writes the swagger_doc(s) to file' do
|
||||
expect(File).to exist("#{swagger_root}/v1/swagger.json")
|
||||
expect(File).to exist("#{swagger_root}/v2/swagger.json")
|
||||
|
||||
@@ -3,33 +3,33 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Swagger UI</title>
|
||||
<link rel="icon" type="image/png" href="/assets/swagger-ui/images/favicon-32x32.png" sizes="32x32" />
|
||||
<link rel="icon" type="image/png" href="/assets/swagger-ui/images/favicon-16x16.png" sizes="16x16" />
|
||||
<link href='/assets/swagger-ui/css/typography.css' media='screen' rel='stylesheet' type='text/css'/>
|
||||
<link href='/assets/swagger-ui/css/reset.css' media='screen' rel='stylesheet' type='text/css'/>
|
||||
<link href='/assets/swagger-ui/css/screen.css' media='screen' rel='stylesheet' type='text/css'/>
|
||||
<link href='/assets/swagger-ui/css/reset.css' media='print' rel='stylesheet' type='text/css'/>
|
||||
<link href='/assets/swagger-ui/css/print.css' media='print' rel='stylesheet' type='text/css'/>
|
||||
<link rel="icon" type="image/png" href="<%= asset_path 'swagger-ui/images/favicon-32x32.png' %>" sizes="32x32" />
|
||||
<link rel="icon" type="image/png" href="<%= asset_path 'swagger-ui/images/favicon-16x16.png' %>" sizes="16x16" />
|
||||
<link href="<%= asset_path 'swagger-ui/css/typography.css' %>" media='screen' rel='stylesheet' type='text/css'/>
|
||||
<link href="<%= asset_path 'swagger-ui/css/reset.css' %>" media='screen' rel='stylesheet' type='text/css'/>
|
||||
<link href="<%= asset_path 'swagger-ui/css/screen.css' %>" media='screen' rel='stylesheet' type='text/css'/>
|
||||
<link href="<%= asset_path 'swagger-ui/css/reset.css' %>" media='print' rel='stylesheet' type='text/css'/>
|
||||
<link href="<%= asset_path 'swagger-ui/css/print.css' %>" media='print' rel='stylesheet' type='text/css'/>
|
||||
|
||||
<script src='/assets/swagger-ui/lib/object-assign-pollyfill.js' type='text/javascript'></script>
|
||||
<script src='/assets/swagger-ui/lib/jquery-1.8.0.min.js' type='text/javascript'></script>
|
||||
<script src='/assets/swagger-ui/lib/jquery.slideto.min.js' type='text/javascript'></script>
|
||||
<script src='/assets/swagger-ui/lib/jquery.wiggle.min.js' type='text/javascript'></script>
|
||||
<script src='/assets/swagger-ui/lib/jquery.ba-bbq.min.js' type='text/javascript'></script>
|
||||
<script src='/assets/swagger-ui/lib/handlebars-4.0.5.js' type='text/javascript'></script>
|
||||
<script src='/assets/swagger-ui/lib/lodash.min.js' type='text/javascript'></script>
|
||||
<script src='/assets/swagger-ui/lib/backbone-min.js' type='text/javascript'></script>
|
||||
<script src='/assets/swagger-ui/swagger-ui.min.js' type='text/javascript'></script>
|
||||
<script src='/assets/swagger-ui/lib/highlight.9.1.0.pack.js' type='text/javascript'></script>
|
||||
<script src='/assets/swagger-ui/lib/highlight.9.1.0.pack_extended.js' type='text/javascript'></script>
|
||||
<script src='/assets/swagger-ui/lib/jsoneditor.min.js' type='text/javascript'></script>
|
||||
<script src='/assets/swagger-ui/lib/marked.js' type='text/javascript'></script>
|
||||
<script src='/assets/swagger-ui/lib/swagger-oauth.js' type='text/javascript'></script>
|
||||
<script src="<%= asset_path 'swagger-ui/lib/object-assign-pollyfill.js' %>" type='text/javascript'></script>
|
||||
<script src="<%= asset_path 'swagger-ui/lib/jquery-1.8.0.min.js' %>" type='text/javascript'></script>
|
||||
<script src="<%= asset_path 'swagger-ui/lib/jquery.slideto.min.js' %>" type='text/javascript'></script>
|
||||
<script src="<%= asset_path 'swagger-ui/lib/jquery.wiggle.min.js' %>" type='text/javascript'></script>
|
||||
<script src="<%= asset_path 'swagger-ui/lib/jquery.ba-bbq.min.js' %>" type='text/javascript'></script>
|
||||
<script src="<%= asset_path 'swagger-ui/lib/handlebars-4.0.5.js' %>" type='text/javascript'></script>
|
||||
<script src="<%= asset_path 'swagger-ui/lib/lodash.min.js' %>" type='text/javascript'></script>
|
||||
<script src="<%= asset_path 'swagger-ui/lib/backbone-min.js' %>" type='text/javascript'></script>
|
||||
<script src="<%= asset_path 'swagger-ui/swagger-ui.min.js' %>" type='text/javascript'></script>
|
||||
<script src="<%= asset_path 'swagger-ui/lib/highlight.9.1.0.pack.js' %>" type='text/javascript'></script>
|
||||
<script src="<%= asset_path 'swagger-ui/lib/highlight.9.1.0.pack_extended.js' %>" type='text/javascript'></script>
|
||||
<script src="<%= asset_path 'swagger-ui/lib/jsoneditor.min.js' %>" type='text/javascript'></script>
|
||||
<script src="<%= asset_path 'swagger-ui/lib/marked.js' %>" type='text/javascript'></script>
|
||||
<script src="<%= asset_path 'swagger-ui/lib/swagger-oauth.js' %>" type='text/javascript'></script>
|
||||
|
||||
<!-- Some basic translations -->
|
||||
<!-- <script src='/assets/swagger-ui/lang/translator.js' type='text/javascript'></script> -->
|
||||
<!-- <script src='/assets/swagger-ui/lang/ru.js' type='text/javascript'></script> -->
|
||||
<!-- <script src='/assets/swagger-ui/lang/en.js' type='text/javascript'></script> -->
|
||||
<!-- <script src="<%= asset_path 'swagger-ui/lang/translator.js' %>" type='text/javascript'></script> -->
|
||||
<!-- <script src="<%= asset_path 'swagger-ui/lang/ru.js' %>" type='text/javascript'></script> -->
|
||||
<!-- <script src="<%= asset_path 'swagger-ui/lang/en.js' %>" type='text/javascript'></script> -->
|
||||
|
||||
<script type="text/javascript">
|
||||
$(function () {
|
||||
@@ -84,7 +84,7 @@
|
||||
<body class="swagger-section">
|
||||
<div id='header'>
|
||||
<div class="swagger-ui-wrap">
|
||||
<a id="logo" href="http://swagger.io"><img class="logo__img" alt="swagger" height="30" width="30" src="/assets/swagger-ui/images/logo_small.png" /><span class="logo__title">swagger</span></a>
|
||||
<a id="logo" href="http://swagger.io"><img class="logo__img" alt="swagger" height="30" width="30" src="<%= asset_path 'swagger-ui/images/logo_small.png' %>" /><span class="logo__title">swagger</span></a>
|
||||
<form id='api_selector'>
|
||||
<div class='input'><input placeholder="http://example.com/api" id="input_baseUrl" name="baseUrl" type="text"/></div>
|
||||
<div id='auth_container'></div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
module Rswag
|
||||
module Ui
|
||||
VERSION = '1.0.1'
|
||||
VERSION = '1.2.0'
|
||||
end
|
||||
end
|
||||
|
||||
@@ -880,7 +880,7 @@
|
||||
padding: 6px 8px;
|
||||
}
|
||||
.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.sandbox_header span.response_throbber {
|
||||
background-image: url('../images/throbber.gif');
|
||||
background-image: url(<%= asset_path('swagger-ui/images/throbber.gif') %>);
|
||||
width: 128px;
|
||||
height: 16px;
|
||||
display: block;
|
||||
@@ -1222,7 +1222,7 @@
|
||||
height: 18px;
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
background: url(../images/explorer_icons.png) no-repeat;
|
||||
background: url(<%= asset_path('swagger-ui/images/explorer_icons.png') %>) no-repeat;
|
||||
}
|
||||
.swagger-section .authorize__btn_operation_login {
|
||||
background-position: 0 0;
|
||||
@@ -880,7 +880,7 @@
|
||||
padding: 6px 8px;
|
||||
}
|
||||
.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.sandbox_header span.response_throbber {
|
||||
background-image: url('../images/throbber.gif');
|
||||
background-image: url(<%= asset_path('swagger-ui/images/throbber.gif') %>);
|
||||
width: 128px;
|
||||
height: 16px;
|
||||
display: block;
|
||||
@@ -1222,7 +1222,7 @@
|
||||
height: 18px;
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
background: url(../images/explorer_icons.png) no-repeat;
|
||||
background: url(<%= asset_path('swagger-ui/images/explorer_icons.png') %>) no-repeat;
|
||||
}
|
||||
.swagger-section .authorize__btn_operation_login {
|
||||
background-position: 0 0;
|
||||
@@ -1354,7 +1354,7 @@
|
||||
height: 18px;
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
background: url(../images/explorer_icons.png) no-repeat;
|
||||
background: url(<%= asset_path('swagger-ui/images/explorer_icons.png') %>) no-repeat;
|
||||
}
|
||||
.swagger-section .api-ic .api_information_panel {
|
||||
position: relative;
|
||||
@@ -2,7 +2,7 @@
|
||||
font-size: 1.5em;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
background: transparent url(../images/logo.png) no-repeat left center;
|
||||
background: transparent url(<%= asset_path('swagger-ui/images/logo.png') %>) no-repeat left center;
|
||||
padding: 20px 0 20px 40px;
|
||||
}
|
||||
#text-head {
|
||||
@@ -64,7 +64,7 @@ h1 {
|
||||
width: 1500px;
|
||||
margin: auto;
|
||||
margin-top: 0;
|
||||
background-image: url('../images/shield.png');
|
||||
background-image: url(<%= asset_path('swagger-ui/images/shield.png') %>);
|
||||
background-repeat: no-repeat;
|
||||
background-position: -40px -20px;
|
||||
margin-bottom: 210px;
|
||||
@@ -1,14 +0,0 @@
|
||||
/* Google Font's Droid Sans */
|
||||
@font-face {
|
||||
font-family: 'Droid Sans';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Droid Sans'), local('DroidSans'), url('../fonts/DroidSans.ttf'), format('truetype');
|
||||
}
|
||||
/* Google Font's Droid Sans Bold */
|
||||
@font-face {
|
||||
font-family: 'Droid Sans';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: local('Droid Sans Bold'), local('DroidSans-Bold'), url('../fonts/DroidSans-Bold.ttf'), format('truetype');
|
||||
}
|
||||
14
rswag-ui/vendor/assets/components/swagger-ui/css/typography.css.erb
vendored
Normal file
14
rswag-ui/vendor/assets/components/swagger-ui/css/typography.css.erb
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
/* Google Font's Droid Sans */
|
||||
@font-face {
|
||||
font-family: 'Droid Sans';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Droid Sans'), local('DroidSans'), url(<%= asset_path('swagger-ui/fonts/DroidSans.ttf') %>), format('truetype');
|
||||
}
|
||||
/* Google Font's Droid Sans Bold */
|
||||
@font-face {
|
||||
font-family: 'Droid Sans';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: local('Droid Sans Bold'), local('DroidSans-Bold'), url(<%= asset_path('swagger-ui/fonts/DroidSans-Bold.ttf') %>), format('truetype');
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
module Rswag
|
||||
VERSION = '1.0.1'
|
||||
VERSION = '1.2.0'
|
||||
end
|
||||
|
||||
@@ -17,6 +17,10 @@ class BlogsController < ApplicationController
|
||||
# GET /blogs/1
|
||||
def show
|
||||
@blog = Blog.find_by_id(params[:id])
|
||||
|
||||
fresh_when(@blog)
|
||||
return unless stale?(@blog)
|
||||
|
||||
respond_with @blog, status: :not_found and return unless @blog
|
||||
respond_with @blog
|
||||
end
|
||||
|
||||
67
test-app/config/environments/production.rb
Normal file
67
test-app/config/environments/production.rb
Normal file
@@ -0,0 +1,67 @@
|
||||
TestApp::Application.configure do
|
||||
# Settings specified here will take precedence over those in config/application.rb
|
||||
|
||||
# Code is not reloaded between requests
|
||||
config.cache_classes = true
|
||||
|
||||
# Full error reports are disabled and caching is turned on
|
||||
config.consider_all_requests_local = false
|
||||
config.action_controller.perform_caching = true
|
||||
|
||||
# Disable Rails's static asset server (Apache or nginx will already do this)
|
||||
config.serve_static_assets = true
|
||||
|
||||
# Compress JavaScripts and CSS
|
||||
config.assets.compress = true
|
||||
|
||||
# Don't fallback to assets pipeline if a precompiled asset is missed
|
||||
config.assets.compile = false
|
||||
|
||||
# Generate digests for assets URLs
|
||||
config.assets.digest = true
|
||||
|
||||
# Defaults to nil and saved in location specified by config.assets.prefix
|
||||
# config.assets.manifest = YOUR_PATH
|
||||
|
||||
# Specifies the header that your server uses for sending files
|
||||
# config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache
|
||||
# config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx
|
||||
|
||||
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
|
||||
# config.force_ssl = true
|
||||
|
||||
# See everything in the log (default is :info)
|
||||
# config.log_level = :debug
|
||||
|
||||
# Prepend all log lines with the following tags
|
||||
# config.log_tags = [ :subdomain, :uuid ]
|
||||
|
||||
# Use a different logger for distributed setups
|
||||
# config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new)
|
||||
|
||||
# Use a different cache store in production
|
||||
# config.cache_store = :mem_cache_store
|
||||
|
||||
# Enable serving of images, stylesheets, and JavaScripts from an asset server
|
||||
# config.action_controller.asset_host = "http://assets.example.com"
|
||||
|
||||
# Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added)
|
||||
# config.assets.precompile += %w( search.js )
|
||||
|
||||
# Disable delivery errors, bad email addresses will be ignored
|
||||
# config.action_mailer.raise_delivery_errors = false
|
||||
|
||||
# Enable threaded mode
|
||||
# config.threadsafe!
|
||||
|
||||
# Enable locale fallbacks for I18n (makes lookups for any locale fall back to
|
||||
# the I18n.default_locale when a translation can not be found)
|
||||
config.i18n.fallbacks = true
|
||||
|
||||
# Send deprecation notices to registered listeners
|
||||
config.active_support.deprecation = :notify
|
||||
|
||||
# Log the query plan for queries taking more than this (works
|
||||
# with SQLite, MySQL, and PostgreSQL)
|
||||
# config.active_record.auto_explain_threshold_in_seconds = 0.5
|
||||
end
|
||||
12
test-app/spec/features/swagger_ui_spec.rb
Normal file
12
test-app/spec/features/swagger_ui_spec.rb
Normal file
@@ -0,0 +1,12 @@
|
||||
require 'rails_helper'
|
||||
|
||||
feature 'swagger-ui', js: true do
|
||||
|
||||
scenario 'browsing api-docs' do
|
||||
visit '/api-docs'
|
||||
|
||||
expect(page).to have_content('GET /blogs Searches blogs')
|
||||
expect(page).to have_content('POST /blogs Creates a blog')
|
||||
expect(page).to have_content('GET /blogs/{id} Retrieves a blog')
|
||||
end
|
||||
end
|
||||
@@ -41,16 +41,27 @@ describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do
|
||||
end
|
||||
|
||||
path '/blogs/{id}' do
|
||||
parameter name: :id, :in => :path, :type => :string
|
||||
|
||||
get 'Retrieves a blog' do
|
||||
tags 'Blogs'
|
||||
description 'Retrieves a specific blog by id'
|
||||
operationId 'getBlog'
|
||||
produces 'application/json'
|
||||
parameter name: :id, :in => :path, :type => :string
|
||||
|
||||
response '200', 'blog found' do
|
||||
header 'ETag', type: :string
|
||||
header 'Last-Modified', type: :string
|
||||
header 'Cache-Control', type: :string
|
||||
|
||||
schema '$ref' => '#/definitions/blog'
|
||||
|
||||
examples 'application/json' => {
|
||||
id: 1,
|
||||
title: 'Hello world!',
|
||||
content: 'Hello world and hello universe. Thank you all very much!!!'
|
||||
}
|
||||
|
||||
let(:blog) { Blog.create(title: 'foo', content: 'bar') }
|
||||
let(:id) { blog.id }
|
||||
run_test!
|
||||
|
||||
@@ -50,4 +50,6 @@ RSpec.configure do |config|
|
||||
config.filter_rails_from_backtrace!
|
||||
# arbitrary gems may also be filtered via:
|
||||
# config.filter_gems_from_backtrace("gem name")
|
||||
|
||||
Capybara.javascript_driver = :webkit
|
||||
end
|
||||
|
||||
@@ -51,9 +51,9 @@ RSpec.configure do |config|
|
||||
in: :query
|
||||
}
|
||||
},
|
||||
security: {
|
||||
api_key: []
|
||||
}
|
||||
security: [
|
||||
{ api_key: [] }
|
||||
]
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
@@ -68,6 +68,14 @@
|
||||
}
|
||||
},
|
||||
"/blogs/{id}": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"type": "string",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"get": {
|
||||
"summary": "Retrieves a blog",
|
||||
"tags": [
|
||||
@@ -78,19 +86,29 @@
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"type": "string",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "blog found",
|
||||
"headers": {
|
||||
"ETag": {
|
||||
"type": "string"
|
||||
},
|
||||
"Last-Modified": {
|
||||
"type": "string"
|
||||
},
|
||||
"Cache-Control": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"schema": {
|
||||
"$ref": "#/definitions/blog"
|
||||
},
|
||||
"examples": {
|
||||
"application/json": {
|
||||
"id": 1,
|
||||
"title": "Hello world!",
|
||||
"content": "Hello world and hello universe. Thank you all very much!!!"
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
@@ -145,9 +163,11 @@
|
||||
"in": "query"
|
||||
}
|
||||
},
|
||||
"security": {
|
||||
"api_key": [
|
||||
"security": [
|
||||
{
|
||||
"api_key": [
|
||||
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user