rename to rswag plus major refactor - almost a rewrite

This commit is contained in:
richie 2016-10-11 18:31:12 -07:00
parent f8d993356f
commit c558098c39
453 changed files with 52410 additions and 35793 deletions

37
Gemfile
View File

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

View File

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

@ -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 '&lt;swagger_root&gt;/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 &lt;swagger_root&gt;. 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_

View File

@ -1,3 +0,0 @@
= SwaggerRails
This project rocks and uses MIT-LICENSE.

View File

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

View File

@ -1,3 +0,0 @@
SwaggerRails::Engine.routes.draw do
root to: 'swagger_ui#index'
end

View File

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

View File

@ -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>&nbsp;</div>
<div id="swagger-ui-container" class="swagger-ui-wrap"></div>
</body>
</html>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,3 +0,0 @@
module SwaggerRails
VERSION = "1.0.1-beta3"
end

View File

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

View File

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

View File

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

View 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

View File

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

View File

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

View 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

View 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

View 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

View 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

View File

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

View 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

View File

@ -0,0 +1,2 @@
TestApp::Application.routes.draw do
end

View File

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

View File

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

View File

@ -0,0 +1,4 @@
TestApp::Application.routes.draw do
mount Rswag::Api::Engine => '/api-docs'
end

View 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

View File

@ -0,0 +1,8 @@
{
"swagger": "2.0",
"info": {
"title": "API V1",
"version": "v1"
},
"paths": {}
}

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

@ -0,0 +1,2 @@
--color
--require spec_helper

20
rswag-specs/MIT-LICENSE Normal file
View 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
View 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

View 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

View File

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

View File

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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View File

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

View 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

View 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

View File

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

View File

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

View 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

View 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

View 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

View 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

View 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

View File

@ -0,0 +1,7 @@
module Rails
module VERSION
MAJOR = 3
end
end
require 'rswag/specs'

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

@ -0,0 +1,2 @@
--color
--require spec_helper

20
rswag-ui/MIT-LICENSE Normal file
View 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
View 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

View 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

View File

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

View File

@ -0,0 +1,3 @@
Rswag::Ui::Engine.routes.draw do
root to: 'home#index'
end

View File

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

View 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

View 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

View File

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

View File

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

View 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

View 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

View File

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

18
rswag-ui/rswag-ui.gemspec Normal file
View 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

View 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

View File

@ -0,0 +1,2 @@
TestApp::Application.routes.draw do
end

View 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

View File

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

View File

@ -0,0 +1,4 @@
TestApp::Application.routes.draw do
mount Rswag::Ui::Engine => '/api-docs'
end

View File

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

View File

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