mirror of
https://github.com/ditkrg/rswag.git
synced 2026-01-23 06:16:42 +00:00
rename to rswag plus major refactor - almost a rewrite
This commit is contained in:
parent
f8d993356f
commit
c558098c39
37
Gemfile
37
Gemfile
@ -1,26 +1,29 @@
|
||||
source 'https://rubygems.org'
|
||||
source "https://rubygems.org"
|
||||
|
||||
# Declare your gem's dependencies in swagger_rails.gemspec.
|
||||
# Bundler will treat runtime dependencies like base dependencies, and
|
||||
# development dependencies will be added by default to the :development group.
|
||||
gemspec
|
||||
# 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'
|
||||
|
||||
# Declare any dependencies that are still in development here instead of in
|
||||
# your gemspec. These might include edge Rails or gems from your path or
|
||||
# Git. Remember to move these dependencies to your gemspec before releasing
|
||||
# your gem to rubygems.org.
|
||||
gem 'rails', "#{rails_version}"
|
||||
|
||||
case rails_version.split('.').first
|
||||
when '3'
|
||||
gem 'strong_parameters'
|
||||
when '4', '5'
|
||||
gem 'responders'
|
||||
end
|
||||
|
||||
# To use a debugger
|
||||
# gem 'debugger', group: [:development, :test]
|
||||
#
|
||||
gem 'sqlite3'
|
||||
|
||||
group :development, :test do
|
||||
gem 'pry'
|
||||
gem 'generator_spec'
|
||||
end
|
||||
gem 'rswag-api', path: './rswag-api'
|
||||
gem 'rswag-ui', path: './rswag-ui'
|
||||
|
||||
# To use debugger
|
||||
# gem 'debugger'
|
||||
|
||||
group :test do
|
||||
gem 'test-unit'
|
||||
gem 'database_cleaner'
|
||||
gem 'rspec-rails'
|
||||
gem 'generator_spec'
|
||||
gem 'rswag-specs', path: '~/src/rswag/rswag-specs'
|
||||
end
|
||||
|
||||
84
Gemfile.lock
84
Gemfile.lock
@ -1,8 +1,20 @@
|
||||
PATH
|
||||
remote: .
|
||||
remote: ./rswag-api
|
||||
specs:
|
||||
swagger_rails (1.0.1.pre.beta3)
|
||||
rack
|
||||
rswag-api (1.0.0)
|
||||
rails (>= 3.1, < 5.1)
|
||||
|
||||
PATH
|
||||
remote: ./rswag-ui
|
||||
specs:
|
||||
rswag-ui (1.0.0)
|
||||
rails (>= 3.1, < 5.1)
|
||||
|
||||
PATH
|
||||
remote: ~/src/rswag/rswag-specs
|
||||
specs:
|
||||
rswag-specs (1.0.0)
|
||||
json-schema
|
||||
rails (>= 3.1, < 5.1)
|
||||
|
||||
GEM
|
||||
@ -35,10 +47,9 @@ GEM
|
||||
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)
|
||||
coderay (1.1.0)
|
||||
database_cleaner (1.5.3)
|
||||
diff-lcs (1.2.5)
|
||||
erubis (2.7.0)
|
||||
generator_spec (0.9.3)
|
||||
@ -48,20 +59,17 @@ GEM
|
||||
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)
|
||||
method_source (0.8.2)
|
||||
mime-types (1.25.1)
|
||||
multi_json (1.11.2)
|
||||
multi_json (1.12.1)
|
||||
polyglot (0.3.5)
|
||||
power_assert (0.2.6)
|
||||
pry (0.10.3)
|
||||
coderay (~> 1.1.0)
|
||||
method_source (~> 0.8.1)
|
||||
slop (~> 3.4)
|
||||
power_assert (0.3.1)
|
||||
rack (1.4.7)
|
||||
rack-cache (1.5.1)
|
||||
rack-cache (1.6.1)
|
||||
rack (>= 0.4)
|
||||
rack-ssl (1.3.4)
|
||||
rack
|
||||
@ -82,34 +90,38 @@ GEM
|
||||
rake (>= 0.8.7)
|
||||
rdoc (~> 3.4)
|
||||
thor (>= 0.14.6, < 2.0)
|
||||
rake (10.4.2)
|
||||
rake (11.3.0)
|
||||
rdoc (3.12.2)
|
||||
json (~> 1.4)
|
||||
rspec-core (3.4.4)
|
||||
rspec-support (~> 3.4.0)
|
||||
rspec-expectations (3.4.0)
|
||||
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.4.0)
|
||||
rspec-mocks (3.4.1)
|
||||
rspec-support (~> 3.5.0)
|
||||
rspec-mocks (3.5.0)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.4.0)
|
||||
rspec-rails (3.4.1)
|
||||
actionpack (>= 3.0, < 4.3)
|
||||
activesupport (>= 3.0, < 4.3)
|
||||
railties (>= 3.0, < 4.3)
|
||||
rspec-core (~> 3.4.0)
|
||||
rspec-expectations (~> 3.4.0)
|
||||
rspec-mocks (~> 3.4.0)
|
||||
rspec-support (~> 3.4.0)
|
||||
rspec-support (3.4.1)
|
||||
slop (3.6.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.11)
|
||||
test-unit (3.1.5)
|
||||
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)
|
||||
@ -122,10 +134,12 @@ PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
database_cleaner
|
||||
generator_spec
|
||||
pry
|
||||
rspec-rails (~> 3.0)
|
||||
rails (= 3.2.22)
|
||||
rspec-rails
|
||||
rswag-api!
|
||||
rswag-specs!
|
||||
rswag-ui!
|
||||
sqlite3
|
||||
swagger_rails!
|
||||
strong_parameters
|
||||
test-unit
|
||||
|
||||
262
README.md
262
README.md
@ -1,50 +1,51 @@
|
||||
swagger_rails
|
||||
rswag (formerly swagger_rails)
|
||||
=========
|
||||
|
||||
Generate API documentation, including a slick discovery UI and playground, directly from your rspec integration specs. Use the provided DSL to describe and test API operations in your spec files. Then, you can easily generate corresponding swagger.json files and serve them up with an embedded version of [swagger-ui](https://github.com/swagger-api/swagger-ui). Best of all, it requires minimal coding and maintenance, allowing you to focus on building an awesome API!
|
||||
[Swagger](http://swagger.io) tooling for Rails API's. Generate beautiful API documentation, including a UI to explore and test operations, directly from your rspec integration tests.
|
||||
|
||||
Rswag extends rspec-rails "request specs" with a Swagger-based DSL for describing and testing API operations. You describe your API operations with a succinct, intuitive syntax, and it automaticaly runs the tests. Once you have green tests, run a rake task to auto-generate corresponding Swagger files and expose them as JSON endpoints. Rswag also provides an embedded version of the awesome [swagger-ui](https://github.com/swagger-api/swagger-ui) that's powered by the exposed JSON. This toolchain makes it seamless to go from integration specs, which you're probably doing in some form already, to living documentation for your API consumers.
|
||||
|
||||
And that's not all ...
|
||||
|
||||
Once you have a Web API that can describe itself in Swagger, you've opened the treasure chest of Swagger-based tools including a client generator that can be targeted to a wide range of popular platforms. See [swagger-codegen](https://github.com/swagger-api/swagger-codegen) for more details.
|
||||
|
||||
__NOTE__: It's early days so please be gentle when reporting issues :) As author of a similar project in the .NET space - [Swashbuckle](https://github.com/domaindrivendev/Swashbuckle), that's become very popular, I think there's real potential here. Please feel free to contribute. I'll be more than happy to consider PR's ... so long as they include tests.
|
||||
Once you have an API that can describe itself in Swagger, you've opened the treasure chest of Swagger-based tools including a client generator that can be targeted to a wide range of popular platforms. See [swagger-codegen](https://github.com/swagger-api/swagger-codegen) for more details.
|
||||
|
||||
## Getting Started ##
|
||||
|
||||
1. Add this line to your applications _Gemfile_:
|
||||
|
||||
```ruby
|
||||
gem 'swagger_rails'
|
||||
gem 'rswag'
|
||||
```
|
||||
|
||||
2. Run the install generator
|
||||
|
||||
```ruby
|
||||
rails g swagger_rails:install
|
||||
rails g rswag:install
|
||||
```
|
||||
|
||||
3. Create an integration spec to describe and test your API
|
||||
3. Create an integration spec to describe and test your API.
|
||||
|
||||
```ruby
|
||||
# spec/integration/blogs_spec.rb
|
||||
require 'swagger_helper'
|
||||
|
||||
describe 'Blogs API' do
|
||||
|
||||
path '/blogs' do
|
||||
|
||||
post 'creates a new blog' do
|
||||
operation_description 'Detailed implementation notes for the create a new blog API endpoint'
|
||||
consumes 'application/json'
|
||||
parameter :blog, in: :body, schema: {
|
||||
post 'Creates a blog' do
|
||||
tags 'Blogs'
|
||||
consumes 'application/json', 'application/xml'
|
||||
parameter name: :blog, in: :body, schema: {
|
||||
type: :object,
|
||||
properties: {
|
||||
title: { :type => :string },
|
||||
content: { :type => :string }
|
||||
title: { type: :string },
|
||||
content: { type: :string }
|
||||
},
|
||||
required: [ 'title', 'content' ]
|
||||
}
|
||||
|
||||
response '200', 'success' do
|
||||
response '201', 'blog created' do
|
||||
let(:blog) { { title: 'foo', content: 'bar' } }
|
||||
run_test!
|
||||
end
|
||||
@ -55,43 +56,76 @@ __NOTE__: It's early days so please be gentle when reporting issues :) As author
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
path '/blogs/{id}' do
|
||||
|
||||
get 'Retrieves a blog' do
|
||||
tags 'Blogs'
|
||||
produces 'application/json', 'application/xml'
|
||||
parameter name: :id, :in => :path, :type => :string
|
||||
|
||||
response '200', 'blog found' do
|
||||
schema type: :object,
|
||||
properties: {
|
||||
id: { type: :integer },
|
||||
title: { type: :string },
|
||||
content: { type: :string }
|
||||
},
|
||||
required: [ 'id', 'title', 'content' ]
|
||||
|
||||
let(:id) { Blog.create(title: 'foo', content: 'bar').id }
|
||||
run_test!
|
||||
end
|
||||
|
||||
response '404', 'blog not found' do
|
||||
let(:id) { 'invalid' }
|
||||
run_test!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
4. Generate the swagger.json file(s)
|
||||
4. Generate the Swagger JSON file(s)
|
||||
|
||||
```ruby
|
||||
rake swaggerize
|
||||
rake rswag:specs:swaggerize
|
||||
```
|
||||
|
||||
5. Spin up your app and check out the awesome, auto-generated docs at _/api-docs_!
|
||||
|
||||
## How does it Work? ##
|
||||
## The rspec DSL ##
|
||||
|
||||
There's two separate parts to swagger rails:
|
||||
### Paths, Operations and Responses ###
|
||||
|
||||
1. Tooling to easily generate swagger descriptions directly from your API tests/specs
|
||||
2. Rails middleware to auto-magically serve a swagger-ui that's powered by those descriptions
|
||||
If you've used [Swagger](http://swagger.io/specification) before, then the syntax should be very familiar. To describe your API operations, start by specifying a path and then list the supported operations (i.e. HTTP verbs) for that path. Path parameters must be surrounded by curly braces ({}). Within an operation block (see "post" or "get" in the example above), most of the fields supported by the [Swagger "Operation" object](http://swagger.io/specification/#operationObject) are available as methods on the example group. To list (and test) the various responses for an operation, create one or more response blocks. Again, you can reference the [Swagger "Response" object](http://swagger.io/specification/#responseObject) for available fields.
|
||||
|
||||
The tooling is designed to fit seamlessly into your development workflow, with the swagger docs and UI being a by-product that you get for free ... well almost free :) You'll need to use the provided rspec DSL. But, it's an intuitive syntax (based on the [swagger-spec](http://swagger.io/specification/)) and, IMO, a very succint and expressive way to write api/integration tests.
|
||||
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).
|
||||
|
||||
Once you've generated the swagger files, the functionality to serve them up, along with the swagger-ui, is provided as a Rails Engine. After running the install generator, you'll see the following line added to _routes.rb_
|
||||
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
|
||||
mount SwaggerRails::Engine => '/api-docs'
|
||||
response '201', 'blog created' do
|
||||
let(:blog) { { title: 'foo', content: 'bar' } }
|
||||
|
||||
before do |example|
|
||||
submit_request(example.metadata)
|
||||
end
|
||||
|
||||
it 'returns a valid 201 response' do |example|
|
||||
assert_response_matches_metadata(example.metadata)
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
This will wire up routes for the swagger docs and swagger-ui assets, all prefixed with "/api-docs". For example, if you navigate to "/api-docs/index.html" you'll get the swagger-ui. If you navigate to "/api-docs/v1/swagger.json", you'll get the swagger.json file under your app root at "swagger/v1/swagger.json" - assuming it was generated.
|
||||
### Global Metadata ###
|
||||
|
||||
If you'd like your swagger resources to appear under a different base path, you can change the Engine mount point from "/api-docs" to something else.
|
||||
|
||||
## Multiple Swagger Documents ##
|
||||
|
||||
By default, the generator will create all operation descriptions in a single swagger.json file. You can customize this by defining additional documents in the swagger_helper (installed under your spec folder) ...
|
||||
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:
|
||||
|
||||
```ruby
|
||||
# spec/swagger_helper.rb
|
||||
RSpec.configure do |config|
|
||||
...
|
||||
config.swagger_root = Rails.root.to_s + '/swagger'
|
||||
|
||||
config.swagger_docs = {
|
||||
'v1/swagger.json' => {
|
||||
@ -99,7 +133,8 @@ By default, the generator will create all operation descriptions in a single swa
|
||||
info: {
|
||||
title: 'API V1',
|
||||
version: 'v1'
|
||||
}
|
||||
},
|
||||
basePath: '/api/v1'
|
||||
},
|
||||
|
||||
'v2/swagger.json' => {
|
||||
@ -107,48 +142,181 @@ By default, the generator will create all operation descriptions in a single swa
|
||||
info: {
|
||||
title: 'API V2',
|
||||
version: 'v2'
|
||||
}
|
||||
},
|
||||
basePath: '/api/v2'
|
||||
}
|
||||
}
|
||||
end
|
||||
```
|
||||
|
||||
And then tagging your spec's with the target swagger_doc:
|
||||
__NOTE__: By default, the paths, operations and responses defined in your spec files will be associated with the first Swagger document in _swagger_helper.rb_. If you're using multiple documents, you'll need to tag the individual specs with their target document name:
|
||||
|
||||
```ruby
|
||||
require 'swagger_helper'
|
||||
|
||||
describe 'Blogs API V2', swagger_doc: 'v2/swagger.json' do
|
||||
# spec/integration/v2/blogs_spec.rb
|
||||
describe 'Blogs API', swagger_doc: 'v2/swagger.json' do
|
||||
|
||||
path '/blogs' do
|
||||
...
|
||||
end
|
||||
end
|
||||
|
||||
path '/blogs/{id}' do
|
||||
...
|
||||
end
|
||||
```
|
||||
|
||||
Then, when you run the generator and spin up the swagger-ui, you'll see a select box in the top right allowing your audience to switch between the different API versions.
|
||||
## Configuration & Customization ##
|
||||
|
||||
## Tweaking the Swagger Document with Request Context ##
|
||||
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.
|
||||
|
||||
You can provide global metadata for Swagger documents in the swagger_helper file and this will be included in the resulting Swagger JSON when you run the "swaggerize" rake task. For the most part, this is sufficient. However, you may want to make some changes that require the current request context. This is possible by applying an optional swagger_filter in the swagger_rails initializer (installed into config/initializers):
|
||||
|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_|
|
||||
|
||||
### Output Location for Generated Swagger Files ###
|
||||
|
||||
You can adjust this in the _swagger_helper.rb_ that's installed with __rspec-specs__:
|
||||
|
||||
```ruby
|
||||
SwaggerRails.configure do |c|
|
||||
# spec/swagger_helper.rb
|
||||
RSpec.configure do |config|
|
||||
config.swagger_root = Rails.root.to_s + '/your-custom-folder-name'
|
||||
...
|
||||
end
|
||||
```
|
||||
|
||||
__NOTE__: If you do change this, you'll also need to update the rswag-api.rb initializer (assuming you're using rswag-api). More on this later.
|
||||
|
||||
### Referenced Parameters and Schema Definitions ###
|
||||
|
||||
Swagger allows you to describe JSON structures inline with your operation descriptions OR as referenced globals. For example, you might have a standard response structure for all failed operations. Rather than repeating the schema in every operation spec, you can define it globally and provide a reference to it in each spec:
|
||||
|
||||
```ruby
|
||||
# spec/swagger_helper.rb
|
||||
config.swagger_docs = {
|
||||
'v1/swagger.json' => {
|
||||
swagger: '2.0',
|
||||
info: {
|
||||
title: 'API V1'
|
||||
},
|
||||
definitions: {
|
||||
errors_object: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
errors: { '$ref' => '#/definitions/errors_map' }
|
||||
}
|
||||
},
|
||||
errors_map: {
|
||||
type: 'object',
|
||||
additionalProperties: {
|
||||
type: 'array',
|
||||
items: { type: 'string' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# spec/integration/blogs_spec.rb
|
||||
describe 'Blogs API' do
|
||||
|
||||
path '/blogs' do
|
||||
|
||||
post 'Creates a blog' do
|
||||
|
||||
response 422, 'invalid request' do
|
||||
schema '$ref' => '#/definitions/errors_object'
|
||||
...
|
||||
end
|
||||
|
||||
# spec/integration/comments_spec.rb
|
||||
describe 'Blogs API' do
|
||||
|
||||
path '/blogs/{blog_id}/comments' do
|
||||
|
||||
post 'Creates a comment' do
|
||||
|
||||
response 422, 'invalid request' do
|
||||
schema '$ref' => '#/definitions/errors_object'
|
||||
...
|
||||
end
|
||||
```
|
||||
|
||||
### 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_:
|
||||
|
||||
```ruby
|
||||
TestApp::Application.routes.draw do
|
||||
...
|
||||
|
||||
mount Rswag::Api::Engine => 'your-custom-prefix'
|
||||
end
|
||||
```
|
||||
|
||||
Assuming a Swagger file exists at '<swagger_root>/v1/swagger.json', this configuration would expose the file as the following JSON endpoint:
|
||||
|
||||
```
|
||||
GET http://<hostname>/your-custom-prefix/v1/swagger.json
|
||||
```
|
||||
|
||||
### Root Location for Swagger Files ###
|
||||
|
||||
You can adjust this in the _rswag-api.rb_ initializer that's installed with __rspec-api__:
|
||||
|
||||
```ruby
|
||||
Rswag::Api.configure do |c|
|
||||
c.swagger_root = Rails.root.to_s + '/your-custom-folder-name'
|
||||
...
|
||||
end
|
||||
```
|
||||
|
||||
__NOTE__: If you're using rswag-specs to generate Swagger files, you'll want to ensure they both use the same <swagger_root>. The reason for separate settings is to maintain independence between the two gems. For example, you could install rswag-api independently and create your Swagger files manually.
|
||||
|
||||
### Dynamic Values for Swagger JSON ##
|
||||
|
||||
There may be cases where you need to add dynamic values to the Swagger JSON that's returned by rswag-api. For example, you may want to provide an explicit host name. Rather than hardcoding it, you can configure a filter that's executed prior to serializing every Swagger document:
|
||||
|
||||
```ruby
|
||||
Rswag::Api.configure do |c|
|
||||
...
|
||||
|
||||
c.swagger_filter = lambda { |swagger, env| swagger['host'] = env['HTTP_HOST'] }
|
||||
end
|
||||
```
|
||||
|
||||
This function will get called prior to serialization of any Swagger file and is passed the rack env for the current request. This provides a lot of flexibilty. For example, you could dynamically assign the "host" property (as shown above) or you could inspect session information or Authoriation header and remove operations based on user permissions.
|
||||
Note how the filter is passed the rack env for the current request. This provides a lot of flexibilty. For example, you can assign the "host" property (as shown) or you could inspect session information or an Authoriation header and remove operations based on user permissions.
|
||||
|
||||
## Customizing the UI ##
|
||||
### Enable Swagger Endpoints for swagger-ui ###
|
||||
|
||||
You can update the _rswag-ui.rb_ initializer, installed with rswag-ui, to specify which Swagger endpoints should be available to power the documentation UI. If you're using rswag-api, these should correspond to the Swagger endpoints it exposes. When the UI is rendered, you'll see these listed in a drop-down to the top right of the page:
|
||||
|
||||
```ruby
|
||||
Rswag::Ui.configure do |c|
|
||||
c.swagger_endpoint '/api-docs/v1/swagger.json', 'API V1 Docs'
|
||||
c.swagger_endpoint '/api-docs/v2/swagger.json', 'API V2 Docs'
|
||||
end
|
||||
```
|
||||
|
||||
### Route Prefix for the swagger-ui ###
|
||||
|
||||
Similar to rswag-api, you can customize the swagger-ui path by changing it's mount prefix in _routes.rb_:
|
||||
|
||||
```ruby
|
||||
TestApp::Application.routes.draw do
|
||||
...
|
||||
|
||||
mount Rswag::Api::Engine => 'api-docs'
|
||||
mount Rswag::Ui::Engine => 'your-custom-prefix'
|
||||
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.
|
||||
|
||||
```ruby
|
||||
rails g swagger_rails:custom_ui
|
||||
rails g rswag:ui:custom_ui
|
||||
```
|
||||
|
||||
This will add a local version that you can customize at "app/views/swagger_rails/swagger_ui/index.html.erb"
|
||||
This will add a local version that you can modify at _app/views/rswag/ui/home/index.html.erb_
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
= SwaggerRails
|
||||
|
||||
This project rocks and uses MIT-LICENSE.
|
||||
@ -1,30 +0,0 @@
|
||||
require 'json'
|
||||
|
||||
module SwaggerRails
|
||||
class SwaggerUiController < ApplicationController
|
||||
|
||||
def index
|
||||
swagger_root = SwaggerRails.config.resolve_swagger_root(request.env)
|
||||
swagger_root.concat('/') unless swagger_root.end_with?('/')
|
||||
|
||||
swagger_filenames = Dir["#{swagger_root}/**/*.json"]
|
||||
|
||||
@discovery_paths = Hash[
|
||||
swagger_filenames.map do |filename|
|
||||
[
|
||||
filename.sub(swagger_root, root_path.chomp('/')),
|
||||
load_json(filename)["info"]["title"]
|
||||
]
|
||||
end
|
||||
]
|
||||
|
||||
render :index, layout: false
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_json(filename)
|
||||
JSON.parse(File.read(filename))
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -1,3 +0,0 @@
|
||||
SwaggerRails::Engine.routes.draw do
|
||||
root to: 'swagger_ui#index'
|
||||
end
|
||||
@ -1,9 +0,0 @@
|
||||
module SwaggerRails
|
||||
class CustomUiGenerator < Rails::Generators::Base
|
||||
source_root File.expand_path('../files', __FILE__)
|
||||
|
||||
def add_custom_index
|
||||
copy_file('index.html.erb', 'app/views/swagger_rails/swagger_ui/index.html.erb')
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -1,153 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Swagger UI</title>
|
||||
<link rel="icon" type="image/png" href="images/favicon-32x32.png" sizes="32x32" />
|
||||
<link rel="icon" type="image/png" href="images/favicon-16x16.png" sizes="16x16" />
|
||||
<link href='css/typography.css' media='screen' rel='stylesheet' type='text/css'/>
|
||||
<link href='css/reset.css' media='screen' rel='stylesheet' type='text/css'/>
|
||||
<link href='css/screen.css' media='screen' rel='stylesheet' type='text/css'/>
|
||||
<link href='css/reset.css' media='print' rel='stylesheet' type='text/css'/>
|
||||
<link href='css/print.css' media='print' rel='stylesheet' type='text/css'/>
|
||||
<script src='lib/jquery-1.8.0.min.js' type='text/javascript'></script>
|
||||
<script src='lib/jquery.slideto.min.js' type='text/javascript'></script>
|
||||
<script src='lib/jquery.wiggle.min.js' type='text/javascript'></script>
|
||||
<script src='lib/jquery.ba-bbq.min.js' type='text/javascript'></script>
|
||||
<script src='lib/handlebars-2.0.0.js' type='text/javascript'></script>
|
||||
<script src='lib/underscore-min.js' type='text/javascript'></script>
|
||||
<script src='lib/backbone-min.js' type='text/javascript'></script>
|
||||
<script src='swagger-ui.js' type='text/javascript'></script>
|
||||
<script src='lib/highlight.7.3.pack.js' type='text/javascript'></script>
|
||||
<script src='lib/marked.js' type='text/javascript'></script>
|
||||
<script src='lib/swagger-oauth.js' type='text/javascript'></script>
|
||||
|
||||
<!-- Some basic translations -->
|
||||
<!-- <script src='lang/translator.js' type='text/javascript'></script> -->
|
||||
<!-- <script src='lang/ru.js' type='text/javascript'></script> -->
|
||||
<!-- <script src='lang/en.js' type='text/javascript'></script> -->
|
||||
|
||||
<script type="text/javascript">
|
||||
$(function () {
|
||||
var url = window.location.search.match(/url=([^&]+)/);
|
||||
if (url && url.length > 1) {
|
||||
url = decodeURIComponent(url[1]);
|
||||
} else {
|
||||
url = "<%= @discovery_paths.values.first %>";
|
||||
}
|
||||
|
||||
// Pre load translate...
|
||||
if(window.SwaggerTranslator) {
|
||||
window.SwaggerTranslator.translate();
|
||||
}
|
||||
window.swaggerUi = new SwaggerUi({
|
||||
url: url,
|
||||
dom_id: "swagger-ui-container",
|
||||
supportedSubmitMethods: ['get', 'post', 'put', 'delete', 'patch'],
|
||||
onComplete: function(swaggerApi, swaggerUi){
|
||||
if(typeof initOAuth == "function") {
|
||||
initOAuth({
|
||||
clientId: "your-client-id",
|
||||
clientSecret: "your-client-secret",
|
||||
realm: "your-realms",
|
||||
appName: "your-app-name",
|
||||
scopeSeparator: ","
|
||||
});
|
||||
}
|
||||
|
||||
if(window.SwaggerTranslator) {
|
||||
window.SwaggerTranslator.translate();
|
||||
}
|
||||
|
||||
$('pre code').each(function(i, e) {
|
||||
hljs.highlightBlock(e)
|
||||
});
|
||||
|
||||
addApiKeyAuthorization();
|
||||
|
||||
// Send Rails CSRF Token with every request
|
||||
var csrfToken = new SwaggerClient.ApiKeyAuthorization(
|
||||
'X-CSRF-Token',
|
||||
'<%= form_authenticity_token %>',
|
||||
'header'
|
||||
);
|
||||
swaggerUi.api.clientAuthorizations.add('csrf-token', csrfToken);
|
||||
},
|
||||
onFailure: function(data) {
|
||||
log("Unable to Load SwaggerUI");
|
||||
},
|
||||
docExpansion: "list",
|
||||
apisSorter: "alpha",
|
||||
showRequestHeaders: false
|
||||
});
|
||||
|
||||
function addApiKeyAuthorization(){
|
||||
var key = encodeURIComponent($('#input_apiKey')[0].value);
|
||||
if(key && key.trim() != "") {
|
||||
var apiKeyAuth = new SwaggerClient.ApiKeyAuthorization("api_key", key, "query");
|
||||
window.swaggerUi.api.clientAuthorizations.add("api_key", apiKeyAuth);
|
||||
log("added key " + key);
|
||||
}
|
||||
}
|
||||
|
||||
$('#input_apiKey').change(addApiKeyAuthorization);
|
||||
|
||||
// if you have an apiKey you would like to pre-populate on the page for demonstration purposes...
|
||||
/*
|
||||
var apiKey = "myApiKeyXXXX123456789";
|
||||
$('#input_apiKey').val(apiKey);
|
||||
*/
|
||||
|
||||
window.swaggerUi.load();
|
||||
|
||||
function log() {
|
||||
if ('console' in window) {
|
||||
console.log.apply(console, arguments);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body class="swagger-section">
|
||||
<div id='header'>
|
||||
<div class="swagger-ui-wrap">
|
||||
<a id="logo" href="http://swagger.io">swagger</a>
|
||||
<form id='api_selector'>
|
||||
<div class='input'><input placeholder="http://example.com/api" id="input_baseUrl" name="baseUrl" type="text" disabled="disabled"/></div>
|
||||
<div class='input'><input placeholder="api_key" id="input_apiKey" name="apiKey" type="text"/></div>
|
||||
<div class='input'>
|
||||
<select id="select_version">
|
||||
<% @discovery_paths.each do |name, path| %>
|
||||
<option value="<%= path %>"><%= name %></option>
|
||||
<% end %>
|
||||
</select>
|
||||
<script type="text/javascript">
|
||||
$('#select_version').change(function () {
|
||||
$('#input_baseUrl').val($(this).val());
|
||||
window.swaggerUi.headerView.showCustom();
|
||||
});
|
||||
</script>
|
||||
<style>
|
||||
#select_version {
|
||||
border: none;
|
||||
-moz-border-radius: 4px;
|
||||
-webkit-border-radius: 4px;
|
||||
-o-border-radius: 4px;
|
||||
-ms-border-radius: 4px;
|
||||
-khtml-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
color: white;
|
||||
background-color: #547f00;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
</style>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="message-bar" class="swagger-ui-wrap" data-sw-translate> </div>
|
||||
<div id="swagger-ui-container" class="swagger-ui-wrap"></div>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,9 +0,0 @@
|
||||
Description:
|
||||
Adds default files required to use swagger_rails
|
||||
|
||||
Example:
|
||||
rails generate swagger_rails:install
|
||||
|
||||
This will create:
|
||||
config/swagger/v1/swagger.json
|
||||
config/initializers/swagger_rails.rb
|
||||
@ -1,24 +0,0 @@
|
||||
require 'rails/generators'
|
||||
|
||||
module SwaggerRails
|
||||
|
||||
class InstallGenerator < Rails::Generators::Base
|
||||
source_root File.expand_path('../templates', __FILE__)
|
||||
|
||||
def add_swagger_dir
|
||||
empty_directory('swagger/v1')
|
||||
end
|
||||
|
||||
def add_initializer
|
||||
template('swagger_rails.rb', 'config/initializers/swagger_rails.rb')
|
||||
end
|
||||
|
||||
def add_rspec_helper
|
||||
template('swagger_helper.rb', 'spec/swagger_helper.rb')
|
||||
end
|
||||
|
||||
def add_routes
|
||||
route("mount SwaggerRails::Engine => '/api-docs'")
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -1,30 +0,0 @@
|
||||
require 'rails_helper'
|
||||
require 'swagger_rails/rspec/dsl'
|
||||
|
||||
RSpec.configure do |config|
|
||||
# NOTE: Should be no need to modify these 3 lines
|
||||
config.add_setting :swagger_root
|
||||
config.add_setting :swagger_docs
|
||||
config.extend SwaggerRails::RSpec::DSL
|
||||
|
||||
# Specify a root folder where Swagger JSON files are generated
|
||||
# NOTE: If you're using the Swagger JSON middleware to serve API descriptions, you'll need
|
||||
# to ensure that the same folder is also specified in the swagger_rails initializer
|
||||
config.swagger_root = Rails.root.to_s + '/swagger'
|
||||
|
||||
# Define one or more Swagger documents and provide global metadata for each one
|
||||
# When you run the "swaggerize" rake task, the complete Swagger will be generated
|
||||
# at the provided relative path under swagger_root
|
||||
# By default, the operations defined in spec files are added to the first
|
||||
# document below. You can override this behavior by adding a swagger_doc tag to the
|
||||
# the root example_group in your specs, e.g. describe '...', swagger_doc: 'v2/swagger.json'
|
||||
config.swagger_docs = {
|
||||
'v1/swagger.json' => {
|
||||
swagger: '2.0',
|
||||
info: {
|
||||
title: 'API V1',
|
||||
version: 'v1'
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
@ -1,15 +0,0 @@
|
||||
require 'swagger_rails/version'
|
||||
require 'swagger_rails/configuration'
|
||||
|
||||
module SwaggerRails
|
||||
|
||||
def self.configure
|
||||
yield(config)
|
||||
end
|
||||
|
||||
def self.config
|
||||
@config ||= Configuration.new
|
||||
end
|
||||
end
|
||||
|
||||
require 'swagger_rails/engine' if defined?(Rails)
|
||||
@ -1,10 +0,0 @@
|
||||
module SwaggerRails
|
||||
class Configuration
|
||||
attr_accessor :swagger_root, :swagger_filter
|
||||
|
||||
def resolve_swagger_root(env)
|
||||
path_params = env['action_dispatch.request.path_parameters'] || {}
|
||||
path_params[:swagger_root] || swagger_root
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -1,15 +0,0 @@
|
||||
require 'swagger_rails/middleware/swagger_json'
|
||||
|
||||
module SwaggerRails
|
||||
class Engine < ::Rails::Engine
|
||||
isolate_namespace SwaggerRails
|
||||
|
||||
initializer 'swagger_rails.initialize' do |app|
|
||||
middleware.use SwaggerJson, SwaggerRails.config
|
||||
|
||||
if app.config.respond_to?(:assets)
|
||||
app.config.assets.precompile += [ 'swagger-ui/*' ]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -1,35 +0,0 @@
|
||||
require 'json'
|
||||
|
||||
module SwaggerRails
|
||||
class SwaggerJson
|
||||
|
||||
def initialize(app, config)
|
||||
@app = app
|
||||
@config = config
|
||||
end
|
||||
|
||||
def call(env)
|
||||
path = env['PATH_INFO']
|
||||
filename = "#{@config.resolve_swagger_root(env)}/#{path}"
|
||||
|
||||
if env['REQUEST_METHOD'] == 'GET' && File.file?(filename)
|
||||
swagger = load_json(filename)
|
||||
@config.swagger_filter.call(swagger, env) unless @config.swagger_filter.nil?
|
||||
|
||||
return [
|
||||
'200',
|
||||
{ 'Content-Type' => 'application/json' },
|
||||
[ JSON.dump(swagger) ]
|
||||
]
|
||||
end
|
||||
|
||||
return @app.call(env)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_json(filename)
|
||||
JSON.parse(File.read(filename))
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -1,46 +0,0 @@
|
||||
module SwaggerRails::RSpec
|
||||
class APIMetadata
|
||||
|
||||
def initialize metadata
|
||||
@metadata = metadata
|
||||
end
|
||||
|
||||
def response_example?
|
||||
@metadata.has_key?(:response_code)
|
||||
end
|
||||
|
||||
def swagger_doc
|
||||
@metadata[:swagger_doc]
|
||||
end
|
||||
|
||||
def swagger_data
|
||||
{
|
||||
paths: {
|
||||
@metadata[:path_template] => {
|
||||
@metadata[:http_verb] => operation_metadata
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
|
||||
def operation_metadata
|
||||
{
|
||||
tags: [find_root_of(@metadata)[:description]],
|
||||
summary: @metadata[:summary],
|
||||
description: @metadata[:operation_description],
|
||||
consumes: @metadata[:consumes],
|
||||
produces: @metadata[:produces],
|
||||
parameters: @metadata[:parameters],
|
||||
responses: { @metadata[:response_code] => @metadata[:response] }
|
||||
}
|
||||
end
|
||||
|
||||
def find_root_of(node)
|
||||
parent = node[:parent_example_group]
|
||||
parent.nil? ? node : find_root_of(parent)
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -1,83 +0,0 @@
|
||||
require 'swagger_rails/test_visitor'
|
||||
|
||||
module SwaggerRails
|
||||
module RSpec
|
||||
module DSL
|
||||
|
||||
def path(path_template, &block)
|
||||
metadata = {
|
||||
path_template: path_template
|
||||
}
|
||||
describe(path_template, metadata, &block)
|
||||
end
|
||||
|
||||
def operation(http_verb, summary=nil, &block)
|
||||
metadata = {
|
||||
http_verb: http_verb,
|
||||
summary: summary,
|
||||
parameters: []
|
||||
}
|
||||
describe(http_verb, metadata, &block)
|
||||
end
|
||||
|
||||
def operation_description(message)
|
||||
metadata[:operation_description] = message
|
||||
end
|
||||
|
||||
[ :get, :post, :patch, :put, :delete, :head ].each do |http_verb|
|
||||
define_method(http_verb) do |summary=nil, &block|
|
||||
operation(http_verb, summary, &block)
|
||||
end
|
||||
end
|
||||
|
||||
def consumes(*mime_types)
|
||||
metadata[:consumes] = mime_types
|
||||
end
|
||||
|
||||
def produces(*mime_types)
|
||||
metadata[:produces] = mime_types
|
||||
end
|
||||
|
||||
# Accepts parameter objects:
|
||||
# parameter :petId, in: :path, type: :integer, required: true
|
||||
# Or references:
|
||||
# parameter ref: '#/parameters/Pet'
|
||||
def parameter(name, attributes={})
|
||||
metadata[:parameters] << if name.respond_to?(:has_key?)
|
||||
{ '$ref': name.delete(:ref) || name.delete('ref') }
|
||||
else
|
||||
{ name: name.to_s }.merge(attributes)
|
||||
end
|
||||
end
|
||||
|
||||
def response(code, description, &block)
|
||||
metadata = {
|
||||
response_code: code,
|
||||
response: {
|
||||
description: description
|
||||
}
|
||||
}
|
||||
context(description, metadata, &block)
|
||||
end
|
||||
|
||||
def run_test!
|
||||
if metadata.has_key?(:swagger_doc)
|
||||
swagger_doc = ::RSpec.configuration.swagger_docs[metadata[:swagger_doc]]
|
||||
else
|
||||
swagger_doc = ::RSpec.configuration.swagger_docs.values.first
|
||||
end
|
||||
|
||||
test_visitor = SwaggerRails::TestVisitor.new(swagger_doc)
|
||||
|
||||
before do |example|
|
||||
test_visitor.submit_request!(self, example.metadata)
|
||||
end
|
||||
|
||||
it "returns a #{metadata[:response_code]} status" do |example|
|
||||
test_visitor.assert_response!(self, example.metadata)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,41 +0,0 @@
|
||||
require 'rspec/core/formatters'
|
||||
require 'swagger_helper'
|
||||
require 'swagger_rails/rspec/api_metadata'
|
||||
|
||||
module SwaggerRails
|
||||
module RSpec
|
||||
class Formatter
|
||||
::RSpec::Core::Formatters.register self,
|
||||
:example_group_finished,
|
||||
:stop
|
||||
|
||||
def initialize(output)
|
||||
@output = output
|
||||
@swagger_root = ::RSpec.configuration.swagger_root
|
||||
@swagger_docs = ::RSpec.configuration.swagger_docs
|
||||
|
||||
@output.puts 'Generating Swagger Docs ...'
|
||||
end
|
||||
|
||||
def example_group_finished(notification)
|
||||
metadata = APIMetadata.new(notification.group.metadata)
|
||||
return unless metadata.response_example?
|
||||
|
||||
swagger_doc = @swagger_docs[metadata.swagger_doc] || @swagger_docs.values.first
|
||||
swagger_doc.deep_merge!(metadata.swagger_data)
|
||||
end
|
||||
|
||||
def stop(notification)
|
||||
@swagger_docs.each do |url_path, doc|
|
||||
file_path = File.join(@swagger_root, url_path)
|
||||
|
||||
File.open(file_path, 'w') do |file|
|
||||
file.write(JSON.pretty_generate(doc))
|
||||
end
|
||||
end
|
||||
|
||||
@output.puts 'Swagger Doc generated'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -1,71 +0,0 @@
|
||||
module SwaggerRails
|
||||
class TestVisitor
|
||||
|
||||
def initialize(swagger_doc)
|
||||
@swagger_doc = swagger_doc
|
||||
end
|
||||
|
||||
def submit_request!(test, metadata)
|
||||
params_data = params_data_for(test, metadata[:parameters])
|
||||
|
||||
path = build_path(metadata[:path_template], params_data)
|
||||
body_or_params = build_body_or_params(params_data)
|
||||
headers = build_headers(params_data, metadata[:consumes], metadata[:produces])
|
||||
test.send(metadata[:http_verb], path, { params: body_or_params, headers: headers })
|
||||
end
|
||||
|
||||
def assert_response!(test, metadata)
|
||||
test.assert_response(metadata[:response_code].to_i)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def params_data_for(test, parameters)
|
||||
parameters.map do |parameter|
|
||||
parameter = resolve_param_ref(parameter[:$ref]) if parameter.has_key?(:$ref)
|
||||
|
||||
parameter
|
||||
.slice(:name, :in)
|
||||
.merge(value: test.send(parameter[:name].to_s.underscore))
|
||||
end
|
||||
end
|
||||
|
||||
def resolve_param_ref ref
|
||||
raise "Invalid parameter reference: #{ref}" unless %r{#/parameters/(?<name>.+)} =~ ref
|
||||
|
||||
parameter = (@swagger_doc[:parameters][name] || @swagger_doc[:parameters][name.to_sym])
|
||||
raise "Unknown parameter reference: #{ref}" unless parameter
|
||||
|
||||
parameter.merge(name: name)
|
||||
end
|
||||
|
||||
def build_path(path_template, params_data)
|
||||
path_params_data = params_data.select { |p| p[:in] == :path }
|
||||
|
||||
path_template.dup.tap do |path|
|
||||
path_params_data.each do |param_data|
|
||||
path.sub!("\{#{param_data[:name]}\}", param_data[:value].to_s)
|
||||
end
|
||||
path.prepend(@swagger_doc[:basePath] || '')
|
||||
end
|
||||
end
|
||||
|
||||
def build_body_or_params(params_data)
|
||||
body_params_data = params_data.select { |p| p[:in] == :body }
|
||||
return body_params_data.first[:value].to_json if body_params_data.any?
|
||||
|
||||
query_params_data = params_data.select { |p| p[:in] == :query }
|
||||
Hash[query_params_data.map { |p| [ p[:name], p[:value] ] }]
|
||||
end
|
||||
|
||||
def build_headers(params_data, consumes, produces)
|
||||
header_params_data = params_data.select { |p| p[:in] == :header }
|
||||
headers = Hash[header_params_data.map { |p| [ p[:name], p[:value] ] }]
|
||||
|
||||
headers['ACCEPT'] = produces.join(';') if produces.present?
|
||||
headers['CONTENT_TYPE'] = consumes.join(';') if consumes.present?
|
||||
|
||||
return headers
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -1,3 +0,0 @@
|
||||
module SwaggerRails
|
||||
VERSION = "1.0.1-beta3"
|
||||
end
|
||||
@ -1,14 +0,0 @@
|
||||
# desc "Explaining what the task does"
|
||||
# task :swagger_rails do
|
||||
# # Task goes here
|
||||
# end
|
||||
|
||||
if defined?(RSpec)
|
||||
require 'rspec/core/rake_task'
|
||||
|
||||
desc 'Generate Swagger JSON files from integration specs'
|
||||
RSpec::Core::RakeTask.new('swaggerize') do |t|
|
||||
t.pattern = 'spec/requests/**/*_spec.rb, spec/api/**/*_spec.rb, spec/integration/**/*_spec.rb'
|
||||
t.rspec_opts = [ '--format SwaggerRails::RSpec::Formatter', '--dry-run', '--order defined' ]
|
||||
end
|
||||
end
|
||||
@ -1,16 +1,20 @@
|
||||
require 'rails'
|
||||
|
||||
#!/usr/bin/env rake
|
||||
begin
|
||||
require 'bundler/setup'
|
||||
rescue LoadError
|
||||
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
||||
end
|
||||
|
||||
begin
|
||||
require 'rdoc/task'
|
||||
rescue LoadError
|
||||
require 'rdoc/rdoc'
|
||||
require 'rake/rdoctask'
|
||||
RDoc::Task = Rake::RDocTask
|
||||
end
|
||||
|
||||
RDoc::Task.new(:rdoc) do |rdoc|
|
||||
rdoc.rdoc_dir = 'rdoc'
|
||||
rdoc.title = 'SwaggerRails'
|
||||
rdoc.title = 'rswag-specs'
|
||||
rdoc.options << '--line-numbers'
|
||||
rdoc.rdoc_files.include('README.rdoc')
|
||||
rdoc.rdoc_files.include('lib/**/*.rb')
|
||||
@ -18,9 +22,6 @@ end
|
||||
|
||||
|
||||
|
||||
load 'rails/tasks/statistics.rake'
|
||||
|
||||
|
||||
|
||||
Bundler::GemHelper.install_tasks
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
# This command will automatically be run when you run "rails" with Rails 4 gems installed from the root of your application.
|
||||
|
||||
ENGINE_ROOT = File.expand_path('../..', __FILE__)
|
||||
ENGINE_PATH = File.expand_path('../../lib/swagger_rails/engine', __FILE__)
|
||||
ENGINE_PATH = File.expand_path('../../lib/rswag/api/engine', __FILE__)
|
||||
|
||||
# Set up gems listed in the Gemfile.
|
||||
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
|
||||
8
rswag-api/lib/generators/rswag/api/install/USAGE
Normal file
8
rswag-api/lib/generators/rswag/api/install/USAGE
Normal file
@ -0,0 +1,8 @@
|
||||
Description:
|
||||
Adds rswag-api initializer for configuration
|
||||
|
||||
Example:
|
||||
rails generate rswag:api:install
|
||||
|
||||
This will create:
|
||||
config/initializers/rswag-api.rb
|
||||
@ -0,0 +1,18 @@
|
||||
require 'rails/generators'
|
||||
|
||||
module Rswag
|
||||
module Api
|
||||
|
||||
class InstallGenerator < Rails::Generators::Base
|
||||
source_root File.expand_path('../templates', __FILE__)
|
||||
|
||||
def add_initializer
|
||||
template('rswag-api.rb', 'config/initializers/rswag-api.rb')
|
||||
end
|
||||
|
||||
def add_routes
|
||||
route("mount Rswag::Api::Engine => '/api-docs'")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -1,9 +1,9 @@
|
||||
SwaggerRails.configure do |c|
|
||||
Rswag::Api.configure do |c|
|
||||
|
||||
# Specify a root folder where Swagger JSON files are located
|
||||
# This is used by the Swagger middleware to serve requests for API descriptions
|
||||
# NOTE: If you're using the rspec DSL to generate Swagger, you'll need to ensure
|
||||
# that the same folder is also specified in spec/swagger_helper.rb
|
||||
# NOTE: If you're using rswag-specs to generate Swagger, you'll need to ensure
|
||||
# that it's configured to generate files in the same folder
|
||||
c.swagger_root = Rails.root.to_s + '/swagger'
|
||||
|
||||
# Inject a lamda function to alter the returned Swagger prior to serialization
|
||||
15
rswag-api/lib/rswag/api.rb
Normal file
15
rswag-api/lib/rswag/api.rb
Normal file
@ -0,0 +1,15 @@
|
||||
require 'rswag/api/version'
|
||||
require 'rswag/api/configuration'
|
||||
require 'rswag/api/engine'
|
||||
|
||||
module Rswag
|
||||
module Api
|
||||
def self.configure
|
||||
yield(config)
|
||||
end
|
||||
|
||||
def self.config
|
||||
@config ||= Configuration.new
|
||||
end
|
||||
end
|
||||
end
|
||||
12
rswag-api/lib/rswag/api/configuration.rb
Normal file
12
rswag-api/lib/rswag/api/configuration.rb
Normal file
@ -0,0 +1,12 @@
|
||||
module Rswag
|
||||
module Api
|
||||
class Configuration
|
||||
attr_accessor :swagger_root, :swagger_filter
|
||||
|
||||
def resolve_swagger_root(env)
|
||||
path_params = env['action_dispatch.request.path_parameters'] || {}
|
||||
path_params[:swagger_root] || swagger_root
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
13
rswag-api/lib/rswag/api/engine.rb
Normal file
13
rswag-api/lib/rswag/api/engine.rb
Normal file
@ -0,0 +1,13 @@
|
||||
require 'rswag/api/middleware'
|
||||
|
||||
module Rswag
|
||||
module Api
|
||||
class Engine < ::Rails::Engine
|
||||
isolate_namespace Rswag::Api
|
||||
|
||||
initializer 'rswag-api.initialize' do |app|
|
||||
middleware.use Rswag::Api::Middleware, Rswag::Api.config
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
37
rswag-api/lib/rswag/api/middleware.rb
Normal file
37
rswag-api/lib/rswag/api/middleware.rb
Normal file
@ -0,0 +1,37 @@
|
||||
require 'json'
|
||||
|
||||
module Rswag
|
||||
module Api
|
||||
class Middleware
|
||||
|
||||
def initialize(app, config)
|
||||
@app = app
|
||||
@config = config
|
||||
end
|
||||
|
||||
def call(env)
|
||||
path = env['PATH_INFO']
|
||||
filename = "#{@config.resolve_swagger_root(env)}/#{path}"
|
||||
|
||||
if env['REQUEST_METHOD'] == 'GET' && File.file?(filename)
|
||||
swagger = load_json(filename)
|
||||
@config.swagger_filter.call(swagger, env) unless @config.swagger_filter.nil?
|
||||
|
||||
return [
|
||||
'200',
|
||||
{ 'Content-Type' => 'application/json' },
|
||||
[ JSON.dump(swagger) ]
|
||||
]
|
||||
end
|
||||
|
||||
return @app.call(env)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_json(filename)
|
||||
JSON.parse(File.read(filename))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
5
rswag-api/lib/rswag/api/version.rb
Normal file
5
rswag-api/lib/rswag/api/version.rb
Normal file
@ -0,0 +1,5 @@
|
||||
module Rswag
|
||||
module Api
|
||||
VERSION = '1.0.0'
|
||||
end
|
||||
end
|
||||
18
rswag-api/rswag-api.gemspec
Normal file
18
rswag-api/rswag-api.gemspec
Normal file
@ -0,0 +1,18 @@
|
||||
$:.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.authors = ["Richie Morris"]
|
||||
s.email = ["domaindrivendev@gmail.com"]
|
||||
s.homepage = "https://github.com/domaindrivendev/rswag"
|
||||
s.summary = "A Rails Engine that exposes Swagger files as JSON endpoints"
|
||||
|
||||
s.files = Dir["{app,config,db,lib}/**/*"] + ["MIT-LICENSE", "Rakefile" ]
|
||||
|
||||
s.add_dependency "rails", ">= 3.1", "< 5.1"
|
||||
end
|
||||
@ -0,0 +1,2 @@
|
||||
TestApp::Application.routes.draw do
|
||||
end
|
||||
@ -0,0 +1,27 @@
|
||||
require 'generator_spec'
|
||||
require 'generators/rswag/api/install/install_generator'
|
||||
|
||||
module Rswag
|
||||
module Api
|
||||
|
||||
describe InstallGenerator do
|
||||
include GeneratorSpec::TestCase
|
||||
destination File.expand_path('../tmp', __FILE__)
|
||||
|
||||
before(:all) do
|
||||
prepare_destination
|
||||
fixtures_dir = File.expand_path('../fixtures', __FILE__)
|
||||
FileUtils.cp_r("#{fixtures_dir}/config", destination_root)
|
||||
|
||||
run_generator
|
||||
end
|
||||
|
||||
it 'installs the Rails initializer' do
|
||||
assert_file('config/initializers/rswag-api.rb')
|
||||
end
|
||||
|
||||
# Don't know how to test this
|
||||
#it 'wires up routes'
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -1,9 +1,9 @@
|
||||
SwaggerRails.configure do |c|
|
||||
Rswag::Api.configure do |c|
|
||||
|
||||
# Specify a root folder where Swagger JSON files are located
|
||||
# This is used by the Swagger middleware to serve requests for API descriptions
|
||||
# NOTE: If you're using the rspec DSL to generate Swagger, you'll need to ensure
|
||||
# that the same folder is also specified in spec/swagger_helper.rb
|
||||
# NOTE: If you're using rswag-specs to generate Swagger, you'll need to ensure
|
||||
# that it's configured to generate files in the same folder
|
||||
c.swagger_root = Rails.root.to_s + '/swagger'
|
||||
|
||||
# Inject a lamda function to alter the returned Swagger prior to serialization
|
||||
4
rswag-api/spec/generators/rswag/api/tmp/config/routes.rb
Normal file
4
rswag-api/spec/generators/rswag/api/tmp/config/routes.rb
Normal file
@ -0,0 +1,4 @@
|
||||
TestApp::Application.routes.draw do
|
||||
mount Rswag::Api::Engine => '/api-docs'
|
||||
|
||||
end
|
||||
6
rswag-api/spec/rswag/api/fixtures/config/routes.rb
Normal file
6
rswag-api/spec/rswag/api/fixtures/config/routes.rb
Normal file
@ -0,0 +1,6 @@
|
||||
TestApp::Application.routes.draw do
|
||||
resources :blogs, defaults: { :format => :json }
|
||||
|
||||
mount Rswag::Api::Engine => 'api-docs'
|
||||
mount Rswag::Ui::Engine => 'api-docs'
|
||||
end
|
||||
@ -0,0 +1,8 @@
|
||||
{
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"title": "API V1",
|
||||
"version": "v1"
|
||||
},
|
||||
"paths": {}
|
||||
}
|
||||
82
rswag-api/spec/rswag/api/middleware_spec.rb
Normal file
82
rswag-api/spec/rswag/api/middleware_spec.rb
Normal file
@ -0,0 +1,82 @@
|
||||
require 'rswag/api/middleware'
|
||||
require 'rswag/api/configuration'
|
||||
|
||||
module Rswag
|
||||
module Api
|
||||
|
||||
describe Middleware do
|
||||
let(:app) { double('app') }
|
||||
let(:swagger_root) { File.expand_path('../fixtures/swagger', __FILE__) }
|
||||
let(:config) do
|
||||
Configuration.new.tap { |c| c.swagger_root = swagger_root }
|
||||
end
|
||||
|
||||
subject { described_class.new(app, config) }
|
||||
|
||||
describe '#call(env)' do
|
||||
let(:response) { subject.call(env) }
|
||||
let(:env_defaults) do
|
||||
{
|
||||
'HTTP_HOST' => 'tempuri.org',
|
||||
'REQUEST_METHOD' => 'GET',
|
||||
}
|
||||
end
|
||||
|
||||
context 'given a path that maps to an existing swagger file' do
|
||||
let(:env) { env_defaults.merge('PATH_INFO' => 'v1/swagger.json') }
|
||||
|
||||
it 'returns a 200 status' do
|
||||
expect(response.length).to eql(3)
|
||||
expect(response.first).to eql('200')
|
||||
end
|
||||
|
||||
it 'returns contents of the swagger file' do
|
||||
expect(response.length).to eql(3)
|
||||
expect(response[1]).to include( 'Content-Type' => 'application/json')
|
||||
expect(response[2].join).to include('"title":"API V1"')
|
||||
end
|
||||
end
|
||||
|
||||
context "given a path that doesn't map to any swagger file" do
|
||||
let(:env) { env_defaults.merge('PATH_INFO' => 'foobar.json') }
|
||||
before do
|
||||
allow(app).to receive(:call).and_return([ '500', {}, [] ])
|
||||
end
|
||||
|
||||
it 'delegates to the next middleware' do
|
||||
expect(response).to include('500')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the env contains a specific swagger_root' do
|
||||
let(:env) do
|
||||
env_defaults.merge(
|
||||
'PATH_INFO' => 'v1/swagger.json',
|
||||
'action_dispatch.request.path_parameters' => {
|
||||
swagger_root: swagger_root
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
it 'locates files at the provided swagger_root' do
|
||||
expect(response.length).to eql(3)
|
||||
expect(response[1]).to include( 'Content-Type' => 'application/json')
|
||||
expect(response[2].join).to include('"swagger":"2.0"')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a swagger_filter is configured' do
|
||||
before do
|
||||
config.swagger_filter = lambda { |swagger, env| swagger['host'] = env['HTTP_HOST'] }
|
||||
end
|
||||
let(:env) { env_defaults.merge('PATH_INFO' => 'v1/swagger.json') }
|
||||
|
||||
it 'applies the filter prior to serialization' do
|
||||
expect(response.length).to eql(3)
|
||||
expect(response[2].join).to include('"host":"tempuri.org"')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
2
rswag-specs/.rspec
Normal file
2
rswag-specs/.rspec
Normal file
@ -0,0 +1,2 @@
|
||||
--color
|
||||
--require spec_helper
|
||||
20
rswag-specs/MIT-LICENSE
Normal file
20
rswag-specs/MIT-LICENSE
Normal file
@ -0,0 +1,20 @@
|
||||
Copyright 2015 domaindrivendev
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
27
rswag-specs/Rakefile
Normal file
27
rswag-specs/Rakefile
Normal file
@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env rake
|
||||
begin
|
||||
require 'bundler/setup'
|
||||
rescue LoadError
|
||||
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
||||
end
|
||||
begin
|
||||
require 'rdoc/task'
|
||||
rescue LoadError
|
||||
require 'rdoc/rdoc'
|
||||
require 'rake/rdoctask'
|
||||
RDoc::Task = Rake::RDocTask
|
||||
end
|
||||
|
||||
RDoc::Task.new(:rdoc) do |rdoc|
|
||||
rdoc.rdoc_dir = 'rdoc'
|
||||
rdoc.title = 'rswag-specs'
|
||||
rdoc.options << '--line-numbers'
|
||||
rdoc.rdoc_files.include('README.rdoc')
|
||||
rdoc.rdoc_files.include('lib/**/*.rb')
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
Bundler::GemHelper.install_tasks
|
||||
|
||||
8
rswag-specs/lib/generators/rswag/specs/install/USAGE
Normal file
8
rswag-specs/lib/generators/rswag/specs/install/USAGE
Normal file
@ -0,0 +1,8 @@
|
||||
Description:
|
||||
Adds swagger_helper to enable Swagger DSL in integration specs
|
||||
|
||||
Example:
|
||||
rails generate rswag:specs:install
|
||||
|
||||
This will create:
|
||||
spec/swagger_helper.rb
|
||||
@ -0,0 +1,14 @@
|
||||
require 'rails/generators'
|
||||
|
||||
module Rswag
|
||||
module Specs
|
||||
|
||||
class InstallGenerator < Rails::Generators::Base
|
||||
source_root File.expand_path('../templates', __FILE__)
|
||||
|
||||
def add_swagger_helper
|
||||
template('swagger_helper.rb', 'spec/swagger_helper.rb')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -1,20 +1,14 @@
|
||||
require 'rails_helper'
|
||||
require 'swagger_rails/rspec/dsl'
|
||||
|
||||
RSpec.configure do |config|
|
||||
# NOTE: Should be no need to modify these 3 lines
|
||||
config.add_setting :swagger_root
|
||||
config.add_setting :swagger_docs
|
||||
config.extend SwaggerRails::RSpec::DSL
|
||||
|
||||
# Specify a root folder where Swagger JSON files are generated
|
||||
# NOTE: If you're using the Swagger JSON middleware to serve API descriptions, you'll need
|
||||
# to ensure that the same folder is also specified in the swagger_rails initializer
|
||||
# NOTE: If you're using the rswag-api to serve API descriptions, you'll need
|
||||
# to ensure that it's confiugred to server Swagger from the same folder
|
||||
config.swagger_root = Rails.root.to_s + '/swagger'
|
||||
|
||||
# Define one or more Swagger documents and provide global metadata for each one
|
||||
# When you run the "swaggerize" rake task, the complete Swagger will be generated
|
||||
# at the provided relative path under swagger_root
|
||||
# When you run the 'rswag:specs:to_swagger' rake task, the complete Swagger will
|
||||
# be generated at the provided relative path under swagger_root
|
||||
# By default, the operations defined in spec files are added to the first
|
||||
# document below. You can override this behavior by adding a swagger_doc tag to the
|
||||
# the root example_group in your specs, e.g. describe '...', swagger_doc: 'v2/swagger.json'
|
||||
@ -24,7 +18,8 @@ RSpec.configure do |config|
|
||||
info: {
|
||||
title: 'API V1',
|
||||
version: 'v1'
|
||||
}
|
||||
},
|
||||
paths: {}
|
||||
}
|
||||
}
|
||||
end
|
||||
22
rswag-specs/lib/rswag/specs.rb
Normal file
22
rswag-specs/lib/rswag/specs.rb
Normal file
@ -0,0 +1,22 @@
|
||||
require 'rspec/core'
|
||||
require 'rswag/specs/version'
|
||||
require 'rswag/specs/example_group_helpers'
|
||||
require 'rswag/specs/example_helpers'
|
||||
require 'rswag/specs/railtie' if defined?(Rails::Railtie)
|
||||
|
||||
module Rswag
|
||||
module Specs
|
||||
|
||||
# Extend RSpec with a swagger-based DSL
|
||||
::RSpec.configure do |c|
|
||||
c.add_setting :swagger_root
|
||||
c.add_setting :swagger_docs
|
||||
c.extend ExampleGroupHelpers, type: :request
|
||||
c.include ExampleHelpers, type: :request
|
||||
end
|
||||
|
||||
# Support Rails 3+ and RSpec 2+ (sigh!)
|
||||
RAILS_VERSION = Rails::VERSION::MAJOR
|
||||
RSPEC_VERSION = RSpec::Core::Version::STRING.split('.').first.to_i
|
||||
end
|
||||
end
|
||||
80
rswag-specs/lib/rswag/specs/example_group_helpers.rb
Normal file
80
rswag-specs/lib/rswag/specs/example_group_helpers.rb
Normal file
@ -0,0 +1,80 @@
|
||||
module Rswag
|
||||
module Specs
|
||||
module ExampleGroupHelpers
|
||||
|
||||
def path(path, &block)
|
||||
api_metadata = { path: path}
|
||||
describe(path, api_metadata, &block)
|
||||
end
|
||||
|
||||
[ :get, :post, :patch, :put, :delete, :head ].each do |verb|
|
||||
define_method(verb) do |summary, &block|
|
||||
api_metadata = { operation: { verb: verb, summary: summary } }
|
||||
describe(verb, api_metadata, &block)
|
||||
end
|
||||
end
|
||||
|
||||
[ :operationId, :deprecated, :security ].each do |attr_name|
|
||||
define_method(attr_name) do |value|
|
||||
metadata[:operation][attr_name] = value
|
||||
end
|
||||
end
|
||||
|
||||
# NOTE: 'description' requires special treatment because ExampleGroup already
|
||||
# defines a method with that name. Provide an override that supports the existing
|
||||
# functionality while also setting the appropriate metadata if applicable
|
||||
def description(value=nil)
|
||||
return super() if value.nil?
|
||||
metadata[:operation][:description] = value
|
||||
end
|
||||
|
||||
# These are array properties - note the splat operator
|
||||
[ :tags, :consumes, :produces, :schemes ].each do |attr_name|
|
||||
define_method(attr_name) do |*value|
|
||||
metadata[:operation][attr_name] = value
|
||||
end
|
||||
end
|
||||
|
||||
def parameter(attributes)
|
||||
attributes[:required] = true if attributes[:in].to_sym == :path
|
||||
metadata[:operation][:parameters] ||= []
|
||||
metadata[:operation][:parameters] << attributes
|
||||
end
|
||||
|
||||
def response(code, description, &block)
|
||||
api_metadata = { response: { code: code, description: description } }
|
||||
context(description, api_metadata, &block)
|
||||
end
|
||||
|
||||
def schema(value)
|
||||
metadata[:response][:schema] = value
|
||||
end
|
||||
|
||||
def header(name, attributes)
|
||||
metadata[:response][:headers] ||= {}
|
||||
metadata[:response][:headers][name] = attributes
|
||||
end
|
||||
|
||||
def run_test!
|
||||
# NOTE: rspec 2.x support
|
||||
if RSPEC_VERSION < 3
|
||||
before do
|
||||
submit_request(example.metadata)
|
||||
end
|
||||
|
||||
it "returns a #{metadata[:response][:code]} response" do
|
||||
assert_response_matches_metadata(example.metadata)
|
||||
end
|
||||
else
|
||||
before do |example|
|
||||
submit_request(example.metadata)
|
||||
end
|
||||
|
||||
it "returns a #{metadata[:response][:code]} response" do |example|
|
||||
assert_response_matches_metadata(example.metadata)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
43
rswag-specs/lib/rswag/specs/example_helpers.rb
Normal file
43
rswag-specs/lib/rswag/specs/example_helpers.rb
Normal file
@ -0,0 +1,43 @@
|
||||
require 'rswag/specs/request_factory'
|
||||
require 'rswag/specs/response_validator'
|
||||
|
||||
module Rswag
|
||||
module Specs
|
||||
module ExampleHelpers
|
||||
|
||||
def submit_request(api_metadata)
|
||||
factory = RequestFactory.new(api_metadata, global_metadata(api_metadata[:swagger_doc]))
|
||||
|
||||
if RAILS_VERSION < 5
|
||||
send(
|
||||
api_metadata[:operation][:verb],
|
||||
factory.build_fullpath(self),
|
||||
factory.build_body(self),
|
||||
factory.build_headers(self)
|
||||
)
|
||||
else
|
||||
send(
|
||||
api_metadata[:operation][:verb],
|
||||
factory.build_fullpath(self),
|
||||
{
|
||||
params: factory.build_body(self),
|
||||
headers: factory.build_headers(self)
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def assert_response_matches_metadata(api_metadata)
|
||||
validator = ResponseValidator.new(api_metadata, global_metadata(api_metadata[:swagger_doc]))
|
||||
validator.validate!(response)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def global_metadata(swagger_doc)
|
||||
swagger_docs = ::RSpec.configuration.swagger_docs
|
||||
swagger_doc.nil? ? swagger_docs.values.first : swagger_docs[swagger_doc]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
10
rswag-specs/lib/rswag/specs/railtie.rb
Normal file
10
rswag-specs/lib/rswag/specs/railtie.rb
Normal file
@ -0,0 +1,10 @@
|
||||
module Rswag
|
||||
module Specs
|
||||
class Railtie < ::Rails::Railtie
|
||||
|
||||
rake_tasks do
|
||||
load File.expand_path('../../../tasks/rswag-specs_tasks.rake', __FILE__)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
70
rswag-specs/lib/rswag/specs/request_factory.rb
Normal file
70
rswag-specs/lib/rswag/specs/request_factory.rb
Normal file
@ -0,0 +1,70 @@
|
||||
require 'active_support/core_ext/hash/slice'
|
||||
require 'json'
|
||||
|
||||
module Rswag
|
||||
module Specs
|
||||
class RequestFactory
|
||||
|
||||
def initialize(api_metadata, global_metadata)
|
||||
@api_metadata = api_metadata
|
||||
@global_metadata = global_metadata
|
||||
end
|
||||
|
||||
def build_fullpath(example)
|
||||
@api_metadata[:path].dup.tap do |t|
|
||||
parameters_in(:path).each { |p| t.gsub!("{#{p[:name]}}", example.send(p[:name]).to_s) }
|
||||
t.concat(build_query(example))
|
||||
t.prepend(@global_metadata[:basePath] || '')
|
||||
end
|
||||
end
|
||||
|
||||
def build_query(example)
|
||||
query_string = parameters_in(:query)
|
||||
.map { |p| "#{p[:name]}=#{example.send(p[:name])}" }
|
||||
.join('&')
|
||||
|
||||
query_string.empty? ? '' : "?#{query_string}"
|
||||
end
|
||||
|
||||
def build_body(example)
|
||||
body_parameter = parameters_in(:body).first
|
||||
body_parameter.nil? ? '' : example.send(body_parameter[:name]).to_json
|
||||
end
|
||||
|
||||
def build_headers(example)
|
||||
headers = Hash[ parameters_in(:header).map { |p| [ p[:name], example.send(p[:name]).to_s ] } ]
|
||||
headers.tap do |h|
|
||||
produces = @api_metadata[:operation][:produces] || @global_metadata[:produces]
|
||||
consumes = @api_metadata[:operation][:consumes] || @global_metadata[:consumes]
|
||||
h['ACCEPT'] = produces.join(';') unless produces.nil?
|
||||
h['CONTENT_TYPE'] = consumes.join(';') unless consumes.nil?
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def parameters_in(location)
|
||||
(@api_metadata[:operation][:parameters] || [])
|
||||
.map { |p| p['$ref'] ? resolve_parameter(p['$ref']) : p } # resolve any references
|
||||
.concat(resolve_api_key_parameters)
|
||||
.select { |p| p[:in] == location }
|
||||
end
|
||||
|
||||
def resolve_parameter(ref)
|
||||
defined_params = @global_metadata[:parameters]
|
||||
key = ref.sub('#/parameters/', '')
|
||||
raise "Referenced parameter '#{ref}' must be defined" unless defined_params && defined_params[key]
|
||||
defined_params[key]
|
||||
end
|
||||
|
||||
def resolve_api_key_parameters
|
||||
@api_key_params ||= begin
|
||||
global_requirements = (@global_metadata[:security] || {})
|
||||
requirements = global_requirements.merge(@api_metadata[:operation][:security] || {})
|
||||
definitions = (@global_metadata[:securityDefinitions] || {}).slice(*requirements.keys)
|
||||
definitions.values.select { |d| d[:type] == :apiKey }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
38
rswag-specs/lib/rswag/specs/response_validator.rb
Normal file
38
rswag-specs/lib/rswag/specs/response_validator.rb
Normal file
@ -0,0 +1,38 @@
|
||||
require 'json-schema'
|
||||
|
||||
module Rswag
|
||||
module Specs
|
||||
class ResponseValidator
|
||||
|
||||
def initialize(api_metadata, global_metadata)
|
||||
@api_metadata = api_metadata
|
||||
@global_metadata = global_metadata
|
||||
end
|
||||
|
||||
def validate!(response)
|
||||
validate_code!(response.code)
|
||||
validate_body!(response.body)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validate_code!(code)
|
||||
if code.to_s != @api_metadata[:response][:code].to_s
|
||||
raise UnexpectedResponse, "Expected response code '#{code}' to match '#{@api_metadata[:response][:code]}'"
|
||||
end
|
||||
end
|
||||
|
||||
def validate_body!(body)
|
||||
schema = @api_metadata[:response][:schema]
|
||||
return if schema.nil?
|
||||
begin
|
||||
JSON::Validator.validate!(schema.merge(@global_metadata), body)
|
||||
rescue JSON::Schema::ValidationError => ex
|
||||
raise UnexpectedResponse, "Expected response body to match schema: #{ex.message}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class UnexpectedResponse < StandardError; end
|
||||
end
|
||||
end
|
||||
79
rswag-specs/lib/rswag/specs/swagger_formatter.rb
Normal file
79
rswag-specs/lib/rswag/specs/swagger_formatter.rb
Normal file
@ -0,0 +1,79 @@
|
||||
require 'active_support/core_ext/hash/deep_merge'
|
||||
require 'rspec/core/formatters/base_text_formatter'
|
||||
require 'swagger_helper'
|
||||
|
||||
module Rswag
|
||||
module Specs
|
||||
class SwaggerFormatter
|
||||
|
||||
# NOTE: rspec 2.x support
|
||||
if RSPEC_VERSION > 2
|
||||
::RSpec::Core::Formatters.register self, :example_group_finished, :stop
|
||||
end
|
||||
|
||||
def initialize(output)
|
||||
@output = output
|
||||
@swagger_root = ::RSpec.configuration.swagger_root
|
||||
raise ConfigurationError, 'Missing swagger_root. See swagger_helper.rb' if @swagger_root.nil?
|
||||
@swagger_docs = ::RSpec.configuration.swagger_docs || []
|
||||
raise ConfigurationError, 'Missing swagger_docs. See swagger_helper.rb' if @swagger_docs.empty?
|
||||
|
||||
@output.puts 'Generating Swagger docs ...'
|
||||
end
|
||||
|
||||
def example_group_finished(notification)
|
||||
# NOTE: rspec 2.x support
|
||||
if RSPEC_VERSION > 2
|
||||
metadata = notification.group.metadata
|
||||
else
|
||||
metadata = notification.metadata
|
||||
end
|
||||
|
||||
return unless metadata.has_key?(:response)
|
||||
swagger_doc = get_swagger_doc(metadata[:swagger_doc])
|
||||
swagger_doc.deep_merge!(metadata_to_swagger(metadata))
|
||||
end
|
||||
|
||||
def stop(notification=nil)
|
||||
@swagger_docs.each do |url_path, doc|
|
||||
file_path = File.join(@swagger_root, url_path)
|
||||
dirname = File.dirname(file_path)
|
||||
FileUtils.mkdir_p dirname unless File.exists?(dirname)
|
||||
|
||||
File.open(file_path, 'w') do |file|
|
||||
file.write(JSON.pretty_generate(doc))
|
||||
end
|
||||
|
||||
@output.puts "Swagger doc generated at #{file_path}"
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def get_swagger_doc(tag)
|
||||
return @swagger_docs.values.first if tag.nil?
|
||||
raise ConfigurationError, "Unknown swagger_doc '#{tag}'" unless @swagger_docs.has_key?(tag)
|
||||
@swagger_docs[tag]
|
||||
end
|
||||
|
||||
def metadata_to_swagger(metadata)
|
||||
response_code = metadata[:response][:code]
|
||||
response = metadata[:response].reject { |k,v| k == :code }
|
||||
verb = metadata[:operation][:verb]
|
||||
operation = metadata[:operation]
|
||||
.reject { |k,v| k == :verb }
|
||||
.merge(responses: { response_code => response })
|
||||
|
||||
{
|
||||
paths: {
|
||||
metadata[:path] => {
|
||||
verb => operation
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
class ConfigurationError < StandardError; end
|
||||
end
|
||||
end
|
||||
5
rswag-specs/lib/rswag/specs/version.rb
Normal file
5
rswag-specs/lib/rswag/specs/version.rb
Normal file
@ -0,0 +1,5 @@
|
||||
module Rswag
|
||||
module Specs
|
||||
VERSION = '1.0.0'
|
||||
end
|
||||
end
|
||||
18
rswag-specs/lib/tasks/rswag-specs_tasks.rake
Normal file
18
rswag-specs/lib/tasks/rswag-specs_tasks.rake
Normal file
@ -0,0 +1,18 @@
|
||||
require 'rspec/core/rake_task'
|
||||
|
||||
namespace :rswag do
|
||||
namespace :specs do
|
||||
|
||||
desc 'Generate Swagger JSON files from integration specs'
|
||||
RSpec::Core::RakeTask.new('swaggerize') do |t|
|
||||
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
|
||||
t.rspec_opts = [ '--format Rswag::Specs::SwaggerFormatter', '--dry-run', '--order defined' ]
|
||||
else
|
||||
t.rspec_opts = [ '--format Rswag::Specs::SwaggerFormatter', '--order defined' ]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
20
rswag-specs/rswag-specs.gemspec
Normal file
20
rswag-specs/rswag-specs.gemspec
Normal file
@ -0,0 +1,20 @@
|
||||
$:.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.authors = ["Richie Morris"]
|
||||
s.email = ["domaindrivendev@gmail.com"]
|
||||
s.homepage = "https://github.com/domaindrivendev/rswag"
|
||||
s.summary = "A Swagger-based DSL for rspec-rails & accompanying rake task for generating Swagger files"
|
||||
|
||||
s.files = Dir["{app,config,db,lib}/**/*"] + ["MIT-LICENSE", "Rakefile" ]
|
||||
|
||||
s.add_dependency "rails", ">= 3.1", "< 5.1"
|
||||
s.add_dependency 'json-schema'
|
||||
s.add_development_dependency 'rspec-rails'
|
||||
end
|
||||
@ -0,0 +1,24 @@
|
||||
require 'generator_spec'
|
||||
require 'generators/rswag/specs/install/install_generator'
|
||||
|
||||
module Rswag
|
||||
module Specs
|
||||
|
||||
describe InstallGenerator do
|
||||
include GeneratorSpec::TestCase
|
||||
destination File.expand_path('../tmp', __FILE__)
|
||||
|
||||
before(:all) do
|
||||
prepare_destination
|
||||
fixtures_dir = File.expand_path('../fixtures', __FILE__)
|
||||
FileUtils.cp_r("#{fixtures_dir}/spec", destination_root)
|
||||
|
||||
run_generator
|
||||
end
|
||||
|
||||
it 'installs the swagger_helper for rspec' do
|
||||
assert_file('spec/swagger_helper.rb')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -1,20 +1,14 @@
|
||||
require 'rails_helper'
|
||||
require 'swagger_rails/rspec/dsl'
|
||||
|
||||
RSpec.configure do |config|
|
||||
# NOTE: Should be no need to modify these 3 lines
|
||||
config.add_setting :swagger_root
|
||||
config.add_setting :swagger_docs
|
||||
config.extend SwaggerRails::RSpec::DSL
|
||||
|
||||
# Specify a root folder where Swagger JSON files are generated
|
||||
# NOTE: If you're using the Swagger JSON middleware to serve API descriptions, you'll need
|
||||
# to ensure that the same folder is also specified in the swagger_rails initializer
|
||||
# NOTE: If you're using the rswag-api to serve API descriptions, you'll need
|
||||
# to ensure that it's confiugred to server Swagger from the same folder
|
||||
config.swagger_root = Rails.root.to_s + '/swagger'
|
||||
|
||||
# Define one or more Swagger documents and provide global metadata for each one
|
||||
# When you run the "swaggerize" rake task, the complete Swagger will be generated
|
||||
# at the provided relative path under swagger_root
|
||||
# When you run the 'rswag:specs:to_swagger' rake task, the complete Swagger will
|
||||
# be generated at the provided relative path under swagger_root
|
||||
# By default, the operations defined in spec files are added to the first
|
||||
# document below. You can override this behavior by adding a swagger_doc tag to the
|
||||
# the root example_group in your specs, e.g. describe '...', swagger_doc: 'v2/swagger.json'
|
||||
@ -24,7 +18,8 @@ RSpec.configure do |config|
|
||||
info: {
|
||||
title: 'API V1',
|
||||
version: 'v1'
|
||||
}
|
||||
},
|
||||
paths: {}
|
||||
}
|
||||
}
|
||||
end
|
||||
143
rswag-specs/spec/rswag/specs/example_group_helpers_spec.rb
Normal file
143
rswag-specs/spec/rswag/specs/example_group_helpers_spec.rb
Normal file
@ -0,0 +1,143 @@
|
||||
require 'rswag/specs/example_group_helpers'
|
||||
|
||||
module Rswag
|
||||
module Specs
|
||||
|
||||
describe ExampleGroupHelpers do
|
||||
subject { double('example_group') }
|
||||
let(:api_metadata) { {} }
|
||||
before do
|
||||
subject.extend ExampleGroupHelpers
|
||||
allow(subject).to receive(:describe)
|
||||
allow(subject).to receive(:context)
|
||||
allow(subject).to receive(:metadata).and_return(api_metadata)
|
||||
end
|
||||
|
||||
describe '#path(path)' do
|
||||
before { subject.path('/blogs') }
|
||||
|
||||
it "delegates to 'describe' with 'path' metadata" do
|
||||
expect(subject).to have_received(:describe).with(
|
||||
'/blogs', path: '/blogs'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#get|post|patch|put|delete|head(verb, summary)' do
|
||||
before { subject.post('Creates a blog') }
|
||||
|
||||
it "delegates to 'describe' with 'operation' metadata" do
|
||||
expect(subject).to have_received(:describe).with(
|
||||
:post, operation: { verb: :post, summary: 'Creates a blog' }
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#tags|description|operationId|consumes|produces|schemes|deprecated(value)' do
|
||||
let(:api_metadata) { { operation: {} } }
|
||||
before do
|
||||
subject.tags('Blogs', 'Admin')
|
||||
subject.description('Some description')
|
||||
subject.operationId('createBlog')
|
||||
subject.consumes('application/json', 'application/xml')
|
||||
subject.produces('application/json', 'application/xml')
|
||||
subject.schemes('http', 'https')
|
||||
subject.deprecated(true)
|
||||
end
|
||||
|
||||
it "adds to the 'operation' metadata" do
|
||||
expect(api_metadata[:operation]).to match(
|
||||
tags: [ 'Blogs', 'Admin' ],
|
||||
description: 'Some description',
|
||||
operationId: 'createBlog',
|
||||
consumes: [ 'application/json', 'application/xml' ],
|
||||
produces: [ 'application/json', 'application/xml' ],
|
||||
schemes: [ 'http', 'https' ],
|
||||
deprecated: true
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#tags|description|operationId|consumes|produces|schemes|deprecated|security(value)' do
|
||||
let(:api_metadata) { { operation: {} } }
|
||||
before do
|
||||
subject.tags('Blogs', 'Admin')
|
||||
subject.description('Some description')
|
||||
subject.operationId('createBlog')
|
||||
subject.consumes('application/json', 'application/xml')
|
||||
subject.produces('application/json', 'application/xml')
|
||||
subject.schemes('http', 'https')
|
||||
subject.deprecated(true)
|
||||
subject.security(api_key: [])
|
||||
end
|
||||
|
||||
it "adds to the 'operation' metadata" do
|
||||
expect(api_metadata[:operation]).to match(
|
||||
tags: [ 'Blogs', 'Admin' ],
|
||||
description: 'Some description',
|
||||
operationId: 'createBlog',
|
||||
consumes: [ 'application/json', 'application/xml' ],
|
||||
produces: [ 'application/json', 'application/xml' ],
|
||||
schemes: [ 'http', 'https' ],
|
||||
deprecated: true,
|
||||
security: { api_key: [] }
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#parameter(attributes)' do
|
||||
let(:api_metadata) { { operation: {} } }
|
||||
|
||||
context 'always' do
|
||||
before { subject.parameter(name: :blog, in: :body, schema: { type: 'object' }) }
|
||||
|
||||
it "adds to the 'operation parameters' metadata" do
|
||||
expect(api_metadata[:operation][:parameters]).to match(
|
||||
[ name: :blog, in: :body, schema: { type: 'object' } ]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context "'path' parameter" do
|
||||
before { subject.parameter(name: :id, in: :path) }
|
||||
|
||||
it "automatically sets the 'required' flag" do
|
||||
expect(api_metadata[:operation][:parameters]).to match(
|
||||
[ name: :id, in: :path, required: true ]
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#response(code, description)' do
|
||||
before { subject.response('201', 'success') }
|
||||
|
||||
it "delegates to 'context' with 'response' metadata" do
|
||||
expect(subject).to have_received(:context).with(
|
||||
'success', response: { code: '201', description: 'success' }
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#schema(value)' do
|
||||
let(:api_metadata) { { response: {} } }
|
||||
before { subject.schema(type: 'object') }
|
||||
|
||||
it "adds to the 'response' metadata" do
|
||||
expect(api_metadata[:response][:schema]).to match(type: 'object')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#header(name, attributes)' do
|
||||
let(:api_metadata) { { response: {} } }
|
||||
before { subject.header('Date', type: 'string') }
|
||||
|
||||
it "adds to the 'response headers' metadata" do
|
||||
expect(api_metadata[:response][:headers]).to match(
|
||||
'Date' => { type: 'string' }
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
66
rswag-specs/spec/rswag/specs/example_helpers_spec.rb
Normal file
66
rswag-specs/spec/rswag/specs/example_helpers_spec.rb
Normal file
@ -0,0 +1,66 @@
|
||||
require 'rswag/specs/example_helpers'
|
||||
|
||||
module Rswag
|
||||
module Specs
|
||||
|
||||
describe ExampleHelpers do
|
||||
let(:api_metadata) do
|
||||
{
|
||||
path: '/blogs/{blog_id}/comments/{id}',
|
||||
operation: {
|
||||
verb: :put,
|
||||
summary: 'Updates a blog',
|
||||
parameters: [
|
||||
{ name: :blog_id, in: :path, type: 'integer' },
|
||||
{ name: 'id', in: :path, type: 'integer' },
|
||||
{ name: 'q1', in: :query, type: 'string' },
|
||||
{ name: :blog, in: :body, schema: { type: 'object' } }
|
||||
],
|
||||
security: {
|
||||
api_key: []
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
let(:global_metadata) do
|
||||
{
|
||||
securityDefinitions: {
|
||||
api_key: {
|
||||
type: :apiKey,
|
||||
name: 'api_key',
|
||||
in: :query
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
subject { double('example') }
|
||||
|
||||
before do
|
||||
subject.extend ExampleHelpers
|
||||
allow(subject).to receive(:blog_id).and_return(1)
|
||||
allow(subject).to receive(:id).and_return(2)
|
||||
allow(subject).to receive(:q1).and_return('foo')
|
||||
allow(subject).to receive(:api_key).and_return('fookey')
|
||||
allow(subject).to receive(:blog).and_return(text: 'Some comment')
|
||||
allow(subject).to receive(:global_metadata).and_return(global_metadata)
|
||||
allow(subject).to receive(:put)
|
||||
end
|
||||
|
||||
describe '#submit_request(api_metadata)' do
|
||||
before do
|
||||
stub_const('Rails::VERSION::MAJOR', 3)
|
||||
subject.submit_request(api_metadata)
|
||||
end
|
||||
|
||||
it "submits a request built from metadata and 'let' values" do
|
||||
expect(subject).to have_received(:put).with(
|
||||
'/blogs/1/comments/2?q1=foo&api_key=fookey',
|
||||
"{\"text\":\"Some comment\"}",
|
||||
{}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
171
rswag-specs/spec/rswag/specs/request_factory_spec.rb
Normal file
171
rswag-specs/spec/rswag/specs/request_factory_spec.rb
Normal file
@ -0,0 +1,171 @@
|
||||
require 'rswag/specs/request_factory'
|
||||
|
||||
module Rswag
|
||||
module Specs
|
||||
|
||||
describe RequestFactory do
|
||||
let(:api_metadata) do
|
||||
{
|
||||
path: '/blogs/{blog_id}/comments/{id}',
|
||||
operation: {
|
||||
verb: :put,
|
||||
summary: 'Updates a blog',
|
||||
parameters: [
|
||||
{ name: :blog_id, in: :path, type: 'integer' },
|
||||
{ name: 'id', in: :path, type: 'integer' }
|
||||
]
|
||||
}
|
||||
}
|
||||
end
|
||||
let(:global_metadata) { {} }
|
||||
|
||||
subject { RequestFactory.new(api_metadata, global_metadata) }
|
||||
|
||||
let(:example) { double('example') }
|
||||
before do
|
||||
allow(example).to receive(:blog_id).and_return(1)
|
||||
allow(example).to receive(:id).and_return('2')
|
||||
end
|
||||
|
||||
describe '#build_fullpath(example)' do
|
||||
let(:path) { subject.build_fullpath(example) }
|
||||
|
||||
context 'always' do
|
||||
it "builds a path using metadata and example values" do
|
||||
expect(path).to eq('/blogs/1/comments/2')
|
||||
end
|
||||
end
|
||||
|
||||
context "'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' }
|
||||
allow(example).to receive(:q1).and_return('foo')
|
||||
allow(example).to receive(:q2).and_return('bar')
|
||||
end
|
||||
|
||||
it "appends a query string using metadata and example values" do
|
||||
expect(path).to eq('/blogs/1/comments/2?q1=foo&q2=bar')
|
||||
end
|
||||
end
|
||||
|
||||
context "global definition for 'api_key in query'" do
|
||||
before do
|
||||
global_metadata[:securityDefinitions] = { api_key: { type: :apiKey, name: 'api_key', in: :query } }
|
||||
allow(example).to receive(:api_key).and_return('fookey')
|
||||
end
|
||||
|
||||
context 'global requirement' do
|
||||
before { global_metadata[:security] = { api_key: [] } }
|
||||
|
||||
it "appends the api_key using metadata and example value" do
|
||||
expect(path).to eq('/blogs/1/comments/2?api_key=fookey')
|
||||
end
|
||||
end
|
||||
|
||||
context 'operation-specific requirement' do
|
||||
before { api_metadata[:operation][:security] = { api_key: [] } }
|
||||
|
||||
it "appends the api_key using metadata and example value" do
|
||||
expect(path).to eq('/blogs/1/comments/2?api_key=fookey')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'global basePath' do
|
||||
before { global_metadata[:basePath] = '/foobar' }
|
||||
|
||||
it 'prepends the basePath' do
|
||||
expect(path).to eq('/foobar/blogs/1/comments/2')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#build_body(example)' do
|
||||
let(:body) { subject.build_body(example) }
|
||||
|
||||
context "no 'body' parameter" do
|
||||
it "returns ''" do
|
||||
expect(body).to eq('')
|
||||
end
|
||||
end
|
||||
|
||||
context "'body' parameter" do
|
||||
before do
|
||||
api_metadata[:operation][:parameters] << { name: 'comment', in: :body, schema: { type: 'object' } }
|
||||
allow(example).to receive(:comment).and_return(text: 'Some comment')
|
||||
end
|
||||
|
||||
it 'returns the example value as a json string' do
|
||||
expect(body).to eq("{\"text\":\"Some comment\"}")
|
||||
end
|
||||
end
|
||||
|
||||
context "referenced 'body' parameter" do
|
||||
before do
|
||||
api_metadata[:operation][:parameters] << { '$ref' => '#/parameters/comment' }
|
||||
global_metadata[:parameters] = {
|
||||
'comment' => { name: 'comment', in: :body, schema: { type: 'object' } }
|
||||
}
|
||||
allow(example).to receive(:comment).and_return(text: 'Some comment')
|
||||
end
|
||||
|
||||
it 'returns the example value as a json string' do
|
||||
expect(body).to eq("{\"text\":\"Some comment\"}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#build_headers' do
|
||||
let(:headers) { subject.build_headers(example) }
|
||||
|
||||
context "no 'header' params" do
|
||||
it 'returns an empty hash' do
|
||||
expect(headers).to eq({})
|
||||
end
|
||||
end
|
||||
|
||||
context "'header' params" do
|
||||
before do
|
||||
api_metadata[:operation][:parameters] << { name: 'Api-Key', in: :header, type: 'string' }
|
||||
allow(example).to receive(:'Api-Key').and_return('foobar')
|
||||
end
|
||||
|
||||
it 'returns a hash of names with example values' do
|
||||
expect(headers).to eq({ 'Api-Key' => 'foobar' })
|
||||
end
|
||||
end
|
||||
|
||||
context 'consumes & produces' do
|
||||
before do
|
||||
api_metadata[:operation][:consumes] = [ 'application/json', 'application/xml' ]
|
||||
api_metadata[:operation][:produces] = [ 'application/json', 'application/xml' ]
|
||||
end
|
||||
|
||||
it "includes corresponding 'Accept' & 'Content-Type' headers" do
|
||||
expect(headers).to match(
|
||||
'ACCEPT' => 'application/json;application/xml',
|
||||
'CONTENT_TYPE' => 'application/json;application/xml'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'global consumes & produces' do
|
||||
let(:global_metadata) do
|
||||
{
|
||||
consumes: [ 'application/json', 'application/xml' ],
|
||||
produces: [ 'application/json', 'application/xml' ]
|
||||
}
|
||||
end
|
||||
|
||||
it "includes corresponding 'Accept' & 'Content-Type' headers" do
|
||||
expect(headers).to match(
|
||||
'ACCEPT' => 'application/json;application/xml',
|
||||
'CONTENT_TYPE' => 'application/json;application/xml'
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
72
rswag-specs/spec/rswag/specs/response_validator_spec.rb
Normal file
72
rswag-specs/spec/rswag/specs/response_validator_spec.rb
Normal file
@ -0,0 +1,72 @@
|
||||
require 'rswag/specs/response_validator'
|
||||
|
||||
module Rswag
|
||||
module Specs
|
||||
|
||||
describe ResponseValidator do
|
||||
let(:api_metadata) { { response: { code: 200 } } }
|
||||
let(:global_metadata) { {} }
|
||||
|
||||
subject { ResponseValidator.new(api_metadata, global_metadata) }
|
||||
|
||||
describe '#validate!(response)' do
|
||||
let(:call) { subject.validate!(response) }
|
||||
|
||||
context "no 'schema' provided" do
|
||||
context 'response code matches' do
|
||||
let(:response) { OpenStruct.new(code: 200, body: '') }
|
||||
it { expect { call }.to_not raise_error }
|
||||
end
|
||||
|
||||
context 'response code does not match' do
|
||||
let(:response) { OpenStruct.new(code: 201, body: '') }
|
||||
it { expect { call }.to raise_error UnexpectedResponse }
|
||||
end
|
||||
end
|
||||
|
||||
context "'schema' provided" do
|
||||
before do
|
||||
api_metadata[:response][:schema] = {
|
||||
type: 'object',
|
||||
properties: { text: { type: 'string' } },
|
||||
required: [ 'text' ]
|
||||
}
|
||||
end
|
||||
|
||||
context 'response code & body matches' do
|
||||
let(:response) { OpenStruct.new(code: 200, body: "{\"text\":\"Some comment\"}") }
|
||||
it { expect { call }.to_not raise_error }
|
||||
end
|
||||
|
||||
context 'response code matches & body does not' do
|
||||
let(:response) { OpenStruct.new(code: 200, body: "{\"foo\":\"Some comment\"}") }
|
||||
it { expect { call }.to raise_error UnexpectedResponse }
|
||||
end
|
||||
end
|
||||
|
||||
context "referenced 'schema' provided" do
|
||||
before do
|
||||
api_metadata[:response][:schema] = { '$ref' => '#/definitions/author' }
|
||||
global_metadata[:definitions] = {
|
||||
author: {
|
||||
type: 'object',
|
||||
properties: { name: { type: 'string' } },
|
||||
required: [ 'name' ]
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
context 'response code & body matches' do
|
||||
let(:response) { OpenStruct.new(code: 200, body: "{\"name\":\"Some name\"}") }
|
||||
it { expect { call }.to_not raise_error }
|
||||
end
|
||||
|
||||
context 'response code matches & body does not' do
|
||||
let(:response) { OpenStruct.new(code: 200, body: "{\"foo\":\"Some name\"}") }
|
||||
it { expect { call }.to raise_error UnexpectedResponse }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
125
rswag-specs/spec/rswag/specs/swagger_formatter_spec.rb
Normal file
125
rswag-specs/spec/rswag/specs/swagger_formatter_spec.rb
Normal file
@ -0,0 +1,125 @@
|
||||
require 'rswag/specs/swagger_formatter'
|
||||
require 'ostruct'
|
||||
|
||||
module Rswag
|
||||
module Specs
|
||||
|
||||
describe SwaggerFormatter do
|
||||
# Mock infrastructure - output, RSpec.configuration etc.
|
||||
let(:output) { double('output').as_null_object }
|
||||
let(:swagger_root) { File.expand_path('../tmp', __FILE__) }
|
||||
let(:swagger_docs) do
|
||||
{
|
||||
'v1/swagger.json' => { info: { version: 'v1' } }
|
||||
}
|
||||
end
|
||||
let(:config) { OpenStruct.new(swagger_root: swagger_root, swagger_docs: swagger_docs) }
|
||||
before { allow(RSpec).to receive(:configuration).and_return(config) }
|
||||
|
||||
subject { described_class.new(output) }
|
||||
|
||||
describe '::new(output)' do
|
||||
context 'swagger_root not configured' do
|
||||
let(:swagger_root) { nil }
|
||||
it { expect { subject }.to raise_error ConfigurationError }
|
||||
end
|
||||
|
||||
context 'swagger_docs not configured' do
|
||||
let(:swagger_docs) { nil }
|
||||
it { expect { subject }.to raise_error ConfigurationError }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#example_group_finished(notification)' do
|
||||
# Mock notification parameter
|
||||
let(:api_metadata) do
|
||||
{
|
||||
path: '/blogs',
|
||||
operation: { verb: :post, summary: 'Creates a blog' },
|
||||
response: { code: '201', description: 'blog created' }
|
||||
}
|
||||
end
|
||||
let(:notification) { OpenStruct.new(group: OpenStruct.new(metadata: api_metadata)) }
|
||||
|
||||
let(:call) { subject.example_group_finished(notification) }
|
||||
|
||||
context 'single swagger_doc' do
|
||||
before { call }
|
||||
|
||||
it 'converts metadata to swagger and merges into the doc' do
|
||||
expect(swagger_docs.values.first).to match(
|
||||
info: { version: 'v1' },
|
||||
paths: {
|
||||
'/blogs' => {
|
||||
post: {
|
||||
summary: 'Creates a blog',
|
||||
responses: {
|
||||
'201' => { description: 'blog created' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'multiple swagger_docs' do
|
||||
let(:swagger_docs) do
|
||||
{
|
||||
'v1/swagger.json' => {},
|
||||
'v2/swagger.json' => {}
|
||||
}
|
||||
end
|
||||
|
||||
context "no 'swagger_doc' tag" do
|
||||
before { call }
|
||||
|
||||
it 'merges into the first doc' do
|
||||
expect(swagger_docs.values.first).to have_key(:paths)
|
||||
end
|
||||
end
|
||||
|
||||
context "matching 'swagger_doc' tag" do
|
||||
before do
|
||||
api_metadata[:swagger_doc] = 'v2/swagger.json'
|
||||
call
|
||||
end
|
||||
|
||||
it 'merges into the matched doc' do
|
||||
expect(swagger_docs.values.last).to have_key(:paths)
|
||||
end
|
||||
end
|
||||
|
||||
context "non matching 'swagger_doc' tag" do
|
||||
before { api_metadata[:swagger_doc] = 'foobar' }
|
||||
it { expect { call }.to raise_error ConfigurationError }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#stop' do
|
||||
let(:notification) { double('notification') }
|
||||
let(:swagger_docs) do
|
||||
{
|
||||
'v1/swagger.json' => { info: { version: 'v1' } },
|
||||
'v2/swagger.json' => { info: { version: 'v2' } },
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
FileUtils.rm_r(swagger_root) if File.exists?(swagger_root)
|
||||
subject.stop(notification)
|
||||
end
|
||||
|
||||
it 'writes the swagger_doc(s) to file' do
|
||||
expect(File).to exist("#{swagger_root}/v1/swagger.json")
|
||||
expect(File).to exist("#{swagger_root}/v2/swagger.json")
|
||||
end
|
||||
|
||||
after do
|
||||
FileUtils.rm_r(swagger_root) if File.exists?(swagger_root)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
7
rswag-specs/spec/spec_helper.rb
Normal file
7
rswag-specs/spec/spec_helper.rb
Normal file
@ -0,0 +1,7 @@
|
||||
module Rails
|
||||
module VERSION
|
||||
MAJOR = 3
|
||||
end
|
||||
end
|
||||
|
||||
require 'rswag/specs'
|
||||
2
rswag-specs/spec/swagger_helper.rb
Normal file
2
rswag-specs/spec/swagger_helper.rb
Normal file
@ -0,0 +1,2 @@
|
||||
# NOTE: For the specs in this gem, all configuration is completely mocked out
|
||||
# The file just needs to be present because it gets required by the swagger_formatter
|
||||
2
rswag-ui/.rspec
Normal file
2
rswag-ui/.rspec
Normal file
@ -0,0 +1,2 @@
|
||||
--color
|
||||
--require spec_helper
|
||||
20
rswag-ui/MIT-LICENSE
Normal file
20
rswag-ui/MIT-LICENSE
Normal file
@ -0,0 +1,20 @@
|
||||
Copyright 2015 domaindrivendev
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
27
rswag-ui/Rakefile
Normal file
27
rswag-ui/Rakefile
Normal file
@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env rake
|
||||
begin
|
||||
require 'bundler/setup'
|
||||
rescue LoadError
|
||||
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
||||
end
|
||||
begin
|
||||
require 'rdoc/task'
|
||||
rescue LoadError
|
||||
require 'rdoc/rdoc'
|
||||
require 'rake/rdoctask'
|
||||
RDoc::Task = Rake::RDocTask
|
||||
end
|
||||
|
||||
RDoc::Task.new(:rdoc) do |rdoc|
|
||||
rdoc.rdoc_dir = 'rdoc'
|
||||
rdoc.title = 'rswag-specs'
|
||||
rdoc.options << '--line-numbers'
|
||||
rdoc.rdoc_files.include('README.rdoc')
|
||||
rdoc.rdoc_files.include('lib/**/*.rb')
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
Bundler::GemHelper.install_tasks
|
||||
|
||||
11
rswag-ui/app/controllers/rswag/ui/home_controller.rb
Normal file
11
rswag-ui/app/controllers/rswag/ui/home_controller.rb
Normal file
@ -0,0 +1,11 @@
|
||||
module Rswag
|
||||
module Ui
|
||||
class HomeController < ActionController::Base
|
||||
|
||||
def index
|
||||
@swagger_endpoints = Rswag::Ui.config.swagger_endpoints
|
||||
render :index, layout: false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -10,15 +10,19 @@
|
||||
<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'/>
|
||||
|
||||
<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-2.0.0.js' type='text/javascript'></script>
|
||||
<script src='/assets/swagger-ui/lib/underscore-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.js' type='text/javascript'></script>
|
||||
<script src='/assets/swagger-ui/lib/highlight.7.3.pack.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>
|
||||
|
||||
@ -29,75 +33,43 @@
|
||||
|
||||
<script type="text/javascript">
|
||||
$(function () {
|
||||
var url = window.location.search.match(/url=([^&]+)/);
|
||||
if (url && url.length > 1) {
|
||||
url = decodeURIComponent(url[1]);
|
||||
} else {
|
||||
url = "<%= @discovery_paths.keys.first %>";
|
||||
}
|
||||
hljs.configure({
|
||||
highlightSizeThreshold: 5000
|
||||
});
|
||||
|
||||
// Pre load translate...
|
||||
if(window.SwaggerTranslator) {
|
||||
window.SwaggerTranslator.translate();
|
||||
}
|
||||
window.swaggerUi = new SwaggerUi({
|
||||
url: url,
|
||||
url: '<%= @swagger_endpoints.first.path %>',
|
||||
dom_id: "swagger-ui-container",
|
||||
supportedSubmitMethods: ['get', 'post', 'put', 'delete', 'patch'],
|
||||
onComplete: function(swaggerApi, swaggerUi){
|
||||
if(typeof initOAuth == "function") {
|
||||
initOAuth({
|
||||
clientId: "your-client-id",
|
||||
clientSecret: "your-client-secret",
|
||||
clientSecret: "your-client-secret-if-required",
|
||||
realm: "your-realms",
|
||||
appName: "your-app-name",
|
||||
scopeSeparator: ","
|
||||
scopeSeparator: " ",
|
||||
additionalQueryStringParams: {}
|
||||
});
|
||||
}
|
||||
|
||||
if(window.SwaggerTranslator) {
|
||||
window.SwaggerTranslator.translate();
|
||||
}
|
||||
|
||||
$('pre code').each(function(i, e) {
|
||||
hljs.highlightBlock(e)
|
||||
});
|
||||
|
||||
addApiKeyAuthorization();
|
||||
|
||||
// Send Rails CSRF Token with every request
|
||||
var csrfToken = new SwaggerClient.ApiKeyAuthorization(
|
||||
'X-CSRF-Token',
|
||||
'<%= form_authenticity_token %>',
|
||||
'header'
|
||||
);
|
||||
swaggerUi.api.clientAuthorizations.add('csrf-token', csrfToken);
|
||||
},
|
||||
onFailure: function(data) {
|
||||
log("Unable to Load SwaggerUI");
|
||||
},
|
||||
docExpansion: "list",
|
||||
apisSorter: "alpha",
|
||||
jsonEditor: false,
|
||||
defaultModelRendering: 'schema',
|
||||
showRequestHeaders: false
|
||||
});
|
||||
|
||||
function addApiKeyAuthorization(){
|
||||
var key = encodeURIComponent($('#input_apiKey')[0].value);
|
||||
if(key && key.trim() != "") {
|
||||
var apiKeyAuth = new SwaggerClient.ApiKeyAuthorization("api_key", key, "query");
|
||||
window.swaggerUi.api.clientAuthorizations.add("api_key", apiKeyAuth);
|
||||
log("added key " + key);
|
||||
}
|
||||
}
|
||||
|
||||
$('#input_apiKey').change(addApiKeyAuthorization);
|
||||
|
||||
// if you have an apiKey you would like to pre-populate on the page for demonstration purposes...
|
||||
/*
|
||||
var apiKey = "myApiKeyXXXX123456789";
|
||||
$('#input_apiKey').val(apiKey);
|
||||
*/
|
||||
|
||||
window.swaggerUi.load();
|
||||
|
||||
function log() {
|
||||
@ -112,37 +84,38 @@
|
||||
<body class="swagger-section">
|
||||
<div id='header'>
|
||||
<div class="swagger-ui-wrap">
|
||||
<a id="logo" href="http://swagger.io">swagger</a>
|
||||
<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>
|
||||
<form id='api_selector'>
|
||||
<div class='input'><input placeholder="http://example.com/api" id="input_baseUrl" name="baseUrl" type="text" disabled="disabled"/></div>
|
||||
<div class='input'><input placeholder="api_key" id="input_apiKey" name="apiKey" type="text"/></div>
|
||||
<div class='input'>
|
||||
<select id="select_version">
|
||||
<% @discovery_paths.each do |path, title| %>
|
||||
<option value="<%= path %>"><%= title %></option>
|
||||
<div class='input'><input placeholder="http://example.com/api" id="input_baseUrl" name="baseUrl" type="text"/></div>
|
||||
<div id='auth_container'></div>
|
||||
<select id="select_document">
|
||||
<% @swagger_endpoints.each do |endpoint| %>
|
||||
<option value='<%= endpoint.path %>'><%= endpoint.title %></option>
|
||||
<% end %>
|
||||
</select>
|
||||
<script type="text/javascript">
|
||||
$('#select_version').change(function () {
|
||||
// Refresh the swagger-ui when a new document is selected
|
||||
$('#select_document').change(function () {
|
||||
$('#input_baseUrl').val($(this).val());
|
||||
window.swaggerUi.headerView.showCustom();
|
||||
});
|
||||
</script>
|
||||
<style>
|
||||
#select_version {
|
||||
#select_document {
|
||||
border: none;
|
||||
height: 1.85em;
|
||||
border-radius: 4px;
|
||||
-moz-border-radius: 4px;
|
||||
-webkit-border-radius: 4px;
|
||||
-o-border-radius: 4px;
|
||||
-ms-border-radius: 4px;
|
||||
-khtml-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
font-size: 0.85em;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
background-color: #547f00;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
</style>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
12
rswag-ui/bin/rails
Executable file
12
rswag-ui/bin/rails
Executable file
@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env ruby
|
||||
# This command will automatically be run when you run "rails" with Rails 4 gems installed from the root of your application.
|
||||
|
||||
ENGINE_ROOT = File.expand_path('../..', __FILE__)
|
||||
ENGINE_PATH = File.expand_path('../../lib/rswag/api/engine', __FILE__)
|
||||
|
||||
# Set up gems listed in the Gemfile.
|
||||
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
|
||||
require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
|
||||
|
||||
require 'rails/all'
|
||||
require 'rails/engine/commands'
|
||||
3
rswag-ui/config/routes.rb
Normal file
3
rswag-ui/config/routes.rb
Normal file
@ -0,0 +1,3 @@
|
||||
Rswag::Ui::Engine.routes.draw do
|
||||
root to: 'home#index'
|
||||
end
|
||||
@ -2,7 +2,7 @@ Description:
|
||||
Adds a local version of index.html.erb for customizing the swagger-ui
|
||||
|
||||
Example:
|
||||
rails generate swagger_rails:custom_ui
|
||||
rails generate rswag:ui:custom
|
||||
|
||||
This will create:
|
||||
app/views/swagger_rails/swagger_ui/index.html.erb
|
||||
app/views/rswag/ui/home/index.html.erb
|
||||
13
rswag-ui/lib/generators/rswag/ui/custom/custom_generator.rb
Normal file
13
rswag-ui/lib/generators/rswag/ui/custom/custom_generator.rb
Normal file
@ -0,0 +1,13 @@
|
||||
require 'rails/generators'
|
||||
|
||||
module Rswag
|
||||
module Ui
|
||||
class CustomGenerator < Rails::Generators::Base
|
||||
source_root File.expand_path('../../../../../../app/views/rswag/ui/home', __FILE__)
|
||||
|
||||
def add_custom_index
|
||||
copy_file('index.html.erb', 'app/views/rswag/ui/home/index.html.erb')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
8
rswag-ui/lib/generators/rswag/ui/install/USAGE
Normal file
8
rswag-ui/lib/generators/rswag/ui/install/USAGE
Normal file
@ -0,0 +1,8 @@
|
||||
Description:
|
||||
Adds rswag-api initializer for configuration
|
||||
|
||||
Example:
|
||||
rails generate rswag:api:install
|
||||
|
||||
This will create:
|
||||
config/initializers/rswag-api.rb
|
||||
@ -0,0 +1,18 @@
|
||||
require 'rails/generators'
|
||||
|
||||
module Rswag
|
||||
module Ui
|
||||
|
||||
class InstallGenerator < Rails::Generators::Base
|
||||
source_root File.expand_path('../templates', __FILE__)
|
||||
|
||||
def add_initializer
|
||||
template('rswag-ui.rb', 'config/initializers/rswag-ui.rb')
|
||||
end
|
||||
|
||||
def add_routes
|
||||
route("mount Rswag::Ui::Engine => '/api-docs'")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,9 @@
|
||||
Rswag::Ui.configure do |c|
|
||||
|
||||
# List the Swagger endpoints that you want to be documented through the swagger-ui
|
||||
# The first parameter is the path (absolute or relative to the UI host) to the corresponding
|
||||
# JSON endpoint and the second is a title that will be displayed in the document selector
|
||||
# NOTE: If you're using rspec-api to expose Swagger files (under swagger_root) as JSON endpoints,
|
||||
# then the list below should correspond to the relative paths for those endpoints
|
||||
c.swagger_endpoint '/api-docs/v1/swagger.json', 'API V1 Docs'
|
||||
end
|
||||
15
rswag-ui/lib/rswag/ui.rb
Normal file
15
rswag-ui/lib/rswag/ui.rb
Normal file
@ -0,0 +1,15 @@
|
||||
require 'rswag/ui/version'
|
||||
require 'rswag/ui/configuration'
|
||||
require 'rswag/ui/engine'
|
||||
|
||||
module Rswag
|
||||
module Ui
|
||||
def self.configure
|
||||
yield(config)
|
||||
end
|
||||
|
||||
def self.config
|
||||
@config ||= Configuration.new
|
||||
end
|
||||
end
|
||||
end
|
||||
17
rswag-ui/lib/rswag/ui/configuration.rb
Normal file
17
rswag-ui/lib/rswag/ui/configuration.rb
Normal file
@ -0,0 +1,17 @@
|
||||
require 'ostruct'
|
||||
|
||||
module Rswag
|
||||
module Ui
|
||||
class Configuration
|
||||
attr_reader :swagger_endpoints
|
||||
|
||||
def initialize
|
||||
@swagger_endpoints = []
|
||||
end
|
||||
|
||||
def swagger_endpoint(path, title)
|
||||
@swagger_endpoints << OpenStruct.new(path: path, title: title)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
13
rswag-ui/lib/rswag/ui/engine.rb
Normal file
13
rswag-ui/lib/rswag/ui/engine.rb
Normal file
@ -0,0 +1,13 @@
|
||||
module Rswag
|
||||
module Ui
|
||||
class Engine < ::Rails::Engine
|
||||
isolate_namespace Rswag::Ui
|
||||
|
||||
initializer 'rswag-ui.initialize' do |app|
|
||||
if app.config.respond_to?(:assets)
|
||||
app.config.assets.precompile += [ 'swagger-ui/*' ]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
5
rswag-ui/lib/rswag/ui/version.rb
Normal file
5
rswag-ui/lib/rswag/ui/version.rb
Normal file
@ -0,0 +1,5 @@
|
||||
module Rswag
|
||||
module Ui
|
||||
VERSION = '1.0.0'
|
||||
end
|
||||
end
|
||||
18
rswag-ui/rswag-ui.gemspec
Normal file
18
rswag-ui/rswag-ui.gemspec
Normal file
@ -0,0 +1,18 @@
|
||||
$:.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.authors = ["Richie Morris"]
|
||||
s.email = ["domaindrivendev@gmail.com"]
|
||||
s.homepage = "https://github.com/domaindrivendev/rswag"
|
||||
s.summary = "A Rails Engine that includes swagger-ui and powers it from configured Swagger endpoints"
|
||||
|
||||
s.files = Dir["{app,config,db,lib}/**/*"] + ["MIT-LICENSE", "Rakefile" ]
|
||||
|
||||
s.add_dependency "rails", ">= 3.1", "< 5.1"
|
||||
end
|
||||
21
rswag-ui/spec/generators/rswag/ui/custom_generator_spec.rb
Normal file
21
rswag-ui/spec/generators/rswag/ui/custom_generator_spec.rb
Normal file
@ -0,0 +1,21 @@
|
||||
require 'generator_spec'
|
||||
require 'generators/rswag/ui/custom/custom_generator'
|
||||
|
||||
module Rswag
|
||||
module Ui
|
||||
|
||||
describe CustomGenerator do
|
||||
include GeneratorSpec::TestCase
|
||||
destination File.expand_path('../tmp', __FILE__)
|
||||
|
||||
before(:all) do
|
||||
prepare_destination
|
||||
run_generator
|
||||
end
|
||||
|
||||
it 'creates a local version of index.html.erb' do
|
||||
assert_file('app/views/rswag/ui/home/index.html.erb')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,2 @@
|
||||
TestApp::Application.routes.draw do
|
||||
end
|
||||
27
rswag-ui/spec/generators/rswag/ui/install_generator_spec.rb
Normal file
27
rswag-ui/spec/generators/rswag/ui/install_generator_spec.rb
Normal file
@ -0,0 +1,27 @@
|
||||
require 'generator_spec'
|
||||
require 'generators/rswag/ui/install/install_generator'
|
||||
|
||||
module Rswag
|
||||
module Ui
|
||||
|
||||
describe InstallGenerator do
|
||||
include GeneratorSpec::TestCase
|
||||
destination File.expand_path('../tmp', __FILE__)
|
||||
|
||||
before(:all) do
|
||||
prepare_destination
|
||||
fixtures_dir = File.expand_path('../fixtures', __FILE__)
|
||||
FileUtils.cp_r("#{fixtures_dir}/config", destination_root)
|
||||
|
||||
run_generator
|
||||
end
|
||||
|
||||
it 'installs the Rails initializer' do
|
||||
assert_file('config/initializers/rswag-ui.rb')
|
||||
end
|
||||
|
||||
# Don't know how to test this
|
||||
#it 'wires up routes'
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,9 @@
|
||||
Rswag::Ui.configure do |c|
|
||||
|
||||
# List the Swagger endpoints that you want to be documented through the swagger-ui
|
||||
# The first parameter is the path (absolute or relative to the UI host) to the corresponding
|
||||
# JSON endpoint and the second is a title that will be displayed in the document selector
|
||||
# NOTE: If you're using rspec-api to expose Swagger files (under swagger_root) as JSON endpoints,
|
||||
# then the list below should correspond to the relative paths for those endpoints
|
||||
c.swagger_endpoint '/api-docs/v1/swagger.json', 'API V1 Docs'
|
||||
end
|
||||
4
rswag-ui/spec/generators/rswag/ui/tmp/config/routes.rb
Normal file
4
rswag-ui/spec/generators/rswag/ui/tmp/config/routes.rb
Normal file
@ -0,0 +1,4 @@
|
||||
TestApp::Application.routes.draw do
|
||||
mount Rswag::Ui::Engine => '/api-docs'
|
||||
|
||||
end
|
||||
@ -82,7 +82,7 @@
|
||||
.swagger-section pre .vhdl .attribute,
|
||||
.swagger-section pre .clojure .attribute,
|
||||
.swagger-section pre .coffeescript .property {
|
||||
color: #8888ff;
|
||||
color: #88F;
|
||||
}
|
||||
.swagger-section pre .keyword,
|
||||
.swagger-section pre .id,
|
||||
@ -120,12 +120,75 @@
|
||||
.swagger-section pre .xml .cdata {
|
||||
opacity: 0.5;
|
||||
}
|
||||
.swagger-section .hljs {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
padding: 0.5em;
|
||||
background: #F0F0F0;
|
||||
}
|
||||
.swagger-section .hljs,
|
||||
.swagger-section .hljs-subst {
|
||||
color: #444;
|
||||
}
|
||||
.swagger-section .hljs-keyword,
|
||||
.swagger-section .hljs-attribute,
|
||||
.swagger-section .hljs-selector-tag,
|
||||
.swagger-section .hljs-meta-keyword,
|
||||
.swagger-section .hljs-doctag,
|
||||
.swagger-section .hljs-name {
|
||||
font-weight: bold;
|
||||
}
|
||||
.swagger-section .hljs-built_in,
|
||||
.swagger-section .hljs-literal,
|
||||
.swagger-section .hljs-bullet,
|
||||
.swagger-section .hljs-code,
|
||||
.swagger-section .hljs-addition {
|
||||
color: #1F811F;
|
||||
}
|
||||
.swagger-section .hljs-regexp,
|
||||
.swagger-section .hljs-symbol,
|
||||
.swagger-section .hljs-variable,
|
||||
.swagger-section .hljs-template-variable,
|
||||
.swagger-section .hljs-link,
|
||||
.swagger-section .hljs-selector-attr,
|
||||
.swagger-section .hljs-selector-pseudo {
|
||||
color: #BC6060;
|
||||
}
|
||||
.swagger-section .hljs-type,
|
||||
.swagger-section .hljs-string,
|
||||
.swagger-section .hljs-number,
|
||||
.swagger-section .hljs-selector-id,
|
||||
.swagger-section .hljs-selector-class,
|
||||
.swagger-section .hljs-quote,
|
||||
.swagger-section .hljs-template-tag,
|
||||
.swagger-section .hljs-deletion {
|
||||
color: #880000;
|
||||
}
|
||||
.swagger-section .hljs-title,
|
||||
.swagger-section .hljs-section {
|
||||
color: #880000;
|
||||
font-weight: bold;
|
||||
}
|
||||
.swagger-section .hljs-comment {
|
||||
color: #888888;
|
||||
}
|
||||
.swagger-section .hljs-meta {
|
||||
color: #2B6EA1;
|
||||
}
|
||||
.swagger-section .hljs-emphasis {
|
||||
font-style: italic;
|
||||
}
|
||||
.swagger-section .hljs-strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
.swagger-section .swagger-ui-wrap {
|
||||
line-height: 1;
|
||||
font-family: "Droid Sans", sans-serif;
|
||||
min-width: 760px;
|
||||
max-width: 960px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
/* JSONEditor specific styling */
|
||||
}
|
||||
.swagger-section .swagger-ui-wrap b,
|
||||
.swagger-section .swagger-ui-wrap strong {
|
||||
@ -364,6 +427,7 @@
|
||||
}
|
||||
.swagger-section .swagger-ui-wrap .model-signature ul.signature-nav {
|
||||
display: block;
|
||||
min-width: 230px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
@ -458,6 +522,17 @@
|
||||
.swagger-section .swagger-ui-wrap .required {
|
||||
font-weight: bold;
|
||||
}
|
||||
.swagger-section .swagger-ui-wrap .editor_holder {
|
||||
font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
.swagger-section .swagger-ui-wrap .editor_holder label {
|
||||
font-weight: normal!important;
|
||||
/* JSONEditor uses bold by default for all labels, we revert that back to normal to not give the impression that by default fields are required */
|
||||
}
|
||||
.swagger-section .swagger-ui-wrap .editor_holder label.required {
|
||||
font-weight: bold!important;
|
||||
}
|
||||
.swagger-section .swagger-ui-wrap input.parameter {
|
||||
width: 300px;
|
||||
border: 1px solid #aaa;
|
||||
@ -592,6 +667,7 @@
|
||||
}
|
||||
.swagger-section .swagger-ui-wrap .markdown pre code {
|
||||
line-height: 1.6em;
|
||||
overflow: auto;
|
||||
}
|
||||
.swagger-section .swagger-ui-wrap div.gist {
|
||||
margin: 20px 0 25px 0 !important;
|
||||
@ -712,6 +788,9 @@
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
}
|
||||
.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span.path a.toggleOperation.deprecated {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span.path a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
@ -753,6 +832,11 @@
|
||||
.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading ul.options li a {
|
||||
text-decoration: none;
|
||||
}
|
||||
.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading ul.options li a .markdown p {
|
||||
color: inherit;
|
||||
padding: 0;
|
||||
line-height: inherit;
|
||||
}
|
||||
.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading ul.options li.access {
|
||||
color: black;
|
||||
}
|
||||
@ -807,6 +891,9 @@
|
||||
outline: 2px solid black;
|
||||
outline-color: #cc0000;
|
||||
}
|
||||
.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content form select[name='parameterContentType'] {
|
||||
max-width: 300px;
|
||||
}
|
||||
.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.response div.block pre {
|
||||
font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace;
|
||||
padding: 10px;
|
||||
@ -1100,18 +1187,122 @@
|
||||
}
|
||||
.swagger-section .oauth_submit {
|
||||
text-align: center;
|
||||
display: inline-block;
|
||||
}
|
||||
.swagger-section .authorize-wrapper {
|
||||
margin: 15px 0 10px;
|
||||
}
|
||||
.swagger-section .authorize-wrapper_operation {
|
||||
float: right;
|
||||
}
|
||||
.swagger-section .authorize__btn:hover {
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
.swagger-section .authorize__btn_operation:hover .authorize-scopes {
|
||||
display: block;
|
||||
}
|
||||
.swagger-section .authorize-scopes {
|
||||
position: absolute;
|
||||
margin-top: 20px;
|
||||
background: #FFF;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
display: none;
|
||||
font-size: 13px;
|
||||
max-width: 300px;
|
||||
line-height: 30px;
|
||||
color: black;
|
||||
padding: 5px;
|
||||
}
|
||||
.swagger-section .authorize-scopes .authorize__scope {
|
||||
text-decoration: none;
|
||||
}
|
||||
.swagger-section .authorize__btn_operation {
|
||||
height: 18px;
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
background: url(../images/explorer_icons.png) no-repeat;
|
||||
}
|
||||
.swagger-section .authorize__btn_operation_login {
|
||||
background-position: 0 0;
|
||||
width: 18px;
|
||||
margin-top: -6px;
|
||||
margin-left: 4px;
|
||||
}
|
||||
.swagger-section .authorize__btn_operation_logout {
|
||||
background-position: -30px 0;
|
||||
width: 18px;
|
||||
margin-top: -6px;
|
||||
margin-left: 4px;
|
||||
}
|
||||
.swagger-section #auth_container {
|
||||
color: #fff;
|
||||
display: inline-block;
|
||||
border: none;
|
||||
padding: 5px;
|
||||
width: 87px;
|
||||
height: 13px;
|
||||
}
|
||||
.swagger-section #auth_container .authorize__btn {
|
||||
color: #fff;
|
||||
}
|
||||
.swagger-section .auth_container {
|
||||
padding: 0 0 10px;
|
||||
margin-bottom: 5px;
|
||||
border-bottom: solid 1px #CCC;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
.swagger-section .auth_container .auth__title {
|
||||
color: #547f00;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
.swagger-section .auth_container .basic_auth__label {
|
||||
display: inline-block;
|
||||
width: 60px;
|
||||
}
|
||||
.swagger-section .auth_container .auth__description {
|
||||
color: #999999;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.swagger-section .auth_container .auth__button {
|
||||
margin-top: 10px;
|
||||
height: 30px;
|
||||
}
|
||||
.swagger-section .auth_container .key_auth__field {
|
||||
margin: 5px 0;
|
||||
}
|
||||
.swagger-section .auth_container .key_auth__label {
|
||||
display: inline-block;
|
||||
width: 60px;
|
||||
}
|
||||
.swagger-section .api-popup-dialog {
|
||||
z-index: 10000;
|
||||
position: absolute;
|
||||
display: none;
|
||||
}
|
||||
.swagger-section .api-popup-dialog-wrapper {
|
||||
z-index: 1000;
|
||||
width: 500px;
|
||||
background: #FFF;
|
||||
padding: 20px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
display: none;
|
||||
font-size: 13px;
|
||||
color: #777;
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
.swagger-section .api-popup-dialog-shadow {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0.2;
|
||||
background-color: gray;
|
||||
z-index: 900;
|
||||
}
|
||||
.swagger-section .api-popup-dialog .api-popup-title {
|
||||
font-size: 24px;
|
||||
@ -1121,14 +1312,18 @@
|
||||
font-size: 24px;
|
||||
padding: 10px 0;
|
||||
}
|
||||
.swagger-section .api-popup-dialog p.error-msg {
|
||||
.swagger-section .api-popup-dialog .error-msg {
|
||||
padding-left: 5px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
.swagger-section .api-popup-dialog button.api-popup-authbtn {
|
||||
.swagger-section .api-popup-dialog .api-popup-content {
|
||||
max-height: 500px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.swagger-section .api-popup-dialog .api-popup-authbtn {
|
||||
height: 30px;
|
||||
}
|
||||
.swagger-section .api-popup-dialog button.api-popup-cancel {
|
||||
.swagger-section .api-popup-dialog .api-popup-cancel {
|
||||
height: 30px;
|
||||
}
|
||||
.swagger-section .api-popup-scopes {
|
||||
@ -1138,14 +1333,14 @@
|
||||
padding: 5px 0;
|
||||
line-height: 20px;
|
||||
}
|
||||
.swagger-section .api-popup-scopes .api-scope-desc {
|
||||
padding-left: 20px;
|
||||
font-style: italic;
|
||||
}
|
||||
.swagger-section .api-popup-scopes li input {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
}
|
||||
.swagger-section .api-popup-scopes .api-scope-desc {
|
||||
padding-left: 20px;
|
||||
font-style: italic;
|
||||
}
|
||||
.swagger-section .api-popup-actions {
|
||||
padding-top: 10px;
|
||||
}
|
||||
@ -82,7 +82,7 @@
|
||||
.swagger-section pre .vhdl .attribute,
|
||||
.swagger-section pre .clojure .attribute,
|
||||
.swagger-section pre .coffeescript .property {
|
||||
color: #8888ff;
|
||||
color: #88F;
|
||||
}
|
||||
.swagger-section pre .keyword,
|
||||
.swagger-section pre .id,
|
||||
@ -120,12 +120,75 @@
|
||||
.swagger-section pre .xml .cdata {
|
||||
opacity: 0.5;
|
||||
}
|
||||
.swagger-section .hljs {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
padding: 0.5em;
|
||||
background: #F0F0F0;
|
||||
}
|
||||
.swagger-section .hljs,
|
||||
.swagger-section .hljs-subst {
|
||||
color: #444;
|
||||
}
|
||||
.swagger-section .hljs-keyword,
|
||||
.swagger-section .hljs-attribute,
|
||||
.swagger-section .hljs-selector-tag,
|
||||
.swagger-section .hljs-meta-keyword,
|
||||
.swagger-section .hljs-doctag,
|
||||
.swagger-section .hljs-name {
|
||||
font-weight: bold;
|
||||
}
|
||||
.swagger-section .hljs-built_in,
|
||||
.swagger-section .hljs-literal,
|
||||
.swagger-section .hljs-bullet,
|
||||
.swagger-section .hljs-code,
|
||||
.swagger-section .hljs-addition {
|
||||
color: #1F811F;
|
||||
}
|
||||
.swagger-section .hljs-regexp,
|
||||
.swagger-section .hljs-symbol,
|
||||
.swagger-section .hljs-variable,
|
||||
.swagger-section .hljs-template-variable,
|
||||
.swagger-section .hljs-link,
|
||||
.swagger-section .hljs-selector-attr,
|
||||
.swagger-section .hljs-selector-pseudo {
|
||||
color: #BC6060;
|
||||
}
|
||||
.swagger-section .hljs-type,
|
||||
.swagger-section .hljs-string,
|
||||
.swagger-section .hljs-number,
|
||||
.swagger-section .hljs-selector-id,
|
||||
.swagger-section .hljs-selector-class,
|
||||
.swagger-section .hljs-quote,
|
||||
.swagger-section .hljs-template-tag,
|
||||
.swagger-section .hljs-deletion {
|
||||
color: #880000;
|
||||
}
|
||||
.swagger-section .hljs-title,
|
||||
.swagger-section .hljs-section {
|
||||
color: #880000;
|
||||
font-weight: bold;
|
||||
}
|
||||
.swagger-section .hljs-comment {
|
||||
color: #888888;
|
||||
}
|
||||
.swagger-section .hljs-meta {
|
||||
color: #2B6EA1;
|
||||
}
|
||||
.swagger-section .hljs-emphasis {
|
||||
font-style: italic;
|
||||
}
|
||||
.swagger-section .hljs-strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
.swagger-section .swagger-ui-wrap {
|
||||
line-height: 1;
|
||||
font-family: "Droid Sans", sans-serif;
|
||||
min-width: 760px;
|
||||
max-width: 960px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
/* JSONEditor specific styling */
|
||||
}
|
||||
.swagger-section .swagger-ui-wrap b,
|
||||
.swagger-section .swagger-ui-wrap strong {
|
||||
@ -364,6 +427,7 @@
|
||||
}
|
||||
.swagger-section .swagger-ui-wrap .model-signature ul.signature-nav {
|
||||
display: block;
|
||||
min-width: 230px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
@ -458,6 +522,17 @@
|
||||
.swagger-section .swagger-ui-wrap .required {
|
||||
font-weight: bold;
|
||||
}
|
||||
.swagger-section .swagger-ui-wrap .editor_holder {
|
||||
font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
.swagger-section .swagger-ui-wrap .editor_holder label {
|
||||
font-weight: normal!important;
|
||||
/* JSONEditor uses bold by default for all labels, we revert that back to normal to not give the impression that by default fields are required */
|
||||
}
|
||||
.swagger-section .swagger-ui-wrap .editor_holder label.required {
|
||||
font-weight: bold!important;
|
||||
}
|
||||
.swagger-section .swagger-ui-wrap input.parameter {
|
||||
width: 300px;
|
||||
border: 1px solid #aaa;
|
||||
@ -592,6 +667,7 @@
|
||||
}
|
||||
.swagger-section .swagger-ui-wrap .markdown pre code {
|
||||
line-height: 1.6em;
|
||||
overflow: auto;
|
||||
}
|
||||
.swagger-section .swagger-ui-wrap div.gist {
|
||||
margin: 20px 0 25px 0 !important;
|
||||
@ -712,6 +788,9 @@
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
}
|
||||
.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span.path a.toggleOperation.deprecated {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span.path a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
@ -753,6 +832,11 @@
|
||||
.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading ul.options li a {
|
||||
text-decoration: none;
|
||||
}
|
||||
.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading ul.options li a .markdown p {
|
||||
color: inherit;
|
||||
padding: 0;
|
||||
line-height: inherit;
|
||||
}
|
||||
.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading ul.options li.access {
|
||||
color: black;
|
||||
}
|
||||
@ -807,6 +891,9 @@
|
||||
outline: 2px solid black;
|
||||
outline-color: #cc0000;
|
||||
}
|
||||
.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content form select[name='parameterContentType'] {
|
||||
max-width: 300px;
|
||||
}
|
||||
.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.response div.block pre {
|
||||
font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace;
|
||||
padding: 10px;
|
||||
@ -1100,18 +1187,122 @@
|
||||
}
|
||||
.swagger-section .oauth_submit {
|
||||
text-align: center;
|
||||
display: inline-block;
|
||||
}
|
||||
.swagger-section .authorize-wrapper {
|
||||
margin: 15px 0 10px;
|
||||
}
|
||||
.swagger-section .authorize-wrapper_operation {
|
||||
float: right;
|
||||
}
|
||||
.swagger-section .authorize__btn:hover {
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
.swagger-section .authorize__btn_operation:hover .authorize-scopes {
|
||||
display: block;
|
||||
}
|
||||
.swagger-section .authorize-scopes {
|
||||
position: absolute;
|
||||
margin-top: 20px;
|
||||
background: #FFF;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
display: none;
|
||||
font-size: 13px;
|
||||
max-width: 300px;
|
||||
line-height: 30px;
|
||||
color: black;
|
||||
padding: 5px;
|
||||
}
|
||||
.swagger-section .authorize-scopes .authorize__scope {
|
||||
text-decoration: none;
|
||||
}
|
||||
.swagger-section .authorize__btn_operation {
|
||||
height: 18px;
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
background: url(../images/explorer_icons.png) no-repeat;
|
||||
}
|
||||
.swagger-section .authorize__btn_operation_login {
|
||||
background-position: 0 0;
|
||||
width: 18px;
|
||||
margin-top: -6px;
|
||||
margin-left: 4px;
|
||||
}
|
||||
.swagger-section .authorize__btn_operation_logout {
|
||||
background-position: -30px 0;
|
||||
width: 18px;
|
||||
margin-top: -6px;
|
||||
margin-left: 4px;
|
||||
}
|
||||
.swagger-section #auth_container {
|
||||
color: #fff;
|
||||
display: inline-block;
|
||||
border: none;
|
||||
padding: 5px;
|
||||
width: 87px;
|
||||
height: 13px;
|
||||
}
|
||||
.swagger-section #auth_container .authorize__btn {
|
||||
color: #fff;
|
||||
}
|
||||
.swagger-section .auth_container {
|
||||
padding: 0 0 10px;
|
||||
margin-bottom: 5px;
|
||||
border-bottom: solid 1px #CCC;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
.swagger-section .auth_container .auth__title {
|
||||
color: #547f00;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
.swagger-section .auth_container .basic_auth__label {
|
||||
display: inline-block;
|
||||
width: 60px;
|
||||
}
|
||||
.swagger-section .auth_container .auth__description {
|
||||
color: #999999;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.swagger-section .auth_container .auth__button {
|
||||
margin-top: 10px;
|
||||
height: 30px;
|
||||
}
|
||||
.swagger-section .auth_container .key_auth__field {
|
||||
margin: 5px 0;
|
||||
}
|
||||
.swagger-section .auth_container .key_auth__label {
|
||||
display: inline-block;
|
||||
width: 60px;
|
||||
}
|
||||
.swagger-section .api-popup-dialog {
|
||||
z-index: 10000;
|
||||
position: absolute;
|
||||
display: none;
|
||||
}
|
||||
.swagger-section .api-popup-dialog-wrapper {
|
||||
z-index: 1000;
|
||||
width: 500px;
|
||||
background: #FFF;
|
||||
padding: 20px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
display: none;
|
||||
font-size: 13px;
|
||||
color: #777;
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
.swagger-section .api-popup-dialog-shadow {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0.2;
|
||||
background-color: gray;
|
||||
z-index: 900;
|
||||
}
|
||||
.swagger-section .api-popup-dialog .api-popup-title {
|
||||
font-size: 24px;
|
||||
@ -1121,14 +1312,18 @@
|
||||
font-size: 24px;
|
||||
padding: 10px 0;
|
||||
}
|
||||
.swagger-section .api-popup-dialog p.error-msg {
|
||||
.swagger-section .api-popup-dialog .error-msg {
|
||||
padding-left: 5px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
.swagger-section .api-popup-dialog button.api-popup-authbtn {
|
||||
.swagger-section .api-popup-dialog .api-popup-content {
|
||||
max-height: 500px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.swagger-section .api-popup-dialog .api-popup-authbtn {
|
||||
height: 30px;
|
||||
}
|
||||
.swagger-section .api-popup-dialog button.api-popup-cancel {
|
||||
.swagger-section .api-popup-dialog .api-popup-cancel {
|
||||
height: 30px;
|
||||
}
|
||||
.swagger-section .api-popup-scopes {
|
||||
@ -1138,14 +1333,14 @@
|
||||
padding: 5px 0;
|
||||
line-height: 20px;
|
||||
}
|
||||
.swagger-section .api-popup-scopes .api-scope-desc {
|
||||
padding-left: 20px;
|
||||
font-style: italic;
|
||||
}
|
||||
.swagger-section .api-popup-scopes li input {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
}
|
||||
.swagger-section .api-popup-scopes .api-scope-desc {
|
||||
padding-left: 20px;
|
||||
font-style: italic;
|
||||
}
|
||||
.swagger-section .api-popup-actions {
|
||||
padding-top: 10px;
|
||||
}
|
||||
@ -1217,34 +1412,33 @@
|
||||
}
|
||||
.swagger-section #header {
|
||||
background-color: #89bf04;
|
||||
padding: 14px;
|
||||
padding: 9px 14px 19px 14px;
|
||||
height: 23px;
|
||||
min-width: 775px;
|
||||
}
|
||||
.swagger-section #header a#logo {
|
||||
font-size: 1.5em;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
background: transparent url(../images/logo_small.png) no-repeat left center;
|
||||
padding: 20px 0 20px 40px;
|
||||
color: white;
|
||||
.swagger-section #input_baseUrl {
|
||||
width: 400px;
|
||||
}
|
||||
.swagger-section #header form#api_selector {
|
||||
.swagger-section #api_selector {
|
||||
display: block;
|
||||
clear: none;
|
||||
float: right;
|
||||
}
|
||||
.swagger-section #header form#api_selector .input {
|
||||
display: block;
|
||||
.swagger-section #api_selector .input {
|
||||
display: inline-block;
|
||||
clear: none;
|
||||
float: left;
|
||||
margin: 0 10px 0 0;
|
||||
}
|
||||
.swagger-section #header form#api_selector .input input#input_apiKey {
|
||||
.swagger-section #api_selector input {
|
||||
font-size: 0.9em;
|
||||
padding: 3px;
|
||||
margin: 0;
|
||||
}
|
||||
.swagger-section #input_apiKey {
|
||||
width: 200px;
|
||||
}
|
||||
.swagger-section #header form#api_selector .input input#input_baseUrl {
|
||||
width: 400px;
|
||||
}
|
||||
.swagger-section #header form#api_selector .input a#explore {
|
||||
.swagger-section #explore,
|
||||
.swagger-section #auth_container .authorize__btn {
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
@ -1259,13 +1453,24 @@
|
||||
-khtml-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.swagger-section #header form#api_selector .input a#explore:hover {
|
||||
.swagger-section #explore:hover,
|
||||
.swagger-section #auth_container .authorize__btn:hover {
|
||||
background-color: #547f00;
|
||||
}
|
||||
.swagger-section #header form#api_selector .input input {
|
||||
font-size: 0.9em;
|
||||
padding: 3px;
|
||||
margin: 0;
|
||||
.swagger-section #header #logo {
|
||||
font-size: 1.5em;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
color: white;
|
||||
}
|
||||
.swagger-section #header #logo .logo__img {
|
||||
display: block;
|
||||
float: left;
|
||||
margin-top: 2px;
|
||||
}
|
||||
.swagger-section #header #logo .logo__title {
|
||||
display: inline-block;
|
||||
padding: 5px 0 0 10px;
|
||||
}
|
||||
.swagger-section #content_message {
|
||||
margin: 10px 15px;
|
||||
@ -1277,3 +1482,13 @@
|
||||
text-align: center;
|
||||
padding-top: 10px;
|
||||
}
|
||||
.swagger-section .swagger-collapse:before {
|
||||
content: "-";
|
||||
}
|
||||
.swagger-section .swagger-expand:before {
|
||||
content: "+";
|
||||
}
|
||||
.swagger-section .error {
|
||||
outline-color: #cc0000;
|
||||
background-color: #f2dede;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user