47 Commits

Author SHA1 Message Date
domaindrivendev
f7c29267fe Merge pull request #78 from bertha-carl/fully-validate
Can we use JSON::Validator.fully_validate() to get better error messages?
2017-07-20 10:09:01 -07:00
Hannes Probst
8ede7f78f1 use fully_validate to get more specific error msg 2017-07-18 15:45:36 +02:00
domaindrivendev
8b937e3585 Merge pull request #76 from ahobson/bugfix-optional-query-params
Support optional query parameters
2017-07-13 16:35:48 -07:00
Andrew Hobson
1515ce4fcb Support optional query parameters 2017-07-11 16:28:45 -04:00
domaindrivendev
741f0dc240 Merge branch 'ci-updates' 2017-07-10 23:55:51 -07:00
domaindrivendev
3404fa72aa Publish gems via CI on tagged builds of master 2017-07-10 23:52:51 -07:00
domaindrivendev
521ab14eaf Merge pull request #72 from domaindrivendev/minimal-dependencies
Reduces dependencies to just the required Rails components
2017-06-28 09:18:32 -07:00
domaindrivendev
8f5cb1aa12 Reduce depencencies to require Rails components only 2017-06-28 09:01:07 -07:00
domaindrivendev
519eb74cdd Merge pull request #71 from domaindrivendev/basic-auth-take2
Basic auth take2
2017-06-28 00:16:38 -07:00
domaindrivendev
de7ec5f15d Leverage security definitions for headers in example requests 2017-06-26 17:52:00 -07:00
domaindrivendev
e40c5fc26e Prep for 1.3.0 2017-06-26 17:39:34 -07:00
domaindrivendev
1de29136ef Merge pull request #64 from jume-dev/swagger-ui-url-patch
Add proper swagger ui url
2017-06-26 17:39:04 -07:00
domaindrivendev
88449944e1 Merge pull request #47 from DMiPartners/fixdepnotice
get no deprecation; add some docs
2017-06-26 17:32:32 -07:00
domaindrivendev
e03a6d333f Merge branch 'master' into fixdepnotice 2017-06-26 17:26:28 -07:00
domaindrivendev
215aaddb7d Merge pull request #69 from lazebny/rswag-ui-issue-with-precompilation
Fixed issue with precomilation
2017-06-19 07:17:15 -07:00
Vadim Lazebny
925b754233 Fixed issue with precomilation 2017-06-19 15:27:21 +03:00
Robert Kranz
69c7decce1 Add proper swagger ui url
As this only supports swagger ui 2, the url should link accordingly
2017-06-01 16:01:15 +02:00
domaindrivendev
353be669e4 Removed some deprecation warnings 2017-05-19 11:11:13 -07:00
domaindrivendev
75d676d753 Merge pull request #59 from grk/rails-5-1
Relax Rails dependency to allow 5.1
2017-05-19 10:39:29 -07:00
domaindrivendev
1b3a976313 Merge branch 'master' into rails-5-1 2017-05-19 10:35:00 -07:00
domaindrivendev
2617fdc48d Prep for v1.2.1 release 2017-05-19 10:32:22 -07:00
Grzesiek Kolodziejczyk
724ede0076 Remove unnecessary whitespace 2017-05-18 11:48:14 +02:00
Grzesiek Kolodziejczyk
8aeb9a6c70 Relax Rails dependency to allow 5.1 2017-05-18 11:47:40 +02:00
domaindrivendev
25d8adaf8b Merge pull request #57 from horiaradu/master
Response body value validation
2017-04-29 12:46:31 -07:00
Horia Radu
f195c82759 yield entire response instead of only the body 2017-04-29 21:17:53 +03:00
Horia Radu
37f86f6d94 yield entire response instead of only the body 2017-04-29 12:06:57 +03:00
Horia Radu
51c9f4e5e6 Response body value validation
Add the possibility to pass a block to the run_test!
method in order to add expectations on your response
2017-04-28 11:46:52 +03:00
Dante Piombino
233c5cc735 get no deprecation; add some docs 2017-02-16 16:31:45 -05:00
domaindrivendev
cf9170101b Merge pull request #42 from PavelBezpalov/master
Add way for examples generation from responses
2017-01-19 08:52:40 -08:00
Pavel Bezpalov
98d5b982c4 Add way for examples generation from responses 2017-01-19 11:46:56 +02:00
richie
77d4cbe0ea Prep for v1.2.0 release 2017-01-08 07:51:24 -08:00
domaindrivendev
32a7ab8234 Merge pull request #40 from PavelBezpalov/master
Add missing in production assets
2017-01-05 08:16:10 -08:00
Pavel Bezpalov
95b009a72f Add missing in production assets 2017-01-04 16:38:12 +02:00
richie
471dff5e34 Prep for v1.1.0 release 2016-11-08 12:43:31 -08:00
richie
99be8135f7 Wire up capybara & add simple feature spec for swagger-ui 2016-11-08 12:38:51 -08:00
richie
8315eda8b2 Update ui to use digest assets in prod 2016-11-02 22:15:09 -07:00
domaindrivendev
e1fe9f3239 Merge pull request #33 from vinh0604/master
Simplify response header validation
2016-10-19 17:15:50 -07:00
vinhbachsy
64b0de494f Simplify response header validation
Change to checks for the presence of required headers instead of using 
JSON::Validator
2016-10-20 01:33:43 +08:00
domaindrivendev
9d4069bcfe A minor readme update 2016-10-19 00:37:06 -07:00
domaindrivendev
17a6cd13c4 Merge pull request #32 from vinh0604/master
Validating response headers and setting response examples
2016-10-19 00:09:48 -07:00
vinhbachsy
0b0acfe4c7 Rename response_examples to examples for consistent DSL
Special handling `examples` invocation with no parameters to avoid
overriding the `examples` method of rspec-core ExampleGroup
2016-10-19 03:04:03 +08:00
vinhbachsy
5ea97a4278 Update README for response headers and examples 2016-10-18 21:46:35 +08:00
vinhbachsy
5cf376891a Validate response headers based on specified header
Add validate_headers step in response validator.
Using JSON::Validator with validate header value with swagger header 
object.
2016-10-18 21:46:35 +08:00
vinhbachsy
10dd37896f Support setting examples for response
Add helper method `response_examples` to inject response examples to swagger
2016-10-18 21:46:35 +08:00
richie
3506fee3d0 Prep for 1.0.3 release 2016-10-17 14:50:57 -07:00
richie
5df130922f Support x-nullable in respone_validator 2016-10-17 14:47:11 -07:00
richie
5a19cd2373 Push latest Gemfile.lock 2016-10-14 17:50:42 -07:00
53 changed files with 685 additions and 374 deletions

