66 Commits
2.3.1 ... 2.3.3

Author SHA1 Message Date
Blake Erickson
7ef900ec1d version bump 2021-02-07 07:55:52 -07:00
Blake Erickson
c0142093d4 Update swagger-ui to 3.42.0 2021-02-07 07:47:52 -07:00
Blake Erickson
1f4ecb3c10 update changelog 2021-02-07 06:31:47 -07:00
Blake Erickson
aa4e6f2070 Include example definition in test app
From changes in this commit: eadaf34ef6

we need to include the new output in the test-app swagger.json file
2021-02-06 17:42:23 -07:00
Blake Erickson
eadaf34ef6 Merge pull request #394 from donny741/master
Fix response examples
2021-02-06 07:30:53 -07:00
Donatas Povilaitis
cbaf6cd3e4 Fix response examples 2021-01-29 20:47:46 +02:00
Blake Erickson
670c94cc44 Update version to 2.3.2 in changelog 2021-01-27 14:53:15 -07:00
Blake Erickson
b86d3063a8 Merge pull request #371 from rswag/jamie
PR Grooming
2021-01-27 07:59:59 -07:00
Blake Erickson
7f88c5d1f3 Merge branch 'master' into jamie 2021-01-27 05:18:35 -07:00
Blake Erickson
d645df207e Merge pull request #392 from pfilipow/master
don't clobber response content
2021-01-26 18:10:21 -07:00
Paul Filipow
b63dd343af safe navigation in build_query_string_part 2021-01-26 13:57:44 -08:00
Paul Filipow
bd48e40529 add schema to mime type instead of overwriting 2021-01-25 21:12:16 -08:00
Paul Filipow
465950a65c don't clobber response content 2021-01-25 16:11:54 -08:00
Blake Erickson
be160c5552 Merge pull request #391 from codeart/master
Inherit consumes from swagger schema. Addresses #320.
2021-01-25 14:30:33 -07:00
Alexander
ad95f1098a Inherit consumes from swagger schema. Addresses #320. 2021-01-22 13:25:27 +02:00
Jamie Macey
83567e0ee2 Merge pull request #372 from rswag/spec-deprecations
Clean up spec ouput
2020-10-18 17:59:40 -07:00
Jamie Macey
206c088d74 WIP-branch changelog updates 2020-10-17 14:16:21 -07:00
Jamie Macey
5a60dee838 Add a spec for #342 body/required 2020-10-17 14:15:41 -07:00
Jamie Macey
13a3977c2c Merge remote-tracking branch 'bspellacy/master' into jamie 2020-10-17 13:56:19 -07:00
Jamie Macey
0d1a742f6f empty content is now pruned 2020-10-17 13:44:04 -07:00
Jamie Macey
c62bfda91d Merge branch 'output-specs' into jamie 2020-10-17 13:43:05 -07:00
Jamie Macey
c161de3899 Merge branch 'spec-deprecations' into jamie 2020-10-17 13:43:00 -07:00
Jamie Macey
ab457743a8 also move away from deprecated type: basic 2020-10-17 13:38:56 -07:00
Jamie Macey
3e10b09f23 swap deprecated SecurityDefinitions for SecuritySchemes 2020-10-17 13:38:56 -07:00
Jamie Macey
d090516f48 properly list servers for openapi v3 2020-10-17 13:38:56 -07:00
Jamie Macey
91a27852f2 more flex in this check 2020-10-17 13:38:56 -07:00
Jamie Macey
68e64dba2c OpenAPI currently at v 3.0.3 2020-10-17 13:38:56 -07:00
Jamie Macey
f6630cc7a6 fix a bug related to upgrade_request_type! translation 2020-10-17 13:38:56 -07:00
Jamie Macey
7e1a79220c start standing up exhaustive output unit tests for OpenAPI v3 2020-10-17 13:38:56 -07:00
Jamie Macey
b37c7905cd stub controller to help with future testing 2020-10-17 13:38:56 -07:00
Jamie Macey
0a9487b328 make spec output less noisy 2020-10-17 13:34:17 -07:00
Jamie Macey
b3fa5ba54e Merge remote-tracking branch 'psmandzich/master' into jamie 2020-10-17 13:21:28 -07:00
Jamie Macey
7e2c52b242 Merge remote-tracking branch 'olegkyz/bugfix/examples-content' into jamie 2020-10-17 13:14:47 -07:00
Jamie Macey
8d673068af Merge pull request #369 from jamie/actions
CI via Github Actions
2020-10-17 08:20:36 -07:00
Greg Myers
d558bd4774 Merge pull request #334 from akofink/issue-333
Update wording in gemspecs to include OpenAPI
2020-10-14 23:08:13 +01:00
Jamie Macey
c990348f0b update cache hash 2020-10-12 13:56:22 -07:00
Jamie Macey
38c861f5e0 strip prelude 2020-10-12 13:51:42 -07:00
Jamie Macey
1cd7df89f1 update conditional tags 2020-10-12 13:48:24 -07:00
Jamie Macey
04059808d5 fix matrix syntax 2020-10-12 13:45:38 -07:00
Jamie Macey
4c06f95a68 run multiple ruby/rails versions 2020-10-12 13:43:58 -07:00
Jamie Macey
08b1678e53 skip a test requiring headless firefox capybara driver 2020-10-12 13:37:57 -07:00
Jamie Macey
27b015b3d2 ensure we run all specs if there are early failures 2020-10-12 13:29:28 -07:00
Jamie Macey
eb14eba7eb build individually 2020-10-12 13:26:31 -07:00
Jamie Macey
e419635209 cache gems/npm 2020-10-12 13:20:46 -07:00
Jamie Macey
ea89b0b0d5 Github Actions CI 2020-10-12 13:16:17 -07:00
Jamie Macey
423e86c463 bump ruby-version, stricter npm lockfile 2020-10-12 13:13:53 -07:00
Greg Myers
89f95694a4 Merge pull request #349 from MikeSmithEU/patch-1
Fix typo in test-app/app/controllers/blogs_controller.rb
2020-08-21 17:08:13 +01:00
Greg Myers
6133179aa5 Merge pull request #312 from levidavidmurray/master
Update README.md
2020-08-21 17:07:12 +01:00
Greg Myers
89e69915f8 Merge pull request #335 from zkatemor/master
Update version Swagger UI to 3.28.0
2020-08-17 15:04:31 +01:00
Greg Myers
4e300b2255 Update changelog, readme
Swagger-ui version no.
2020-08-17 15:04:22 +01:00
Greg Myers
3cb3f4a651 Merge pull request #324 from graceful-potato/fix_route_parser
Fix routes method in RoutePareser
2020-08-17 15:03:00 +01:00
MikeSmithEU
267007ef1b Update blogs_controller.rb
Fix typo
2020-07-22 11:23:01 +02:00
psmandzich
347f9da32e enable swagger empty body responses 2020-07-21 09:50:47 +02:00
Brennan Spellacy
30002e5b98 Add required field 2020-07-01 20:20:41 -07:00
Ekaterina Zababurina
a7944d48d6 Update Swagger UI to 3.28.0 2020-06-30 09:45:55 +03:00
Ekaterina Zababurina
015b1c8b56 Update Swagger UI to 3.27.0 2020-06-25 13:37:23 +03:00
Ekaterina Zababurina
36e79da0fd Update swagger UI to 3.26.2 2020-06-16 10:15:35 +03:00
Ekaterina Zababurina
3aff6bca1f Update version Swagger UI to 3.26.0 2020-06-10 09:19:57 +03:00
Andrew Kofink
f0fba9c093 Update wording in gemspecs to include OpenAPI
Fixes #333

