mirror of
https://github.com/ditkrg/rswag.git
synced 2026-01-25 15:22:56 +00:00
Compare commits
47 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f7c29267fe | ||
|
|
8ede7f78f1 | ||
|
|
8b937e3585 | ||
|
|
1515ce4fcb | ||
|
|
741f0dc240 | ||
|
|
3404fa72aa | ||
|
|
521ab14eaf | ||
|
|
8f5cb1aa12 | ||
|
|
519eb74cdd | ||
|
|
de7ec5f15d | ||
|
|
e40c5fc26e | ||
|
|
1de29136ef | ||
|
|
88449944e1 | ||
|
|
e03a6d333f | ||
|
|
215aaddb7d | ||
|
|
925b754233 | ||
|
|
69c7decce1 | ||
|
|
353be669e4 | ||
|
|
75d676d753 | ||
|
|
1b3a976313 | ||
|
|
2617fdc48d | ||
|
|
724ede0076 | ||
|
|
8aeb9a6c70 | ||
|
|
25d8adaf8b | ||
|
|
f195c82759 | ||
|
|
37f86f6d94 | ||
|
|
51c9f4e5e6 | ||
|
|
233c5cc735 | ||
|
|
cf9170101b | ||
|
|
98d5b982c4 | ||
|
|
77d4cbe0ea | ||
|
|
32a7ab8234 | ||
|
|
95b009a72f | ||
|
|
471dff5e34 | ||
|
|
99be8135f7 | ||
|
|
8315eda8b2 | ||
|
|
e1fe9f3239 | ||
|
|
64b0de494f | ||
|
|
9d4069bcfe | ||
|
|
17a6cd13c4 | ||
|
|
0b0acfe4c7 | ||
|
|
5ea97a4278 | ||
|
|
5cf376891a | ||
|
|
10dd37896f | ||
|
|
3506fee3d0 | ||
|
|
5df130922f | ||
|
|
5a19cd2373 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -2,3 +2,6 @@
|
||||
**/*/log
|
||||
**/*/*.gem
|
||||
**/*/*.sqlite3
|
||||
**/*/public/assets
|
||||
*.swp
|
||||
Gemfile.lock
|
||||
|
||||
1
.ruby-version
Normal file
1
.ruby-version
Normal file
@@ -0,0 +1 @@
|
||||
2.3.0
|
||||
66
.travis.yml
66
.travis.yml
@@ -1,10 +1,64 @@
|
||||
nguage: ruby
|
||||
language: ruby
|
||||
|
||||
rvm:
|
||||
- 2.2.5
|
||||
|
||||
env:
|
||||
- "RAILS_VERSION=3.2.22"
|
||||
- "RAILS_VERSION=4.2.0"
|
||||
- "RAILS_VERSION=5.0.0"
|
||||
cache: bundler
|
||||
install: bundle update
|
||||
- RAILS_VERSION=5.1.2
|
||||
- RAILS_VERSION=4.2.0
|
||||
- RAILS_VERSION=3.2.22
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- /home/travis/.rvm/gems/ruby-2.2.5
|
||||
|
||||
install: bundle install
|
||||
|
||||
before_script:
|
||||
- export DISPLAY=:99.0
|
||||
- sh -e /etc/init.d/xvfb start
|
||||
- sleep 3
|
||||
|
||||
script: ./ci/test.sh
|
||||
|
||||
jobs:
|
||||
include:
|
||||
- stage: publish components
|
||||
script: 'cd rswag-api'
|
||||
deploy:
|
||||
gemspec: rswag-api.gemspec
|
||||
provider: rubygems
|
||||
api_key: $RUBYGEMS_API_KEY
|
||||
on:
|
||||
branch: master
|
||||
tags: true
|
||||
|
||||
- stage: publish components
|
||||
script: 'cd rswag-specs'
|
||||
deploy:
|
||||
gemspec: rswag-specs.gemspec
|
||||
provider: rubygems
|
||||
api_key: $RUBYGEMS_API_KEY
|
||||
on:
|
||||
branch: master
|
||||
tags: true
|
||||
|
||||
- stage: publish components
|
||||
script: 'cd rswag-ui'
|
||||
deploy:
|
||||
gemspec: rswag-ui.gemspec
|
||||
provider: rubygems
|
||||
api_key: $RUBYGEMS_API_KEY
|
||||
on:
|
||||
branch: master
|
||||
tags: true
|
||||
|
||||
- stage: publish rswag
|
||||
script: 'cd rswag'
|
||||
deploy:
|
||||
gemspec: rswag.gemspec
|
||||
provider: rubygems
|
||||
api_key: $RUBYGEMS_API_KEY
|
||||
on:
|
||||
branch: master
|
||||
tags: true
|
||||
|
||||
11
Gemfile
11
Gemfile
@@ -2,7 +2,7 @@ source "https://rubygems.org"
|
||||
|
||||
# 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/
|
||||
rails_version = ENV['RAILS_VERSION'] || '3.2.22'
|
||||
rails_version = ENV['RAILS_VERSION'] || '5.1.2'
|
||||
|
||||
gem 'rails', "#{rails_version}"
|
||||
|
||||
@@ -15,6 +15,7 @@ end
|
||||
|
||||
gem 'sqlite3'
|
||||
|
||||
gem 'rswag-specs', path: './rswag-specs'
|
||||
gem 'rswag-api', path: './rswag-api'
|
||||
gem 'rswag-ui', path: './rswag-ui'
|
||||
|
||||
@@ -25,5 +26,11 @@ group :test do
|
||||
gem 'test-unit'
|
||||
gem 'rspec-rails'
|
||||
gem 'generator_spec'
|
||||
gem 'rswag-specs', path: './rswag-specs'
|
||||
gem 'capybara'
|
||||
gem 'capybara-webkit'
|
||||
end
|
||||
|
||||
group :assets do
|
||||
gem 'uglifier'
|
||||
gem 'therubyracer'
|
||||
end
|
||||
|
||||
146
Gemfile.lock
146
Gemfile.lock
@@ -1,146 +0,0 @@
|
||||
PATH
|
||||
remote: ./rswag-api
|
||||
specs:
|
||||
rswag-api (1.0.1)
|
||||
rails (>= 3.1, < 5.1)
|
||||
|
||||
PATH
|
||||
remote: ./rswag-specs
|
||||
specs:
|
||||
rswag-specs (1.0.1)
|
||||
json-schema (~> 2.2)
|
||||
rails (>= 3.1, < 5.1)
|
||||
rspec-rails (>= 2.14, < 4)
|
||||
|
||||
PATH
|
||||
remote: ./rswag-ui
|
||||
specs:
|
||||
rswag-ui (1.0.1)
|
||||
rails (>= 3.1, < 5.1)
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
actionmailer (3.2.22)
|
||||
actionpack (= 3.2.22)
|
||||
mail (~> 2.5.4)
|
||||
actionpack (3.2.22)
|
||||
activemodel (= 3.2.22)
|
||||
activesupport (= 3.2.22)
|
||||
builder (~> 3.0.0)
|
||||
erubis (~> 2.7.0)
|
||||
journey (~> 1.0.4)
|
||||
rack (~> 1.4.5)
|
||||
rack-cache (~> 1.2)
|
||||
rack-test (~> 0.6.1)
|
||||
sprockets (~> 2.2.1)
|
||||
activemodel (3.2.22)
|
||||
activesupport (= 3.2.22)
|
||||
builder (~> 3.0.0)
|
||||
activerecord (3.2.22)
|
||||
activemodel (= 3.2.22)
|
||||
activesupport (= 3.2.22)
|
||||
arel (~> 3.0.2)
|
||||
tzinfo (~> 0.3.29)
|
||||
activeresource (3.2.22)
|
||||
activemodel (= 3.2.22)
|
||||
activesupport (= 3.2.22)
|
||||
activesupport (3.2.22)
|
||||
i18n (~> 0.6, >= 0.6.4)
|
||||
multi_json (~> 1.0)
|
||||
addressable (2.4.0)
|
||||
arel (3.0.3)
|
||||
builder (3.0.4)
|
||||
diff-lcs (1.2.5)
|
||||
erubis (2.7.0)
|
||||
generator_spec (0.9.3)
|
||||
activesupport (>= 3.0.0)
|
||||
railties (>= 3.0.0)
|
||||
hike (1.2.3)
|
||||
i18n (0.7.0)
|
||||
journey (1.0.4)
|
||||
json (1.8.3)
|
||||
json-schema (2.7.0)
|
||||
addressable (>= 2.4)
|
||||
mail (2.5.4)
|
||||
mime-types (~> 1.16)
|
||||
treetop (~> 1.4.8)
|
||||
mime-types (1.25.1)
|
||||
multi_json (1.12.1)
|
||||
polyglot (0.3.5)
|
||||
power_assert (0.3.1)
|
||||
rack (1.4.7)
|
||||
rack-cache (1.6.1)
|
||||
rack (>= 0.4)
|
||||
rack-ssl (1.3.4)
|
||||
rack
|
||||
rack-test (0.6.3)
|
||||
rack (>= 1.0)
|
||||
rails (3.2.22)
|
||||
actionmailer (= 3.2.22)
|
||||
actionpack (= 3.2.22)
|
||||
activerecord (= 3.2.22)
|
||||
activeresource (= 3.2.22)
|
||||
activesupport (= 3.2.22)
|
||||
bundler (~> 1.0)
|
||||
railties (= 3.2.22)
|
||||
railties (3.2.22)
|
||||
actionpack (= 3.2.22)
|
||||
activesupport (= 3.2.22)
|
||||
rack-ssl (~> 1.3.2)
|
||||
rake (>= 0.8.7)
|
||||
rdoc (~> 3.4)
|
||||
thor (>= 0.14.6, < 2.0)
|
||||
rake (11.3.0)
|
||||
rdoc (3.12.2)
|
||||
json (~> 1.4)
|
||||
rspec-core (3.5.4)
|
||||
rspec-support (~> 3.5.0)
|
||||
rspec-expectations (3.5.0)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.5.0)
|
||||
rspec-mocks (3.5.0)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.5.0)
|
||||
rspec-rails (3.5.2)
|
||||
actionpack (>= 3.0)
|
||||
activesupport (>= 3.0)
|
||||
railties (>= 3.0)
|
||||
rspec-core (~> 3.5.0)
|
||||
rspec-expectations (~> 3.5.0)
|
||||
rspec-mocks (~> 3.5.0)
|
||||
rspec-support (~> 3.5.0)
|
||||
rspec-support (3.5.0)
|
||||
sprockets (2.2.3)
|
||||
hike (~> 1.2)
|
||||
multi_json (~> 1.0)
|
||||
rack (~> 1.0)
|
||||
tilt (~> 1.1, != 1.3.0)
|
||||
sqlite3 (1.3.12)
|
||||
strong_parameters (0.2.3)
|
||||
actionpack (~> 3.0)
|
||||
activemodel (~> 3.0)
|
||||
activesupport (~> 3.0)
|
||||
railties (~> 3.0)
|
||||
test-unit (3.2.1)
|
||||
power_assert
|
||||
thor (0.19.1)
|
||||
tilt (1.4.1)
|
||||
treetop (1.4.15)
|
||||
polyglot
|
||||
polyglot (>= 0.3.1)
|
||||
tzinfo (0.3.51)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
generator_spec
|
||||
rails (= 3.2.22)
|
||||
rspec-rails
|
||||
rswag-api!
|
||||
rswag-specs!
|
||||
rswag-ui!
|
||||
sqlite3
|
||||
strong_parameters
|
||||
test-unit
|
||||
151
README.md
151
README.md
@@ -103,6 +103,17 @@ If you've used [Swagger](http://swagger.io/specification) before, then the synta
|
||||
|
||||
Take special note of the __run_test!__ method that's called within each response block. This tells rswag to create and execute a corresponding example. It builds and submits a request based on parameter descriptions and corresponding values that have been provided using the rspec "let" syntax. For example, the "post" description in the example above specifies a "body" parameter called "blog". It also lists 2 different responses. For the success case (i.e. the 201 response), notice how "let" is used to set the blog parameter to a value that matches the provided schema. For the failure case (i.e. the 422 response), notice how it's set to a value that does not match the provided schema. When the test is executed, rswag also validates the actual response code and, where applicable, the response body against the provided [JSON Schema](http://json-schema.org/documentation.html).
|
||||
|
||||
If you want to do additional validation on the response, pass a block to the __run_test!__ method:
|
||||
|
||||
```ruby
|
||||
response '201', 'blog created' do
|
||||
run_test! do |response|
|
||||
data = JSON.parse(response.body)
|
||||
expect(data['title']).to eq('foo')
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
If you'd like your specs to be a little more explicit about what's going on here, you can replace the call to __run_test!__ with equivalent "before" and "it" blocks:
|
||||
|
||||
```ruby
|
||||
@@ -119,6 +130,31 @@ response '201', 'blog created' do
|
||||
end
|
||||
```
|
||||
|
||||
### Null Values ###
|
||||
|
||||
This library is currently using JSON::Draft4 for validation of response models. It does not support null as a value. So you can add the property 'x-nullable' to a definition to allow null/nil values to pass.
|
||||
```ruby
|
||||
describe 'Blogs API' do
|
||||
path '/blogs' do
|
||||
post 'Creates a blog' do
|
||||
...
|
||||
|
||||
response '200', 'blog found' do
|
||||
schema type: :object,
|
||||
properties: {
|
||||
id: { type: :integer },
|
||||
title: { type: :string },
|
||||
content: { type: :string, 'x-nullable': true }
|
||||
}
|
||||
....
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
*Note:* the OAI v3 may be released soon(ish?) and include a nullable property. This may have an effect on the need/use of custom extension to the draft. Do not use this property if you don't understand the implications.
|
||||
<https://github.com/OAI/OpenAPI-Specification/issues/229#issuecomment-280376087>
|
||||
|
||||
### Global Metadata ###
|
||||
|
||||
In addition to paths, operations and responses, Swagger also supports global API metadata. When you install rswag, a file called _swagger_helper.rb_ is added to your spec folder. This is where you define one or more Swagger documents and provide global metadata. Again, the format is based on Swagger so most of the global fields supported by the top level ["Swagger" object](http://swagger.io/specification/#swaggerObject) can be provided with each document definition. As an example, you could define a Swagger document for each version of your API and in each case specify a title, version string and URL basePath:
|
||||
@@ -164,6 +200,58 @@ describe 'Blogs API', swagger_doc: 'v2/swagger.json' do
|
||||
end
|
||||
```
|
||||
|
||||
### Specifying/Testing API Security ###
|
||||
|
||||
Swagger allows for the specification of different security schemes and their applicability to operations in an API. To leverage this in rswag, you define the schemes globally in _swagger_helper.rb_ and then use the "security" attribute at the operation level to specify which schemes, if any, are applicable to that operation. Swagger supports :basic, :apiKey and :oauth2 scheme types. See [the spec](http://swagger.io/specification/#security-definitions-object-109) for more info.
|
||||
|
||||
```ruby
|
||||
# spec/swagger_helper.rb
|
||||
RSpec.configure do |config|
|
||||
config.swagger_root = Rails.root.to_s + '/swagger'
|
||||
|
||||
config.swagger_docs = {
|
||||
'v1/swagger.json' => {
|
||||
...
|
||||
securityDefinitions: {
|
||||
basic: {
|
||||
type: :basic
|
||||
},
|
||||
apiKey: {
|
||||
type: :apiKey,
|
||||
name: 'api_key',
|
||||
in: :query
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
# spec/integration/blogs_spec.rb
|
||||
describe 'Blogs API' do
|
||||
|
||||
path '/blogs' do
|
||||
|
||||
post 'Creates a blog' do
|
||||
tags 'Blogs'
|
||||
security [ basic: [] ]
|
||||
...
|
||||
|
||||
response '201', 'blog created' do
|
||||
let(:Authorization) { "Basic #{::Base64.strict_encode64('jsmith:jspass')}" }
|
||||
run_test!
|
||||
end
|
||||
|
||||
response '401', 'authentication failed' do
|
||||
let(:Authorization) { "Basic #{::Base64.strict_encode64('bogus:bogus')}" }
|
||||
run_test!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
__NOTE:__ Depending on the scheme types, you'll be required to assign a corresponding parameter value with each example. For example, :basic auth is required above and so the :Authorization (header) parameter must be set accordingly
|
||||
|
||||
## Configuration & Customization ##
|
||||
|
||||
The steps described above will get you up and running with minimal setup. However, rswag offers a lot of flexibility to customize as you see fit. Before exploring the various options, you'll need to be aware of it's different components. The following table lists each of them and the files that get added/updated as part of a standard install.
|
||||
@@ -171,8 +259,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 ###
|
||||
|
||||
@@ -243,6 +331,63 @@ describe 'Blogs API' do
|
||||
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
|
||||
```
|
||||
|
||||
### Enable generation examples from responses ###
|
||||
|
||||
To enable examples generation from responses add callback above run_test! like:
|
||||
```ruby
|
||||
after do |example|
|
||||
example.metadata[:response][:examples] = { 'application/json' => JSON.parse(response.body, symbolize_names: true) }
|
||||
end
|
||||
```
|
||||
You need to disable --dry-run option for Rspec > 3
|
||||
|
||||
Add to application.rb:
|
||||
```ruby
|
||||
RSpec.configure do |config|
|
||||
config.swagger_dry_run = false
|
||||
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_:
|
||||
@@ -314,7 +459,7 @@ end
|
||||
|
||||
### Customizing the swagger-ui ###
|
||||
|
||||
The swagger-ui provides several options for customizing it's behavior, all of which are documented here https://github.com/swagger-api/swagger-ui#swaggerui. If you need to tweak these or customize the overall look and feel of your swagger-ui, then you'll need to provide your own version of index.html. You can do this with the following generator.
|
||||
The swagger-ui provides several options for customizing it's behavior, all of which are documented here https://github.com/swagger-api/swagger-ui/tree/2.x#swaggerui. If you need to tweak these or customize the overall look and feel of your swagger-ui, then you'll need to provide your own version of index.html. You can do this with the following generator.
|
||||
|
||||
```ruby
|
||||
rails g rswag:ui:custom
|
||||
|
||||
58
ci/deploy.sh
58
ci/deploy.sh
@@ -1,58 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
ROOT_PATH=$PWD
|
||||
set -e # abort if anything fails
|
||||
|
||||
bundle check || bundle
|
||||
|
||||
echo '####################'
|
||||
echo 'Build Gems'
|
||||
echo '####################'
|
||||
echo ''
|
||||
|
||||
echo '##### rswag-api #####'
|
||||
cd $ROOT_PATH/rswag-api
|
||||
gem build rswag-api.gemspec
|
||||
|
||||
echo '##### rswag-specs #####'
|
||||
cd $ROOT_PATH/rswag-specs
|
||||
gem build rswag-specs.gemspec
|
||||
|
||||
echo '##### rswag-ui #####'
|
||||
cd $ROOT_PATH/rswag-ui
|
||||
gem build rswag-ui.gemspec
|
||||
|
||||
echo '##### rswag #####'
|
||||
cd $ROOT_PATH/rswag
|
||||
gem build rswag.gemspec
|
||||
|
||||
echo '####################'
|
||||
echo 'Push to RubyGems'
|
||||
echo '####################'
|
||||
echo ''
|
||||
|
||||
echo 'Type the version no, followed by [ENTER]:'
|
||||
read version
|
||||
|
||||
echo '##### rswag-api #####'
|
||||
cd $ROOT_PATH/rswag-api
|
||||
gem push rswag-api-$version.gem
|
||||
|
||||
echo '##### rswag-specs #####'
|
||||
cd $ROOT_PATH/rswag-specs
|
||||
gem push rswag-specs-$version.gem
|
||||
|
||||
echo '##### rswag-ui #####'
|
||||
cd $ROOT_PATH/rswag-ui
|
||||
gem push rswag-ui-$version.gem
|
||||
|
||||
echo '##### rswag #####'
|
||||
cd $ROOT_PATH/rswag
|
||||
gem push rswag-$version.gem
|
||||
|
||||
# Cleanup
|
||||
cd $ROOT_PATH
|
||||
|
||||
# Create git tag
|
||||
git tag v$version
|
||||
git push origin v$version
|
||||
@@ -1,4 +1,3 @@
|
||||
require 'rswag/api/version'
|
||||
require 'rswag/api/configuration'
|
||||
require 'rswag/api/engine'
|
||||
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
module Rswag
|
||||
module Api
|
||||
VERSION = '1.0.2'
|
||||
end
|
||||
end
|
||||
@@ -1,12 +1,9 @@
|
||||
$:.push File.expand_path("../lib", __FILE__)
|
||||
|
||||
# Maintain your gem's version:
|
||||
require 'rswag/api/version'
|
||||
|
||||
# Describe your gem and declare its dependencies:
|
||||
Gem::Specification.new do |s|
|
||||
s.name = "rswag-api"
|
||||
s.version = Rswag::Api::VERSION
|
||||
s.version = ENV['TRAVIS_TAG'] || '0.0.0'
|
||||
s.authors = ["Richie Morris"]
|
||||
s.email = ["domaindrivendev@gmail.com"]
|
||||
s.homepage = "https://github.com/domaindrivendev/rswag"
|
||||
@@ -16,5 +13,5 @@ Gem::Specification.new do |s|
|
||||
|
||||
s.files = Dir["{lib}/**/*"] + ["MIT-LICENSE", "Rakefile"]
|
||||
|
||||
s.add_dependency "rails", ">= 3.1", "< 5.1"
|
||||
s.add_dependency 'railties', '>= 3.1', '< 6.0'
|
||||
end
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
require 'rspec/core'
|
||||
require 'rswag/specs/version'
|
||||
require 'rswag/specs/example_group_helpers'
|
||||
require 'rswag/specs/example_helpers'
|
||||
require 'rswag/specs/configuration'
|
||||
@@ -12,6 +11,7 @@ module Rswag
|
||||
::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
|
||||
|
||||
@@ -25,6 +25,12 @@ module Rswag
|
||||
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]
|
||||
|
||||
@@ -61,7 +61,15 @@ module Rswag
|
||||
metadata[:response][:headers][name] = attributes
|
||||
end
|
||||
|
||||
def run_test!
|
||||
# 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
|
||||
@@ -69,7 +77,7 @@ module Rswag
|
||||
end
|
||||
|
||||
it "returns a #{metadata[:response][:code]} response" do
|
||||
assert_response_matches_metadata(example.metadata)
|
||||
assert_response_matches_metadata(example.metadata, &block)
|
||||
end
|
||||
else
|
||||
before do |example|
|
||||
@@ -77,7 +85,7 @@ module Rswag
|
||||
end
|
||||
|
||||
it "returns a #{metadata[:response][:code]} response" do |example|
|
||||
assert_response_matches_metadata(example.metadata)
|
||||
assert_response_matches_metadata(example.metadata, &block)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -14,7 +14,7 @@ module Rswag
|
||||
api_metadata[:operation][:verb],
|
||||
factory.build_fullpath(self),
|
||||
factory.build_body(self),
|
||||
factory.build_headers(self)
|
||||
rackify_headers(factory.build_headers(self)) # Rails test infrastructure requires Rack headers
|
||||
)
|
||||
else
|
||||
send(
|
||||
@@ -28,14 +28,30 @@ module Rswag
|
||||
end
|
||||
end
|
||||
|
||||
def assert_response_matches_metadata(api_metadata)
|
||||
def assert_response_matches_metadata(api_metadata, &block)
|
||||
global_metadata = rswag_config.get_swagger_doc(api_metadata[:swagger_doc])
|
||||
validator = ResponseValidator.new(api_metadata, global_metadata)
|
||||
validator.validate!(response)
|
||||
validator.validate!(response, &block)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def rackify_headers(headers)
|
||||
name_value_pairs = headers.map do |name, value|
|
||||
[
|
||||
case name
|
||||
when 'Accept' then 'HTTP_ACCEPT'
|
||||
when 'Content-Type' then 'CONTENT_TYPE'
|
||||
when 'Authorization' then 'HTTP_AUTHORIZATION'
|
||||
else name
|
||||
end,
|
||||
value
|
||||
]
|
||||
end
|
||||
|
||||
Hash[ name_value_pairs ]
|
||||
end
|
||||
|
||||
def rswag_config
|
||||
::Rswag::Specs.config
|
||||
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::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
|
||||
@@ -20,6 +20,8 @@ module Rswag
|
||||
|
||||
def build_query_string(example)
|
||||
query_string = parameters_in(:query)
|
||||
.select { |p| p.fetch(:required, true) ||
|
||||
example.respond_to?(p[:name]) }
|
||||
.map { |p| build_query_string_part(p, example.send(p[:name])) }
|
||||
.join('&')
|
||||
|
||||
@@ -32,13 +34,20 @@ module Rswag
|
||||
end
|
||||
|
||||
def build_headers(example)
|
||||
headers = Hash[ parameters_in(:header).map { |p| [ p[:name], example.send(p[:name]).to_s ] } ]
|
||||
headers.tap do |h|
|
||||
name_value_pairs = parameters_in(:header).map do |param|
|
||||
[
|
||||
param[:name],
|
||||
example.send(param[:name]).to_s
|
||||
]
|
||||
end
|
||||
|
||||
# Add MIME type headers based on produces/consumes metadata
|
||||
produces = @api_metadata[:operation][:produces] || @global_metadata[:produces]
|
||||
consumes = @api_metadata[:operation][:consumes] || @global_metadata[:consumes]
|
||||
h['ACCEPT'] = produces.join(';') unless produces.nil?
|
||||
h['CONTENT_TYPE'] = consumes.join(';') unless consumes.nil?
|
||||
end
|
||||
name_value_pairs << [ 'Accept', produces.join(';') ] unless produces.nil?
|
||||
name_value_pairs << [ 'Content-Type', consumes.join(';') ] unless consumes.nil?
|
||||
|
||||
Hash[ name_value_pairs ]
|
||||
end
|
||||
|
||||
private
|
||||
@@ -52,7 +61,7 @@ module Rswag
|
||||
|
||||
applicable_params
|
||||
.map { |p| p['$ref'] ? resolve_parameter(p['$ref']) : p } # resolve any references
|
||||
.concat(resolve_api_key_parameters)
|
||||
.concat(security_parameters)
|
||||
.select { |p| p[:in] == location }
|
||||
end
|
||||
|
||||
@@ -63,17 +72,23 @@ module Rswag
|
||||
defined_params[key]
|
||||
end
|
||||
|
||||
def resolve_api_key_parameters
|
||||
@api_key_params ||= begin
|
||||
def security_parameters
|
||||
applicable_security_schemes.map do |scheme|
|
||||
if scheme[:type] == :apiKey
|
||||
{ name: scheme[:name], type: :string, in: scheme[:in] }
|
||||
else
|
||||
{ name: 'Authorization', type: :string, in: :header } # use auth header for basic & oauth2
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def applicable_security_schemes
|
||||
# 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
|
||||
requirements = @api_metadata[:operation][:security] || @global_metadata[:security]
|
||||
scheme_names = requirements ? requirements.map { |r| r.keys.first } : []
|
||||
|
||||
# Then obtain the scheme definitions for those requirements
|
||||
definitions = (@global_metadata[:securityDefinitions] || {}).slice(*requirements)
|
||||
definitions.values.select { |d| d[:type] == :apiKey }
|
||||
end
|
||||
(@global_metadata[:securityDefinitions] || {}).slice(*scheme_names).values
|
||||
end
|
||||
|
||||
def build_query_string_part(param, value)
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
require 'active_support/core_ext/hash/slice'
|
||||
require 'json-schema'
|
||||
require 'json'
|
||||
require 'rswag/specs/extended_schema'
|
||||
|
||||
module Rswag
|
||||
module Specs
|
||||
@@ -9,9 +12,11 @@ module Rswag
|
||||
@global_metadata = global_metadata
|
||||
end
|
||||
|
||||
def validate!(response)
|
||||
def validate!(response, &block)
|
||||
validate_code!(response.code)
|
||||
validate_body!(response.body)
|
||||
validate_headers!(response.headers)
|
||||
validate_body!(response.body, &block)
|
||||
block.call(response) if block_given?
|
||||
end
|
||||
|
||||
private
|
||||
@@ -22,13 +27,25 @@ 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?
|
||||
begin
|
||||
JSON::Validator.validate!(schema.merge(@global_metadata), body)
|
||||
rescue JSON::Schema::ValidationError => ex
|
||||
raise UnexpectedResponse, "Expected response body to match schema: #{ex.message}"
|
||||
response_schema = @api_metadata[:response][:schema]
|
||||
return if response_schema.nil?
|
||||
|
||||
validation_schema = response_schema
|
||||
.merge('$schema' => 'http://tempuri.org/rswag/specs/extended_schema')
|
||||
.merge(@global_metadata.slice(:definitions))
|
||||
error_messages = JSON::Validator.fully_validate(validation_schema, body)
|
||||
if error_messages.any?
|
||||
raise UnexpectedResponse, "Expected response body to match schema: #{error_messages[0]}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
module Rswag
|
||||
module Specs
|
||||
VERSION = '1.0.2'
|
||||
end
|
||||
end
|
||||
@@ -8,7 +8,7 @@ namespace :rswag do
|
||||
t.pattern = 'spec/requests/**/*_spec.rb, spec/api/**/*_spec.rb, spec/integration/**/*_spec.rb'
|
||||
|
||||
# NOTE: rspec 2.x support
|
||||
if Rswag::Specs::RSPEC_VERSION > 2
|
||||
if Rswag::Specs::RSPEC_VERSION > 2 && Rswag::Specs.config.swagger_dry_run
|
||||
t.rspec_opts = [ '--format Rswag::Specs::SwaggerFormatter', '--dry-run', '--order defined' ]
|
||||
else
|
||||
t.rspec_opts = [ '--format Rswag::Specs::SwaggerFormatter', '--order defined' ]
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
$:.push File.expand_path("../lib", __FILE__)
|
||||
|
||||
# Maintain your gem's version:
|
||||
require 'rswag/specs/version'
|
||||
|
||||
# Describe your gem and declare its dependencies:
|
||||
Gem::Specification.new do |s|
|
||||
s.name = "rswag-specs"
|
||||
s.version = Rswag::Specs::VERSION
|
||||
s.version = ENV['TRAVIS_TAG'] || '0.0.0'
|
||||
s.authors = ["Richie Morris"]
|
||||
s.email = ["domaindrivendev@gmail.com"]
|
||||
s.homepage = "https://github.com/domaindrivendev/rswag"
|
||||
@@ -16,7 +13,7 @@ Gem::Specification.new do |s|
|
||||
|
||||
s.files = Dir["{lib}/**/*"] + ["MIT-LICENSE", "Rakefile" ]
|
||||
|
||||
s.add_dependency "rails", ">= 3.1", "< 5.1"
|
||||
s.add_dependency 'activesupport', '>= 3.1', '< 6.0'
|
||||
s.add_dependency 'railties', '>= 3.1', '< 6.0'
|
||||
s.add_dependency 'json-schema', '~> 2.2'
|
||||
s.add_dependency 'rspec-rails', '>= 2.14', '< 4'
|
||||
end
|
||||
|
||||
@@ -151,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
|
||||
|
||||
@@ -48,6 +48,22 @@ module Rswag
|
||||
end
|
||||
end
|
||||
|
||||
context "optional 'query' parameters" do
|
||||
before do
|
||||
api_metadata[:operation][:parameters] << { name: 'q1', in: :query, type: 'string' }
|
||||
api_metadata[:operation][:parameters] << { name: 'q2', in: :query, type: 'string', required: true }
|
||||
api_metadata[:operation][:parameters] << { name: 'q3', in: :query, type: 'string', required: false }
|
||||
api_metadata[:operation][:parameters] << { name: 'q4', in: :query, type: 'string', required: false }
|
||||
allow(example).to receive(:q1).and_return('foo')
|
||||
allow(example).to receive(:q2).and_return('bar')
|
||||
allow(example).to receive(:q3).and_return('baz')
|
||||
end
|
||||
|
||||
it "appends a query string using metadata and example values" do
|
||||
expect(path).to eq('/blogs/1/comments/2?q1=foo&q2=bar&q3=baz')
|
||||
end
|
||||
end
|
||||
|
||||
context "'query' parameter of type 'array'" do
|
||||
before do
|
||||
api_metadata[:operation][:parameters] << {
|
||||
@@ -193,6 +209,33 @@ module Rswag
|
||||
end
|
||||
end
|
||||
|
||||
context "global definition for 'basic auth'" do
|
||||
before do
|
||||
global_metadata[:securityDefinitions] = { basic_auth: { type: :basic} }
|
||||
allow(example).to receive(:'Authorization').and_return('Basic foobar')
|
||||
end
|
||||
|
||||
context 'global requirement' do
|
||||
before { global_metadata[:security] = [ { basic_auth: [] } ] }
|
||||
|
||||
it "includes a corresponding Authorization header" do
|
||||
expect(headers).to match(
|
||||
'Authorization' => 'Basic foobar'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'operation-specific requirement' do
|
||||
before { api_metadata[:operation][:security] = [ { basic_auth: [] } ] }
|
||||
|
||||
it "includes a corresponding Authorization header" do
|
||||
expect(headers).to match(
|
||||
'Authorization' => 'Basic foobar'
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'consumes & produces' do
|
||||
before do
|
||||
api_metadata[:operation][:consumes] = [ 'application/json', 'application/xml' ]
|
||||
@@ -201,8 +244,8 @@ module Rswag
|
||||
|
||||
it "includes corresponding 'Accept' & 'Content-Type' headers" do
|
||||
expect(headers).to match(
|
||||
'ACCEPT' => 'application/json;application/xml',
|
||||
'CONTENT_TYPE' => 'application/json;application/xml'
|
||||
'Accept' => 'application/json;application/xml',
|
||||
'Content-Type' => 'application/json;application/xml'
|
||||
)
|
||||
end
|
||||
end
|
||||
@@ -217,8 +260,8 @@ module Rswag
|
||||
|
||||
it "includes corresponding 'Accept' & 'Content-Type' headers" do
|
||||
expect(headers).to match(
|
||||
'ACCEPT' => 'application/json;application/xml',
|
||||
'CONTENT_TYPE' => 'application/json;application/xml'
|
||||
'Accept' => 'application/json;application/xml',
|
||||
'Content-Type' => 'application/json;application/xml'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -42,6 +42,25 @@ module Rswag
|
||||
let(:response) { OpenStruct.new(code: 200, body: "{\"foo\":\"Some comment\"}") }
|
||||
it { expect { call }.to raise_error UnexpectedResponse }
|
||||
end
|
||||
|
||||
context "'block' provided" do
|
||||
let(:call) do
|
||||
subject.validate!(response) do |response|
|
||||
data = JSON.parse(response.body)
|
||||
expect(data['text']).to eq('Some comment')
|
||||
end
|
||||
end
|
||||
|
||||
context 'the block validation passes' do
|
||||
let(:response) { OpenStruct.new(code: 200, body: "{\"text\":\"Some comment\"}") }
|
||||
it { expect { call }.to_not raise_error }
|
||||
end
|
||||
|
||||
context 'the block validation fails' do
|
||||
let(:response) { OpenStruct.new(code: 200, body: "{\"text\":\"Some other comment\"}") }
|
||||
it { expect { call }.to raise_error(RSpec::Expectations::ExpectationNotMetError) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "referenced 'schema' provided" do
|
||||
@@ -66,6 +85,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
|
||||
|
||||
@@ -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,4 +1,3 @@
|
||||
require 'rswag/ui/version'
|
||||
require 'rswag/ui/configuration'
|
||||
require 'rswag/ui/engine'
|
||||
|
||||
|
||||
@@ -5,7 +5,14 @@ module Rswag
|
||||
|
||||
initializer 'rswag-ui.initialize' do |app|
|
||||
if app.config.respond_to?(:assets)
|
||||
app.config.assets.precompile += [ 'swagger-ui/*' ]
|
||||
app.config.assets.precompile += [
|
||||
'swagger-ui/css/*',
|
||||
'swagger-ui/fonts/*',
|
||||
'swagger-ui/images/*',
|
||||
'swagger-ui/lang/*',
|
||||
'swagger-ui/lib/*',
|
||||
'swagger-ui/swagger-ui.min.js'
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
module Rswag
|
||||
module Ui
|
||||
VERSION = '1.0.2'
|
||||
end
|
||||
end
|
||||
@@ -1,12 +1,9 @@
|
||||
$:.push File.expand_path("../lib", __FILE__)
|
||||
|
||||
# Maintain your gem's version:
|
||||
require 'rswag/ui/version'
|
||||
|
||||
# Describe your gem and declare its dependencies:
|
||||
Gem::Specification.new do |s|
|
||||
s.name = "rswag-ui"
|
||||
s.version = Rswag::Ui::VERSION
|
||||
s.version = ENV['TRAVIS_TAG'] || '0.0.0'
|
||||
s.authors = ["Richie Morris"]
|
||||
s.email = ["domaindrivendev@gmail.com"]
|
||||
s.homepage = "https://github.com/domaindrivendev/rswag"
|
||||
@@ -16,5 +13,6 @@ Gem::Specification.new do |s|
|
||||
|
||||
s.files = Dir["{app,config,lib,vendor}/**/*"] + ["MIT-LICENSE", "Rakefile" ]
|
||||
|
||||
s.add_dependency "rails", ">= 3.1", "< 5.1"
|
||||
s.add_dependency 'actionpack', '>=3.1', '< 6.0'
|
||||
s.add_dependency 'railties', '>= 3.1', '< 6.0'
|
||||
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,4 +1,3 @@
|
||||
require 'rswag/version'
|
||||
require 'rswag/specs'
|
||||
require 'rswag/api'
|
||||
require 'rswag/ui'
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
module Rswag
|
||||
VERSION = '1.0.2'
|
||||
end
|
||||
@@ -1,12 +1,9 @@
|
||||
$:.push File.expand_path("../lib", __FILE__)
|
||||
|
||||
# Maintain your gem's version:
|
||||
require 'rswag/version'
|
||||
|
||||
# Describe your gem and declare its dependencies:
|
||||
Gem::Specification.new do |s|
|
||||
s.name = "rswag"
|
||||
s.version = Rswag::VERSION
|
||||
s.version = ENV['TRAVIS_TAG'] || '0.0.0'
|
||||
s.authors = ["Richie Morris"]
|
||||
s.email = ["domaindrivendev@gmail.com"]
|
||||
s.homepage = "https://github.com/domaindrivendev/rswag"
|
||||
@@ -16,7 +13,7 @@ Gem::Specification.new do |s|
|
||||
|
||||
s.files = Dir["{lib}/**/*"] + [ "MIT-LICENSE" ]
|
||||
|
||||
s.add_dependency 'rswag-specs', Rswag::VERSION
|
||||
s.add_dependency 'rswag-api', Rswag::VERSION
|
||||
s.add_dependency 'rswag-ui', Rswag::VERSION
|
||||
s.add_dependency 'rswag-specs', ENV['TRAVIS_TAG'] || '0.0.0'
|
||||
s.add_dependency 'rswag-api', ENV['TRAVIS_TAG'] || '0.0.0'
|
||||
s.add_dependency 'rswag-ui', ENV['TRAVIS_TAG'] || '0.0.0'
|
||||
end
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
class ApplicationController < ActionController::Base
|
||||
# Prevent CSRF attacks by raising an exception.
|
||||
# For APIs, you may want to use :null_session instead.
|
||||
protect_from_forgery with: :exception
|
||||
protect_from_forgery with: :null_session
|
||||
|
||||
wrap_parameters format: [ :json ]
|
||||
end
|
||||
|
||||
13
test-app/app/controllers/auth_tests_controller.rb
Normal file
13
test-app/app/controllers/auth_tests_controller.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
class AuthTestsController < ApplicationController
|
||||
wrap_parameters Blog
|
||||
respond_to :json
|
||||
|
||||
# POST /auth-tests/basic
|
||||
def basic
|
||||
if authenticate_with_http_basic { |u, p| u == 'jsmith' && p == 'jspass' }
|
||||
head :no_content
|
||||
else
|
||||
request_http_basic_authentication
|
||||
end
|
||||
end
|
||||
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
|
||||
|
||||
@@ -5,7 +5,7 @@ class Blog < ActiveRecord::Base
|
||||
{
|
||||
id: id,
|
||||
title: title,
|
||||
content: content
|
||||
content: nil
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
require 'rubygems'
|
||||
gemfile = File.expand_path('../../../../Gemfile', __FILE__)
|
||||
gemfile = File.expand_path('../../../Gemfile', __FILE__)
|
||||
|
||||
if File.exist?(gemfile)
|
||||
ENV['BUNDLE_GEMFILE'] = gemfile
|
||||
|
||||
@@ -28,4 +28,6 @@ TestApp::Application.configure do
|
||||
|
||||
# Expands the lines which load the assets
|
||||
config.assets.debug = true
|
||||
|
||||
config.eager_load = false
|
||||
end
|
||||
|
||||
@@ -32,4 +32,6 @@ TestApp::Application.configure do
|
||||
|
||||
# Print deprecation notices to the stderr
|
||||
config.active_support.deprecation = :stderr
|
||||
|
||||
config.eager_load = false
|
||||
end
|
||||
|
||||
@@ -5,3 +5,6 @@
|
||||
# Make sure the secret is at least 30 characters and all random,
|
||||
# no regular words or you'll be exposed to dictionary attacks.
|
||||
TestApp::Application.config.secret_token = '60f36cd33756d73f362053f1d45256ae50d75440b634ae73b070a6e35a2df38692f59e28e5ecbd1f9f2e850255f6d29a468bc59ac4484c2b7f0548ddbfc1b870'
|
||||
|
||||
# See http://guides.rubyonrails.org/upgrading_ruby_on_rails.html#config-secrets-yml
|
||||
TestApp::Application.config.secret_key_base = 'f6a820cc8aa76094583cd68ef46a735e25e3278648086355f8bd24721f036959c728c06a28dcecfe695f17ae2db44dfa1424f22b81377f2a1496d4e19f6f7faa'
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
TestApp::Application.routes.draw do
|
||||
resources :blogs, defaults: { :format => :json }
|
||||
|
||||
post 'auth-tests/basic', to: 'auth_tests#basic'
|
||||
|
||||
mount Rswag::Api::Engine => 'api-docs'
|
||||
mount Rswag::Ui::Engine => 'api-docs'
|
||||
end
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
class CreateBlogs < ActiveRecord::Migration
|
||||
migration_class = if Gem::Version.new(Rails.version) >= Gem::Version.new("5.0")
|
||||
ActiveRecord::Migration[4.2]
|
||||
else
|
||||
ActiveRecord::Migration
|
||||
end
|
||||
|
||||
class CreateBlogs < migration_class
|
||||
def change
|
||||
create_table :blogs do |t|
|
||||
t.string :title
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# encoding: UTF-8
|
||||
# This file is auto-generated from the current state of the database. Instead
|
||||
# of editing this file, please use the migrations feature of Active Record to
|
||||
# incrementally modify your database, and then regenerate this schema definition.
|
||||
@@ -9,15 +8,15 @@
|
||||
# from scratch. The latter is a flawed and unsustainable approach (the more migrations
|
||||
# you'll amass, the slower it'll run and the greater likelihood for issues).
|
||||
#
|
||||
# It's strongly recommended to check this file into your version control system.
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(:version => 20160218212104) do
|
||||
ActiveRecord::Schema.define(version: 20160218212104) do
|
||||
|
||||
create_table "blogs", :force => true do |t|
|
||||
create_table "blogs", force: :cascade do |t|
|
||||
t.string "title"
|
||||
t.text "content"
|
||||
t.datetime "created_at", :null => false
|
||||
t.datetime "updated_at", :null => false
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
end
|
||||
|
||||
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
|
||||
22
test-app/spec/integration/auth_tests_spec.rb
Normal file
22
test-app/spec/integration/auth_tests_spec.rb
Normal file
@@ -0,0 +1,22 @@
|
||||
require 'swagger_helper'
|
||||
|
||||
describe 'Auth Tests API', type: :request, swagger_doc: 'v1/swagger.json' do
|
||||
|
||||
path '/auth-tests/basic' do
|
||||
post 'Authenticates with basic auth' do
|
||||
tags 'Auth Test'
|
||||
operationId 'testBasicAuth'
|
||||
security [ basic_auth: [] ]
|
||||
|
||||
response '204', 'Valid credentials' do
|
||||
let(:Authorization) { "Basic #{::Base64.strict_encode64('jsmith:jspass')}" }
|
||||
run_test!
|
||||
end
|
||||
|
||||
response '401', 'Invalid credentials' do
|
||||
let(:Authorization) { "Basic #{::Base64.strict_encode64('foo:bar')}" }
|
||||
run_test!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -50,8 +50,18 @@ describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do
|
||||
produces 'application/json'
|
||||
|
||||
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
|
||||
|
||||
@@ -39,12 +39,15 @@ RSpec.configure do |config|
|
||||
properties: {
|
||||
id: { type: 'integer' },
|
||||
title: { type: 'string' },
|
||||
content: { type: 'string' }
|
||||
content: { type: 'string', 'x-nullable': true }
|
||||
},
|
||||
required: [ 'id', 'title', 'content' ]
|
||||
}
|
||||
},
|
||||
securityDefinitions: {
|
||||
basic_auth: {
|
||||
type: :basic
|
||||
},
|
||||
api_key: {
|
||||
type: :apiKey,
|
||||
name: 'api_key',
|
||||
|
||||
@@ -5,6 +5,30 @@
|
||||
"version": "v1"
|
||||
},
|
||||
"paths": {
|
||||
"/auth-tests/basic": {
|
||||
"post": {
|
||||
"summary": "Authenticates with basic auth",
|
||||
"tags": [
|
||||
"Auth Test"
|
||||
],
|
||||
"operationId": "testBasicAuth",
|
||||
"security": [
|
||||
{
|
||||
"basic_auth": [
|
||||
|
||||
]
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "Valid credentials"
|
||||
},
|
||||
"401": {
|
||||
"description": "Invalid credentials"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/blogs": {
|
||||
"post": {
|
||||
"summary": "Creates a blog",
|
||||
@@ -89,8 +113,26 @@
|
||||
"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": {
|
||||
@@ -128,7 +170,8 @@
|
||||
"type": "string"
|
||||
},
|
||||
"content": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"x-nullable": true
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -139,6 +182,9 @@
|
||||
}
|
||||
},
|
||||
"securityDefinitions": {
|
||||
"basic_auth": {
|
||||
"type": "basic"
|
||||
},
|
||||
"api_key": {
|
||||
"type": "apiKey",
|
||||
"name": "api_key",
|
||||
|
||||
Reference in New Issue
Block a user