43 Commits

Author SHA1 Message Date
domaindrivendev
d91601b02c Merge pull request #87 from BC-THooper/allow_for_undefined_in_parameter_key
Allows for parameters to be defined without the 'in' key defined.
2017-07-31 17:54:43 -07:00
Travis Hooper
037c0e374a Allows for parameters to be defined without the 'in' key defined to allow for parameter 2017-07-31 17:44:43 -05:00
domaindrivendev
8f16492462 Merge pull request #82 from domaindrivendev/per-response-metadata
Allow arbitrary metadata for path/response blocks
2017-07-22 10:33:05 -07:00
domaindrivendev
732cab994c simplify validation blocks and use correct scope 2017-07-21 22:26:14 -07:00
domaindrivendev
452d9176cc Allow arbitrary metadata for path/response blocks 2017-07-21 21:19:25 -07:00
domaindrivendev
7f0e437f8b For rswag, param names MUST be unique 2017-07-21 21:04:46 -07:00
domaindrivendev
700b227231 Merge branch 'thg303-add-formData-support' 2017-07-21 14:25:51 -07:00
domaindrivendev
b16198377b Merge branch 'add-formData-support' of https://github.com/thg303/rswag into thg303-add-formData-support 2017-07-21 14:25:29 -07:00
domaindrivendev
e18a001e9b Move rackify headers to RequestFactory + other minor refactors 2017-07-21 13:49:01 -07:00
domaindrivendev
6ee53b08ae Merge branch 'refactor-request-factory' 2017-07-20 23:42:49 -07:00
domaindrivendev
97c2a39cfa Refactor request_factory & response_validator 2017-07-20 23:42:40 -07:00
domaindrivendev
f7c29267fe Merge pull request #78 from bertha-carl/fully-validate
Can we use JSON::Validator.fully_validate() to get better error messages?
2017-07-20 10:09:01 -07:00
Hannes Probst
8ede7f78f1 use fully_validate to get more specific error msg 2017-07-18 15:45:36 +02:00
domaindrivendev
8b937e3585 Merge pull request #76 from ahobson/bugfix-optional-query-params
Support optional query parameters
2017-07-13 16:35:48 -07:00
Andrew Hobson
1515ce4fcb Support optional query parameters 2017-07-11 16:28:45 -04:00
domaindrivendev
741f0dc240 Merge branch 'ci-updates' 2017-07-10 23:55:51 -07:00
domaindrivendev
3404fa72aa Publish gems via CI on tagged builds of master 2017-07-10 23:52:51 -07:00
Ali Qanavatian
44840ab836 Merge branch 'master' into add-formData-support 2017-07-04 09:45:33 +04:30
domaindrivendev
521ab14eaf Merge pull request #72 from domaindrivendev/minimal-dependencies
Reduces dependencies to just the required Rails components
2017-06-28 09:18:32 -07:00
domaindrivendev
8f5cb1aa12 Reduce depencencies to require Rails components only 2017-06-28 09:01:07 -07:00
domaindrivendev
519eb74cdd Merge pull request #71 from domaindrivendev/basic-auth-take2
Basic auth take2
2017-06-28 00:16:38 -07:00
domaindrivendev
de7ec5f15d Leverage security definitions for headers in example requests 2017-06-26 17:52:00 -07:00
domaindrivendev
e40c5fc26e Prep for 1.3.0 2017-06-26 17:39:34 -07:00
domaindrivendev
1de29136ef Merge pull request #64 from jume-dev/swagger-ui-url-patch
Add proper swagger ui url
2017-06-26 17:39:04 -07:00
domaindrivendev
88449944e1 Merge pull request #47 from DMiPartners/fixdepnotice
get no deprecation; add some docs
2017-06-26 17:32:32 -07:00
domaindrivendev
e03a6d333f Merge branch 'master' into fixdepnotice 2017-06-26 17:26:28 -07:00
domaindrivendev
215aaddb7d Merge pull request #69 from lazebny/rswag-ui-issue-with-precompilation
Fixed issue with precomilation
2017-06-19 07:17:15 -07:00
Vadim Lazebny
925b754233 Fixed issue with precomilation 2017-06-19 15:27:21 +03:00
Robert Kranz
69c7decce1 Add proper swagger ui url
As this only supports swagger ui 2, the url should link accordingly
2017-06-01 16:01:15 +02:00
domaindrivendev
353be669e4 Removed some deprecation warnings 2017-05-19 11:11:13 -07:00
domaindrivendev
75d676d753 Merge pull request #59 from grk/rails-5-1
Relax Rails dependency to allow 5.1
2017-05-19 10:39:29 -07:00
domaindrivendev
1b3a976313 Merge branch 'master' into rails-5-1 2017-05-19 10:35:00 -07:00
domaindrivendev
2617fdc48d Prep for v1.2.1 release 2017-05-19 10:32:22 -07:00
Grzesiek Kolodziejczyk
724ede0076 Remove unnecessary whitespace 2017-05-18 11:48:14 +02:00
Grzesiek Kolodziejczyk
8aeb9a6c70 Relax Rails dependency to allow 5.1 2017-05-18 11:47:40 +02:00
ali.q
182ee093f4 add formData support 2017-05-11 05:06:46 +04:30
domaindrivendev
25d8adaf8b Merge pull request #57 from horiaradu/master
Response body value validation
2017-04-29 12:46:31 -07:00
Horia Radu
f195c82759 yield entire response instead of only the body 2017-04-29 21:17:53 +03:00
Horia Radu
37f86f6d94 yield entire response instead of only the body 2017-04-29 12:06:57 +03:00
Horia Radu
51c9f4e5e6 Response body value validation
Add the possibility to pass a block to the run_test!
method in order to add expectations on your response
2017-04-28 11:46:52 +03:00
Dante Piombino
233c5cc735 get no deprecation; add some docs 2017-02-16 16:31:45 -05:00
domaindrivendev
cf9170101b Merge pull request #42 from PavelBezpalov/master
Add way for examples generation from responses
2017-01-19 08:52:40 -08:00
Pavel Bezpalov
98d5b982c4 Add way for examples generation from responses 2017-01-19 11:46:56 +02:00
49 changed files with 895 additions and 764 deletions

2
.gitignore vendored
View File

@@ -3,3 +3,5 @@
**/*/*.gem
**/*/*.sqlite3
**/*/public/assets
*.swp
Gemfile.lock

1
.ruby-version Normal file
View File

@@ -0,0 +1 @@
2.3.0

View File

@@ -1,14 +1,64 @@
nguage: ruby
language: ruby
rvm:
- 2.2.5
env:
- "RAILS_VERSION=3.2.22"
- "RAILS_VERSION=4.2.0"
- "RAILS_VERSION=5.0.0"
cache: bundler
install: bundle update
- RAILS_VERSION=5.1.2
- RAILS_VERSION=4.2.0
- RAILS_VERSION=3.2.22
cache:
directories:
- /home/travis/.rvm/gems/ruby-2.2.5
install: bundle install
before_script:
- "export DISPLAY=:99.0"
- "sh -e /etc/init.d/xvfb start"
- sleep 3 # give xvfb some time to start
- export DISPLAY=:99.0
- sh -e /etc/init.d/xvfb start
- sleep 3
script: ./ci/test.sh
jobs:
include:
- stage: publish components
script: 'cd rswag-api'
deploy:
gemspec: rswag-api.gemspec
provider: rubygems
api_key: $RUBYGEMS_API_KEY
on:
branch: master
tags: true
- stage: publish components
script: 'cd rswag-specs'
deploy:
gemspec: rswag-specs.gemspec
provider: rubygems
api_key: $RUBYGEMS_API_KEY
on:
branch: master
tags: true
- stage: publish components
script: 'cd rswag-ui'
deploy:
gemspec: rswag-ui.gemspec
provider: rubygems
api_key: $RUBYGEMS_API_KEY
on:
branch: master
tags: true
- stage: publish rswag
script: 'cd rswag'
deploy:
gemspec: rswag.gemspec
provider: rubygems
api_key: $RUBYGEMS_API_KEY
on:
branch: master
tags: true