Mentioning OpenAPI in the gemspec allows users on rubygems.org to find
this gem easily. Those who are still looking for Swagger tools will also
continue to find the gem, since I've mentioned that OpenAPI used to be
called Swagger.

Signed-off-by: Andrew Kofink <akofink@redhat.com>
2020-06-08 12:00:08 -04:00
Greg Myers
bec4f16676 fix #322 2020-06-06 19:56:28 +01:00
Oleg Yakovenko
7c3c357291 cleanup 2020-06-04 17:22:20 +03:00
Oleg Yakovenko
81c110022e example json actualized 2020-06-04 17:19:15 +03:00
Oleg Yakovenko
1ff396fb56 example group helper adjustment 2020-06-04 17:17:30 +03:00
Oleg Yakovenko
9c297317b2 keep examples content 2020-06-04 16:21:43 +03:00
Graceful Potato
3b85f09acf Fix RoutePareser#routes and add spec for it 2020-05-26 02:01:38 +03:00
Levi Murray
9727ec34b7 Update README.md 2020-05-03 13:58:04 -07:00
32 changed files with 949 additions and 99 deletions

59
.github/workflows/ruby.yml vendored Normal file
View File

@@ -0,0 +1,59 @@
name: Ruby
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
ruby: [2.6, 2.7]
rails: [5.2.4.4, 6.0.3.4]
env:
RAILS_VERSION: ${{ matrix.rails }}
steps:
- uses: actions/checkout@v2
- uses: ruby/setup-ruby@v1
with: { ruby-version: 2.6 }
- uses: actions/cache@v2
id: cache
with:
path: |
rswag-ui/node_modules
vendor/bundle
key: ${{ runner.os }}-ruby_${{ matrix.ruby }}-rails_${{ matrix.rails }}-${{ hashFiles('Gemfile', '**/package-lock.json') }}
- name: Install dependencies
run: |
bundle install
cd rswag-ui && npm install
- name: rswag-api
run: |
cd rswag-api
bundle exec rspec
- name: rswag-specs
if: success() || failure()
run: |
cd rswag-specs
bundle exec rspec
- name: rswag-ui
if: success() || failure()
run: |
cd rswag-ui
bundle exec rspec
- name: test-app
if: success() || failure()
run: |
cd test-app
bundle exec rake db:migrate db:test:prepare
bundle exec rspec

View File

@@ -1 +1 @@
2.6.3 2.7.2

View File