3
.gitignore vendored
View File

@@ -2,3 +2,6 @@
**/*/log **/*/log
**/*/*.gem **/*/*.gem
**/*/*.sqlite3 **/*/*.sqlite3
**/*/public/assets
*.swp
Gemfile.lock

1
.ruby-version Normal file
View File

@@ -0,0 +1 @@
2.3.0

View File

@@ -1,10 +1,64 @@
nguage: ruby language: ruby
rvm: rvm:
- 2.2.5 - 2.2.5
env: env:
- "RAILS_VERSION=3.2.22" - RAILS_VERSION=5.1.2
- "RAILS_VERSION=4.2.0" - RAILS_VERSION=4.2.0
- "RAILS_VERSION=5.0.0" - RAILS_VERSION=3.2.22
cache: bundler
install: bundle update 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 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
View File

@@ -2,7 +2,7 @@ source "https://rubygems.org"
# Allow the rails version to come from an ENV setting so Travis can test multiple versions. # Allow the rails version to come from an ENV setting so Travis can test multiple versions.
# See http://www.schneems.com/post/50991826838/testing-against-multiple-rails-versions/ # See http://www.schneems.com/post/50991826838/testing-against-multiple-rails-versions/
rails_version = ENV['RAILS_VERSION'] || '3.2.22' rails_version = ENV['RAILS_VERSION'] || '5.1.2'
gem 'rails', "#{rails_version}" gem 'rails', "#{rails_version}"
@@ -15,6 +15,7 @@ end
gem 'sqlite3' gem 'sqlite3'
gem 'rswag-specs', path: './rswag-specs'
gem 'rswag-api', path: './rswag-api' gem 'rswag-api', path: './rswag-api'
gem 'rswag-ui', path: './rswag-ui' gem 'rswag-ui', path: './rswag-ui'
@@ -25,5 +26,11 @@ group :test do
gem 'test-unit' gem 'test-unit'
gem 'rspec-rails' gem 'rspec-rails'
gem 'generator_spec' gem 'generator_spec'
gem 'rswag-specs', path: './rswag-specs' gem 'capybara'
gem 'capybara-webkit'
end
group :assets do
gem 'uglifier'
gem 'therubyracer'
end end

View File

@@ -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
View File

