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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fbf70bfdf1bb5ead3794e4d64a1afb85e00864701ec0cf42416a063b522fe489
4
- data.tar.gz: ff39363f981f9a868e5afc47a00d87fbc629cda498118b024131318696e5e734
3
+ metadata.gz: d6672807e8e018237741224a8adc8c7103a001f2e03b57663771979864820593
4
+ data.tar.gz: 57b604d7d2211136046d61d93ede936f6bec0f162bc6424f97db81e2e71033cb
5
5
  SHA512:
6
- metadata.gz: e21b1bc504582ad44c5df4f24465a3751136ccd2e9114a95bc1aca95cc69b7a6110017c18faf3f2d854bdd596bf0612a56cb40803543c0a078143bc204083f37
7
- data.tar.gz: bf21e075a70eb3e3a8712b8ee081aad9358cfe6472d258bacca82565b7d32426023e1c779a8b3e00b5c3496629357749d26daa6713bdfdd3a461cb72b601c773
6
+ metadata.gz: 38dc2d2ef165069f34ad96289841a56bafee6b388777036349b36f782c18dad4241d278d1ee8911761a563d3c25ee2c70f434cbd502a7131b3d7e88abb62b786
7
+ data.tar.gz: 7cbb9dbd0073d29176af4a8bccd9534e5468f5c56db7e4ae04e37401bc377135b3f11821ffa5f2f955fe28c73d011460d6fc8adeb497c9618dc660c8a0892e03
data/Rakefile CHANGED
@@ -20,8 +20,4 @@ RDoc::Task.new(:rdoc) do |rdoc|
20
20
  rdoc.rdoc_files.include('lib/**/*.rb')
21
21
  end
22
22
 
23
-
24
-
25
-
26
23
  Bundler::GemHelper.install_tasks
27
-
@@ -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('../templates', __FILE__)
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('../templates', __FILE__)
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
 
@@ -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.reduce({}) do |tree, route|
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.kind_of? String
33
+ if verb.is_a? String
32
34
  verb.downcase
33
35
  else
34
36
  verb.source.gsub(/[$^]/, '').downcase
@@ -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
- [ :get, :post, :patch, :put, :delete, :head, :options, :trace ].each do |verb|
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
- [ :operationId, :deprecated, :security ].each do |attr_name|
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
- [ :tags, :consumes, :produces, :schemes ].each do |attr_name|
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.has_key?(:operation)
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
- params: request[:payload],
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
@@ -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('../../../tasks/rswag-specs_tasks.rake', __FILE__)
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 { |r| r.keys }
42
- schemes = (swagger_doc[:securityDefinitions] || {}).slice(*scheme_names).values
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.sub('#/parameters/', '').to_sym
52
- definitions = swagger_doc[:parameters]
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 |template|
108
+ request[:path] = template.tap do |path_template|
65
109
  parameters.select { |p| p[:in] == :path }.each do |p|
66
- template.gsub!("{#{p[:name]}}", example.send(p[:name]).to_s)
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
- template.concat(i == 0 ? '?' : '&')
71
- template.concat(build_query_string_part(p, example.send(p[:name])))
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.to_s}" unless param[:type].to_sym == :array
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| [ p[:name], example.send(p[:name]).to_s ] }
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?(:'Accept') ? example.send(:'Accept') : produces.first
103
- tuples << [ 'Accept', accept ]
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 << [ 'Content-Type', content_type ]
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
- when 'Accept' then 'HTTP_ACCEPT'
118
- when 'Content-Type' then 'CONTENT_TYPE'
119
- when 'Authorization' then 'HTTP_AUTHORIZATION'
120
- else pair[0]
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[ rackified_tuples ]
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 [ 'application/x-www-form-urlencoded', 'multipart/form-data' ].include?(content_type)
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| [ p[:name], example.send(p[:name]) ] }
148
- Hash[ tuples ]
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
- "Expected response code '#{response.code}' to match '#{expected}'\n" \
29
- "Response body: #{response.body}"
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(swagger_doc.slice(:definitions))
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
- metadata = notification.group.metadata
25
+ metadata = if RSPEC_VERSION > 2
26
+ notification.group.metadata
24
27
  else
25
- metadata = notification.metadata
28
+ notification.metadata
26
29
  end
27
30
 
28
- return unless metadata.has_key?(:response)
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(notification=nil)
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.exists?(dirname)
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
- clean_doc = JSON.parse(json_doc)
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,v| k == :code }
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,v| k == :verb }
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,v| k == :template }
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 = 'spec/requests/**/*_spec.rb, spec/api/**/*_spec.rb, spec/integration/**/*_spec.rb'
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 = [ '--format Rswag::Specs::SwaggerFormatter', '--dry-run', '--order defined' ]
16
+ t.rspec_opts = ['--format Rswag::Specs::SwaggerFormatter', '--dry-run', '--order defined']
13
17
  else
14
- t.rspec_opts = [ '--format Rswag::Specs::SwaggerFormatter', '--order defined' ]
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 :rswag => ['rswag:specs:swaggerize']
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.2.0
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: 2019-11-02 00:00:00.000000000 Z
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: '6.1'
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: '6.1'
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: '6.1'
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: '6.1'
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/domaindrivendev/rswag
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
- rubyforge_project:
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