@@ -1,16 +1,24 @@
# rswag # rswag
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased] ## [Unreleased]
### Added
### Changed ## [2.3.3] - 2021-02-07
### Deprecated
### Removed
### Fixed ### Fixed
### Security - Include response examples [#394](https://github.com/rswag/rswag/pull/394)
### Changed
- Update swagger-ui to 3.42.0
## [2.3.2] - 2021-01-27
### Added
- RequestBody now supports the `required` flag [#342](https://github.com/rswag/rswag/pull/342)
### Fixed
- Fix response example rendering [#330](https://github.com/rswag/rswag/pull/330)
- Fix empty content block [#347](https://github.com/rswag/rswag/pull/347)
## [2.3.1] - 2020-04-08 ## [2.3.1] - 2020-04-08
### Fixed ### Fixed
@@ -28,6 +36,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Update swagger-ui version to 3.23.11 [#239](https://github.com/rswag/rswag/pull/239) - Update swagger-ui version to 3.23.11 [#239](https://github.com/rswag/rswag/pull/239)
- Rails constraint moved from < 6.1 to < 7 [#253](https://github.com/rswag/rswag/pull/253) - Rails constraint moved from < 6.1 to < 7 [#253](https://github.com/rswag/rswag/pull/253)
- Swaggerize now outputs base RSpec text on completion to avoid silent failures [#293](https://github.com/rswag/rswag/pull/293) - Swaggerize now outputs base RSpec text on completion to avoid silent failures [#293](https://github.com/rswag/rswag/pull/293)
- Update swagger-ui version to 3.28.0
## [2.2.0] - 2019-11-01 ## [2.2.0] - 2019-11-01
### Added ### Added

20
MIT-LICENCE 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.

View File

@@ -18,7 +18,7 @@ Once you have an API that can describe itself in Swagger, you've opened the trea
|Rswag Version|Swagger (OpenAPI) Spec.|swagger-ui| |Rswag Version|Swagger (OpenAPI) Spec.|swagger-ui|
|----------|----------|----------| |----------|----------|----------|
|[master](https://github.com/rswag/rswag/tree/master)|3.0.3|3.23.11| |[master](https://github.com/rswag/rswag/tree/master)|3.0.3|3.42.0|
|[2.3.0](https://github.com/rswag/rswag/tree/2.3.0)|3.0.3|3.23.11| |[2.3.0](https://github.com/rswag/rswag/tree/2.3.0)|3.0.3|3.23.11|
|[2.2.0](https://github.com/rswag/rswag/tree/2.2.0)|2.0|3.18.2| |[2.2.0](https://github.com/rswag/rswag/tree/2.2.0)|2.0|3.18.2|
|[1.6.0](https://github.com/rswag/rswag/tree/1.6.0)|2.0|2.2.5| |[1.6.0](https://github.com/rswag/rswag/tree/1.6.0)|2.0|2.2.5|
@@ -516,6 +516,15 @@ config.swagger_docs = {
thumbnail: { type: 'string', nullable: true } thumbnail: { type: 'string', nullable: true }
}, },
required: %w[id title] required: %w[id title]
},
new_blog: {
type: 'object',
properties: {
title: { type: 'string' },
content: { type: 'string', nullable: true },
thumbnail: { type: 'string', format: 'binary', nullable: true }
},
required: %w[title]
} }
} }
} }
@@ -529,6 +538,8 @@ describe 'Blogs API' do
post 'Creates a blog' do post 'Creates a blog' do
parameter name: :new_blog, in: :body, schema: { '$ref' => '#/components/schemas/new_blog' }
response 422, 'invalid request' do response 422, 'invalid request' do
schema '$ref' => '#/components/schemas/errors_object' schema '$ref' => '#/components/schemas/errors_object'
... ...
@@ -597,7 +608,11 @@ To enable examples generation from responses add callback above run_test! like:
``` ```
after do |example| after do |example|
example.metadata[:response][:examples] = { 'application/json' => JSON.parse(response.body, symbolize_names: true) } example.metadata[:response][:content] = {
'application/json' => {
example: JSON.parse(response.body, symbolize_names: true)
}
}
end end
``` ```
@@ -900,4 +915,3 @@ docker run -d -p 80:8080 swaggerapi/swagger-editor
``` ```
This will run the swagger editor in the docker daemon and can be accessed This will run the swagger editor in the docker daemon and can be accessed
at ```http://localhost```. From here, you can use the UI to load the generated swagger.json to validate the output. at ```http://localhost```. From here, you can use the UI to load the generated swagger.json to validate the output.

View File

@@ -9,8 +9,8 @@ Gem::Specification.new do |s|
s.authors = ['Richie Morris', 'Greg Myers', 'Jay Danielian'] s.authors = ['Richie Morris', 'Greg Myers', 'Jay Danielian']
s.email = ['domaindrivendev@gmail.com'] s.email = ['domaindrivendev@gmail.com']
s.homepage = 'https://github.com/rswag/rswag' s.homepage = 'https://github.com/rswag/rswag'
s.summary = 'A Rails Engine that exposes Swagger files as JSON endpoints' s.summary = 'A Rails Engine that exposes OpenAPI (formerly called Swagger) files as JSON endpoints'
s.description = 'Open up your API to the phenomenal Swagger ecosystem by exposing Swagger files, that describe your service, as JSON endpoints' s.description = 'Open up your API to the phenomenal OpenAPI ecosystem by exposing OpenAPI files, that describe your service, as JSON endpoints. More about the OpenAPI initiative here: http://spec.openapis.org/'
s.license = 'MIT' s.license = 'MIT'
s.files = Dir['{lib}/**/*'] + ['MIT-LICENSE', 'Rakefile'] s.files = Dir['{lib}/**/*'] + ['MIT-LICENSE', 'Rakefile']

View File

@@ -19,7 +19,11 @@ RSpec.describe '<%= controller_path %>', type: :request do
<% end -%> <% end -%>
after do |example| after do |example|
example.metadata[:response][:examples] = { 'application/json' => JSON.parse(response.body, symbolize_names: true) } example.metadata[:response][:content] = {
'application/json' => {
example: JSON.parse(response.body, symbolize_names: true)
}
}
end end
run_test! run_test!
end end

View File

@@ -11,7 +11,7 @@ module Rswag
def routes def routes
::Rails.application.routes.routes.select do |route| ::Rails.application.routes.routes.select do |route|
route.defaults[:controller] == controller route.defaults[:controller] == controller
end.each_with_object({}) do |tree, route| end.each_with_object({}) do |route, tree|
path = path_from(route) path = path_from(route)
verb = verb_from(route) verb = verb_from(route)
tree[path] ||= { params: params_from(route), actions: {} } tree[path] ||= { params: params_from(route), actions: {} }

View File

@@ -72,7 +72,10 @@ module Rswag
def examples(example = nil) def examples(example = nil)
return super() if example.nil? return super() if example.nil?
metadata[:response][:examples] = example metadata[:response][:content] =
example.each_with_object({}) do |(mime, example_object), memo|
memo[mime] = { example: example_object }
end
end end
def run_test!(&block) def run_test!(&block)

View File

@@ -118,7 +118,8 @@ module Rswag
def build_query_string_part(param, value) def build_query_string_part(param, value)
name = param[:name] name = param[:name]
return "#{name}=#{value}" unless param[:type].to_sym == :array type = param[:type] || param.dig(:schema, :type)
return "#{name}=#{value}" unless type&.to_sym == :array
case param[:collectionFormat] case param[:collectionFormat]
when :ssv when :ssv

View File

@@ -56,9 +56,10 @@ module Rswag
is_hash = value.is_a?(Hash) is_hash = value.is_a?(Hash)
if is_hash && value.dig(:parameters) if is_hash && value.dig(:parameters)
schema_param = value.dig(:parameters)&.find { |p| (p[:in] == :body || p[:in] == :formData) && p[:schema] } schema_param = value.dig(:parameters)&.find { |p| (p[:in] == :body || p[:in] == :formData) && p[:schema] }
mime_list = value.dig(:consumes) mime_list = value.dig(:consumes) || doc[:consumes]
if value && schema_param && mime_list if value && schema_param && mime_list
value[:requestBody] = { content: {} } unless value.dig(:requestBody, :content) value[:requestBody] = { content: {} } unless value.dig(:requestBody, :content)
value[:requestBody][:required] = true if schema_param[:required]
mime_list.each do |mime| mime_list.each do |mime|
value[:requestBody][:content][mime] = { schema: schema_param[:schema] } value[:requestBody][:content][mime] = { schema: schema_param[:schema] }
end end
@@ -129,13 +130,13 @@ module Rswag
end end
def upgrade_content!(mime_list, target_node) def upgrade_content!(mime_list, target_node)
target_node.merge!(content: {})
schema = target_node[:schema] schema = target_node[:schema]
return if mime_list.empty? || schema.nil? return if mime_list.empty? || schema.nil?
target_node[:content] ||= {}
mime_list.each do |mime_type| mime_list.each do |mime_type|
# TODO upgrade to have content-type specific schema # TODO upgrade to have content-type specific schema
target_node[:content][mime_type] = { schema: schema } (target_node[:content][mime_type] ||= {}).merge!(schema: schema)
end end
end end

View File

@@ -9,8 +9,8 @@ Gem::Specification.new do |s|
s.authors = ['Richie Morris', 'Greg Myers', 'Jay Danielian'] s.authors = ['Richie Morris', 'Greg Myers', 'Jay Danielian']
s.email = ['domaindrivendev@gmail.com'] s.email = ['domaindrivendev@gmail.com']
s.homepage = 'https://github.com/rswag/rswag' s.homepage = 'https://github.com/rswag/rswag'
s.summary = 'A Swagger-based DSL for rspec-rails & accompanying rake task for generating Swagger files' s.summary = 'An OpenAPI-based (formerly called Swagger) DSL for rspec-rails & accompanying rake task for generating OpenAPI specification files'
s.description = 'Simplify API integration testing with a succinct rspec DSL and generate Swagger files directly from your rspecs' s.description = 'Simplify API integration testing with a succinct rspec DSL and generate OpenAPI specification files directly from your rspecs. More about the OpenAPI initiative here: http://spec.openapis.org/'
s.license = 'MIT' s.license = 'MIT'
s.files = Dir['{lib}/**/*'] + ['MIT-LICENSE', 'Rakefile'] s.files = Dir['{lib}/**/*'] + ['MIT-LICENSE', 'Rakefile']

View File

@@ -0,0 +1,50 @@
# frozen_string_literal: true
RSpec.describe Rswag::RouteParser do
describe "#routes" do
let(:controller) { "api/v1/posts" }
subject { described_class.new(controller) }
let(:routes) do
[
double(
defaults: {
controller: controller
},
path: double(
spec: double(
to_s: "/api/v1/posts/:id(.:format)"
)
),
verb: "GET",
requirements: {
action: "show",
controller: "api/v1/posts"
},
segments: ["id", "format"]
)
]
end
let(:expectation) do
{
"/api/v1/posts/{id}" => {
params: ["id"],
actions: {
"get" => {
summary: "show post"
}
}
}
}
end
before do
allow(::Rails).to receive_message_chain("application.routes.routes") { routes }
end
it "returns correct routes" do
expect(subject.routes).to eq(expectation)
end
end
end

View File

@@ -137,9 +137,10 @@ module Rswag
end end
describe '#examples(example)' do describe '#examples(example)' do
let(:mime) { 'application/json' }
let(:json_example) do let(:json_example) do
{ {
'application/json' => { mime => {
foo: 'bar' foo: 'bar'
} }
} }
@@ -151,7 +152,11 @@ module Rswag
end end
it "adds to the 'response examples' metadata" do it "adds to the 'response examples' metadata" do
expect(api_metadata[:response][:examples]).to eq(json_example) expect(api_metadata[:response][:content]).to match(
mime => {
example: json_example[mime]
}
)
end end
end end
end end

View File

@@ -11,6 +11,8 @@ module Rswag
# Mock out some infrastructure # Mock out some infrastructure
before do before do
allow(config).to receive(:swagger_root).and_return(swagger_root) allow(config).to receive(:swagger_root).and_return(swagger_root)
allow(ActiveSupport::Deprecation).to receive(:warn) # Silence deprecation output from specs
end end
let(:config) { double('config') } let(:config) { double('config') }
let(:output) { double('output').as_null_object } let(:output) { double('output').as_null_object }
@@ -26,10 +28,11 @@ module Rswag
{ {
path_item: { template: '/blogs', parameters: [{ type: :string }] }, path_item: { template: '/blogs', parameters: [{ type: :string }] },
operation: { verb: :post, summary: 'Creates a blog', parameters: [{ type: :string }] }, operation: { verb: :post, summary: 'Creates a blog', parameters: [{ type: :string }] },
response: { code: '201', description: 'blog created', headers: { type: :string }, schema: { '$ref' => '#/definitions/blog' } }, response: response_metadata,
document: document document: document
} }
end end
let(:response_metadata) { { code: '201', description: 'blog created', headers: { type: :string }, schema: { '$ref' => '#/definitions/blog' } } }
context 'with the document tag set to false' do context 'with the document tag set to false' do
let(:swagger_doc) { { swagger: '2.0' } } let(:swagger_doc) { { swagger: '2.0' } }
@@ -127,6 +130,97 @@ module Rswag
) )
end end
context 'with response example' do
let(:response_metadata) do
{
code: '201',
description: 'blog created',
headers: { type: :string },
content: { 'application/json' => { example: { foo: :bar } } },
schema: { '$ref' => '#/definitions/blog' }
}
end
it 'adds example to definition' do
expect(swagger_doc.slice(:paths)).to match(
paths: {
'/blogs' => {
parameters: [{ schema: { type: :string } }],
post: {
parameters: [{ schema: { type: :string } }],
summary: 'Creates a blog',
responses: {
'201' => {
content: {
'application/vnd.my_mime' => {
schema: { '$ref' => '#/definitions/blog' }
},
'application/json' => {
schema: { '$ref' => '#/definitions/blog' },
example: { foo: :bar }
}
},
description: 'blog created',
headers: { schema: { type: :string } }
}
}
}
}
}
)
end
end
context 'with empty content' do
let(:swagger_doc) do
{
openapi: '3.0.1',
basePath: '/foo',
schemes: ['http', 'https'],
host: 'api.example.com',
components: {
securitySchemes: {
myClientCredentials: {
type: :oauth2,
flow: :application,
token_url: :somewhere
},
myAuthorizationCode: {
type: :oauth2,
flow: :accessCode,
token_url: :somewhere
},
myImplicit: {
type: :oauth2,
flow: :implicit,
token_url: :somewhere
}
}
}
}
end
it 'converts query and path params, type: to schema: { type: }' do
expect(swagger_doc.slice(:paths)).to match(
paths: {
'/blogs' => {
parameters: [{ schema: { type: :string } }],
post: {
parameters: [{ schema: { type: :string } }],
summary: 'Creates a blog',
responses: {
'201' => {
description: 'blog created',
headers: { schema: { type: :string } }
}
}
}
}
}
)
end
end
it 'converts basePath, schemas and host to urls' do it 'converts basePath, schemas and host to urls' do
expect(swagger_doc.slice(:servers)).to match( expect(swagger_doc.slice(:servers)).to match(
servers: { servers: {

View File

@@ -5,9 +5,9 @@
"requires": true, "requires": true,
"dependencies": { "dependencies": {
"swagger-ui-dist": { "swagger-ui-dist": {
"version": "3.23.11", "version": "3.42.0",
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.23.11.tgz", "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.42.0.tgz",
"integrity": "sha512-ipENHHH/sqpngTpHXUwg55eAOZ7b2UVayUwwuWPA6nQSPhjBVXX4zOPpNKUwQIFOl3oIwVvZF7mqoxH7pMgVzA==" "integrity": "sha512-hTNX6cX7KWtBZgk6ZQSOzsBJhqdCmD5NOIjb6dBPKSnYZidSkIXOcaPMR3+kwxLrj8bDC881bSDlNbLsHikacg=="
} }
} }
} }

View File

@@ -3,6 +3,6 @@
"version": "1.0.0", "version": "1.0.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"swagger-ui-dist": "3.23.11" "swagger-ui-dist": "3.42.0"
} }
} }