@@ -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). 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: 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 ```ruby
@@ -119,6 +130,31 @@ response '201', 'blog created' do
end 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 ### ### 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: 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 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 ## ## 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. 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| |Gem|Description|Added/Updated|
|---------|-----------|-------------| |---------|-----------|-------------|
|__rswag-specs__|Swagger-based DSL for rspec & accompanying rake task for generating Swagger files|_spec/swagger_helper.rb_| |__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-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 the Swagger endpoints|_config/initializers/rswag-ui.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 ### ### Output Location for Generated Swagger Files ###
@@ -243,6 +331,63 @@ describe 'Blogs API' do
end 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 ### ### 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_: 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 ### ### 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 ```ruby
rails g rswag:ui:custom rails g rswag:ui:custom

View File

@@ -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

View File

@@ -1,4 +1,3 @@
require 'rswag/api/version'
require 'rswag/api/configuration' require 'rswag/api/configuration'
require 'rswag/api/engine' require 'rswag/api/engine'

View File

@@ -1,5 +0,0 @@
module Rswag
module Api
VERSION = '1.0.2'
end
end

View File

@@ -1,12 +1,9 @@
$:.push File.expand_path("../lib", __FILE__) $:.push File.expand_path("../lib", __FILE__)
# Maintain your gem's version:
require 'rswag/api/version'
# Describe your gem and declare its dependencies: # Describe your gem and declare its dependencies:
Gem::Specification.new do |s| Gem::Specification.new do |s|
s.name = "rswag-api" s.name = "rswag-api"
s.version = Rswag::Api::VERSION s.version = ENV['TRAVIS_TAG'] || '0.0.0'
s.authors = ["Richie Morris"] s.authors = ["Richie Morris"]
s.email = ["domaindrivendev@gmail.com"] s.email = ["domaindrivendev@gmail.com"]
s.homepage = "https://github.com/domaindrivendev/rswag" s.homepage = "https://github.com/domaindrivendev/rswag"
@@ -14,7 +11,7 @@ Gem::Specification.new do |s|
s.description = "Open up your API to the phenomenal Swagger ecosystem by exposing Swagger files, that describe your service, as JSON endpoints" s.description = "Open up your API to the phenomenal Swagger ecosystem by exposing Swagger files, that describe your service, as JSON endpoints"
s.license = "MIT" s.license = "MIT"
s.files = Dir["{lib}/**/*"] + ["MIT-LICENSE", "Rakefile" ] s.files = Dir["{lib}/**/*"] + ["MIT-LICENSE", "Rakefile"]
s.add_dependency "rails", ">= 3.1", "< 5.1" s.add_dependency 'railties', '>= 3.1', '< 6.0'
end end

View File

@@ -1,5 +1,4 @@
require 'rspec/core' require 'rspec/core'
require 'rswag/specs/version'
require 'rswag/specs/example_group_helpers' require 'rswag/specs/example_group_helpers'
require 'rswag/specs/example_helpers' require 'rswag/specs/example_helpers'
require 'rswag/specs/configuration' require 'rswag/specs/configuration'
@@ -12,6 +11,7 @@ module Rswag
::RSpec.configure do |c| ::RSpec.configure do |c|
c.add_setting :swagger_root c.add_setting :swagger_root
c.add_setting :swagger_docs c.add_setting :swagger_docs
c.add_setting :swagger_dry_run
c.extend ExampleGroupHelpers, type: :request c.extend ExampleGroupHelpers, type: :request
c.include ExampleHelpers, type: :request c.include ExampleHelpers, type: :request
end end

View File

@@ -25,6 +25,12 @@ module Rswag
end end
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) def get_swagger_doc(name)
return swagger_docs.values.first if name.nil? return swagger_docs.values.first if name.nil?
raise ConfigurationError, "Unknown swagger_doc '#{name}'" unless swagger_docs[name] raise ConfigurationError, "Unknown swagger_doc '#{name}'" unless swagger_docs[name]

View File

@@ -61,7 +61,15 @@ module Rswag
metadata[:response][:headers][name] = attributes metadata[:response][:headers][name] = attributes
end 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 # NOTE: rspec 2.x support
if RSPEC_VERSION < 3 if RSPEC_VERSION < 3
before do before do
@@ -69,7 +77,7 @@ module Rswag
end end
it "returns a #{metadata[:response][:code]} response" do it "returns a #{metadata[:response][:code]} response" do
assert_response_matches_metadata(example.metadata) assert_response_matches_metadata(example.metadata, &block)
end end
else else
before do |example| before do |example|
@@ -77,7 +85,7 @@ module Rswag
end end
it "returns a #{metadata[:response][:code]} response" do |example| 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 end
end end