View File

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

View File

@@ -1,173 +0,0 @@
PATH
remote: ./rswag-api
specs:
rswag-api (1.2.0)
rails (>= 3.1, < 5.1)
PATH
remote: ./rswag-specs
specs:
rswag-specs (1.2.0)
json-schema (~> 2.2)
rails (>= 3.1, < 5.1)
rspec-rails (>= 2.14, < 4)
PATH
remote: ./rswag-ui
specs:
rswag-ui (1.2.0)
rails (>= 3.1, < 5.1)
GEM
remote: https://rubygems.org/
specs:
actionmailer (3.2.22)
actionpack (= 3.2.22)
mail (~> 2.5.4)
actionpack (3.2.22)
activemodel (= 3.2.22)
activesupport (= 3.2.22)
builder (~> 3.0.0)
erubis (~> 2.7.0)
journey (~> 1.0.4)
rack (~> 1.4.5)
rack-cache (~> 1.2)
rack-test (~> 0.6.1)
sprockets (~> 2.2.1)
activemodel (3.2.22)
activesupport (= 3.2.22)
builder (~> 3.0.0)
activerecord (3.2.22)
activemodel (= 3.2.22)
activesupport (= 3.2.22)
arel (~> 3.0.2)
tzinfo (~> 0.3.29)
activeresource (3.2.22)
activemodel (= 3.2.22)
activesupport (= 3.2.22)
activesupport (3.2.22)
i18n (~> 0.6, >= 0.6.4)
multi_json (~> 1.0)
addressable (2.4.0)
arel (3.0.3)
builder (3.0.4)
capybara (2.10.1)
addressable
mime-types (>= 1.16)
nokogiri (>= 1.3.3)
rack (>= 1.0.0)
rack-test (>= 0.5.4)
xpath (~> 2.0)
capybara-webkit (1.1.0)
capybara (~> 2.0, >= 2.0.2)
json
diff-lcs (1.2.5)
erubis (2.7.0)
execjs (2.7.0)
generator_spec (0.9.3)
activesupport (>= 3.0.0)
railties (>= 3.0.0)
hike (1.2.3)
i18n (0.7.0)
journey (1.0.4)
json (1.8.3)
json-schema (2.7.0)
addressable (>= 2.4)
libv8 (3.16.14.15)
mail (2.5.4)
mime-types (~> 1.16)
treetop (~> 1.4.8)
mime-types (1.25.1)
mini_portile2 (2.1.0)
multi_json (1.12.1)
nokogiri (1.6.8.1)
mini_portile2 (~> 2.1.0)
polyglot (0.3.5)
power_assert (0.3.1)
rack (1.4.7)
rack-cache (1.6.1)
rack (>= 0.4)
rack-ssl (1.3.4)
rack
rack-test (0.6.3)
rack (>= 1.0)
rails (3.2.22)
actionmailer (= 3.2.22)
actionpack (= 3.2.22)
activerecord (= 3.2.22)
activeresource (= 3.2.22)
activesupport (= 3.2.22)
bundler (~> 1.0)
railties (= 3.2.22)
railties (3.2.22)
actionpack (= 3.2.22)
activesupport (= 3.2.22)
rack-ssl (~> 1.3.2)
rake (>= 0.8.7)
rdoc (~> 3.4)
thor (>= 0.14.6, < 2.0)
rake (11.3.0)
rdoc (3.12.2)
json (~> 1.4)
ref (2.0.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.5.0)
rspec-mocks (3.5.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.5.0)
rspec-rails (3.5.2)
actionpack (>= 3.0)
activesupport (>= 3.0)
railties (>= 3.0)
rspec-core (~> 3.5.0)
rspec-expectations (~> 3.5.0)
rspec-mocks (~> 3.5.0)
rspec-support (~> 3.5.0)
rspec-support (3.5.0)
sprockets (2.2.3)
hike (~> 1.2)
multi_json (~> 1.0)
rack (~> 1.0)
tilt (~> 1.1, != 1.3.0)
sqlite3 (1.3.12)
strong_parameters (0.2.3)
actionpack (~> 3.0)
activemodel (~> 3.0)
activesupport (~> 3.0)
railties (~> 3.0)
test-unit (3.2.1)
power_assert
therubyracer (0.12.2)
libv8 (~> 3.16.14.0)
ref
thor (0.19.1)
tilt (1.4.1)
treetop (1.4.15)
polyglot
polyglot (>= 0.3.1)
tzinfo (0.3.51)
uglifier (3.0.2)
execjs (>= 0.3.0, < 3)
xpath (2.0.0)
nokogiri (~> 1.3)
PLATFORMS
ruby
DEPENDENCIES
capybara
capybara-webkit
generator_spec
rails (= 3.2.22)
rspec-rails
rswag-api!
rswag-specs!
rswag-ui!
sqlite3
strong_parameters
test-unit
therubyracer
uglifier

111
README.md
View File