View File

@@ -9,8 +9,8 @@ Gem::Specification.new do |s|
s.authors = ['Richie Morris', 'Greg Myers', 'Jay Danielian'] s.authors = ['Richie Morris', 'Greg Myers', 'Jay Danielian']
s.email = ['domaindrivendev@gmail.com'] s.email = ['domaindrivendev@gmail.com']
s.homepage = 'https://github.com/rswag/rswag' s.homepage = 'https://github.com/rswag/rswag'
s.summary = 'A Rails Engine that includes swagger-ui and powers it from configured Swagger endpoints' s.summary = 'A Rails Engine that includes swagger-ui and powers it from configured OpenAPI (formerly named Swagger) endpoints'
s.description = 'Expose beautiful API documentation, powered by Swagger JSON endpoints, including a UI to explore and test operations' s.description = 'Expose beautiful API documentation, powered by Swagger JSON endpoints, including a UI to explore and test operations. More about the OpenAPI initiative here: http://spec.openapis.org/'
s.license = 'MIT' s.license = 'MIT'
s.files = Dir.glob('{lib,node_modules}/**/*') + ['MIT-LICENSE', 'Rakefile' ] s.files = Dir.glob('{lib,node_modules}/**/*') + ['MIT-LICENSE', 'Rakefile' ]