View File

@@ -14,7 +14,7 @@ module Rswag
api_metadata[:operation][:verb], api_metadata[:operation][:verb],
factory.build_fullpath(self), factory.build_fullpath(self),
factory.build_body(self), factory.build_body(self),
factory.build_headers(self) rackify_headers(factory.build_headers(self)) # Rails test infrastructure requires Rack headers
) )
else else
send( send(
@@ -28,14 +28,30 @@ module Rswag
end end
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]) global_metadata = rswag_config.get_swagger_doc(api_metadata[:swagger_doc])
validator = ResponseValidator.new(api_metadata, global_metadata) validator = ResponseValidator.new(api_metadata, global_metadata)
validator.validate!(response) validator.validate!(response, &block)
end end
private 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 def rswag_config
::Rswag::Specs.config ::Rswag::Specs.config
end end

View 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

View File

@@ -20,6 +20,8 @@ module Rswag
def build_query_string(example) def build_query_string(example)
query_string = parameters_in(:query) 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])) } .map { |p| build_query_string_part(p, example.send(p[:name])) }
.join('&') .join('&')
@@ -32,13 +34,20 @@ module Rswag
end end
def build_headers(example) def build_headers(example)
headers = Hash[ parameters_in(:header).map { |p| [ p[:name], example.send(p[:name]).to_s ] } ] name_value_pairs = parameters_in(:header).map do |param|
headers.tap do |h| [
produces = @api_metadata[:operation][:produces] || @global_metadata[:produces] param[:name],
consumes = @api_metadata[:operation][:consumes] || @global_metadata[:consumes] example.send(param[:name]).to_s
h['ACCEPT'] = produces.join(';') unless produces.nil? ]
h['CONTENT_TYPE'] = consumes.join(';') unless consumes.nil?
end 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]
name_value_pairs << [ 'Accept', produces.join(';') ] unless produces.nil?
name_value_pairs << [ 'Content-Type', consumes.join(';') ] unless consumes.nil?
Hash[ name_value_pairs ]
end end
private private
@@ -52,7 +61,7 @@ module Rswag
applicable_params applicable_params
.map { |p| p['$ref'] ? resolve_parameter(p['$ref']) : p } # resolve any references .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 } .select { |p| p[:in] == location }
end end
@@ -63,19 +72,25 @@ module Rswag
defined_params[key] defined_params[key]
end end
def resolve_api_key_parameters def security_parameters
@api_key_params ||= begin applicable_security_schemes.map do |scheme|
# First figure out the security requirement applicable to the operation if scheme[:type] == :apiKey
global_requirements = (@global_metadata[:security] || [] ).map { |r| r.keys.first } { name: scheme[:name], type: :string, in: scheme[:in] }
operation_requirements = (@api_metadata[:operation][:security] || [] ).map { |r| r.keys.first } else
requirements = global_requirements | operation_requirements { name: 'Authorization', type: :string, in: :header } # use auth header for basic & oauth2
end
# Then obtain the scheme definitions for those requirements
definitions = (@global_metadata[:securityDefinitions] || {}).slice(*requirements)
definitions.values.select { |d| d[:type] == :apiKey }
end end
end end
def applicable_security_schemes
# First figure out the security requirement applicable to the operation
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
(@global_metadata[:securityDefinitions] || {}).slice(*scheme_names).values
end
def build_query_string_part(param, value) def build_query_string_part(param, value)
return "#{param[:name]}=#{value.to_s}" unless param[:type].to_sym == :array return "#{param[:name]}=#{value.to_s}" unless param[:type].to_sym == :array

View File

@@ -1,4 +1,7 @@
require 'active_support/core_ext/hash/slice'
require 'json-schema' require 'json-schema'
require 'json'
require 'rswag/specs/extended_schema'
module Rswag module Rswag
module Specs module Specs
@@ -9,9 +12,11 @@ module Rswag
@global_metadata = global_metadata @global_metadata = global_metadata
end end
def validate!(response) def validate!(response, &block)
validate_code!(response.code) validate_code!(response.code)
validate_body!(response.body) validate_headers!(response.headers)
validate_body!(response.body, &block)
block.call(response) if block_given?
end end
private private
@@ -22,13 +27,25 @@ module Rswag
end end
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) def validate_body!(body)
schema = @api_metadata[:response][:schema] response_schema = @api_metadata[:response][:schema]
return if schema.nil? return if response_schema.nil?
begin
JSON::Validator.validate!(schema.merge(@global_metadata), body) validation_schema = response_schema
rescue JSON::Schema::ValidationError => ex .merge('$schema' => 'http://tempuri.org/rswag/specs/extended_schema')
raise UnexpectedResponse, "Expected response body to match schema: #{ex.message}" .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 end
end end