@@ -82,6 +82,11 @@ Once you have an API that can describe itself in Swagger, you've opened the trea
let(:id) { 'invalid' }
run_test!
end
response '406', 'unsupported accept header' do
let(:'Accept') { 'application/foo' }
run_test!
end
end
end
end
@@ -103,6 +108,17 @@ If you've used [Swagger](http://swagger.io/specification) before, then the synta
Take special note of the __run_test!__ method that's called within each response block. This tells rswag to create and execute a corresponding example. It builds and submits a request based on parameter descriptions and corresponding values that have been provided using the rspec "let" syntax. For example, the "post" description in the example above specifies a "body" parameter called "blog". It also lists 2 different responses. For the success case (i.e. the 201 response), notice how "let" is used to set the blog parameter to a value that matches the provided schema. For the failure case (i.e. the 422 response), notice how it's set to a value that does not match the provided schema. When the test is executed, rswag also validates the actual response code and, where applicable, the response body against the provided [JSON Schema](http://json-schema.org/documentation.html).
If you want to do additional validation on the response, pass a block to the __run_test!__ method:
```ruby
response '201', 'blog created' do
run_test! do |response|
data = JSON.parse(response.body)
expect(data['title']).to eq('foo')
end
end
```
If you'd like your specs to be a little more explicit about what's going on here, you can replace the call to __run_test!__ with equivalent "before" and "it" blocks:
```ruby
@@ -119,6 +135,31 @@ response '201', 'blog created' do
end
```
### Null Values ###
This library is currently using JSON::Draft4 for validation of response models. It does not support null as a value. So you can add the property 'x-nullable' to a definition to allow null/nil values to pass.
```ruby
describe 'Blogs API' do
path '/blogs' do
post 'Creates a blog' do
...
response '200', 'blog found' do
schema type: :object,
properties: {
id: { type: :integer },
title: { type: :string },
content: { type: :string, 'x-nullable': true }
}
....
end
end
end
end
```
*Note:* the OAI v3 may be released soon(ish?) and include a nullable property. This may have an effect on the need/use of custom extension to the draft. Do not use this property if you don't understand the implications.
<https://github.com/OAI/OpenAPI-Specification/issues/229#issuecomment-280376087>
### Global Metadata ###
In addition to paths, operations and responses, Swagger also supports global API metadata. When you install rswag, a file called _swagger_helper.rb_ is added to your spec folder. This is where you define one or more Swagger documents and provide global metadata. Again, the format is based on Swagger so most of the global fields supported by the top level ["Swagger" object](http://swagger.io/specification/#swaggerObject) can be provided with each document definition. As an example, you could define a Swagger document for each version of your API and in each case specify a title, version string and URL basePath:
@@ -164,6 +205,58 @@ describe 'Blogs API', swagger_doc: 'v2/swagger.json' do
end
```
### Specifying/Testing API Security ###
Swagger allows for the specification of different security schemes and their applicability to operations in an API. To leverage this in rswag, you define the schemes globally in _swagger_helper.rb_ and then use the "security" attribute at the operation level to specify which schemes, if any, are applicable to that operation. Swagger supports :basic, :apiKey and :oauth2 scheme types. See [the spec](http://swagger.io/specification/#security-definitions-object-109) for more info.
```ruby
# spec/swagger_helper.rb
RSpec.configure do |config|
config.swagger_root = Rails.root.to_s + '/swagger'
config.swagger_docs = {
'v1/swagger.json' => {
...
securityDefinitions: {
basic: {
type: :basic
},
apiKey: {
type: :apiKey,
name: 'api_key',
in: :query
}
}
}
}
end
# spec/integration/blogs_spec.rb
describe 'Blogs API' do
path '/blogs' do
post 'Creates a blog' do
tags 'Blogs'
security [ basic: [] ]
...
response '201', 'blog created' do
let(:Authorization) { "Basic #{::Base64.strict_encode64('jsmith:jspass')}" }
run_test!
end
response '401', 'authentication failed' do
let(:Authorization) { "Basic #{::Base64.strict_encode64('bogus:bogus')}" }
run_test!
end
end
end
end
```
__NOTE:__ Depending on the scheme types, you'll be required to assign a corresponding parameter value with each example. For example, :basic auth is required above and so the :Authorization (header) parameter must be set accordingly
## Configuration & Customization ##
The steps described above will get you up and running with minimal setup. However, rswag offers a lot of flexibility to customize as you see fit. Before exploring the various options, you'll need to be aware of it's different components. The following table lists each of them and the files that get added/updated as part of a standard install.
@@ -284,6 +377,22 @@ describe 'Blogs API' do
end
```
### Enable generation examples from responses ###
To enable examples generation from responses add callback above run_test! like:
```ruby
after do |example|
example.metadata[:response][:examples] = { 'application/json' => JSON.parse(response.body, symbolize_names: true) }
end
```
You need to disable --dry-run option for Rspec > 3
Add to application.rb:
```ruby
RSpec.configure do |config|
config.swagger_dry_run = false
end
```
### Route Prefix for Swagger JSON Endpoints ###
The functionality to expose Swagger files, such as those generated by rswag-specs, as JSON endpoints is implemented as a Rails Engine. As with any Engine, you can change it's mount prefix in _routes.rb_:
@@ -355,7 +464,7 @@ end
### Customizing the swagger-ui ###
The swagger-ui provides several options for customizing it's behavior, all of which are documented here https://github.com/swagger-api/swagger-ui#swaggerui. If you need to tweak these or customize the overall look and feel of your swagger-ui, then you'll need to provide your own version of index.html. You can do this with the following generator.
The swagger-ui provides several options for customizing it's behavior, all of which are documented here https://github.com/swagger-api/swagger-ui/tree/2.x#swaggerui. If you need to tweak these or customize the overall look and feel of your swagger-ui, then you'll need to provide your own version of index.html. You can do this with the following generator.
```ruby
rails g rswag:ui:custom

View File

@@ -1,58 +0,0 @@
#!/usr/bin/env bash
ROOT_PATH=$PWD
set -e # abort if anything fails
bundle check || bundle
echo '####################'
echo 'Build Gems'
echo '####################'
echo ''
echo '##### rswag-api #####'
cd $ROOT_PATH/rswag-api
gem build rswag-api.gemspec
echo '##### rswag-specs #####'
cd $ROOT_PATH/rswag-specs
gem build rswag-specs.gemspec
echo '##### rswag-ui #####'
cd $ROOT_PATH/rswag-ui
gem build rswag-ui.gemspec
echo '##### rswag #####'
cd $ROOT_PATH/rswag
gem build rswag.gemspec
echo '####################'
echo 'Push to RubyGems'
echo '####################'
echo ''
echo 'Type the version no, followed by [ENTER]:'
read version
echo '##### rswag-api #####'
cd $ROOT_PATH/rswag-api
gem push rswag-api-$version.gem
echo '##### rswag-specs #####'
cd $ROOT_PATH/rswag-specs
gem push rswag-specs-$version.gem
echo '##### rswag-ui #####'
cd $ROOT_PATH/rswag-ui
gem push rswag-ui-$version.gem
echo '##### rswag #####'
cd $ROOT_PATH/rswag
gem push rswag-$version.gem
# Cleanup
cd $ROOT_PATH
# Create git tag
git tag v$version
git push origin v$version

View File

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

View File

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

View File

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

View File

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

View File

@@ -25,6 +25,12 @@ module Rswag
end
end
def swagger_dry_run
@swagger_dry_run ||= begin
@rspec_config.swagger_dry_run.nil? || @rspec_config.swagger_dry_run
end
end
def get_swagger_doc(name)
return swagger_docs.values.first if name.nil?
raise ConfigurationError, "Unknown swagger_doc '#{name}'" unless swagger_docs[name]

View File

@@ -2,9 +2,9 @@ module Rswag
module Specs
module ExampleGroupHelpers
def path(template, &block)
api_metadata = { path_item: { template: template } }
describe(template, api_metadata, &block)
def path(template, metadata={}, &block)
metadata[:path_item] = { template: template }
describe(template, metadata, &block)
end
[ :get, :post, :patch, :put, :delete, :head ].each do |verb|
@@ -36,7 +36,9 @@ module Rswag
end
def parameter(attributes)
attributes[:required] = true if attributes[:in].to_sym == :path
if attributes[:in] && attributes[:in].to_sym == :path
attributes[:required] = true
end
if metadata.has_key?(:operation)
metadata[:operation][:parameters] ||= []
@@ -47,9 +49,9 @@ module Rswag
end
end
def response(code, description, &block)
api_metadata = { response: { code: code, description: description } }
context(description, api_metadata, &block)
def response(code, description, metadata={}, &block)
metadata[:response] = { code: code, description: description }
context(description, metadata, &block)
end
def schema(value)
@@ -69,7 +71,7 @@ module Rswag
metadata[:response][:examples] = example
end
def run_test!
def run_test!(&block)
# NOTE: rspec 2.x support
if RSPEC_VERSION < 3
before do
@@ -77,7 +79,8 @@ module Rswag
end
it "returns a #{metadata[:response][:code]} response" do
assert_response_matches_metadata(example.metadata)
assert_response_matches_metadata(metadata)
block.call(response) if block_given?
end
else
before do |example|
@@ -85,7 +88,8 @@ module Rswag
end
it "returns a #{metadata[:response][:code]} response" do |example|
assert_response_matches_metadata(example.metadata)
assert_response_matches_metadata(example.metadata, &block)
example.instance_exec(response, &block) if block_given?
end
end
end

View File

@@ -5,39 +5,30 @@ module Rswag
module Specs
module ExampleHelpers
def submit_request(api_metadata)
global_metadata = rswag_config.get_swagger_doc(api_metadata[:swagger_doc])
factory = RequestFactory.new(api_metadata, global_metadata)
def submit_request(metadata)
request = RequestFactory.new.build_request(metadata, self)
if RAILS_VERSION < 5
send(
api_metadata[:operation][:verb],
factory.build_fullpath(self),
factory.build_body(self),
factory.build_headers(self)
request[:verb],
request[:path],
request[:payload],
request[:headers]
)
else
send(
api_metadata[:operation][:verb],
factory.build_fullpath(self),
request[:verb],
request[:path],
{
params: factory.build_body(self),
headers: factory.build_headers(self)
params: request[:payload],
headers: request[:headers]
}
)
end
end
def assert_response_matches_metadata(api_metadata)
global_metadata = rswag_config.get_swagger_doc(api_metadata[:swagger_doc])
validator = ResponseValidator.new(api_metadata, global_metadata)
validator.validate!(response)
end
private
def rswag_config
::Rswag::Specs.config
def assert_response_matches_metadata(metadata)
ResponseValidator.new.validate!(metadata, response)
end
end
end

View File

@@ -2,13 +2,13 @@ require 'json-schema'
module Rswag
module Specs
class ExtendedSchema < JSON::Schema::Validator
class ExtendedSchema < JSON::Schema::Draft4
def initialize
super
extend_schema_definition("http://json-schema.org/draft-04/schema#")
@attributes['type'] = ExtendedTypeAttribute
@uri = URI.parse('http://tempuri.org/rswag/specs/extended_schema')
@names = ['http://tempuri.org/rswag/specs/extended_schema']
end
end

View File

@@ -1,85 +1,83 @@
require 'active_support/core_ext/hash/slice'
require 'active_support/core_ext/hash/conversions'
require 'json'
module Rswag
module Specs
class RequestFactory
def initialize(api_metadata, global_metadata)
@api_metadata = api_metadata
@global_metadata = global_metadata
def initialize(config = ::Rswag::Specs.config)
@config = config
end
def build_fullpath(example)
@api_metadata[:path_item][:template].dup.tap do |t|
t.prepend(@global_metadata[:basePath] || '')
parameters_in(:path).each { |p| t.gsub!("{#{p[:name]}}", example.send(p[:name]).to_s) }
t.concat(build_query_string(example))
end
end
def build_request(metadata, example)
swagger_doc = @config.get_swagger_doc(metadata[:swagger_doc])
parameters = expand_parameters(metadata, swagger_doc, example)
def build_query_string(example)
query_string = parameters_in(:query)
.map { |p| build_query_string_part(p, 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?
{}.tap do |request|
add_verb(request, metadata)
add_path(request, metadata, swagger_doc, parameters, example)
add_headers(request, metadata, swagger_doc, parameters, example)
add_payload(request, parameters, example)
end
end
private
def parameters_in(location)
path_item_params = @api_metadata[:path_item][:parameters] || []
operation_params = @api_metadata[:operation][:parameters] || []
applicable_params = operation_params
def expand_parameters(metadata, swagger_doc, example)
operation_params = metadata[:operation][:parameters] || []
path_item_params = metadata[:path_item][:parameters] || []
security_params = derive_security_params(metadata, swagger_doc)
operation_params
.concat(path_item_params)
.uniq { |p| p[:name] } # operation params should override path_item params
applicable_params
.map { |p| p['$ref'] ? resolve_parameter(p['$ref']) : p } # resolve any references
.concat(resolve_api_key_parameters)
.select { |p| p[:in] == location }
.concat(security_params)
.map { |p| p['$ref'] ? resolve_parameter(p['$ref'], swagger_doc) : p }
.uniq { |p| p[:name] }
.reject { |p| p[:required] == false && !example.respond_to?(p[:name]) }
end
def resolve_parameter(ref)
defined_params = @global_metadata[:parameters]
def derive_security_params(metadata, swagger_doc)
requirements = metadata[:operation][:security] || swagger_doc[:security]
scheme_names = requirements ? requirements.map { |r| r.keys.first } : []
applicable_schemes = (swagger_doc[:securityDefinitions] || {}).slice(*scheme_names).values
applicable_schemes.map do |scheme|
param = (scheme[:type] == :apiKey) ? scheme.slice(:name, :in) : { name: 'Authorization', in: :header }
param.merge(type: :string)
end
end
def resolve_parameter(ref, swagger_doc)
definitions = swagger_doc[:parameters]
key = ref.sub('#/parameters/', '')
raise "Referenced parameter '#{ref}' must be defined" unless defined_params && defined_params[key]
defined_params[key]
raise "Referenced parameter '#{ref}' must be defined" unless definitions && definitions[key]
definitions[key]
end
def resolve_api_key_parameters
@api_key_params ||= begin
# First figure out the security requirement applicable to the operation
global_requirements = (@global_metadata[:security] || [] ).map { |r| r.keys.first }
operation_requirements = (@api_metadata[:operation][:security] || [] ).map { |r| r.keys.first }
requirements = global_requirements | operation_requirements
def add_verb(request, metadata)
request[:verb] = metadata[:operation][:verb]
end
# Then obtain the scheme definitions for those requirements
definitions = (@global_metadata[:securityDefinitions] || {}).slice(*requirements)
definitions.values.select { |d| d[:type] == :apiKey }
def add_path(request, metadata, swagger_doc, parameters, example)
template = (swagger_doc[:basePath] || '') + metadata[:path_item][:template]
request[:path] = template.tap do |template|
parameters.select { |p| p[:in] == :path }.each do |p|
template.gsub!("{#{p[:name]}}", example.send(p[:name]).to_s)
end
parameters.select { |p| p[:in] == :query }.each_with_index do |p, i|
template.concat(i == 0 ? '?' : '&')
template.concat(build_query_string_part(p, example.send(p[:name])))
end
end
end
def build_query_string_part(param, value)
return "#{param[:name]}=#{value.to_s}" unless param[:type].to_sym == :array
name = param[:name]
return "#{name}=#{value.to_s}" unless param[:type].to_sym == :array
case param[:collectionFormat]
when :ssv
"#{name}=#{value.join(' ')}"
@@ -93,6 +91,68 @@ module Rswag
"#{name}=#{value.join(',')}" # csv is default
end
end
def add_headers(request, metadata, swagger_doc, parameters, example)
tuples = parameters
.select { |p| p[:in] == :header }
.map { |p| [ p[:name], example.send(p[:name]).to_s ] }
# Accept header
produces = metadata[:operation][:produces] || swagger_doc[:produces]
if produces
accept = example.respond_to?(:'Accept') ? example.send(:'Accept') : produces.first
tuples << [ 'Accept', accept ]
end
# Content-Type header
consumes = metadata[:operation][:consumes] || swagger_doc[:consumes]
if consumes
content_type = example.respond_to?(:'Content-Type') ? example.send(:'Content-Type') : consumes.first
tuples << [ 'Content-Type', content_type ]
end
# Rails test infrastructure requires rackified headers
rackified_tuples = tuples.map do |pair|
[
case pair[0]
when 'Accept' then 'HTTP_ACCEPT'
when 'Content-Type' then 'CONTENT_TYPE'
when 'Authorization' then 'HTTP_AUTHORIZATION'
else pair[0]
end,
pair[1]
]
end
request[:headers] = Hash[ rackified_tuples ]
end
def add_payload(request, parameters, example)
content_type = request[:headers]['CONTENT_TYPE']
return if content_type.nil?
if [ 'application/x-www-form-urlencoded', 'multipart/form-data' ].include?(content_type)
request[:payload] = build_form_payload(parameters, example)
else
request[:payload] = build_json_payload(parameters, example)
end
end
def build_form_payload(parameters, example)
# See http://seejohncode.com/2012/04/29/quick-tip-testing-multipart-uploads-with-rspec/
# Rather that serializing with the appropriate encoding (e.g. multipart/form-data),
# Rails test infrastructure allows us to send the values directly as a hash
# PROS: simple to implement, CONS: serialization/deserialization is bypassed in test
tuples = parameters
.select { |p| p[:in] == :formData }
.map { |p| [ p[:name], example.send(p[:name]) ] }
Hash[ tuples ]
end
def build_json_payload(parameters, example)
body_param = parameters.select { |p| p[:in] == :body }.first
body_param ? example.send(body_param[:name]).to_json : nil
end
end
end
end

View File

@@ -1,51 +1,49 @@
require 'active_support/core_ext/hash/slice'
require 'json-schema'
require 'json'
require 'rswag/specs/extended_schema'
module Rswag
module Specs
class ResponseValidator
def initialize(api_metadata, global_metadata)
@api_metadata = api_metadata
@global_metadata = global_metadata
def initialize(config = ::Rswag::Specs.config)
@config = config
end
def validate!(response)
validate_code!(response.code)
validate_headers!(response.headers)
validate_body!(response.body)
def validate!(metadata, response)
swagger_doc = @config.get_swagger_doc(metadata[:swagger_doc])
validate_code!(metadata, response.code)
validate_headers!(metadata, response.headers)
validate_body!(metadata, swagger_doc, 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]}'"
def validate_code!(metadata, code)
expected = metadata[:response][:code].to_s
if code != expected
raise UnexpectedResponse, "Expected response code '#{code}' to match '#{expected}'"
end
end
def validate_headers!(headers)
header_schema = @api_metadata[:response][:headers]
return if header_schema.nil?
header_schema.keys.each do |header_name|
raise UnexpectedResponse, "Expected response header #{header_name} to be present" if headers[header_name.to_s].nil?
def validate_headers!(metadata, headers)
expected = (metadata[:response][:headers] || {}).keys
expected.each do |name|
raise UnexpectedResponse, "Expected response header #{name} to be present" if headers[name.to_s].nil?
end
end
def validate_body!(body)
response_schema = @api_metadata[:response][:schema]
def validate_body!(metadata, swagger_doc, body)
response_schema = metadata[:response][:schema]
return if response_schema.nil?
begin
validation_schema = response_schema
.merge('$schema' => 'http://tempuri.org/rswag/specs/extended_schema')
.merge(@global_metadata.slice(:definitions))
JSON::Validator.validate!(validation_schema, body)
rescue JSON::Schema::ValidationError => ex
raise UnexpectedResponse, "Expected response body to match schema: #{ex.message}"
end
.merge(swagger_doc.slice(:definitions))
errors = JSON::Validator.fully_validate(validation_schema, body)
raise UnexpectedResponse, "Expected response body to match schema: #{errors[0]}" if errors.any?
end
end

View File

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

View File

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

View File

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

View File

@@ -120,6 +120,15 @@ module Rswag
)
end
end
context "when 'in' parameter key is not defined" do
before { subject.parameter(name: :id) }
let(:api_metadata) { { operation: {} } }
it "does not require the 'in' parameter key" do
expect(api_metadata[:operation][:parameters]).to match([ name: :id ])
end
end
end
describe '#response(code, description)' do

View File

@@ -7,19 +7,30 @@ module Rswag
subject { double('example') }
before do
subject.extend ExampleHelpers
# Mock out some infrastructure
stub_const('Rails::VERSION::MAJOR', 3)
rswag_config = double('rswag_config')
allow(rswag_config).to receive(:get_swagger_doc).and_return(global_metadata)
allow(subject).to receive(:rswag_config).and_return(rswag_config)
subject.extend(ExampleHelpers)
allow(Rswag::Specs).to receive(:config).and_return(config)
allow(config).to receive(:get_swagger_doc).and_return(swagger_doc)
stub_const('Rswag::Specs::RAILS_VERSION', 3)
end
let(:api_metadata) do
let(:config) { double('config') }
let(:swagger_doc) do
{
securityDefinitions: {
api_key: {
type: :apiKey,
name: 'api_key',
in: :query
}
}
}
end
let(:metadata) do
{
path_item: { template: '/blogs/{blog_id}/comments/{id}' },
operation: {
verb: :put,
summary: 'Updates a blog',
consumes: [ 'application/json' ],
parameters: [
{ name: :blog_id, in: :path, type: 'integer' },
{ name: 'id', in: :path, type: 'integer' },
@@ -32,19 +43,8 @@ module Rswag
}
}
end
let(:global_metadata) do
{
securityDefinitions: {
api_key: {
type: :apiKey,
name: 'api_key',
in: :query
}
}
}
end
describe '#submit_request(api_metadata)' do
describe '#submit_request(metadata)' do
before do
allow(subject).to receive(:blog_id).and_return(1)
allow(subject).to receive(:id).and_return(2)
@@ -52,14 +52,14 @@ module Rswag
allow(subject).to receive(:api_key).and_return('fookey')
allow(subject).to receive(:blog).and_return(text: 'Some comment')
allow(subject).to receive(:put)
subject.submit_request(api_metadata)
subject.submit_request(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\"}",
{}
{ 'CONTENT_TYPE' => 'application/json' }
)
end
end

View File

@@ -4,222 +4,301 @@ module Rswag
module Specs
describe RequestFactory do
subject { RequestFactory.new(api_metadata, global_metadata) }
subject { RequestFactory.new(config) }
before do
allow(example).to receive(:blog_id).and_return(1)
allow(example).to receive(:id).and_return('2')
allow(config).to receive(:get_swagger_doc).and_return(swagger_doc)
end
let(:api_metadata) do
{
path_item: { template: '/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) { {} }
let(:config) { double('config') }
let(:swagger_doc) { {} }
let(:example) { double('example') }
let(:metadata) do
{
path_item: { template: '/blogs' },
operation: { verb: :get }
}
end
describe '#build_fullpath(example)' do
let(:path) { subject.build_fullpath(example) }
describe '#build_request(metadata, example)' do
let(:request) { subject.build_request(metadata, example) }
context 'always' do
it "builds a path using metadata and example values" do
expect(path).to eq('/blogs/1/comments/2')
it 'builds request hash for given example' do
expect(request[:verb]).to eq(:get)
expect(request[:path]).to eq('/blogs')
end
context "'path' parameters" do
before do
metadata[:path_item][:template] = '/blogs/{blog_id}/comments/{id}'
metadata[:operation][:parameters] = [
{ name: 'blog_id', in: :path, type: :number },
{ name: 'id', in: :path, type: :number }
]
allow(example).to receive(:blog_id).and_return(1)
allow(example).to receive(:id).and_return(2)
end
it 'builds the path from example values' do
expect(request[: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' }
metadata[:operation][:parameters] = [
{ name: 'q1', in: :query, type: :string },
{ 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')
it "builds the query string from example values" do
expect(request[:path]).to eq('/blogs?q1=foo&q2=bar')
end
end
context "'query' parameter of type 'array'" do
context "'query' parameters of type 'array'" do
before do
api_metadata[:operation][:parameters] << {
name: 'things',
in: :query,
type: :array,
collectionFormat: collectionFormat
}
metadata[:operation][:parameters] = [
{ name: 'things', in: :query, type: :array, collectionFormat: collection_format }
]
allow(example).to receive(:things).and_return([ 'foo', 'bar' ])
end
context 'collectionFormat = csv' do
let(:collectionFormat) { :csv }
let(:collection_format) { :csv }
it "formats as comma separated values" do
expect(path).to eq('/blogs/1/comments/2?things=foo,bar')
expect(request[:path]).to eq('/blogs?things=foo,bar')
end
end
context 'collectionFormat = ssv' do
let(:collectionFormat) { :ssv }
let(:collection_format) { :ssv }
it "formats as space separated values" do
expect(path).to eq('/blogs/1/comments/2?things=foo bar')
expect(request[:path]).to eq('/blogs?things=foo bar')
end
end
context 'collectionFormat = tsv' do
let(:collectionFormat) { :tsv }
let(:collection_format) { :tsv }
it "formats as tab separated values" do
expect(path).to eq('/blogs/1/comments/2?things=foo\tbar')
expect(request[:path]).to eq('/blogs?things=foo\tbar')
end
end
context 'collectionFormat = pipes' do
let(:collectionFormat) { :pipes }
let(:collection_format) { :pipes }
it "formats as pipe separated values" do
expect(path).to eq('/blogs/1/comments/2?things=foo|bar')
expect(request[:path]).to eq('/blogs?things=foo|bar')
end
end
context 'collectionFormat = multi' do
let(:collectionFormat) { :multi }
let(:collection_format) { :multi }
it "formats as multiple parameter instances" do
expect(path).to eq('/blogs/1/comments/2?things=foo&things=bar')
expect(request[:path]).to eq('/blogs?things=foo&things=bar')
end
end
end
context "global definition for 'api_key in query'" do
context "'header' parameters" do
before do
global_metadata[:securityDefinitions] = { api_key: { type: :apiKey, name: 'api_key', in: :query } }
allow(example).to receive(:api_key).and_return('fookey')
metadata[:operation][:parameters] = [ { name: 'Api-Key', in: :header, type: :string } ]
allow(example).to receive(:'Api-Key').and_return('foobar')
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')
it 'adds names and example values to headers' do
expect(request[:headers]).to eq({ 'Api-Key' => 'foobar' })
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')
context 'optional parameters not provided' do
before do
metadata[:operation][:parameters] = [
{ name: 'q1', in: :query, type: :string, required: false },
{ name: 'Api-Key', in: :header, type: :string, required: false }
]
end
it 'builds request hash without them' do
expect(request[:path]).to eq('/blogs')
expect(request[:headers]).to eq({})
end
end
context "consumes content" do
before do
metadata[:operation][:consumes] = [ 'application/json', 'application/xml' ]
end
context "no 'Content-Type' provided" do
it "sets 'CONTENT_TYPE' header to first in list" do
expect(request[:headers]).to eq('CONTENT_TYPE' => 'application/json')
end
end
context "explicit 'Content-Type' provided" do
before do
allow(example).to receive(:'Content-Type').and_return('application/xml')
end
it "sets 'CONTENT_TYPE' header to example value" do
expect(request[:headers]).to eq('CONTENT_TYPE' => 'application/xml')
end
end
context 'JSON payload' do
before do
metadata[:operation][:parameters] = [ { name: 'comment', in: :body, schema: { type: 'object' } } ]
allow(example).to receive(:comment).and_return(text: 'Some comment')
end
it "serializes first 'body' parameter to JSON string" do
expect(request[:payload]).to eq("{\"text\":\"Some comment\"}")
end
end
context 'form payload' do
before do
metadata[:operation][:consumes] = [ 'multipart/form-data' ]
metadata[:operation][:parameters] = [
{ name: 'f1', in: :formData, type: :string },
{ name: 'f2', in: :formData, type: :string }
]
allow(example).to receive(:f1).and_return('foo blah')
allow(example).to receive(:f2).and_return('bar blah')
end
it 'sets payload to hash of names and example values' do
expect(request[:payload]).to eq(
'f1' => 'foo blah',
'f2' => 'bar blah'
)
end
end
end
context 'produces content' do
before do
metadata[:operation][:produces] = [ 'application/json', 'application/xml' ]
end
context "no 'Accept' value provided" do
it "sets 'HTTP_ACCEPT' header to first in list" do
expect(request[:headers]).to eq('HTTP_ACCEPT' => 'application/json')
end
end
context "explicit 'Accept' value provided" do
before do
allow(example).to receive(:'Accept').and_return('application/xml')
end
it "sets 'HTTP_ACCEPT' header to example value" do
expect(request[:headers]).to eq('HTTP_ACCEPT' => 'application/xml')
end
end
end
context 'apiKey' do
before do
swagger_doc[:securityDefinitions] = { apiKey: { type: :apiKey, name: 'api_key', in: key_location } }
metadata[:operation][:security] = [ apiKey: [] ]
allow(example).to receive(:api_key).and_return('foobar')
end
context 'in query' do
let(:key_location) { :query }
it 'adds name and example value to the query string' do
expect(request[:path]).to eq('/blogs?api_key=foobar')
end
end
context 'in header' do
let(:key_location) { :header }
it 'adds name and example value to the headers' do
expect(request[:headers]).to eq('api_key' => 'foobar')
end
end
end
context 'basic auth' do
before do
swagger_doc[:securityDefinitions] = { basic: { type: :basic } }
metadata[:operation][:security] = [ basic: [] ]
allow(example).to receive(:Authorization).and_return('Basic foobar')
end
it "sets 'HTTP_AUTHORIZATION' header to example value" do
expect(request[:headers]).to eq('HTTP_AUTHORIZATION' => 'Basic foobar')
end
end
context 'oauth2' do
before do
swagger_doc[:securityDefinitions] = { oauth2: { type: :oauth2, scopes: [ 'read:blogs' ] } }
metadata[:operation][:security] = [ oauth2: [ 'read:blogs' ] ]
allow(example).to receive(:Authorization).and_return('Bearer foobar')
end
it "sets 'HTTP_AUTHORIZATION' header to example value" do
expect(request[:headers]).to eq('HTTP_AUTHORIZATION' => 'Bearer foobar')
end
end
context "path-level parameters" do
before do
metadata[:operation][:parameters] = [ { name: 'q1', in: :query, type: :string } ]
metadata[:path_item][: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 "populates operation and path level parameters " do
expect(request[:path]).to eq('/blogs?q1=foo&q2=bar')
end
end
context 'referenced parameters' do
before do
swagger_doc[:parameters] = { 'q1' => { name: 'q1', in: :query, type: :string } }
metadata[:operation][:parameters] = [ { '$ref' => '#/parameters/q1' } ]
allow(example).to receive(:q1).and_return('foo')
end
it 'uses the referenced metadata to build the request' do
expect(request[:path]).to eq('/blogs?q1=foo')
end
end
context 'global basePath' do
before { global_metadata[:basePath] = '/foobar' }
before { swagger_doc[:basePath] = '/api' }
it 'prepends the basePath' do
expect(path).to eq('/foobar/blogs/1/comments/2')
it 'prepends to the path' do
expect(request[:path]).to eq('/api/blogs')
end
end
context "defined at the 'path' level" do
context "global consumes" do
before { swagger_doc[:consumes] = [ 'application/xml' ] }
it "defaults 'CONTENT_TYPE' to global value(s)" do
expect(request[:headers]).to eq('CONTENT_TYPE' => 'application/xml')
end
end
context "global security requirements" do
before do
api_metadata[:path_item][:parameters] = [ { name: :blog_id, in: :path } ]
api_metadata[:operation][:parameters] = [ { name: :id, in: :path } ]
swagger_doc[:securityDefinitions] = { apiKey: { type: :apiKey, name: 'api_key', in: :query } }
swagger_doc[:security] = [ apiKey: [] ]
allow(example).to receive(:api_key).and_return('foobar')
end
it "builds path from parameters defined at path and operation levels" do
expect(path).to eq('/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'
)
it 'applieds the scheme by default' do
expect(request[:path]).to eq('/blogs?api_key=foobar')
end
end
end

View File

@@ -4,102 +4,71 @@ module Rswag
module Specs
describe ResponseValidator do
subject { ResponseValidator.new(api_metadata, global_metadata) }
subject { ResponseValidator.new(config) }
let(:api_metadata) { { response: { code: 200 } } }
let(: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' } },
allow(config).to receive(:get_swagger_doc).and_return(swagger_doc)
end
let(:config) { double('config') }
let(:swagger_doc) { {} }
let(:example) { double('example') }
let(:metadata) do
{
response: {
code: 200,
headers: { 'X-Rate-Limit-Limit' => { type: :integer } },
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\"}") }
describe '#validate!(metadata, response)' do
let(:call) { subject.validate!(metadata, response) }
let(:response) do
OpenStruct.new(
code: '200',
headers: { 'X-Rate-Limit-Limit' => '10' },
body: "{\"text\":\"Some comment\"}"
)
end
context "response matches metadata" do
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
context "response code differs from metadata" do
before { response.code = '400' }
it { expect { call }.to raise_error /Expected response code/ }
end
context "referenced 'schema' provided" do
context "response headers differ from metadata" do
before { response.headers = {} }
it { expect { call }.to raise_error /Expected response header/ }
end
context "response body differs from metadata" do
before { response.body = "{\"foo\":\"Some comment\"}" }
it { expect { call }.to raise_error /Expected response body/ }
end
context 'referenced schemas' do
before do
api_metadata[:response][:schema] = { '$ref' => '#/definitions/author' }
global_metadata[:definitions] = {
author: {
type: 'object',
properties: { name: { type: 'string' } },
required: [ 'name' ]
swagger_doc[:definitions] = {
'blog' => {
type: :object,
properties: { foo: { type: :string } },
required: [ 'foo' ]
}
}
metadata[:response][:schema] = { '$ref' => '#/definitions/blog' }
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
context "'headers' provided" do
before do
api_metadata[:response][:headers] = {
'X-Rate-Limit-Limit' => {
description: 'The number of allowed requests in the current period',
type: 'integer'
},
'X-Rate-Limit-Remaining' => {
description: 'The number of remaining requests in the current period',
type: 'integer'
},
'X-Rate-Limit-Reset' => {
description: 'The number of seconds left in the current period',
type: 'integer'
}
}
end
context 'response code & body matches' do
let(:response) { OpenStruct.new(code: 200, body: '{}', headers: {
'X-Rate-Limit-Limit' => 1,
'X-Rate-Limit-Remaining' => 1,
'X-Rate-Limit-Reset' => 1
}) }
it { expect { call }.to_not raise_error }
end
context 'response code matches & body does not' do
let(:response) { OpenStruct.new(code: 200, body: '{}', headers: {
'X-Rate-Limit-Limit' => 1,
'X-Rate-Limit-Remaining' => 1
}) }
it { expect { call }.to raise_error UnexpectedResponse }
it 'uses the referenced schema to validate the response body' do
expect { call }.to raise_error /Expected response body/
end
end
end

View File

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

View File

@@ -5,7 +5,14 @@ module Rswag
initializer 'rswag-ui.initialize' do |app|
if app.config.respond_to?(:assets)
app.config.assets.precompile += [ 'swagger-ui/*' ]
app.config.assets.precompile += [
'swagger-ui/css/*',
'swagger-ui/fonts/*',
'swagger-ui/images/*',
'swagger-ui/lang/*',
'swagger-ui/lib/*',
'swagger-ui/swagger-ui.min.js'
]
end
end
end

View File

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

View File

@@ -1,12 +1,9 @@
$:.push File.expand_path("../lib", __FILE__)
# Maintain your gem's version:
require 'rswag/ui/version'
# Describe your gem and declare its dependencies:
Gem::Specification.new do |s|
s.name = "rswag-ui"
s.version = Rswag::Ui::VERSION
s.version = ENV['TRAVIS_TAG'] || '0.0.0'
s.authors = ["Richie Morris"]
s.email = ["domaindrivendev@gmail.com"]
s.homepage = "https://github.com/domaindrivendev/rswag"
@@ -16,5 +13,6 @@ Gem::Specification.new do |s|
s.files = Dir["{app,config,lib,vendor}/**/*"] + ["MIT-LICENSE", "Rakefile" ]
s.add_dependency "rails", ">= 3.1", "< 5.1"
s.add_dependency 'actionpack', '>=3.1', '< 6.0'
s.add_dependency 'railties', '>= 3.1', '< 6.0'
end

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,12 @@
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
protect_from_forgery with: :null_session
wrap_parameters format: [ :json ]
respond_to :json
rescue_from 'ActionController::UnknownFormat' do |ex|
head :not_acceptable
end
end

View File

@@ -0,0 +1,11 @@
class AuthTestsController < ApplicationController
# POST /auth-tests/basic
def basic
if authenticate_with_http_basic { |u, p| u == 'jsmith' && p == 'jspass' }
head :no_content
else
request_http_basic_authentication
end
end
end

View File

@@ -1,6 +1,6 @@
require 'fileutils'
class BlogsController < ApplicationController
wrap_parameters Blog
respond_to :json
# POST /blogs
def create
@@ -8,6 +8,14 @@ class BlogsController < ApplicationController
respond_with @blog
end
# Put /blogs/1
def upload
@blog = Blog.find_by_id(params[:id])
return head :not_found if @blog.nil?
@blog.thumbnail = save_uploaded_file params[:file]
head @blog.save ? :ok : :unprocsessible_entity
end
# GET /blogs
def index
@blogs = Blog.all
@@ -24,4 +32,13 @@ class BlogsController < ApplicationController
respond_with @blog, status: :not_found and return unless @blog
respond_with @blog
end
private
def save_uploaded_file(field)
return if field.nil?
file = File.join('tmp', field.original_filename)
FileUtils.cp field.tempfile.path, file
field.original_filename
end
end

View File

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

View File

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

View File

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

View File

@@ -1,67 +0,0 @@
TestApp::Application.configure do
# Settings specified here will take precedence over those in config/application.rb
# Code is not reloaded between requests
config.cache_classes = true
# Full error reports are disabled and caching is turned on
config.consider_all_requests_local = false
config.action_controller.perform_caching = true
# Disable Rails's static asset server (Apache or nginx will already do this)
config.serve_static_assets = true
# Compress JavaScripts and CSS
config.assets.compress = true
# Don't fallback to assets pipeline if a precompiled asset is missed
config.assets.compile = false
# Generate digests for assets URLs
config.assets.digest = true
# Defaults to nil and saved in location specified by config.assets.prefix
# config.assets.manifest = YOUR_PATH
# Specifies the header that your server uses for sending files
# config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache
# config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
# config.force_ssl = true
# See everything in the log (default is :info)
# config.log_level = :debug
# Prepend all log lines with the following tags
# config.log_tags = [ :subdomain, :uuid ]
# Use a different logger for distributed setups
# config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new)
# Use a different cache store in production
# config.cache_store = :mem_cache_store
# Enable serving of images, stylesheets, and JavaScripts from an asset server
# config.action_controller.asset_host = "http://assets.example.com"
# Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added)
# config.assets.precompile += %w( search.js )
# Disable delivery errors, bad email addresses will be ignored
# config.action_mailer.raise_delivery_errors = false
# Enable threaded mode
# config.threadsafe!
# Enable locale fallbacks for I18n (makes lookups for any locale fall back to
# the I18n.default_locale when a translation can not be found)
config.i18n.fallbacks = true
# Send deprecation notices to registered listeners
config.active_support.deprecation = :notify
# Log the query plan for queries taking more than this (works
# with SQLite, MySQL, and PostgreSQL)
# config.active_record.auto_explain_threshold_in_seconds = 0.5
end

View File

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

View File

@@ -5,3 +5,6 @@
# Make sure the secret is at least 30 characters and all random,
# no regular words or you'll be exposed to dictionary attacks.
TestApp::Application.config.secret_token = '60f36cd33756d73f362053f1d45256ae50d75440b634ae73b070a6e35a2df38692f59e28e5ecbd1f9f2e850255f6d29a468bc59ac4484c2b7f0548ddbfc1b870'
# See http://guides.rubyonrails.org/upgrading_ruby_on_rails.html#config-secrets-yml
TestApp::Application.config.secret_key_base = 'f6a820cc8aa76094583cd68ef46a735e25e3278648086355f8bd24721f036959c728c06a28dcecfe695f17ae2db44dfa1424f22b81377f2a1496d4e19f6f7faa'

View File

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

View File

@@ -1,8 +1,15 @@
class CreateBlogs < ActiveRecord::Migration
migration_class = if Gem::Version.new(Rails.version) >= Gem::Version.new("5.0")
ActiveRecord::Migration[4.2]
else
ActiveRecord::Migration
end
class CreateBlogs < migration_class
def change
create_table :blogs do |t|
t.string :title
t.text :content
t.string :thumbnail
t.timestamps
end

View File

@@ -1,4 +1,3 @@
# encoding: UTF-8
# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
@@ -9,15 +8,16 @@
# from scratch. The latter is a flawed and unsustainable approach (the more migrations
# you'll amass, the slower it'll run and the greater likelihood for issues).
#
# It's strongly recommended to check this file into your version control system.
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(:version => 20160218212104) do
ActiveRecord::Schema.define(version: 20160218212104) do
create_table "blogs", :force => true do |t|
create_table "blogs", force: :cascade do |t|
t.string "title"
t.text "content"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.string "thumbnail"
t.datetime "created_at"
t.datetime "updated_at"
end
end

BIN
test-app/spec/fixtures/thumbnail.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,22 @@
require 'swagger_helper'
describe 'Auth Tests API', type: :request, swagger_doc: 'v1/swagger.json' do
path '/auth-tests/basic' do
post 'Authenticates with basic auth' do
tags 'Auth Test'
operationId 'testBasicAuth'
security [ basic_auth: [] ]
response '204', 'Valid credentials' do
let(:Authorization) { "Basic #{::Base64.strict_encode64('jsmith:jspass')}" }
run_test!
end
response '401', 'Invalid credentials' do
let(:Authorization) { "Basic #{::Base64.strict_encode64('foo:bar')}" }
run_test!
end
end
end
end

View File

@@ -9,10 +9,12 @@ describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do
description 'Creates a new blog from provided data'
operationId 'createBlog'
consumes 'application/json'
parameter name: :blog, :in => :body, schema: { '$ref' => '#/definitions/blog' }
produces 'application/json'
parameter name: :blog, in: :body, schema: { '$ref' => '#/definitions/blog' }
let(:blog) { { title: 'foo', content: 'bar' } }
response '201', 'blog created' do
let(:blog) { { title: 'foo', content: 'bar' } }
run_test!
end
@@ -20,7 +22,9 @@ describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do
schema '$ref' => '#/definitions/errors_object'
let(:blog) { { title: 'foo' } }
run_test!
run_test! do |response|
expect(response.body).to include("can't be blank")
end
end
end
@@ -31,17 +35,24 @@ describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do
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
let(:keywords) { 'foo bar' }
response '406', 'unsupported accept header' do
let(:'Accept') { 'application/foo' }
run_test!
end
end
end
path '/blogs/{id}' do
parameter name: :id, :in => :path, :type => :string
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'
@@ -59,10 +70,10 @@ describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do
examples 'application/json' => {
id: 1,
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"
}
let(:blog) { Blog.create(title: 'foo', content: 'bar') }
let(:id) { blog.id }
run_test!
end
@@ -73,4 +84,24 @@ describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do
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 'upload 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

@@ -47,6 +47,10 @@ RSpec.configure do |config|
# triggering implicit auto-inclusion in groups with matching metadata.
config.shared_context_metadata_behavior = :apply_to_host_groups
config.after(:suite) do
File.delete("#{Rails.root}/tmp/thumbnail.png") if File.file?("#{Rails.root}/tmp/thumbnail.png")
end
# The settings below are suggested to provide a good initial experience
# with RSpec, but feel free to customize to your heart's content.
=begin

View File

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

View File

@@ -5,6 +5,30 @@
"version": "v1"
},
"paths": {
"/auth-tests/basic": {
"post": {
"summary": "Authenticates with basic auth",
"tags": [
"Auth Test"
],
"operationId": "testBasicAuth",
"security": [
{
"basic_auth": [
]
}
],
"responses": {
"204": {
"description": "Valid credentials"
},
"401": {
"description": "Invalid credentials"
}
}
}
},
"/blogs": {
"post": {
"summary": "Creates a blog",
@@ -16,6 +40,9 @@
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"parameters": [
{
"name": "blog",
@@ -55,14 +82,8 @@
}
],
"responses": {
"200": {
"description": "success",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/blog"
}
}
"406": {
"description": "unsupported accept header"
}
}
}
@@ -107,7 +128,8 @@
"application/json": {
"id": 1,
"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"
}
}
},
@@ -116,6 +138,40 @@
}
}
}
},
"/blogs/{id}/upload": {
"parameters": [
{
"name": "id",
"in": "path",
"type": "string",
"required": true
}
],
"put": {
"summary": "upload a blog thumbnail",
"tags": [
"Blogs"
],
"description": "Upload a thumbnail for specific blog by id",
"operationId": "uploadThumbnailBlog",
"consumes": [
"multipart/form-data"
],
"parameters": [
{
"name": "file",
"in": "formData",
"type": "file",
"required": true
}
],
"responses": {
"200": {
"description": "blog updated"
}
}
}
}
},
"definitions": {
@@ -146,17 +202,25 @@
"type": "string"
},
"content": {
"type": "string",
"x-nullable": true
},
"thumbnail": {
"type": "string"
}
},
"required": [
"id",
"title",
"content"
"content",
"thumbnail"
]
}
},
"securityDefinitions": {
"basic_auth": {
"type": "basic"
},
"api_key": {
"type": "apiKey",
"name": "api_key",