View File

@@ -9,8 +9,8 @@ Gem::Specification.new do |s|
s.authors = ['Richie Morris', 'Greg Myers', 'Jay Danielian'] s.authors = ['Richie Morris', 'Greg Myers', 'Jay Danielian']
s.email = ['domaindrivendev@gmail.com'] s.email = ['domaindrivendev@gmail.com']
s.homepage = 'https://github.com/rswag/rswag' s.homepage = 'https://github.com/rswag/rswag'
s.summary = 'Swagger tooling for Rails APIs' s.summary = 'OpenAPI (formerly named Swagger) tooling for Rails APIs'
s.description = 'Generate beautiful API documentation, including a UI to explore and test operations, directly from your rspec integration tests' s.description = 'Generate beautiful API documentation, including a UI to explore and test operations, directly from your rspec integration tests. OpenAPI 2 and 3 supported. More about the OpenAPI initiative here: http://spec.openapis.org/'
s.license = 'MIT' s.license = 'MIT'
s.files = Dir['{lib}/**/*'] + [ 'MIT-LICENSE' ] s.files = Dir['{lib}/**/*'] + [ 'MIT-LICENSE' ]

View File

@@ -0,0 +1,2 @@
// Place all the behaviors and hooks related to the matching controller here.
// All this logic will automatically be available in application.js.

View File

@@ -0,0 +1,4 @@
/*
Place all the styles related to the matching controller here.
They will automatically be included in application.css.
*/

View File

@@ -32,7 +32,7 @@ class BlogsController < ApplicationController
@blog = Blog.find_by_id(params[:id]) @blog = Blog.find_by_id(params[:id])
return head :not_found if @blog.nil? return head :not_found if @blog.nil?
@blog.thumbnail = save_uploaded_file params[:file] @blog.thumbnail = save_uploaded_file params[:file]
head @blog.save ? :ok : :unprocsessible_entity head @blog.save ? :ok : :unprocessable_entity
end end
# GET /blogs # GET /blogs

View File

