rswag-specs 2.2.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Rakefile +0 -4
- data/lib/generators/rspec/swagger_generator.rb +3 -1
- data/lib/generators/rswag/specs/install/install_generator.rb +3 -2
- data/lib/generators/rswag/specs/install/templates/swagger_helper.rb +13 -1
- data/lib/rswag/route_parser.rb +4 -2
- data/lib/rswag/specs.rb +2 -1
- data/lib/rswag/specs/configuration.rb +11 -2
- data/lib/rswag/specs/example_group_helpers.rb +12 -8
- data/lib/rswag/specs/example_helpers.rb +4 -5
- data/lib/rswag/specs/extended_schema.rb +4 -3
- data/lib/rswag/specs/railtie.rb +3 -2
- data/lib/rswag/specs/request_factory.rb +70 -22
- data/lib/rswag/specs/response_validator.rb +23 -4
- data/lib/rswag/specs/swagger_formatter.rb +133 -11
- data/lib/tasks/rswag-specs_tasks.rake +9 -6
- metadata +10 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d6672807e8e018237741224a8adc8c7103a001f2e03b57663771979864820593
|
4
|
+
data.tar.gz: 57b604d7d2211136046d61d93ede936f6bec0f162bc6424f97db81e2e71033cb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 38dc2d2ef165069f34ad96289841a56bafee6b388777036349b36f782c18dad4241d278d1ee8911761a563d3c25ee2c70f434cbd502a7131b3d7e88abb62b786
|
7
|
+
data.tar.gz: 7cbb9dbd0073d29176af4a8bccd9534e5468f5c56db7e4ae04e37401bc377135b3f11821ffa5f2f955fe28c73d011460d6fc8adeb497c9618dc660c8a0892e03
|
data/Rakefile
CHANGED
@@ -1,9 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'rswag/route_parser'
|
2
4
|
require 'rails/generators'
|
3
5
|
|
4
6
|
module Rspec
|
5
7
|
class SwaggerGenerator < ::Rails::Generators::NamedBase
|
6
|
-
source_root File.expand_path('
|
8
|
+
source_root File.expand_path('templates', __dir__)
|
7
9
|
|
8
10
|
def setup
|
9
11
|
@routes = Rswag::RouteParser.new(controller_path).routes
|
@@ -1,10 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'rails/generators'
|
2
4
|
|
3
5
|
module Rswag
|
4
6
|
module Specs
|
5
|
-
|
6
7
|
class InstallGenerator < Rails::Generators::Base
|
7
|
-
source_root File.expand_path('
|
8
|
+
source_root File.expand_path('templates', __dir__)
|
8
9
|
|
9
10
|
def add_swagger_helper
|
10
11
|
template('swagger_helper.rb', 'spec/swagger_helper.rb')
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'rails_helper'
|
2
4
|
|
3
5
|
RSpec.configure do |config|
|
@@ -19,7 +21,17 @@ RSpec.configure do |config|
|
|
19
21
|
title: 'API V1',
|
20
22
|
version: 'v1'
|
21
23
|
},
|
22
|
-
paths: {}
|
24
|
+
paths: {},
|
25
|
+
servers: [
|
26
|
+
{
|
27
|
+
url: 'https://{defaultHost}',
|
28
|
+
variables: {
|
29
|
+
defaultHost: {
|
30
|
+
default: 'www.example.com'
|
31
|
+
}
|
32
|
+
}
|
33
|
+
}
|
34
|
+
]
|
23
35
|
}
|
24
36
|
}
|
25
37
|
|
data/lib/rswag/route_parser.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Rswag
|
2
4
|
class RouteParser
|
3
5
|
attr_reader :controller
|
@@ -9,7 +11,7 @@ module Rswag
|
|
9
11
|
def routes
|
10
12
|
::Rails.application.routes.routes.select do |route|
|
11
13
|
route.defaults[:controller] == controller
|
12
|
-
end.
|
14
|
+
end.each_with_object({}) do |tree, route|
|
13
15
|
path = path_from(route)
|
14
16
|
verb = verb_from(route)
|
15
17
|
tree[path] ||= { params: params_from(route), actions: {} }
|
@@ -28,7 +30,7 @@ module Rswag
|
|
28
30
|
|
29
31
|
def verb_from(route)
|
30
32
|
verb = route.verb
|
31
|
-
if verb.
|
33
|
+
if verb.is_a? String
|
32
34
|
verb.downcase
|
33
35
|
else
|
34
36
|
verb.source.gsub(/[$^]/, '').downcase
|
data/lib/rswag/specs.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'rspec/core'
|
2
4
|
require 'rswag/specs/example_group_helpers'
|
3
5
|
require 'rswag/specs/example_helpers'
|
@@ -6,7 +8,6 @@ require 'rswag/specs/railtie' if defined?(Rails::Railtie)
|
|
6
8
|
|
7
9
|
module Rswag
|
8
10
|
module Specs
|
9
|
-
|
10
11
|
# Extend RSpec with a swagger-based DSL
|
11
12
|
::RSpec.configure do |c|
|
12
13
|
c.add_setting :swagger_root
|
@@ -1,8 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Rswag
|
2
4
|
module Specs
|
3
|
-
|
4
5
|
class Configuration
|
5
|
-
|
6
6
|
def initialize(rspec_config)
|
7
7
|
@rspec_config = rspec_config
|
8
8
|
end
|
@@ -12,6 +12,7 @@ module Rswag
|
|
12
12
|
if @rspec_config.swagger_root.nil?
|
13
13
|
raise ConfigurationError, 'No swagger_root provided. See swagger_helper.rb'
|
14
14
|
end
|
15
|
+
|
15
16
|
@rspec_config.swagger_root
|
16
17
|
end
|
17
18
|
end
|
@@ -21,6 +22,7 @@ module Rswag
|
|
21
22
|
if @rspec_config.swagger_docs.nil? || @rspec_config.swagger_docs.empty?
|
22
23
|
raise ConfigurationError, 'No swagger_docs defined. See swagger_helper.rb'
|
23
24
|
end
|
25
|
+
|
24
26
|
@rspec_config.swagger_docs
|
25
27
|
end
|
26
28
|
end
|
@@ -35,6 +37,7 @@ module Rswag
|
|
35
37
|
@swagger_format ||= begin
|
36
38
|
@rspec_config.swagger_format = :json if @rspec_config.swagger_format.nil? || @rspec_config.swagger_format.empty?
|
37
39
|
raise ConfigurationError, "Unknown swagger_format '#{@rspec_config.swagger_format}'" unless [:json, :yaml].include?(@rspec_config.swagger_format)
|
40
|
+
|
38
41
|
@rspec_config.swagger_format
|
39
42
|
end
|
40
43
|
end
|
@@ -42,8 +45,14 @@ module Rswag
|
|
42
45
|
def get_swagger_doc(name)
|
43
46
|
return swagger_docs.values.first if name.nil?
|
44
47
|
raise ConfigurationError, "Unknown swagger_doc '#{name}'" unless swagger_docs[name]
|
48
|
+
|
45
49
|
swagger_docs[name]
|
46
50
|
end
|
51
|
+
|
52
|
+
def get_swagger_doc_version(name)
|
53
|
+
doc = get_swagger_doc(name)
|
54
|
+
doc[:openapi] || doc[:swagger]
|
55
|
+
end
|
47
56
|
end
|
48
57
|
|
49
58
|
class ConfigurationError < StandardError; end
|
@@ -1,20 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Rswag
|
2
4
|
module Specs
|
3
5
|
module ExampleGroupHelpers
|
4
|
-
|
5
|
-
def path(template, metadata={}, &block)
|
6
|
+
def path(template, metadata = {}, &block)
|
6
7
|
metadata[:path_item] = { template: template }
|
7
8
|
describe(template, metadata, &block)
|
8
9
|
end
|
9
10
|
|
10
|
-
[
|
11
|
+
[:get, :post, :patch, :put, :delete, :head, :options, :trace].each do |verb|
|
11
12
|
define_method(verb) do |summary, &block|
|
12
13
|
api_metadata = { operation: { verb: verb, summary: summary } }
|
13
14
|
describe(verb, api_metadata, &block)
|
14
15
|
end
|
15
16
|
end
|
16
17
|
|
17
|
-
[
|
18
|
+
[:operationId, :deprecated, :security].each do |attr_name|
|
18
19
|
define_method(attr_name) do |value|
|
19
20
|
metadata[:operation][attr_name] = value
|
20
21
|
end
|
@@ -23,13 +24,14 @@ module Rswag
|
|
23
24
|
# NOTE: 'description' requires special treatment because ExampleGroup already
|
24
25
|
# defines a method with that name. Provide an override that supports the existing
|
25
26
|
# functionality while also setting the appropriate metadata if applicable
|
26
|
-
def description(value=nil)
|
27
|
+
def description(value = nil)
|
27
28
|
return super() if value.nil?
|
29
|
+
|
28
30
|
metadata[:operation][:description] = value
|
29
31
|
end
|
30
32
|
|
31
33
|
# These are array properties - note the splat operator
|
32
|
-
[
|
34
|
+
[:tags, :consumes, :produces, :schemes].each do |attr_name|
|
33
35
|
define_method(attr_name) do |*value|
|
34
36
|
metadata[:operation][attr_name] = value
|
35
37
|
end
|
@@ -40,7 +42,7 @@ module Rswag
|
|
40
42
|
attributes[:required] = true
|
41
43
|
end
|
42
44
|
|
43
|
-
if metadata.
|
45
|
+
if metadata.key?(:operation)
|
44
46
|
metadata[:operation][:parameters] ||= []
|
45
47
|
metadata[:operation][:parameters] << attributes
|
46
48
|
else
|
@@ -49,7 +51,7 @@ module Rswag
|
|
49
51
|
end
|
50
52
|
end
|
51
53
|
|
52
|
-
def response(code, description, metadata={}, &block)
|
54
|
+
def response(code, description, metadata = {}, &block)
|
53
55
|
metadata[:response] = { code: code, description: description }
|
54
56
|
context(description, metadata, &block)
|
55
57
|
end
|
@@ -60,6 +62,7 @@ module Rswag
|
|
60
62
|
|
61
63
|
def header(name, attributes)
|
62
64
|
metadata[:response][:headers] ||= {}
|
65
|
+
|
63
66
|
metadata[:response][:headers][name] = attributes
|
64
67
|
end
|
65
68
|
|
@@ -68,6 +71,7 @@ module Rswag
|
|
68
71
|
# rspec-core ExampleGroup
|
69
72
|
def examples(example = nil)
|
70
73
|
return super() if example.nil?
|
74
|
+
|
71
75
|
metadata[:response][:examples] = example
|
72
76
|
end
|
73
77
|
|
@@ -1,10 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'rswag/specs/request_factory'
|
2
4
|
require 'rswag/specs/response_validator'
|
3
5
|
|
4
6
|
module Rswag
|
5
7
|
module Specs
|
6
8
|
module ExampleHelpers
|
7
|
-
|
8
9
|
def submit_request(metadata)
|
9
10
|
request = RequestFactory.new.build_request(metadata, self)
|
10
11
|
|
@@ -19,10 +20,8 @@ module Rswag
|
|
19
20
|
send(
|
20
21
|
request[:verb],
|
21
22
|
request[:path],
|
22
|
-
|
23
|
-
|
24
|
-
headers: request[:headers]
|
25
|
-
}
|
23
|
+
params: request[:payload],
|
24
|
+
headers: request[:headers]
|
26
25
|
)
|
27
26
|
end
|
28
27
|
end
|
@@ -1,9 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'json-schema'
|
2
4
|
|
3
5
|
module Rswag
|
4
6
|
module Specs
|
5
7
|
class ExtendedSchema < JSON::Schema::Draft4
|
6
|
-
|
7
8
|
def initialize
|
8
9
|
super
|
9
10
|
@attributes['type'] = ExtendedTypeAttribute
|
@@ -13,9 +14,9 @@ module Rswag
|
|
13
14
|
end
|
14
15
|
|
15
16
|
class ExtendedTypeAttribute < JSON::Schema::TypeV4Attribute
|
17
|
+
def self.validate(current_schema, data, fragments, processor, validator, options = {})
|
18
|
+
return if data.nil? && (current_schema.schema['nullable'] == true || current_schema.schema['x-nullable'] == true)
|
16
19
|
|
17
|
-
def self.validate(current_schema, data, fragments, processor, validator, options={})
|
18
|
-
return if data.nil? && current_schema.schema['x-nullable'] == true
|
19
20
|
super
|
20
21
|
end
|
21
22
|
end
|
data/lib/rswag/specs/railtie.rb
CHANGED
@@ -1,9 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Rswag
|
2
4
|
module Specs
|
3
5
|
class Railtie < ::Rails::Railtie
|
4
|
-
|
5
6
|
rake_tasks do
|
6
|
-
load File.expand_path('
|
7
|
+
load File.expand_path('../../tasks/rswag-specs_tasks.rake', __dir__)
|
7
8
|
end
|
8
9
|
|
9
10
|
generators do
|
@@ -1,11 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'active_support/core_ext/hash/slice'
|
2
4
|
require 'active_support/core_ext/hash/conversions'
|
3
5
|
require 'json'
|
6
|
+
require 'byebug'
|
4
7
|
|
5
8
|
module Rswag
|
6
9
|
module Specs
|
7
10
|
class RequestFactory
|
8
|
-
|
9
11
|
def initialize(config = ::Rswag::Specs.config)
|
10
12
|
@config = config
|
11
13
|
end
|
@@ -38,8 +40,8 @@ module Rswag
|
|
38
40
|
|
39
41
|
def derive_security_params(metadata, swagger_doc)
|
40
42
|
requirements = metadata[:operation][:security] || swagger_doc[:security] || []
|
41
|
-
scheme_names = requirements.flat_map
|
42
|
-
schemes = (swagger_doc
|
43
|
+
scheme_names = requirements.flat_map(&:keys)
|
44
|
+
schemes = security_version(scheme_names, swagger_doc)
|
43
45
|
|
44
46
|
schemes.map do |scheme|
|
45
47
|
param = (scheme[:type] == :apiKey) ? scheme.slice(:name, :in) : { name: 'Authorization', in: :header }
|
@@ -47,13 +49,55 @@ module Rswag
|
|
47
49
|
end
|
48
50
|
end
|
49
51
|
|
52
|
+
def security_version(scheme_names, swagger_doc)
|
53
|
+
if doc_version(swagger_doc).start_with?('2')
|
54
|
+
(swagger_doc[:securityDefinitions] || {}).slice(*scheme_names).values
|
55
|
+
else # Openapi3
|
56
|
+
if swagger_doc.key?(:securityDefinitions)
|
57
|
+
ActiveSupport::Deprecation.warn('Rswag::Specs: WARNING: securityDefinitions is replaced in OpenAPI3! Rename to components/securitySchemes (in swagger_helper.rb)')
|
58
|
+
swagger_doc[:components] ||= { securitySchemes: swagger_doc[:securityDefinitions] }
|
59
|
+
swagger_doc.delete(:securityDefinitions)
|
60
|
+
end
|
61
|
+
components = swagger_doc[:components] || {}
|
62
|
+
(components[:securitySchemes] || {}).slice(*scheme_names).values
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
50
66
|
def resolve_parameter(ref, swagger_doc)
|
51
|
-
key = ref
|
52
|
-
definitions = swagger_doc
|
67
|
+
key = key_version(ref, swagger_doc)
|
68
|
+
definitions = definition_version(swagger_doc)
|
53
69
|
raise "Referenced parameter '#{ref}' must be defined" unless definitions && definitions[key]
|
70
|
+
|
54
71
|
definitions[key]
|
55
72
|
end
|
56
73
|
|
74
|
+
def key_version(ref, swagger_doc)
|
75
|
+
if doc_version(swagger_doc).start_with?('2')
|
76
|
+
ref.sub('#/parameters/', '').to_sym
|
77
|
+
else # Openapi3
|
78
|
+
if ref.start_with?('#/parameters/')
|
79
|
+
ActiveSupport::Deprecation.warn('Rswag::Specs: WARNING: #/parameters/ refs are replaced in OpenAPI3! Rename to #/components/parameters/')
|
80
|
+
ref.sub('#/parameters/', '').to_sym
|
81
|
+
else
|
82
|
+
ref.sub('#/components/parameters/', '').to_sym
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def definition_version(swagger_doc)
|
88
|
+
if doc_version(swagger_doc).start_with?('2')
|
89
|
+
swagger_doc[:parameters]
|
90
|
+
else # Openapi3
|
91
|
+
if swagger_doc.key?(:parameters)
|
92
|
+
ActiveSupport::Deprecation.warn('Rswag::Specs: WARNING: parameters is replaced in OpenAPI3! Rename to components/parameters (in swagger_helper.rb)')
|
93
|
+
swagger_doc[:parameters]
|
94
|
+
else
|
95
|
+
components = swagger_doc[:components] || {}
|
96
|
+
components[:parameters]
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
57
101
|
def add_verb(request, metadata)
|
58
102
|
request[:verb] = metadata[:operation][:verb]
|
59
103
|
end
|
@@ -61,21 +105,21 @@ module Rswag
|
|
61
105
|
def add_path(request, metadata, swagger_doc, parameters, example)
|
62
106
|
template = (swagger_doc[:basePath] || '') + metadata[:path_item][:template]
|
63
107
|
|
64
|
-
request[:path] = template.tap do |
|
108
|
+
request[:path] = template.tap do |path_template|
|
65
109
|
parameters.select { |p| p[:in] == :path }.each do |p|
|
66
|
-
|
110
|
+
path_template.gsub!("{#{p[:name]}}", example.send(p[:name]).to_s)
|
67
111
|
end
|
68
112
|
|
69
113
|
parameters.select { |p| p[:in] == :query }.each_with_index do |p, i|
|
70
|
-
|
71
|
-
|
114
|
+
path_template.concat(i.zero? ? '?' : '&')
|
115
|
+
path_template.concat(build_query_string_part(p, example.send(p[:name])))
|
72
116
|
end
|
73
117
|
end
|
74
118
|
end
|
75
119
|
|
76
120
|
def build_query_string_part(param, value)
|
77
121
|
name = param[:name]
|
78
|
-
return "#{name}=#{value
|
122
|
+
return "#{name}=#{value}" unless param[:type].to_sym == :array
|
79
123
|
|
80
124
|
case param[:collectionFormat]
|
81
125
|
when :ssv
|
@@ -94,43 +138,43 @@ module Rswag
|
|
94
138
|
def add_headers(request, metadata, swagger_doc, parameters, example)
|
95
139
|
tuples = parameters
|
96
140
|
.select { |p| p[:in] == :header }
|
97
|
-
.map { |p| [
|
141
|
+
.map { |p| [p[:name], example.send(p[:name]).to_s] }
|
98
142
|
|
99
143
|
# Accept header
|
100
144
|
produces = metadata[:operation][:produces] || swagger_doc[:produces]
|
101
145
|
if produces
|
102
|
-
accept = example.respond_to?(:
|
103
|
-
tuples << [
|
146
|
+
accept = example.respond_to?(:Accept) ? example.send(:Accept) : produces.first
|
147
|
+
tuples << ['Accept', accept]
|
104
148
|
end
|
105
149
|
|
106
150
|
# Content-Type header
|
107
151
|
consumes = metadata[:operation][:consumes] || swagger_doc[:consumes]
|
108
152
|
if consumes
|
109
153
|
content_type = example.respond_to?(:'Content-Type') ? example.send(:'Content-Type') : consumes.first
|
110
|
-
tuples << [
|
154
|
+
tuples << ['Content-Type', content_type]
|
111
155
|
end
|
112
156
|
|
113
157
|
# Rails test infrastructure requires rackified headers
|
114
158
|
rackified_tuples = tuples.map do |pair|
|
115
159
|
[
|
116
160
|
case pair[0]
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
161
|
+
when 'Accept' then 'HTTP_ACCEPT'
|
162
|
+
when 'Content-Type' then 'CONTENT_TYPE'
|
163
|
+
when 'Authorization' then 'HTTP_AUTHORIZATION'
|
164
|
+
else pair[0]
|
121
165
|
end,
|
122
166
|
pair[1]
|
123
167
|
]
|
124
168
|
end
|
125
169
|
|
126
|
-
request[:headers] = Hash[
|
170
|
+
request[:headers] = Hash[rackified_tuples]
|
127
171
|
end
|
128
172
|
|
129
173
|
def add_payload(request, parameters, example)
|
130
174
|
content_type = request[:headers]['CONTENT_TYPE']
|
131
175
|
return if content_type.nil?
|
132
176
|
|
133
|
-
if [
|
177
|
+
if ['application/x-www-form-urlencoded', 'multipart/form-data'].include?(content_type)
|
134
178
|
request[:payload] = build_form_payload(parameters, example)
|
135
179
|
else
|
136
180
|
request[:payload] = build_json_payload(parameters, example)
|
@@ -144,14 +188,18 @@ module Rswag
|
|
144
188
|
# PROS: simple to implement, CONS: serialization/deserialization is bypassed in test
|
145
189
|
tuples = parameters
|
146
190
|
.select { |p| p[:in] == :formData }
|
147
|
-
.map { |p| [
|
148
|
-
Hash[
|
191
|
+
.map { |p| [p[:name], example.send(p[:name])] }
|
192
|
+
Hash[tuples]
|
149
193
|
end
|
150
194
|
|
151
195
|
def build_json_payload(parameters, example)
|
152
196
|
body_param = parameters.select { |p| p[:in] == :body }.first
|
153
197
|
body_param ? example.send(body_param[:name]).to_json : nil
|
154
198
|
end
|
199
|
+
|
200
|
+
def doc_version(doc)
|
201
|
+
doc[:openapi] || doc[:swagger] || '3'
|
202
|
+
end
|
155
203
|
end
|
156
204
|
end
|
157
205
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'active_support/core_ext/hash/slice'
|
2
4
|
require 'json-schema'
|
3
5
|
require 'json'
|
@@ -6,7 +8,6 @@ require 'rswag/specs/extended_schema'
|
|
6
8
|
module Rswag
|
7
9
|
module Specs
|
8
10
|
class ResponseValidator
|
9
|
-
|
10
11
|
def initialize(config = ::Rswag::Specs.config)
|
11
12
|
@config = config
|
12
13
|
end
|
@@ -25,8 +26,8 @@ module Rswag
|
|
25
26
|
expected = metadata[:response][:code].to_s
|
26
27
|
if response.code != expected
|
27
28
|
raise UnexpectedResponse,
|
28
|
-
|
29
|
-
|
29
|
+
"Expected response code '#{response.code}' to match '#{expected}'\n" \
|
30
|
+
"Response body: #{response.body}"
|
30
31
|
end
|
31
32
|
end
|
32
33
|
|
@@ -41,12 +42,30 @@ module Rswag
|
|
41
42
|
response_schema = metadata[:response][:schema]
|
42
43
|
return if response_schema.nil?
|
43
44
|
|
45
|
+
version = @config.get_swagger_doc_version(metadata[:swagger_doc])
|
46
|
+
schemas = definitions_or_component_schemas(swagger_doc, version)
|
47
|
+
|
44
48
|
validation_schema = response_schema
|
45
49
|
.merge('$schema' => 'http://tempuri.org/rswag/specs/extended_schema')
|
46
|
-
.merge(
|
50
|
+
.merge(schemas)
|
51
|
+
|
47
52
|
errors = JSON::Validator.fully_validate(validation_schema, body)
|
48
53
|
raise UnexpectedResponse, "Expected response body to match schema: #{errors[0]}" if errors.any?
|
49
54
|
end
|
55
|
+
|
56
|
+
def definitions_or_component_schemas(swagger_doc, version)
|
57
|
+
if version.start_with?('2')
|
58
|
+
swagger_doc.slice(:definitions)
|
59
|
+
else # Openapi3
|
60
|
+
if swagger_doc.key?(:definitions)
|
61
|
+
ActiveSupport::Deprecation.warn('Rswag::Specs: WARNING: definitions is replaced in OpenAPI3! Rename to components/schemas (in swagger_helper.rb)')
|
62
|
+
swagger_doc.slice(:definitions)
|
63
|
+
else
|
64
|
+
components = swagger_doc[:components] || {}
|
65
|
+
{ components: { schemas: components[:schemas] } }
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
50
69
|
end
|
51
70
|
|
52
71
|
class UnexpectedResponse < StandardError; end
|
@@ -1,9 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'active_support/core_ext/hash/deep_merge'
|
4
|
+
require 'rspec/core/formatters/base_text_formatter'
|
2
5
|
require 'swagger_helper'
|
3
6
|
|
4
7
|
module Rswag
|
5
8
|
module Specs
|
6
|
-
class SwaggerFormatter
|
9
|
+
class SwaggerFormatter < ::RSpec::Core::Formatters::BaseTextFormatter
|
7
10
|
|
8
11
|
# NOTE: rspec 2.x support
|
9
12
|
if RSPEC_VERSION > 2
|
@@ -19,22 +22,58 @@ module Rswag
|
|
19
22
|
|
20
23
|
def example_group_finished(notification)
|
21
24
|
# NOTE: rspec 2.x support
|
22
|
-
if RSPEC_VERSION > 2
|
23
|
-
|
25
|
+
metadata = if RSPEC_VERSION > 2
|
26
|
+
notification.group.metadata
|
24
27
|
else
|
25
|
-
|
28
|
+
notification.metadata
|
26
29
|
end
|
27
30
|
|
28
|
-
|
31
|
+
# !metadata[:document] won't work, since nil means we should generate
|
32
|
+
# docs.
|
33
|
+
return if metadata[:document] == false
|
34
|
+
return unless metadata.key?(:response)
|
35
|
+
|
29
36
|
swagger_doc = @config.get_swagger_doc(metadata[:swagger_doc])
|
37
|
+
|
38
|
+
unless doc_version(swagger_doc).start_with?('2')
|
39
|
+
# This is called multiple times per file!
|
40
|
+
# metadata[:operation] is also re-used between examples within file
|
41
|
+
# therefore be careful NOT to modify its content here.
|
42
|
+
upgrade_request_type!(metadata)
|
43
|
+
upgrade_servers!(swagger_doc)
|
44
|
+
upgrade_oauth!(swagger_doc)
|
45
|
+
upgrade_response_produces!(swagger_doc, metadata)
|
46
|
+
end
|
47
|
+
|
30
48
|
swagger_doc.deep_merge!(metadata_to_swagger(metadata))
|
31
49
|
end
|
32
50
|
|
33
|
-
def stop(
|
51
|
+
def stop(_notification = nil)
|
34
52
|
@config.swagger_docs.each do |url_path, doc|
|
53
|
+
unless doc_version(doc).start_with?('2')
|
54
|
+
doc[:paths]&.each_pair do |_k, v|
|
55
|
+
v.each_pair do |_verb, value|
|
56
|
+
is_hash = value.is_a?(Hash)
|
57
|
+
if is_hash && value.dig(:parameters)
|
58
|
+
schema_param = value.dig(:parameters)&.find { |p| (p[:in] == :body || p[:in] == :formData) && p[:schema] }
|
59
|
+
mime_list = value.dig(:consumes)
|
60
|
+
if value && schema_param && mime_list
|
61
|
+
value[:requestBody] = { content: {} } unless value.dig(:requestBody, :content)
|
62
|
+
mime_list.each do |mime|
|
63
|
+
value[:requestBody][:content][mime] = { schema: schema_param[:schema] }
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
value[:parameters].reject! { |p| p[:in] == :body || p[:in] == :formData }
|
68
|
+
end
|
69
|
+
remove_invalid_operation_keys!(value)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
35
74
|
file_path = File.join(@config.swagger_root, url_path)
|
36
75
|
dirname = File.dirname(file_path)
|
37
|
-
FileUtils.mkdir_p dirname unless File.
|
76
|
+
FileUtils.mkdir_p dirname unless File.exist?(dirname)
|
38
77
|
|
39
78
|
File.open(file_path, 'w') do |file|
|
40
79
|
file.write(pretty_generate(doc))
|
@@ -57,25 +96,108 @@ module Rswag
|
|
57
96
|
|
58
97
|
def yaml_prepare(doc)
|
59
98
|
json_doc = JSON.pretty_generate(doc)
|
60
|
-
|
99
|
+
JSON.parse(json_doc)
|
61
100
|
end
|
62
101
|
|
63
102
|
def metadata_to_swagger(metadata)
|
64
103
|
response_code = metadata[:response][:code]
|
65
|
-
response = metadata[:response].reject { |k,
|
104
|
+
response = metadata[:response].reject { |k, _v| k == :code }
|
66
105
|
|
67
106
|
verb = metadata[:operation][:verb]
|
68
107
|
operation = metadata[:operation]
|
69
|
-
.reject { |k,
|
108
|
+
.reject { |k, _v| k == :verb }
|
70
109
|
.merge(responses: { response_code => response })
|
71
110
|
|
72
111
|
path_template = metadata[:path_item][:template]
|
73
112
|
path_item = metadata[:path_item]
|
74
|
-
.reject { |k,
|
113
|
+
.reject { |k, _v| k == :template }
|
75
114
|
.merge(verb => operation)
|
76
115
|
|
77
116
|
{ paths: { path_template => path_item } }
|
78
117
|
end
|
118
|
+
|
119
|
+
def doc_version(doc)
|
120
|
+
doc[:openapi] || doc[:swagger] || '3'
|
121
|
+
end
|
122
|
+
|
123
|
+
def upgrade_response_produces!(swagger_doc, metadata)
|
124
|
+
# Accept header
|
125
|
+
mime_list = Array(metadata[:operation][:produces] || swagger_doc[:produces])
|
126
|
+
target_node = metadata[:response]
|
127
|
+
upgrade_content!(mime_list, target_node)
|
128
|
+
metadata[:response].delete(:schema)
|
129
|
+
end
|
130
|
+
|
131
|
+
def upgrade_content!(mime_list, target_node)
|
132
|
+
target_node.merge!(content: {})
|
133
|
+
schema = target_node[:schema]
|
134
|
+
return if mime_list.empty? || schema.nil?
|
135
|
+
|
136
|
+
mime_list.each do |mime_type|
|
137
|
+
# TODO upgrade to have content-type specific schema
|
138
|
+
target_node[:content][mime_type] = { schema: schema }
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def upgrade_request_type!(metadata)
|
143
|
+
# No deprecation here as it seems valid to allow type as a shorthand
|
144
|
+
operation_nodes = metadata[:operation][:parameters] || []
|
145
|
+
path_nodes = metadata[:path_item][:parameters] || []
|
146
|
+
header_node = metadata[:response][:headers] || {}
|
147
|
+
|
148
|
+
(operation_nodes + path_nodes + [header_node]).each do |node|
|
149
|
+
if node && node[:type] && node[:schema].nil?
|
150
|
+
node[:schema] = { type: node[:type] }
|
151
|
+
node.delete(:type)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def upgrade_servers!(swagger_doc)
|
157
|
+
return unless swagger_doc[:servers].nil? && swagger_doc.key?(:schemes)
|
158
|
+
|
159
|
+
ActiveSupport::Deprecation.warn('Rswag::Specs: WARNING: schemes, host, and basePath are replaced in OpenAPI3! Rename to array of servers[{url}] (in swagger_helper.rb)')
|
160
|
+
|
161
|
+
swagger_doc[:servers] = { urls: [] }
|
162
|
+
swagger_doc[:schemes].each do |scheme|
|
163
|
+
swagger_doc[:servers][:urls] << scheme + '://' + swagger_doc[:host] + swagger_doc[:basePath]
|
164
|
+
end
|
165
|
+
|
166
|
+
swagger_doc.delete(:schemes)
|
167
|
+
swagger_doc.delete(:host)
|
168
|
+
swagger_doc.delete(:basePath)
|
169
|
+
end
|
170
|
+
|
171
|
+
def upgrade_oauth!(swagger_doc)
|
172
|
+
# find flow in securitySchemes (securityDefinitions will have been re-written)
|
173
|
+
schemes = swagger_doc.dig(:components, :securitySchemes)
|
174
|
+
return unless schemes&.any? { |_k, v| v.key?(:flow) }
|
175
|
+
|
176
|
+
schemes.each do |name, v|
|
177
|
+
next unless v.key?(:flow)
|
178
|
+
|
179
|
+
ActiveSupport::Deprecation.warn("Rswag::Specs: WARNING: securityDefinitions flow is replaced in OpenAPI3! Rename to components/securitySchemes/#{name}/flows[] (in swagger_helper.rb)")
|
180
|
+
flow = swagger_doc[:components][:securitySchemes][name].delete(:flow).to_s
|
181
|
+
if flow == 'accessCode'
|
182
|
+
ActiveSupport::Deprecation.warn("Rswag::Specs: WARNING: securityDefinitions accessCode is replaced in OpenAPI3! Rename to clientCredentials (in swagger_helper.rb)")
|
183
|
+
flow = 'authorizationCode'
|
184
|
+
end
|
185
|
+
if flow == 'application'
|
186
|
+
ActiveSupport::Deprecation.warn("Rswag::Specs: WARNING: securityDefinitions application is replaced in OpenAPI3! Rename to authorizationCode (in swagger_helper.rb)")
|
187
|
+
flow = 'clientCredentials'
|
188
|
+
end
|
189
|
+
flow_elements = swagger_doc[:components][:securitySchemes][name].except(:type).each_with_object({}) do |(k, _v), a|
|
190
|
+
a[k] = swagger_doc[:components][:securitySchemes][name].delete(k)
|
191
|
+
end
|
192
|
+
swagger_doc[:components][:securitySchemes][name].merge!(flows: { flow => flow_elements })
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
def remove_invalid_operation_keys!(value)
|
197
|
+
is_hash = value.is_a?(Hash)
|
198
|
+
value.delete(:consumes) if is_hash && value.dig(:consumes)
|
199
|
+
value.delete(:produces) if is_hash && value.dig(:produces)
|
200
|
+
end
|
79
201
|
end
|
80
202
|
end
|
81
203
|
end
|
@@ -1,21 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'rspec/core/rake_task'
|
2
4
|
|
3
5
|
namespace :rswag do
|
4
6
|
namespace :specs do
|
5
|
-
|
6
7
|
desc 'Generate Swagger JSON files from integration specs'
|
7
8
|
RSpec::Core::RakeTask.new('swaggerize') do |t|
|
8
|
-
t.pattern =
|
9
|
+
t.pattern = ENV.fetch(
|
10
|
+
'PATTERN',
|
11
|
+
'spec/requests/**/*_spec.rb, spec/api/**/*_spec.rb, spec/integration/**/*_spec.rb'
|
12
|
+
)
|
9
13
|
|
10
14
|
# NOTE: rspec 2.x support
|
11
15
|
if Rswag::Specs::RSPEC_VERSION > 2 && Rswag::Specs.config.swagger_dry_run
|
12
|
-
t.rspec_opts = [
|
16
|
+
t.rspec_opts = ['--format Rswag::Specs::SwaggerFormatter', '--dry-run', '--order defined']
|
13
17
|
else
|
14
|
-
t.rspec_opts = [
|
18
|
+
t.rspec_opts = ['--format Rswag::Specs::SwaggerFormatter', '--order defined']
|
15
19
|
end
|
16
20
|
end
|
17
21
|
end
|
18
22
|
end
|
19
23
|
|
20
|
-
task :
|
21
|
-
|
24
|
+
task rswag: ['rswag:specs:swaggerize']
|
metadata
CHANGED
@@ -1,14 +1,16 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rswag-specs
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Richie Morris
|
8
|
+
- Greg Myers
|
9
|
+
- Jay Danielian
|
8
10
|
autorequire:
|
9
11
|
bindir: bin
|
10
12
|
cert_chain: []
|
11
|
-
date:
|
13
|
+
date: 2020-04-05 00:00:00.000000000 Z
|
12
14
|
dependencies:
|
13
15
|
- !ruby/object:Gem::Dependency
|
14
16
|
name: activesupport
|
@@ -19,7 +21,7 @@ dependencies:
|
|
19
21
|
version: '3.1'
|
20
22
|
- - "<"
|
21
23
|
- !ruby/object:Gem::Version
|
22
|
-
version: '
|
24
|
+
version: '7.0'
|
23
25
|
type: :runtime
|
24
26
|
prerelease: false
|
25
27
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -29,7 +31,7 @@ dependencies:
|
|
29
31
|
version: '3.1'
|
30
32
|
- - "<"
|
31
33
|
- !ruby/object:Gem::Version
|
32
|
-
version: '
|
34
|
+
version: '7.0'
|
33
35
|
- !ruby/object:Gem::Dependency
|
34
36
|
name: railties
|
35
37
|
requirement: !ruby/object:Gem::Requirement
|
@@ -39,7 +41,7 @@ dependencies:
|
|
39
41
|
version: '3.1'
|
40
42
|
- - "<"
|
41
43
|
- !ruby/object:Gem::Version
|
42
|
-
version: '
|
44
|
+
version: '7.0'
|
43
45
|
type: :runtime
|
44
46
|
prerelease: false
|
45
47
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -49,7 +51,7 @@ dependencies:
|
|
49
51
|
version: '3.1'
|
50
52
|
- - "<"
|
51
53
|
- !ruby/object:Gem::Version
|
52
|
-
version: '
|
54
|
+
version: '7.0'
|
53
55
|
- !ruby/object:Gem::Dependency
|
54
56
|
name: json-schema
|
55
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -91,7 +93,7 @@ files:
|
|
91
93
|
- lib/rswag/specs/response_validator.rb
|
92
94
|
- lib/rswag/specs/swagger_formatter.rb
|
93
95
|
- lib/tasks/rswag-specs_tasks.rake
|
94
|
-
homepage: https://github.com/
|
96
|
+
homepage: https://github.com/rswag/rswag
|
95
97
|
licenses:
|
96
98
|
- MIT
|
97
99
|
metadata: {}
|
@@ -110,8 +112,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
110
112
|
- !ruby/object:Gem::Version
|
111
113
|
version: '0'
|
112
114
|
requirements: []
|
113
|
-
|
114
|
-
rubygems_version: 2.7.8
|
115
|
+
rubygems_version: 3.0.6
|
115
116
|
signing_key:
|
116
117
|
specification_version: 4
|
117
118
|
summary: A Swagger-based DSL for rspec-rails & accompanying rake task for generating
|