rspec-openapi 0.16.0 → 0.17.0

Sign up to get free protection for your applications and to get access to all the features.
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: []