@@ -0,0 +1,13 @@
class StubsController < ApplicationController
def index
render plain: 'OK'
end
def create
render plain: 'OK'
end
def show
render plain: 'OK'
end
end

View File

@@ -0,0 +1,2 @@
module StubsHelper
end

View File

@@ -9,6 +9,8 @@ TestApp::Application.routes.draw do
post 'auth-tests/api-key', to: 'auth_tests#api_key' post 'auth-tests/api-key', to: 'auth_tests#api_key'
post 'auth-tests/basic-and-api-key', to: 'auth_tests#basic_and_api_key' post 'auth-tests/basic-and-api-key', to: 'auth_tests#basic_and_api_key'
resources :stubs
mount Rswag::Api::Engine => 'api-docs' mount Rswag::Api::Engine => 'api-docs'
mount Rswag::Ui::Engine => 'api-docs' mount Rswag::Ui::Engine => 'api-docs'
end end

View File

@@ -3,6 +3,7 @@ require 'rails_helper'
RSpec.feature 'swagger-ui', js: true do RSpec.feature 'swagger-ui', js: true do
scenario 'browsing api-docs' do scenario 'browsing api-docs' do
skip "Needs work to run on others' machines"
visit '/api-docs' visit '/api-docs'
expect(page).to have_content('GET /blogs Searches blogs', normalize_ws: true) expect(page).to have_content('GET /blogs Searches blogs', normalize_ws: true)

View File

@@ -3,6 +3,10 @@
require 'swagger_helper' require 'swagger_helper'
RSpec.describe 'Auth Tests API', type: :request, swagger_doc: 'v1/swagger.json' do RSpec.describe 'Auth Tests API', type: :request, swagger_doc: 'v1/swagger.json' do
before do
allow(ActiveSupport::Deprecation).to receive(:warn) # Silence deprecation output from specs
end
path '/auth-tests/basic' do path '/auth-tests/basic' do
post 'Authenticates with basic auth' do post 'Authenticates with basic auth' do
tags 'Auth Tests' tags 'Auth Tests'

View File

@@ -3,6 +3,10 @@ require 'swagger_helper'
RSpec.describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do RSpec.describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do
let(:api_key) { 'fake_key' } let(:api_key) { 'fake_key' }
before do
allow(ActiveSupport::Deprecation).to receive(:warn) # Silence deprecation output from specs
end
path '/blogs' do path '/blogs' do
post 'Creates a blog' do post 'Creates a blog' do
tags 'Blogs' tags 'Blogs'

View File

