rspec-openapi 0.16.0 → 0.17.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: 61aad8140991defb871eca3436054e43a6e933d6062ac813c987faf93aa767b5
4
- data.tar.gz: 8c3d02cf5be2e31131c1d855528f597b0bd604d306dba79e91c782448ae111a3
3
+ metadata.gz: '04058c6bcfd8f9ec499d5c7eb82da1e1123bfd9a666056384cda5a39aa70e4b7'
4
+ data.tar.gz: a4df891dbf2ba621f1cf3a78dbfcaab22da75583a40ea974935ad6e9aad1dfa0
5
5
  SHA512:
6
- metadata.gz: 2b879c9733c1704b94c2f5649de5a3200df3bbdac2b5cb76d668e94da32a45262bf40301d74359f30a86af7622851131243c8e95e3ffcfbc716bbd1d6765b47f
7
- data.tar.gz: 16f048fae7721a7986de3dc0a293a29f1986085c55cb6fc793d596826e3fedff0540de4ab94dca2a3363a3c233c58f7d2f63fdcf8ec66793eed4ae72fe237a48
6
+ metadata.gz: 9764345746f2c8133a18580eb1dffcf97709dd6e077b86e326f62604daffc856fd0c67e2735c7e8dbc16ae56828d9d3d9222cbc75f2f4a2e63789308df6ea4fd
7
+ data.tar.gz: e54bf06b9d2bd91952b56e91cc2cfcbae1cfb0998ab00a7019c4176b114a3652ef3e3323bf922530c2cf8e207742df6ff6fb6764be680ba8db7a8910bf260cdc
data/.rubocop.yml CHANGED
@@ -5,7 +5,7 @@ AllCops:
5
5
  SuggestExtensions: false
6
6
  TargetRubyVersion: 2.7
7
7
  Exclude:
8
- - 'spec/rails/**/*'
8
+ - 'spec/apps/**/*'
9
9
  - 'vendor/**/*'
10
10
 
11
11
  Style/TrailingCommaInHashLiteral:
data/.rubocop_todo.yml CHANGED
@@ -1,35 +1,35 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2024-03-25 05:35:37 UTC using RuboCop version 1.62.1.
3
+ # on 2024-04-12 14:06:44 UTC using RuboCop version 1.62.1.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
7
7
  # versions of RuboCop, may require this file to be generated again.
8
8
 
9
- # Offense count: 11
9
+ # Offense count: 13
10
10
  # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes.
11
11
  Metrics/AbcSize:
12
- Max: 48
12
+ Max: 47
13
13
 
14
- # Offense count: 2
14
+ # Offense count: 1
15
15
  # Configuration parameters: CountComments, CountAsOne.
16
16
  Metrics/ClassLength:
17
- Max: 207
17
+ Max: 195
18
18
 
19
- # Offense count: 8
19
+ # Offense count: 9
20
20
  # Configuration parameters: AllowedMethods, AllowedPatterns.
21
21
  Metrics/CyclomaticComplexity:
22
- Max: 13
22
+ Max: 12
23
23
 
24
- # Offense count: 19
24
+ # Offense count: 22
25
25
  # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
26
26
  Metrics/MethodLength:
27
- Max: 34
27
+ Max: 36
28
28
 
29
- # Offense count: 3
29
+ # Offense count: 5
30
30
  # Configuration parameters: AllowedMethods, AllowedPatterns.
31
31
  Metrics/PerceivedComplexity:
32
- Max: 13
32
+ Max: 12
33
33
 
34
34
  # Offense count: 1
35
35
  # Configuration parameters: EnforcedStyle, CheckMethodNames, CheckSymbols, AllowedIdentifiers, AllowedPatterns.
@@ -39,7 +39,7 @@ Naming/VariableNumber:
39
39
  Exclude:
40
40
  - 'spec/integration_tests/roda_test.rb'
41
41
 
42
- # Offense count: 6
42
+ # Offense count: 7
43
43
  # Configuration parameters: AllowedConstants.
44
44
  Style/Documentation:
45
45
  Exclude:
@@ -49,3 +49,4 @@ Style/Documentation:
49
49
  - 'lib/rspec/openapi/minitest_hooks.rb'
50
50
  - 'lib/rspec/openapi/result_recorder.rb'
51
51
  - 'lib/rspec/openapi/schema_file.rb'
52
+ - 'lib/rspec/openapi/shared_hooks.rb'
data/Gemfile CHANGED
@@ -6,7 +6,16 @@ source 'https://rubygems.org'
6
6
  gemspec
7
7
 
8
8
  gem 'rails', ENV['RAILS_VERSION'] || '6.0.3.7'
9
+
10
+ if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('3.0.0')
11
+ gem 'hanami', ENV['HANAMI_VERSION'] || '2.1.0'
12
+ gem 'hanami-controller', ENV['HANAMI_VERSION'] || '2.1.0'
13
+ gem 'hanami-router', ENV['HANAMI_VERSION'] || '2.1.0'
14
+ end
15
+
9
16
  gem 'roda'
17
+
18
+ gem 'rails-dom-testing', '~> 2.2'
10
19
  gem 'rspec-rails'
11
20
 
12
21
  group :test do
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # rspec-openapi [![Gem Version](https://badge.fury.io/rb/rspec-openapi.svg)](https://badge.fury.io/rb/rspec-openapi) [![test](https://github.com/exoego/rspec-openapi/actions/workflows/test.yml/badge.svg)](https://github.com/exoego/rspec-openapi/actions/workflows/test.yml) [![codecov](https://codecov.io/gh/exoego/rspec-openapi/branch/master/graph/badge.svg?token=egYm6AlxkD)](https://codecov.io/gh/exoego/rspec-openapi)
1
+ # rspec-openapi [![Gem Version](https://badge.fury.io/rb/rspec-openapi.svg)](https://rubygems.org/gems/rspec-openapi) [![test](https://github.com/exoego/rspec-openapi/actions/workflows/test.yml/badge.svg)](https://github.com/exoego/rspec-openapi/actions/workflows/test.yml) [![codecov](https://codecov.io/gh/exoego/rspec-openapi/branch/master/graph/badge.svg?token=egYm6AlxkD)](https://codecov.io/gh/exoego/rspec-openapi) [![Ruby-toolbox](https://img.shields.io/badge/ruby-toolbox-a61414?cacheSeconds=31536000)](https://www.ruby-toolbox.com/projects/rspec-openapi)
2
2
 
3
3
  Generate OpenAPI schema from RSpec request specs.
4
4
 
@@ -97,7 +97,7 @@ paths:
97
97
 