View File

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

View File

@@ -8,7 +8,7 @@ namespace :rswag do
t.pattern = 'spec/requests/**/*_spec.rb, spec/api/**/*_spec.rb, spec/integration/**/*_spec.rb' t.pattern = 'spec/requests/**/*_spec.rb, spec/api/**/*_spec.rb, spec/integration/**/*_spec.rb'
# NOTE: rspec 2.x support # NOTE: rspec 2.x support
if Rswag::Specs::RSPEC_VERSION > 2 if Rswag::Specs::RSPEC_VERSION > 2 && Rswag::Specs.config.swagger_dry_run
t.rspec_opts = [ '--format Rswag::Specs::SwaggerFormatter', '--dry-run', '--order defined' ] t.rspec_opts = [ '--format Rswag::Specs::SwaggerFormatter', '--dry-run', '--order defined' ]
else else
t.rspec_opts = [ '--format Rswag::Specs::SwaggerFormatter', '--order defined' ] t.rspec_opts = [ '--format Rswag::Specs::SwaggerFormatter', '--order defined' ]

View File

@@ -1,12 +1,9 @@
$:.push File.expand_path("../lib", __FILE__) $:.push File.expand_path("../lib", __FILE__)
# Maintain your gem's version:
require 'rswag/specs/version'
# Describe your gem and declare its dependencies: # Describe your gem and declare its dependencies:
Gem::Specification.new do |s| Gem::Specification.new do |s|
s.name = "rswag-specs" s.name = "rswag-specs"
s.version = Rswag::Specs::VERSION s.version = ENV['TRAVIS_TAG'] || '0.0.0'
s.authors = ["Richie Morris"] s.authors = ["Richie Morris"]
s.email = ["domaindrivendev@gmail.com"] s.email = ["domaindrivendev@gmail.com"]
s.homepage = "https://github.com/domaindrivendev/rswag" s.homepage = "https://github.com/domaindrivendev/rswag"
@@ -16,7 +13,7 @@ Gem::Specification.new do |s|
s.files = Dir["{lib}/**/*"] + ["MIT-LICENSE", "Rakefile" ] 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 'json-schema', '~> 2.2'
s.add_dependency 'rspec-rails', '>= 2.14', '< 4'
end end

View File

@@ -151,6 +151,25 @@ module Rswag
) )
end end
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 end
end end

View File

@@ -48,6 +48,22 @@ module Rswag
end end
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 context "'query' parameter of type 'array'" do
before do before do
api_metadata[:operation][:parameters] << { api_metadata[:operation][:parameters] << {
@@ -193,6 +209,33 @@ module Rswag
end end
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 context 'consumes & produces' do
before do before do
api_metadata[:operation][:consumes] = [ 'application/json', 'application/xml' ] api_metadata[:operation][:consumes] = [ 'application/json', 'application/xml' ]
@@ -201,8 +244,8 @@ module Rswag
it "includes corresponding 'Accept' & 'Content-Type' headers" do it "includes corresponding 'Accept' & 'Content-Type' headers" do
expect(headers).to match( expect(headers).to match(
'ACCEPT' => 'application/json;application/xml', 'Accept' => 'application/json;application/xml',
'CONTENT_TYPE' => 'application/json;application/xml' 'Content-Type' => 'application/json;application/xml'
) )
end end
end end
@@ -217,8 +260,8 @@ module Rswag
it "includes corresponding 'Accept' & 'Content-Type' headers" do it "includes corresponding 'Accept' & 'Content-Type' headers" do
expect(headers).to match( expect(headers).to match(
'ACCEPT' => 'application/json;application/xml', 'Accept' => 'application/json;application/xml',
'CONTENT_TYPE' => 'application/json;application/xml' 'Content-Type' => 'application/json;application/xml'
) )
end end
end end

View File

@@ -29,7 +29,7 @@ module Rswag
api_metadata[:response][:schema] = { api_metadata[:response][:schema] = {
type: 'object', type: 'object',
properties: { text: { type: 'string' } }, properties: { text: { type: 'string' } },
required: [ 'text' ] required: ['text']
} }
end end
@@ -42,6 +42,25 @@ module Rswag
let(:response) { OpenStruct.new(code: 200, body: "{\"foo\":\"Some comment\"}") } let(:response) { OpenStruct.new(code: 200, body: "{\"foo\":\"Some comment\"}") }
it { expect { call }.to raise_error UnexpectedResponse } it { expect { call }.to raise_error UnexpectedResponse }
end 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 end
context "referenced 'schema' provided" do context "referenced 'schema' provided" do
@@ -51,7 +70,7 @@ module Rswag
author: { author: {
type: 'object', type: 'object',
properties: { name: { type: 'string' } }, properties: { name: { type: 'string' } },
required: [ 'name' ] required: ['name']
} }
} }
end end
@@ -66,6 +85,42 @@ module Rswag
it { expect { call }.to raise_error UnexpectedResponse } it { expect { call }.to raise_error UnexpectedResponse }
end end
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 end
end end