@@ -0,0 +1,349 @@
require 'swagger_helper'
require 'rswag/specs/swagger_formatter'
# This spec file validates OpenAPI output generated by spec metadata.
# Specifically here, we look at OpenApi 3 as documented at
# https://swagger.io/docs/specification/about/
RSpec.describe 'Generated OpenApi', type: :request, swagger_doc: 'v3/openapi.json' do
before do |example|
output = double('output').as_null_object
swagger_root = File.expand_path('tmp/swagger', __dir__)
config = double('config', swagger_root: swagger_root, get_swagger_doc: swagger_doc )
formatter = Rswag::Specs::SwaggerFormatter.new(output, config)
example_group = OpenStruct.new(group: OpenStruct.new(metadata: example.metadata))
formatter.example_group_finished(example_group)
end
# Framework definition, to be overridden for contexts
let(:swagger_doc) do
{ # That which would be defined in swagger_helper.rb
openapi: api_openapi,
info: {},
servers: api_servers,
produces: api_produces,
components: api_components
}
end
let(:api_openapi) { '3.0.3' }
let(:api_servers) {[{ url: "https://api.example.com/foo" }]}
let(:api_produces) { ['application/json'] }
let(:api_components) { {} }
describe 'Basic Structure'
describe 'API Server and Base Path' do
path '/stubs' do
get 'a summary' do
tags 'Server and Path'
response '200', 'OK' do
run_test!
it 'lists server' do
tree = swagger_doc.dig(:servers)
expect(tree).to eq([
{ url: "https://api.example.com/foo" }
])
end
context "multiple" do
let(:api_servers) {[
{ url: "https://api.example.com/foo" },
{ url: "http://api.example.com/foo" },
]}
it 'lists servers' do
tree = swagger_doc.dig(:servers)
expect(tree).to eq([
{ url: "https://api.example.com/foo" },
{ url: "http://api.example.com/foo" }
])
end
end
context "with variables" do
let(:api_servers) {[{
url: "https://{defaultHost}/foo",
variables: {
defaultHost: {
default: "api.example.com"
}
}
}]}
it 'lists server and variables' do
tree = swagger_doc.dig(:servers)
expect(tree).to eq([{
url: "https://{defaultHost}/foo",
variables: {
defaultHost: {
default: "api.example.com"
}
}
}])
end
end
# TODO: Enum variables, defaults, override at path/operation
end
end
end
end
describe 'Media Types' do
path '/stubs' do
get 'a summary' do
tags 'Media Types'
response '200', 'OK' do
run_test!
it 'declares output as application/json' do
pending "Not yet implemented?"
tree = swagger_doc.dig(:paths, "/stubs", :get, :responses, '200', :content)
expect(tree).to have_key('application/json')
end
end
end
end
end
describe 'Paths and Operations'
describe 'Parameter Serialization' do
describe 'Path Parameters' do
path '/stubs/{a_param}' do
get 'a summary' do
tags 'Parameter Serialization: Query String'
produces 'application/json'
parameter(
name: 'a_param',
in: :path,
)
let(:a_param) { "42" }
response '200', 'OK' do
run_test!
it 'declares parameter in path' do
tree = swagger_doc.dig(:paths, "/stubs/{a_param}", :get, :parameters)
expect(tree.first[:name]).to eq('a_param')
expect(tree.first[:in]).to eq(:path)
end
it 'declares path parameters as required' do
tree = swagger_doc.dig(:paths, "/stubs/{a_param}", :get, :parameters)
expect(tree.first[:required]).to eq(true)
end
end
end
end
end
describe 'Query Parameters' do
path '/stubs' do
get 'a summary' do
tags 'Parameter Serialization: Query String'
produces 'application/json'
parameter(
name: 'a_param',
in: :query,
)
let(:a_param) { "a foo" }
response '200', 'OK' do
run_test!
it 'declares parameter in query string' do
tree = swagger_doc.dig(:paths, "/stubs", :get, :parameters)
expect(tree.first[:name]).to eq('a_param')
expect(tree.first[:in]).to eq(:query)
end
end
# TODO: Serialization (form/spaceDelimited/pipeDelimited/deepObject)
end
end
end
# TODO: Header
# TODO: Cookie
# TODO: Default values
# TODO: Enum
# TODO: Constant
# TODO: Empty/Nullable
# TODO: Examples
# TODO: Deprecated
# TODO: Common Parameters
end
describe 'Request Body' do
path '/stubs' do
post 'body is required' do
tags 'Media Types'
consumes 'multipart/form-data'
parameter name: :file, :in => :formData, :type => :file, required: true
let(:file) { Rack::Test::UploadedFile.new(Rails.root.join("spec/fixtures/thumbnail.png")) }
response '200', 'OK' do
run_test!
it 'declares requestBody is required' do
pending "This output is massaged in SwaggerFormatter#stop, and isn't quite ready here to assert"
tree = swagger_doc.dig(:paths, "/stubs", :post, :requestBody)
expect(tree[:required]).to eq(true)
end
end
end
end
end
describe 'Responses'
describe 'Data Models (Schemas)'
describe 'Examples'
describe 'Authentication'
describe 'Links'
describe 'Callbacks'
describe 'Components Section'
describe 'Using $ref'
describe 'Grouping Operations with Tags'
# path '/blogs' do
# post 'Creates a blog' do
# tags 'Blogs'
# description 'Creates a new blog from provided data'
# operationId 'createBlog'
# consumes 'application/json'
# produces 'application/json'
# parameter name: :blog, in: :body, schema: { '$ref' => '#/definitions/blog' }
# let(:blog) { { title: 'foo', content: 'bar' } }
# response '201', 'blog created' do
# # schema '$ref' => '#/definitions/blog'
# run_test!
# end
# response '422', 'invalid request' do
# schema '$ref' => '#/definitions/errors_object'
# let(:blog) { { title: 'foo' } }
# run_test! do |response|
# expect(response.body).to include("can't be blank")
# end
# it 'outputs parameters' do
# pp swagger_doc
# params = swagger_doc.dig(:paths, "/blogs", :post, :parameters)
# expect(params[0][:name]).to eq(:blog)
# end
# end
# end
# get 'Searches blogs' do
# tags 'Blogs'
# description 'Searches blogs by keywords'
# operationId 'searchBlogs'
# produces 'application/json'
# parameter name: :keywords, in: :query, type: 'string'
# let(:keywords) { 'foo bar' }
# response '200', 'success' do
# schema type: 'array', items: { '$ref' => '#/definitions/blog' }
# end
# response '406', 'unsupported accept header' do
# let(:'Accept') { 'application/foo' }
# run_test!
# end
# end
# end
# path '/blogs/flexible' do
# post 'Creates a blog flexible body' do
# tags 'Blogs'
# description 'Creates a flexible blog from provided data'
# operationId 'createFlexibleBlog'
# consumes 'application/json'
# produces 'application/json'
# parameter name: :flexible_blog, in: :body, schema: {
# oneOf: [
# { '$ref' => '#/definitions/blog' },
# { '$ref' => '#/definitions/flexible_blog' }
# ]
# }
# let(:flexible_blog) { { blog: { headline: 'my headline', text: 'my text' } } }
# response '201', 'flexible blog created' do
# schema oneOf: [{ '$ref' => '#/definitions/blog' }, { '$ref' => '#/definitions/flexible_blog' }]
# run_test!
# end
# end
# end
# path '/blogs/{id}' do
# parameter name: :id, in: :path, type: :string
# let(:id) { blog.id }
# let(:blog) { Blog.create(title: 'foo', content: 'bar', thumbnail: 'thumbnail.png') }
# get 'Retrieves a blog' do
# tags 'Blogs'
# description 'Retrieves a specific blog by id'
# operationId 'getBlog'
# produces 'application/json'
# response '200', 'blog found' do
# header 'ETag', type: :string
# header 'Last-Modified', type: :string
# header 'Cache-Control', type: :string
# schema '$ref' => '#/definitions/blog'
# examples 'application/json' => {
# id: 1,
# title: 'Hello world!',
# content: 'Hello world and hello universe. Thank you all very much!!!',
# thumbnail: 'thumbnail.png'
# }
# let(:id) { blog.id }
# run_test!
# end
# response '404', 'blog not found' do
# let(:id) { 'invalid' }
# run_test!
# end
# end
# end
# path '/blogs/{id}/upload' do
# parameter name: :id, in: :path, type: :string
# let(:id) { blog.id }
# let(:blog) { Blog.create(title: 'foo', content: 'bar') }
# put 'Uploads a blog thumbnail' do
# tags 'Blogs'
# description 'Upload a thumbnail for specific blog by id'
# operationId 'uploadThumbnailBlog'
# consumes 'multipart/form-data'
# parameter name: :file, :in => :formData, :type => :file, required: true
# response '200', 'blog updated' do
# let(:file) { Rack::Test::UploadedFile.new(Rails.root.join("spec/fixtures/thumbnail.png")) }
# run_test!
# end
# end
# end
end

View File