98
98
  and the schema file can be used as an input of [Swagger UI](https://github.com/swagger-api/swagger-ui) or [Redoc](https://github.com/Redocly/redoc).
99
99
 
100
- ![Redoc example](./spec/rails/doc/screenshot.png)
100
+ ![Redoc example](./spec/apps/rails/doc/screenshot.png)
101
101
 
102
102
 
103
103
  ### Configuration
@@ -190,6 +190,13 @@ RSpec::OpenAPI.ignored_path_params = %i[controller action format]
190
190
  # In that case, you can specify the paths to ignore.
191
191
  # String or Regexp is acceptable.
192
192
  RSpec::OpenAPI.ignored_paths = ["/admin/full/path/", Regexp.new("^/_internal/")]
193
+
194
+ # Your custom post-processing hook (like unrandomizing IDs)
195
+ RSpec::OpenAPI.post_process_hook = -> (path, records, spec) do
196
+ RSpec::OpenAPI::HashHelper.matched_paths(spec, 'paths.*.*.responses.*.content.*.*.*.id').each do |paths|
197
+ spec.dig(*paths[0..-2]).merge!(id: '123')
198
+ end
199
+ end
193
200
  ```
194
201
 
195
202
  ### Can I use rspec-openapi with `$ref` to minimize duplication of schema?
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry/inflector'
4
+ require 'hanami'
5
+
6
+ # https://github.com/hanami/router/blob/97f75b8529574bd4ff23165460e82a6587bc323c/lib/hanami/router/inspector.rb#L13
7
+ class Inspector
8
+ attr_accessor :routes, :inflector
9
+
10
+ def initialize(routes: [])
11
+ @routes = routes
12
+ @inflector = Dry::Inflector.new
13
+ end
14
+
15
+ def add_route(route)
16
+ routes.push(route)
17
+ end
18
+
19
+ def call(verb, path)
20
+ route = routes.find { |r| r.http_method == verb && r.path == path }
21
+
22
+ if route.to.is_a?(Proc)
23
+ {
24
+ tags: [],
25
+ summary: "#{verb} #{path}",
26
+ }
27
+ else
28
+ data = route.to.split('.')
29
+
30
+ {
31
+ tags: [inflector.classify(data[0])],
32
+ summary: data[1],
33
+ }
34
+ end
35
+ end
36
+ end
37
+
38
+ InspectorAnalyzer = Inspector.new
39
+
40
+ # Add default parameter to load inspector before test cases run
41
+ module InspectorAnalyzerPrepender
42
+ def router(inspector: InspectorAnalyzer)
43
+ super
44
+ end
45
+ end
46
+
47
+ Hanami::Slice::ClassMethods.prepend(InspectorAnalyzerPrepender)
48
+
49
+ # Extractor for hanami
50
+ class << RSpec::OpenAPI::Extractors::Hanami = Object.new
51
+ # @param [RSpec::ExampleGroups::*] context
52
+ # @param [RSpec::Core::Example] example
53
+ # @return Array
54
+ def request_attributes(request, example)
55
+ metadata = example.metadata[:openapi] || {}
56
+ summary = metadata[:summary] || RSpec::OpenAPI.summary_builder.call(example)
57
+ tags = metadata[:tags] || RSpec::OpenAPI.tags_builder.call(example)
58
+ operation_id = metadata[:operation_id]
59
+ required_request_params = metadata[:required_request_params] || []
60
+ security = metadata[:security]
61
+ description = metadata[:description] || RSpec::OpenAPI.description_builder.call(example)
62
+ deprecated = metadata[:deprecated]
63
+ path = request.path
64
+
65
+ route = Hanami.app.router.recognize(request.path, method: request.method)
66
+
67
+ raw_path_params = route.params.filter { |_key, value| number_or_nil(value) }
68
+
69
+ result = InspectorAnalyzer.call(request.method, add_id(path, route))
70
+
71
+ summary ||= result[:summary]
72
+ tags ||= result[:tags]
73
+ path = add_openapi_id(path, route)
74
+
75
+ raw_path_params = raw_path_params.slice(*(raw_path_params.keys - RSpec::OpenAPI.ignored_path_params))
76
+
77
+ [path, summary, tags, operation_id, required_request_params, raw_path_params, description, security, deprecated]
78
+ end
79
+
80
+ # @param [RSpec::ExampleGroups::*] context
81
+ def request_response(context)
82
+ request = ActionDispatch::Request.new(context.last_request.env)
83
+ request.body.rewind if request.body.respond_to?(:rewind)
84
+ response = ActionDispatch::TestResponse.new(*context.last_response.to_a)
85
+
86
+ [request, response]
87
+ end
88
+
89
+ def add_id(path, route)
90
+ return path if route.params.empty?
91
+
92
+ route.params.each_pair do |key, value|
93
+ next unless number_or_nil(value)
94
+
95
+ path = path.sub("/#{value}", "/:#{key}")
96
+ end
97
+
98
+ path
99
+ end
100
+
101
+ def add_openapi_id(path, route)
102
+ return path if route.params.empty?
103
+
104
+ route.params.each_pair do |key, value|
105
+ next unless number_or_nil(value)
106
+
107
+ path = path.sub("/#{value}", "/{#{key}}")
108
+ end
109
+
110
+ path
111
+ end
112
+
113
+ def number_or_nil(string)
114
+ Integer(string || '')
115
+ rescue ArgumentError
116
+ nil
117
+ end
118
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Extractor for rack
4
+ class << RSpec::OpenAPI::Extractors::Rack = Object.new
5
+ # @param [RSpec::ExampleGroups::*] context
6
+ # @param [RSpec::Core::Example] example
7
+ # @return Array
8
+ def request_attributes(request, example)
9
+ metadata = example.metadata[:openapi] || {}
10
+ summary = metadata[:summary] || RSpec::OpenAPI.summary_builder.call(example)
11
+ tags = metadata[:tags] || RSpec::OpenAPI.tags_builder.call(example)
12
+ operation_id = metadata[:operation_id]
13
+ required_request_params = metadata[:required_request_params] || []
14
+ security = metadata[:security]
15
+ description = metadata[:description] || RSpec::OpenAPI.description_builder.call(example)
16
+ deprecated = metadata[:deprecated]
17
+ raw_path_params = request.path_parameters
18
+ path = request.path
19
+ summary ||= "#{request.method} #{path}"
20
+ [path, summary, tags, operation_id, required_request_params, raw_path_params, description, security, deprecated]
21
+ end
22
+
23
+ # @param [RSpec::ExampleGroups::*] context
24
+ def request_response(context)
25
+ request = ActionDispatch::Request.new(context.last_request.env)
26
+ request.body.rewind if request.body.respond_to?(:rewind)
27
+ response = ActionDispatch::TestResponse.new(*context.last_response.to_a)
28
+
29
+ [request, response]
30
+ end
31
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Extractor for rails
4
+ class << RSpec::OpenAPI::Extractors::Rails = Object.new
5
+ # @param [RSpec::ExampleGroups::*] context
6
+ # @param [RSpec::Core::Example] example
7
+ # @return Array
8
+ def request_attributes(request, example)
9
+ metadata = example.metadata[:openapi] || {}
10
+ summary = metadata[:summary] || RSpec::OpenAPI.summary_builder.call(example)
11
+ tags = metadata[:tags] || RSpec::OpenAPI.tags_builder.call(example)
12
+ operation_id = metadata[:operation_id]
13
+ required_request_params = metadata[:required_request_params] || []
14
+ security = metadata[:security]
15
+ description = metadata[:description] || RSpec::OpenAPI.description_builder.call(example)
16
+ deprecated = metadata[:deprecated]
17
+ raw_path_params = request.path_parameters
18
+
19
+ # Reverse the destructive modification by Rails https://github.com/rails/rails/blob/v6.0.3.4/actionpack/lib/action_dispatch/journey/router.rb#L33-L41
20
+ fixed_request = request.dup
21
+ fixed_request.path_info = File.join(request.script_name, request.path_info) if request.script_name.present?
22
+
23
+ route, path = find_rails_route(fixed_request)
24
+ raise "No route matched for #{fixed_request.request_method} #{fixed_request.path_info}" if route.nil?
25
+
26
+ path = path.delete_suffix('(.:format)')
27
+ summary ||= route.requirements[:action]
28
+ tags ||= [route.requirements[:controller]&.classify].compact
29
+ # :controller and :action always exist. :format is added when routes is configured as such.
30
+ # TODO: Use .except(:controller, :action, :format) when we drop support for Ruby 2.x
31
+ raw_path_params = raw_path_params.slice(*(raw_path_params.keys - RSpec::OpenAPI.ignored_path_params))
32
+
33
+ summary ||= "#{request.method} #{path}"
34
+
35
+ [path, summary, tags, operation_id, required_request_params, raw_path_params, description, security, deprecated]
36
+ end
37
+
38
+ # @param [RSpec::ExampleGroups::*] context
39
+ def request_response(context)
40
+ [context.request, context.response]
41
+ end
42
+
43
+ # @param [ActionDispatch::Request] request
44
+ def find_rails_route(request, app: Rails.application, path_prefix: '')
45
+ app.routes.router.recognize(request) do |route|
46
+ path = route.path.spec.to_s
47
+ if route.app.matches?(request)
48
+ if route.app.engine?
49
+ route, path = find_rails_route(request, app: route.app.app, path_prefix: path)
50
+ next if route.nil?
51
+ end
52
+ return [route, path_prefix + path]
53
+ end
54
+ end
55
+
56
+ nil
57
+ end
58
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Create namespace
4
+ module RSpec::OpenAPI::Extractors
5
+ end
@@ -13,7 +13,7 @@ module RSpec::OpenAPI::Minitest
13
13
  human_name = name.sub(/^test_/, '').gsub('_', ' ')
14
14
  example = Example.new(self, human_name, {}, file_path)
15
15
  path = RSpec::OpenAPI.path.then { |p| p.is_a?(Proc) ? p.call(example) : p }
16
- record = RSpec::OpenAPI::RecordBuilder.build(self, example: example)
16
+ record = RSpec::OpenAPI::RecordBuilder.build(self, example: example, extractor: SharedHooks.find_extractor)
17
17
  RSpec::OpenAPI.path_records[path] << record if record
18
18
  end
19
19
  result
@@ -14,6 +14,7 @@ RSpec::OpenAPI::Record = Struct.new(
14
14
  :operation_id, # @param [String] - "request-1234"
15
15
  :description, # @param [String] - "returns a status"
16
16
  :security, # @param [Array] - [{securityScheme1: []}]
17
+ :deprecated, # @param [Boolean] - true
17
18
  :status, # @param [Integer] - 200
18
19
  :response_body, # @param [Object] - {"status" => "ok"}
19
20
  :response_headers, # @param [Array] - [["header_key1", "header_value1"], ["header_key2", "header_value2"]]
@@ -7,12 +7,12 @@ class << RSpec::OpenAPI::RecordBuilder = Object.new
7
7
  # @param [RSpec::ExampleGroups::*] context
8
8
  # @param [RSpec::Core::Example] example
9
9
  # @return [RSpec::OpenAPI::Record,nil]
10
- def build(context, example:)
11
- request, response = extract_request_response(context)
10
+ def build(context, example:, extractor:)
11
+ request, response = extractor.request_response(context)
12
12
  return if request.nil?
13
13
 
14
- path, summary, tags, operation_id, required_request_params, raw_path_params, description, security =
15
- extract_request_attributes(request, example)
14
+ path, summary, tags, operation_id, required_request_params, raw_path_params, description, security, deprecated =
15
+ extractor.request_attributes(request, example)
16
16
 
17
17
  return if RSpec::OpenAPI.ignored_paths.any? { |ignored_path| path.match?(ignored_path) }
18
18
 
@@ -32,6 +32,7 @@ class << RSpec::OpenAPI::RecordBuilder = Object.new
32
32
  operation_id: operation_id,
33
33
  description: description,
34
34
  security: security,
35
+ deprecated: deprecated,
35
36
  status: response.status,
36
37
  response_body: safe_parse_body(response, response.media_type),
37
38
  response_headers: response_headers,
@@ -54,7 +55,10 @@ class << RSpec::OpenAPI::RecordBuilder = Object.new
54
55
  def extract_headers(request, response)
55
56
  request_headers = RSpec::OpenAPI.request_headers.each_with_object([]) do |header, headers_arr|
56
57
  header_key = header.gsub('-', '_').upcase.to_sym
57
- header_value = request.get_header(['HTTP', header_key].join('_')) || request.get_header(header_key)
58
+
59
+ header_value = request.get_header(['HTTP', header_key].join('_')) ||
60
+ request.get_header(header_key) ||
61
+ request.get_header(header_key.to_s)
58
62
  headers_arr << [header, header_value] if header_value
59
63
  end
60
64
  response_headers = RSpec::OpenAPI.response_headers.each_with_object([]) do |header, headers_arr|
@@ -65,70 +69,6 @@ class << RSpec::OpenAPI::RecordBuilder = Object.new
65
69
  [request_headers, response_headers]
66
70
  end
67
71
 
68
- def extract_request_attributes(request, example)
69
- metadata = example.metadata[:openapi] || {}
70
- summary = metadata[:summary] || RSpec::OpenAPI.summary_builder.call(example)
71
- tags = metadata[:tags] || RSpec::OpenAPI.tags_builder.call(example)
72
- operation_id = metadata[:operation_id]
73
- required_request_params = metadata[:required_request_params] || []
74
- security = metadata[:security]
75
- description = metadata[:description] || RSpec::OpenAPI.description_builder.call(example)
76
- raw_path_params = request.path_parameters
77
- path = request.path
78
- if rails?
79
- # Reverse the destructive modification by Rails https://github.com/rails/rails/blob/v6.0.3.4/actionpack/lib/action_dispatch/journey/router.rb#L33-L41
80
- fixed_request = request.dup
81
- fixed_request.path_info = File.join(request.script_name, request.path_info) if request.script_name.present?
82
-
83
- route, path = find_rails_route(fixed_request)
84
- raise "No route matched for #{fixed_request.request_method} #{fixed_request.path_info}" if route.nil?
85
-
86
- path = path.delete_suffix('(.:format)')
87
- summary ||= route.requirements[:action]
88
- tags ||= [route.requirements[:controller]&.classify].compact
89
- # :controller and :action always exist. :format is added when routes is configured as such.
90
- # TODO: Use .except(:controller, :action, :format) when we drop support for Ruby 2.x
91
- raw_path_params = raw_path_params.slice(*(raw_path_params.keys - RSpec::OpenAPI.ignored_path_params))
92
- end
93
- summary ||= "#{request.method} #{path}"
94
- [path, summary, tags, operation_id, required_request_params, raw_path_params, description, security]
95
- end
96
-
97
- def extract_request_response(context)
98
- if rack_test?(context)
99
- request = ActionDispatch::Request.new(context.last_request.env)
100
- request.body.rewind if request.body.respond_to?(:rewind)
101
- response = ActionDispatch::TestResponse.new(*context.last_response.to_a)
102
- else
103
- request = context.request
104
- response = context.response
105
- end
106
- [request, response]
107
- end
108
-
109
- def rails?
110
- defined?(Rails) && Rails.respond_to?(:application) && Rails.application
111
- end
112
-
113
- def rack_test?(context)
114
- defined?(Rack::Test::Methods) && context.class < Rack::Test::Methods
115
- end
116
-
117
- # @param [ActionDispatch::Request] request
118
- def find_rails_route(request, app: Rails.application, path_prefix: '')
119
- app.routes.router.recognize(request) do |route|
120
- path = route.path.spec.to_s
121
- if route.app.matches?(request)
122
- if route.app.engine?
123
- route, path = find_rails_route(request, app: route.app.app, path_prefix: path)
124
- next if route.nil?
125
- end
126
- return [route, path_prefix + path]
127
- end
128
- end
129
- nil
130
- end
131
-
132
72
  # workaround to get real request parameters
133
73
  # because ActionController::ParamsWrapper overwrites request_parameters
134
74
  def raw_request_params(request)
@@ -29,11 +29,8 @@ class RSpec::OpenAPI::ResultRecorder
29
29
  rescue StandardError, NotImplementedError => e # e.g. SchemaBuilder raises a NotImplementedError
30
30
  @error_records[e] = record # Avoid failing the build
31
31
  end
32
- RSpec::OpenAPI::SchemaCleaner.cleanup_conflicting_security_parameters!(spec)
33
- RSpec::OpenAPI::SchemaCleaner.cleanup!(spec, new_from_zero)
34
- RSpec::OpenAPI::ComponentsUpdater.update!(spec, new_from_zero)
35
- RSpec::OpenAPI::SchemaCleaner.cleanup_empty_required_array!(spec)
36
- RSpec::OpenAPI::SchemaSorter.deep_sort!(spec)
32
+ cleanup_schema!(new_from_zero, spec)
33
+ execute_post_process_hook(path, records, spec)
37
34
  end
38
35
  end
39
36
  end
@@ -49,4 +46,18 @@ class RSpec::OpenAPI::ResultRecorder
49
46
  #{@error_records.map { |e, record| "#{e.inspect}: #{record.inspect}" }.join("\n")}
50
47
  ERR_MSG
51
48
  end
49
+
50
+ private
51
+
52
+ def execute_post_process_hook(path, records, spec)
53
+ RSpec::OpenAPI.post_process_hook.call(path, records, spec) if RSpec::OpenAPI.post_process_hook.is_a?(Proc)
54
+ end
55
+
56
+ def cleanup_schema!(new_from_zero, spec)
57
+ RSpec::OpenAPI::SchemaCleaner.cleanup_conflicting_security_parameters!(spec)
58
+ RSpec::OpenAPI::SchemaCleaner.cleanup!(spec, new_from_zero)
59
+ RSpec::OpenAPI::ComponentsUpdater.update!(spec, new_from_zero)
60
+ RSpec::OpenAPI::SchemaCleaner.cleanup_empty_required_array!(spec)
61
+ RSpec::OpenAPI::SchemaSorter.deep_sort!(spec)
62
+ end
52
63
  end
@@ -5,7 +5,7 @@ require 'rspec/core'
5
5
  RSpec.configuration.after(:each) do |example|
6
6
  if RSpec::OpenAPI.example_types.include?(example.metadata[:type]) && example.metadata[:openapi] != false
7
7
  path = RSpec::OpenAPI.path.then { |p| p.is_a?(Proc) ? p.call(example) : p }
8
- record = RSpec::OpenAPI::RecordBuilder.build(self, example: example)
8
+ record = RSpec::OpenAPI::RecordBuilder.build(self, example: example, extractor: SharedHooks.find_extractor)
9
9
  RSpec::OpenAPI.path_records[path] << record if record
10
10
  end
11
11
  end
@@ -34,6 +34,7 @@ class << RSpec::OpenAPI::SchemaBuilder = Object.new
34
34
  tags: record.tags,
35
35
  operationId: record.operation_id,
36
36
  security: record.security,
37
+ deprecated: record.deprecated ? true : nil,
37
38
  parameters: build_parameters(record),
38
39
  requestBody: http_method == 'get' ? nil : build_request_body(record),
39
40
  responses: {
@@ -53,7 +53,8 @@ class << RSpec::OpenAPI::SchemaMerger = Object.new
53
53
  def merge_parameters(base, key, value)
54
54
  all_parameters = value | base[key]
55
55
 
56
- unique_base_parameters = base[key].index_by { |parameter| [parameter[:name], parameter[:in]] }
56
+ unique_base_parameters = build_unique_params(base, key)
57
+
57
58
  all_parameters = all_parameters.map do |parameter|
58
59
  base_parameter = unique_base_parameters[[parameter[:name], parameter[:in]]] || {}
59
60
  base_parameter ? base_parameter.merge(parameter) : parameter
@@ -63,6 +64,12 @@ class << RSpec::OpenAPI::SchemaMerger = Object.new
63
64
  base[key] = all_parameters
64
65
  end
65
66
 
67
+ def build_unique_params(base, key)
68
+ base[key].each_with_object({}) do |parameter, hash|
69
+ hash[[parameter[:name], parameter[:in]]] = parameter
70
+ end
71
+ end
72
+
66
73
  SIMILARITY_THRESHOLD = 0.5
67
74
 
68
75
  def merge_closest_match!(options, spec)
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SharedHooks
4
+ def self.find_extractor
5
+ if defined?(Rails) && Rails.respond_to?(:application) && Rails.application
6
+ RSpec::OpenAPI::Extractors::Rails
7
+ elsif defined?(Hanami) && Hanami.respond_to?(:app) && Hanami.app?
8
+ RSpec::OpenAPI::Extractors::Hanami
9
+ # elsif defined?(Roda)
10
+ # some Roda extractor
11
+ else
12
+ RSpec::OpenAPI::Extractors::Rack
13
+ end
14
+ end
15
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RSpec
4
4
  module OpenAPI
5
- VERSION = '0.16.0'
5
+ VERSION = '0.17.0'
6
6
  end
7
7
  end
data/lib/rspec/openapi.rb CHANGED
@@ -11,6 +11,23 @@ require 'rspec/openapi/schema_merger'
11
11
  require 'rspec/openapi/schema_cleaner'
12
12
  require 'rspec/openapi/schema_sorter'
13
13
  require 'rspec/openapi/key_transformer'
14
+ require 'rspec/openapi/shared_hooks'
15
+ require 'rspec/openapi/extractors'
16
+ require 'rspec/openapi/extractors/rack'
17
+
18
+ begin
19
+ require 'hanami'
20
+ require 'rspec/openapi/extractors/hanami'
21
+ rescue LoadError
22
+ puts 'Hanami not detected'
23
+ end
24
+
25
+ begin
26
+ require 'rails'
27
+ require 'rspec/openapi/extractors/rails'
28
+ rescue LoadError
29
+ puts 'Rails not detected'
30
+ end
14
31
 
15
32
  require 'rspec/openapi/minitest_hooks' if Object.const_defined?('Minitest')
16
33
  require 'rspec/openapi/rspec_hooks' if ENV['OPENAPI'] && Object.const_defined?('RSpec')
@@ -33,6 +50,7 @@ module RSpec::OpenAPI
33
50
  @path_records = Hash.new { |h, k| h[k] = [] }
34
51
  @ignored_path_params = %i[controller action format]
35
52
  @ignored_paths = []
53
+ @post_process_hook = nil
36
54
 
37
55
  # This is the configuraion override file name we look for within each path.
38
56
  @config_filename = 'rspec_openapi.rb'
@@ -54,7 +72,8 @@ module RSpec::OpenAPI
54
72
  :response_headers,
55
73
  :path_records,
56
74
  :ignored_paths,
57
- :ignored_path_params
75
+ :ignored_path_params,
76
+ :post_process_hook
58
77
 
59
78
  attr_reader :config_filename
60
79
  end
@@ -29,6 +29,7 @@ Gem::Specification.new do |spec|
29
29
  spec.require_paths = ['lib']
30
30
 
31
31
  spec.add_dependency 'actionpack', '>= 5.2.0'
32
+ spec.add_dependency 'rails-dom-testing'
32
33
  spec.add_dependency 'rspec-core'
33
34
  spec.metadata['rubygems_mfa_required'] = 'true'
34
35
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rspec-openapi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.16.0
4
+ version: 0.17.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Takashi Kokubun
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2024-03-28 00:00:00.000000000 Z
12
+ date: 2024-04-12 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: actionpack
@@ -25,6 +25,20 @@ dependencies:
25
25
  - - ">="
26
26
  - !ruby/object:Gem::Version
27
27
  version: 5.2.0
28
+ - !ruby/object:Gem::Dependency
29
+ name: rails-dom-testing
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: '0'
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
28
42
  - !ruby/object:Gem::Dependency
29
43
  name: rspec-core
30
44
  requirement: !ruby/object:Gem::Requirement
@@ -66,6 +80,10 @@ files:
66
80
  - lib/rspec/openapi.rb
67
81
  - lib/rspec/openapi/components_updater.rb
68
82
  - lib/rspec/openapi/default_schema.rb
83
+ - lib/rspec/openapi/extractors.rb
84
+ - lib/rspec/openapi/extractors/hanami.rb
85
+ - lib/rspec/openapi/extractors/rack.rb
86
+ - lib/rspec/openapi/extractors/rails.rb
69
87
  - lib/rspec/openapi/hash_helper.rb
70
88
  - lib/rspec/openapi/key_transformer.rb
71
89
  - lib/rspec/openapi/minitest_hooks.rb
@@ -78,6 +96,7 @@ files:
78
96
  - lib/rspec/openapi/schema_file.rb
79
97
  - lib/rspec/openapi/schema_merger.rb
80
98
  - lib/rspec/openapi/schema_sorter.rb
99
+ - lib/rspec/openapi/shared_hooks.rb
81
100
  - lib/rspec/openapi/version.rb
82
101
  - rspec-openapi.gemspec
83
102
  - scripts/rspec
@@ -89,7 +108,7 @@ licenses:
89
108
  metadata:
90
109
  homepage_uri: https://github.com/exoego/rspec-openapi
91
110
  source_code_uri: https://github.com/exoego/rspec-openapi
92
- changelog_uri: https://github.com/exoego/rspec-openapi/releases/tag/v0.16.0
111
+ changelog_uri: https://github.com/exoego/rspec-openapi/releases/tag/v0.17.0
93
112
  rubygems_mfa_required: 'true'
94
113
  post_install_message:
95
114
  rdoc_options: []