View File

@@ -3,33 +3,33 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Swagger UI</title> <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="<%= asset_path '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 rel="icon" type="image/png" href="<%= asset_path '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="<%= asset_path '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="<%= asset_path '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="<%= asset_path '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="<%= asset_path '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 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="<%= asset_path '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="<%= asset_path '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="<%= asset_path '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="<%= asset_path '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="<%= asset_path '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="<%= asset_path '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="<%= asset_path '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="<%= asset_path '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="<%= asset_path '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="<%= asset_path '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="<%= asset_path '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="<%= asset_path 'swagger-ui/lib/jsoneditor.min.js' %>" type='text/javascript'></script>
<script src='/assets/swagger-ui/lib/marked.js' type='text/javascript'></script> <script src="<%= asset_path '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/swagger-oauth.js' %>" type='text/javascript'></script>
<!-- Some basic translations --> <!-- Some basic translations -->
<!-- <script src='/assets/swagger-ui/lang/translator.js' type='text/javascript'></script> --> <!-- <script src="<%= asset_path 'swagger-ui/lang/translator.js' %>" type='text/javascript'></script> -->
<!-- <script src='/assets/swagger-ui/lang/ru.js' type='text/javascript'></script> --> <!-- <script src="<%= asset_path '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/en.js' %>" type='text/javascript'></script> -->
<script type="text/javascript"> <script type="text/javascript">
$(function () { $(function () {
@@ -84,7 +84,7 @@
<body class="swagger-section"> <body class="swagger-section">
<div id='header'> <div id='header'>
<div class="swagger-ui-wrap"> <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'> <form id='api_selector'>
<div class='input'><input placeholder="http://example.com/api" id="input_baseUrl" name="baseUrl" type="text"/></div> <div class='input'><input placeholder="http://example.com/api" id="input_baseUrl" name="baseUrl" type="text"/></div>
<div id='auth_container'></div> <div id='auth_container'></div>

View File

@@ -1,4 +1,3 @@
require 'rswag/ui/version'
require 'rswag/ui/configuration' require 'rswag/ui/configuration'
require 'rswag/ui/engine' require 'rswag/ui/engine'

View File

@@ -5,7 +5,14 @@ module Rswag
initializer 'rswag-ui.initialize' do |app| initializer 'rswag-ui.initialize' do |app|
if app.config.respond_to?(:assets) 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 end
end end

View File

@@ -1,5 +0,0 @@
module Rswag
module Ui
VERSION = '1.0.2'
end
end

View File

@@ -1,12 +1,9 @@
$:.push File.expand_path("../lib", __FILE__) $:.push File.expand_path("../lib", __FILE__)
# Maintain your gem's version:
require 'rswag/ui/version'
# Describe your gem and declare its dependencies: # Describe your gem and declare its dependencies:
Gem::Specification.new do |s| Gem::Specification.new do |s|
s.name = "rswag-ui" s.name = "rswag-ui"
s.version = Rswag::Ui::VERSION s.version = ENV['TRAVIS_TAG'] || '0.0.0'
s.authors = ["Richie Morris"] s.authors = ["Richie Morris"]
s.email = ["domaindrivendev@gmail.com"] s.email = ["domaindrivendev@gmail.com"]
s.homepage = "https://github.com/domaindrivendev/rswag" 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.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 end

View File

@@ -880,7 +880,7 @@
padding: 6px 8px; 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 { .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; width: 128px;
height: 16px; height: 16px;
display: block; display: block;
@@ -1222,7 +1222,7 @@
height: 18px; height: 18px;
vertical-align: middle; vertical-align: middle;
display: inline-block; 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 { .swagger-section .authorize__btn_operation_login {
background-position: 0 0; background-position: 0 0;

View File

@@ -880,7 +880,7 @@
padding: 6px 8px; 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 { .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; width: 128px;
height: 16px; height: 16px;
display: block; display: block;
@@ -1222,7 +1222,7 @@
height: 18px; height: 18px;
vertical-align: middle; vertical-align: middle;
display: inline-block; 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 { .swagger-section .authorize__btn_operation_login {
background-position: 0 0; background-position: 0 0;
@@ -1354,7 +1354,7 @@
height: 18px; height: 18px;
vertical-align: middle; vertical-align: middle;
display: inline-block; 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 { .swagger-section .api-ic .api_information_panel {
position: relative; position: relative;

View File

@@ -2,7 +2,7 @@
font-size: 1.5em; font-size: 1.5em;
font-weight: bold; font-weight: bold;
text-decoration: none; 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; padding: 20px 0 20px 40px;
} }
#text-head { #text-head {
@@ -64,7 +64,7 @@ h1 {
width: 1500px; width: 1500px;
margin: auto; margin: auto;
margin-top: 0; margin-top: 0;
background-image: url('../images/shield.png'); background-image: url(<%= asset_path('swagger-ui/images/shield.png') %>);
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: -40px -20px; background-position: -40px -20px;
margin-bottom: 210px; margin-bottom: 210px;

View File

@@ -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');
}

View 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');
}

View File

@@ -1,4 +1,3 @@
require 'rswag/version'
require 'rswag/specs' require 'rswag/specs'
require 'rswag/api' require 'rswag/api'
require 'rswag/ui' require 'rswag/ui'

View File

@@ -1,3 +0,0 @@
module Rswag
VERSION = '1.0.2'
end

View File

@@ -1,12 +1,9 @@
$:.push File.expand_path("../lib", __FILE__) $:.push File.expand_path("../lib", __FILE__)
# Maintain your gem's version:
require 'rswag/version'
# Describe your gem and declare its dependencies: # Describe your gem and declare its dependencies:
Gem::Specification.new do |s| Gem::Specification.new do |s|
s.name = "rswag" s.name = "rswag"
s.version = Rswag::VERSION s.version = ENV['TRAVIS_TAG'] || '0.0.0'
s.authors = ["Richie Morris"] s.authors = ["Richie Morris"]
s.email = ["domaindrivendev@gmail.com"] s.email = ["domaindrivendev@gmail.com"]
s.homepage = "https://github.com/domaindrivendev/rswag" s.homepage = "https://github.com/domaindrivendev/rswag"
@@ -16,7 +13,7 @@ Gem::Specification.new do |s|
s.files = Dir["{lib}/**/*"] + [ "MIT-LICENSE" ] s.files = Dir["{lib}/**/*"] + [ "MIT-LICENSE" ]
s.add_dependency 'rswag-specs', Rswag::VERSION s.add_dependency 'rswag-specs', ENV['TRAVIS_TAG'] || '0.0.0'
s.add_dependency 'rswag-api', Rswag::VERSION s.add_dependency 'rswag-api', ENV['TRAVIS_TAG'] || '0.0.0'
s.add_dependency 'rswag-ui', Rswag::VERSION s.add_dependency 'rswag-ui', ENV['TRAVIS_TAG'] || '0.0.0'
end end

View File

@@ -1,7 +1,7 @@
class ApplicationController < ActionController::Base class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception. # Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead. # 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 ] wrap_parameters format: [ :json ]
end end

View 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

View File

@@ -17,6 +17,10 @@ class BlogsController < ApplicationController
# GET /blogs/1 # GET /blogs/1
def show def show
@blog = Blog.find_by_id(params[:id]) @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, status: :not_found and return unless @blog
respond_with @blog respond_with @blog
end end

View File

@@ -5,7 +5,7 @@ class Blog < ActiveRecord::Base
{ {
id: id, id: id,
title: title, title: title,
content: content content: nil
} }
end end
end end

View File

@@ -1,5 +1,5 @@
require 'rubygems' require 'rubygems'
gemfile = File.expand_path('../../../../Gemfile', __FILE__) gemfile = File.expand_path('../../../Gemfile', __FILE__)
if File.exist?(gemfile) if File.exist?(gemfile)
ENV['BUNDLE_GEMFILE'] = gemfile ENV['BUNDLE_GEMFILE'] = gemfile

View File

@@ -28,4 +28,6 @@ TestApp::Application.configure do
# Expands the lines which load the assets # Expands the lines which load the assets
config.assets.debug = true config.assets.debug = true
config.eager_load = false
end end

View File

@@ -32,4 +32,6 @@ TestApp::Application.configure do
# Print deprecation notices to the stderr # Print deprecation notices to the stderr
config.active_support.deprecation = :stderr config.active_support.deprecation = :stderr
config.eager_load = false
end end

View File

@@ -5,3 +5,6 @@
# Make sure the secret is at least 30 characters and all random, # Make sure the secret is at least 30 characters and all random,
# no regular words or you'll be exposed to dictionary attacks. # no regular words or you'll be exposed to dictionary attacks.
TestApp::Application.config.secret_token = '60f36cd33756d73f362053f1d45256ae50d75440b634ae73b070a6e35a2df38692f59e28e5ecbd1f9f2e850255f6d29a468bc59ac4484c2b7f0548ddbfc1b870' 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'

View File

@@ -1,6 +1,8 @@
TestApp::Application.routes.draw do TestApp::Application.routes.draw do
resources :blogs, defaults: { :format => :json } resources :blogs, defaults: { :format => :json }
post 'auth-tests/basic', to: 'auth_tests#basic'
mount Rswag::Api::Engine => 'api-docs' mount Rswag::Api::Engine => 'api-docs'
mount Rswag::Ui::Engine => 'api-docs' mount Rswag::Ui::Engine => 'api-docs'
end end

View File

@@ -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 def change
create_table :blogs do |t| create_table :blogs do |t|
t.string :title t.string :title

View File

@@ -1,4 +1,3 @@
# encoding: UTF-8
# This file is auto-generated from the current state of the database. Instead # 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 # of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition. # 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 # 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). # 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.string "title"
t.text "content" t.text "content"
t.datetime "created_at", :null => false t.datetime "created_at"
t.datetime "updated_at", :null => false t.datetime "updated_at"
end end
end end

View 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

View 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

View File

@@ -50,8 +50,18 @@ describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do
produces 'application/json' produces 'application/json'
response '200', 'blog found' do response '200', 'blog found' do
header 'ETag', type: :string
header 'Last-Modified', type: :string
header 'Cache-Control', type: :string
schema '$ref' => '#/definitions/blog' 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(:blog) { Blog.create(title: 'foo', content: 'bar') }
let(:id) { blog.id } let(:id) { blog.id }
run_test! run_test!

View File

@@ -50,4 +50,6 @@ RSpec.configure do |config|
config.filter_rails_from_backtrace! config.filter_rails_from_backtrace!
# arbitrary gems may also be filtered via: # arbitrary gems may also be filtered via:
# config.filter_gems_from_backtrace("gem name") # config.filter_gems_from_backtrace("gem name")
Capybara.javascript_driver = :webkit
end end

View File

@@ -39,12 +39,15 @@ RSpec.configure do |config|
properties: { properties: {
id: { type: 'integer' }, id: { type: 'integer' },
title: { type: 'string' }, title: { type: 'string' },
content: { type: 'string' } content: { type: 'string', 'x-nullable': true }
}, },
required: [ 'id', 'title', 'content' ] required: [ 'id', 'title', 'content' ]
} }
}, },
securityDefinitions: { securityDefinitions: {
basic_auth: {
type: :basic
},
api_key: { api_key: {
type: :apiKey, type: :apiKey,
name: 'api_key', name: 'api_key',

View File

@@ -5,6 +5,30 @@
"version": "v1" "version": "v1"
}, },
"paths": { "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": { "/blogs": {
"post": { "post": {
"summary": "Creates a blog", "summary": "Creates a blog",
@@ -89,8 +113,26 @@
"responses": { "responses": {
"200": { "200": {
"description": "blog found", "description": "blog found",
"headers": {
"ETag": {
"type": "string"
},
"Last-Modified": {
"type": "string"
},
"Cache-Control": {
"type": "string"
}
},
"schema": { "schema": {
"$ref": "#/definitions/blog" "$ref": "#/definitions/blog"
},
"examples": {
"application/json": {
"id": 1,
"title": "Hello world!",
"content": "Hello world and hello universe. Thank you all very much!!!"
}
} }
}, },
"404": { "404": {
@@ -128,7 +170,8 @@
"type": "string" "type": "string"
}, },
"content": { "content": {
"type": "string" "type": "string",
"x-nullable": true
} }
}, },
"required": [ "required": [
@@ -139,6 +182,9 @@
} }
}, },
"securityDefinitions": { "securityDefinitions": {
"basic_auth": {
"type": "basic"
},
"api_key": { "api_key": {
"type": "apiKey", "type": "apiKey",
"name": "api_key", "name": "api_key",