@@ -67,9 +67,77 @@ RSpec.configure do |config|
required: ['id', 'headline'] required: ['id', 'headline']
} }
}, },
securityDefinitions: { components: {
securitySchemes: {
basic_auth: { basic_auth: {
type: :basic type: :http,
scheme: :basic
},
api_key: {
type: :apiKey,
name: 'api_key',
in: :query
}
}
}
},
'v3/openapi.json' => {
openapi: '3.0.0',
info: {
title: 'API V1',
version: 'v1'
},
paths: {},
servers: [
{
url: 'https://{defaultHost}',
variables: {
defaultHost: {
default: 'www.example.com'
}
}
}
],
definitions: {
errors_object: {
type: 'object',
properties: {
errors: { '$ref' => '#/definitions/errors_map' }
}
},
errors_map: {
type: 'object',
additionalProperties: {
type: 'array',
items: { type: 'string' }
}
},
blog: {
type: 'object',
properties: {
id: { type: 'integer' },
title: { type: 'string' },
content: { type: 'string', 'x-nullable': true },
thumbnail: { type: 'string', 'x-nullable': true}
},
required: [ 'id', 'title' ]
},
flexible_blog: {
type: 'object',
properties: {
id: { type: 'integer' },
headline: { type: 'string' },
text: { type: 'string', nullable: true },
thumbnail: { type: 'string', nullable: true }
},
required: ['id', 'headline']
}
},
components: {
securitySchemes: {
basic_auth: {
type: :http,
scheme: :basic
}, },
api_key: { api_key: {
type: :apiKey, type: :apiKey,
@@ -79,4 +147,5 @@ RSpec.configure do |config|
} }
} }
} }
}
end end

View File

@@ -21,14 +21,10 @@
], ],
"responses": { "responses": {
"204": { "204": {
"description": "Valid credentials", "description": "Valid credentials"
"content": {
}
}, },
"401": { "401": {
"description": "Invalid credentials", "description": "Invalid credentials"
"content": {
}
} }
} }
} }
@@ -49,14 +45,10 @@
], ],
"responses": { "responses": {
"204": { "204": {
"description": "Valid credentials", "description": "Valid credentials"
"content": {
}
}, },
"401": { "401": {
"description": "Invalid credentials", "description": "Invalid credentials"
"content": {
}
} }
} }
} }
@@ -80,14 +72,10 @@
], ],
"responses": { "responses": {
"204": { "204": {
"description": "Valid credentials", "description": "Valid credentials"
"content": {
}
}, },
"401": { "401": {
"description": "Invalid credentials", "description": "Invalid credentials"
"content": {
}
} }
} }
} }
@@ -105,9 +93,7 @@
], ],
"responses": { "responses": {
"201": { "201": {
"description": "blog created", "description": "blog created"
"content": {
}
}, },
"422": { "422": {
"description": "invalid request", "description": "invalid request",
@@ -148,9 +134,7 @@
], ],
"responses": { "responses": {
"406": { "406": {
"description": "unsupported accept header", "description": "unsupported accept header"
"content": {
}
} }
} }
} }
@@ -235,16 +219,14 @@
"type": "string" "type": "string"
} }
}, },
"examples": { "content": {
"application/json": { "application/json": {
"example": {
"id": 1, "id": 1,
"title": "Hello world!", "title": "Hello world!",
"content": "Hello world and hello universe. Thank you all very much!!!", "content": "Hello world and hello universe. Thank you all very much!!!",
"thumbnail": "thumbnail.png" "thumbnail": "thumbnail.png"
}
}, },
"content": {
"application/json": {
"schema": { "schema": {
"$ref": "#/definitions/blog" "$ref": "#/definitions/blog"
} }
@@ -252,9 +234,7 @@
} }
}, },
"404": { "404": {
"description": "blog not found", "description": "blog not found"
"content": {
}
} }
} }
} }
@@ -282,9 +262,7 @@
], ],
"responses": { "responses": {
"200": { "200": {
"description": "blog updated", "description": "blog updated"
"content": {
}
} }
}, },
"requestBody": { "requestBody": {
@@ -294,7 +272,8 @@
"type": "file" "type": "file"
} }
} }
} },
"required": true
} }
} }
} }
@@ -377,7 +356,8 @@
"components": { "components": {
"securitySchemes": { "securitySchemes": {
"basic_auth": { "basic_auth": {
"type": "basic" "type": "http",
"scheme": "basic"
}, },
"api_key": { "api_key": {
"type": "apiKey", "type": "apiKey",

View File

@@ -0,0 +1,160 @@
{
"openapi": "3.0.0",
"info": {
"title": "API V1",
"version": "v1"
},
"paths": {
"/stubs": {
"get": {
"summary": "a summary",
"tags": [
"Parameter Serialization: Query String"
],
"responses": {
"200": {
"description": "OK"
}
},
"parameters": [
{
"name": "a_param",
"in": "query"
}
]
},
"post": {
"summary": "body is required",
"tags": [
"Media Types"
],
"parameters": [
],
"responses": {
"200": {
"description": "OK"
}
},
"requestBody": {
"content": {
"multipart/form-data": {
"schema": {
"type": "file"
}
}
},
"required": true
}
}
},
"/stubs/{a_param}": {
"get": {
"summary": "a summary",
"tags": [
"Parameter Serialization: Query String"
],
"parameters": [
{
"name": "a_param",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
}
},
"servers": [
{
"url": "https://{defaultHost}",
"variables": {
"defaultHost": {
"default": "www.example.com"
}
}
}
],
"definitions": {
"errors_object": {
"type": "object",
"properties": {
"errors": {
"$ref": "#/definitions/errors_map"
}
}
},
"errors_map": {
"type": "object",
"additionalProperties": {
"type": "array",
"items": {
"type": "string"
}
}
},
"blog": {
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"title": {
"type": "string"
},
"content": {
"type": "string",
"x-nullable": true
},
"thumbnail": {
"type": "string",
"x-nullable": true
}
},
"required": [
"id",
"title"
]
},
"flexible_blog": {
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"headline": {
"type": "string"
},
"text": {
"type": "string",
"nullable": true
},
"thumbnail": {
"type": "string",
"nullable": true
}
},
"required": [
"id",
"headline"
]
}
},
"components": {
"securitySchemes": {
"basic_auth": {
"type": "http",
"scheme": "basic"
},
"api_key": {
"type": "apiKey",
"name": "api_key",
"in": "query"
}
}
}
}