rspec-openapi 0.16.1 → 0.18.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: c69f9d896be2ad115cb158175dbcf2c1cba9b6d08a7214d23a107a67ec5fc529
4
- data.tar.gz: a5af92611c4b47790f165ce6d861bfdedd1214f5dcd9c5641e618f272fe5a7ca
3
+ metadata.gz: c6e2096ff66c70fb288c3a18a0a9e14c47908ce55963f1cd22a26fa676f86858
4
+ data.tar.gz: 1e2baa5f03c45f80b26647ef2c623bf9af4ae281fd4305c682112786d509ef44
5
5
  SHA512:
6
- metadata.gz: 8e345e850fc539eb4e9fb018cdc48e7acadb47c774b47b438fcd1a95ebc680708999ae6d5440ee45f793bab47d27effd4e054fa3a08e25bf4e6956085effe9ea
7
- data.tar.gz: b1f47ba0a95186141f38465ac4e6a89636185606c48b8a0a67b7f163934c18181ea59e4c7fe94e54291aa821f662ee726437f350f69f872399063b024f7cbfde
6
+ metadata.gz: 13be21458d7e45bb8c2cba02dc89e125efccc1c56a2f2cb44e18bd607e859634bf8174e7ad650c920254ef0c649453f0f6ac38e0ed20e33b689a80e4f457a41e
7
+ data.tar.gz: e77910efe4dfb113fab8f022a7333aa81f1aae36004715ffdeef93706e77921f639ee743f143829943259d6ba562441ebfeb8fa8ade8d653416b2c755c8ee7f8
@@ -0,0 +1,24 @@
1
+ changelog:
2
+ exclude:
3
+ labels:
4
+ - ignore-for-release
5
+ authors:
6
+ - octocat
7
+ categories:
8
+ - title: 🛠 Breaking Changes
9
+ labels:
10
+ - semver-major
11
+ - breaking-change
12
+ - title: 🎉 Exciting New Features
13
+ labels:
14
+ - semver-minor
15
+ - enhancement
16
+ - title: 🐞 Bugfixes
17
+ labels:
18
+ - bug
19
+ - title: 📄 Documentation
20
+ labels:
21
+ - documentation
22
+ - title: 📦 Other Changes
23
+ labels:
24
+ - "*"
@@ -19,13 +19,12 @@ jobs:
19
19
  - ruby: ruby:2.7
20
20
  - ruby: ruby:3.0
21
21
  - ruby: ruby:3.1
22
- rails: 6.0.5
23
22
  - ruby: ruby:3.1
24
- rails: 6.1.6
23
+ rails: 6.1.7
25
24
  - ruby: ruby:3.1
26
- rails: 7.0.3
25
+ rails: 7.0.8
27
26
  - ruby: ruby:3.3
28
- rails: 7.1.2
27
+ rails: 7.1.3.2
29
28
  coverage: coverage
30
29
  env:
31
30
  RAILS_VERSION: ${{ matrix.rails == '' && '6.1.6' || matrix.rails }}
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-04-01 14:13:50 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: 49
12
+ Max: 47
13
13
 
14
- # Offense count: 2
14
+ # Offense count: 1
15
15
  # Configuration parameters: CountComments, CountAsOne.
16
16
  Metrics/ClassLength:
17
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
27
  Max: 36
28
28
 
29
- # Offense count: 4
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
@@ -5,7 +5,7 @@ source 'https://rubygems.org'
5
5
  # Specify your gem's dependencies in rspec-openapi.gemspec
6
6
  gemspec
7
7
 
8
- gem 'rails', ENV['RAILS_VERSION'] || '6.0.3.7'
8
+ gem 'rails', ENV['RAILS_VERSION'] || '6.0.6.1'
9
9
 
10
10
  if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('3.0.0')
11
11
  gem 'hanami', ENV['HANAMI_VERSION'] || '2.1.0'
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,120 @@
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 [ActionDispatch::Request] request
52
+ # @param [RSpec::Core::Example] example
53
+ # @return Array
54
+ def request_attributes(request, example)
55
+ route = Hanami.app.router.recognize(request.path, method: request.method)
56
+
57
+ return RSpec::OpenAPI::Extractors::Rack.request_attributes(request, example) unless route.routable?
58
+
59
+ metadata = example.metadata[:openapi] || {}
60
+ summary = metadata[:summary] || RSpec::OpenAPI.summary_builder.call(example)
61
+ tags = metadata[:tags] || RSpec::OpenAPI.tags_builder.call(example)
62
+ operation_id = metadata[:operation_id]
63
+ required_request_params = metadata[:required_request_params] || []
64
+ security = metadata[:security]
65
+ description = metadata[:description] || RSpec::OpenAPI.description_builder.call(example)
66
+ deprecated = metadata[:deprecated]
67
+ path = request.path
68
+
69
+ raw_path_params = route.params.filter { |_key, value| number_or_nil(value) }
70
+
71
+ result = InspectorAnalyzer.call(request.method, add_id(path, route))
72
+
73
+ summary ||= result[:summary]
74
+ tags ||= result[:tags]
75
+ path = add_openapi_id(path, route)
76
+
77
+ raw_path_params = raw_path_params.slice(*(raw_path_params.keys - RSpec::OpenAPI.ignored_path_params))
78
+
79
+ [path, summary, tags, operation_id, required_request_params, raw_path_params, description, security, deprecated]
80
+ end
81
+
82
+ # @param [RSpec::ExampleGroups::*] context
83
+ def request_response(context)
84
+ request = ActionDispatch::Request.new(context.last_request.env)
85
+ request.body.rewind if request.body.respond_to?(:rewind)
86
+ response = ActionDispatch::TestResponse.new(*context.last_response.to_a)
87
+
88
+ [request, response]
89
+ end
90
+
91
+ def add_id(path, route)
92
+ return path if route.params.empty?
93
+
94
+ route.params.each_pair do |key, value|
95
+ next unless number_or_nil(value)
96
+
97
+ path = path.sub("/#{value}", "/:#{key}")
98
+ end
99
+
100
+ path
101
+ end
102
+
103
+ def add_openapi_id(path, route)
104
+ return path if route.params.empty?
105
+
106
+ route.params.each_pair do |key, value|
107
+ next unless number_or_nil(value)
108
+
109
+ path = path.sub("/#{value}", "/{#{key}}")
110
+ end
111
+
112
+ path
113
+ end
114
+
115
+ def number_or_nil(string)
116
+ Integer(string || '')
117
+ rescue ArgumentError
118
+ nil
119
+ end
120
+ 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 [ActionDispatch::Request] request
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,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Extractor for rails
4
+ class << RSpec::OpenAPI::Extractors::Rails = Object.new
5
+ # @param [ActionDispatch::Request] request
6
+ # @param [RSpec::Core::Example] example
7
+ # @return Array
8
+ def request_attributes(request, example)
9
+ # 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
10
+ fixed_request = request.dup
11
+ fixed_request.path_info = File.join(request.script_name, request.path_info) if request.script_name.present?
12
+
13
+ route, path = find_rails_route(fixed_request)
14
+
15
+ raise "No route matched for #{fixed_request.request_method} #{fixed_request.path_info}" if route.nil?
16
+
17
+ return RSpec::OpenAPI::Extractors::Rack.request_attributes(request, example) unless path
18
+
19
+ metadata = example.metadata[:openapi] || {}
20
+ summary = metadata[:summary] || RSpec::OpenAPI.summary_builder.call(example)
21
+ tags = metadata[:tags] || RSpec::OpenAPI.tags_builder.call(example)
22
+ operation_id = metadata[:operation_id]
23
+ required_request_params = metadata[:required_request_params] || []
24
+ security = metadata[:security]
25
+ description = metadata[:description] || RSpec::OpenAPI.description_builder.call(example)
26
+ deprecated = metadata[:deprecated]
27
+ raw_path_params = request.path_parameters
28
+
29
+ summary ||= route.requirements[:action]
30
+ tags ||= [route.requirements[:controller]&.classify].compact
31
+ # :controller and :action always exist. :format is added when routes is configured as such.
32
+ # TODO: Use .except(:controller, :action, :format) when we drop support for Ruby 2.x
33
+ raw_path_params = raw_path_params.slice(*(raw_path_params.keys - RSpec::OpenAPI.ignored_path_params))
34
+
35
+ summary ||= "#{request.method} #{path}"
36
+
37
+ [path, summary, tags, operation_id, required_request_params, raw_path_params, description, security, deprecated]
38
+ end
39
+
40
+ # @param [RSpec::ExampleGroups::*] context
41
+ def request_response(context)
42
+ [context.request, context.response]
43
+ end
44
+
45
+ # @param [ActionDispatch::Request] request
46
+ def find_rails_route(request, app: Rails.application, path_prefix: '')
47
+ app.routes.router.recognize(request) do |route, parameters|
48
+ path = route.path.spec.to_s.delete_suffix('(.:format)')
49
+
50
+ if route.app.matches?(request)
51
+ if route.app.engine?
52
+ route, path = find_rails_route(request, app: route.app.app, path_prefix: path)
53
+ next if route.nil?
54
+ elsif path_prefix + path == add_id(request.path, parameters)
55
+ return [route, path_prefix + path]
56
+ else
57
+ return [route, nil]
58
+ end
59
+ return [route, path_prefix + path]
60
+ end
61
+ end
62
+
63
+ nil
64
+ end
65
+
66
+ def add_id(path, parameters)
67
+ parameters.each_pair do |key, value|
68
+ next unless number_or_nil(value)
69
+
70
+ path = path.sub("/#{value}", "/:#{key}")
71
+ end
72
+
73
+ path
74
+ end
75
+
76
+ def number_or_nil(string)
77
+ Integer(string || '')
78
+ rescue ArgumentError
79
+ nil
80
+ end
81
+ 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
@@ -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
14
  path, summary, tags, operation_id, required_request_params, raw_path_params, description, security, deprecated =
15
- extract_request_attributes(request, example)
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
 
@@ -69,71 +69,6 @@ class << RSpec::OpenAPI::RecordBuilder = Object.new
69
69
  [request_headers, response_headers]
70
70
  end
71
71
 
72
- def extract_request_attributes(request, example)
73
- metadata = example.metadata[:openapi] || {}
74
- summary = metadata[:summary] || RSpec::OpenAPI.summary_builder.call(example)
75
- tags = metadata[:tags] || RSpec::OpenAPI.tags_builder.call(example)
76
- operation_id = metadata[:operation_id]
77
- required_request_params = metadata[:required_request_params] || []
78
- security = metadata[:security]
79
- description = metadata[:description] || RSpec::OpenAPI.description_builder.call(example)
80
- deprecated = metadata[:deprecated]
81
- raw_path_params = request.path_parameters
82
- path = request.path
83
- if rails?
84
- # 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
85
- fixed_request = request.dup
86
- fixed_request.path_info = File.join(request.script_name, request.path_info) if request.script_name.present?
87
-
88
- route, path = find_rails_route(fixed_request)
89
- raise "No route matched for #{fixed_request.request_method} #{fixed_request.path_info}" if route.nil?
90
-
91
- path = path.delete_suffix('(.:format)')
92
- summary ||= route.requirements[:action]
93
- tags ||= [route.requirements[:controller]&.classify].compact
94
- # :controller and :action always exist. :format is added when routes is configured as such.
95
- # TODO: Use .except(:controller, :action, :format) when we drop support for Ruby 2.x
96
- raw_path_params = raw_path_params.slice(*(raw_path_params.keys - RSpec::OpenAPI.ignored_path_params))
97
- end
98
- summary ||= "#{request.method} #{path}"
99
- [path, summary, tags, operation_id, required_request_params, raw_path_params, description, security, deprecated]
100
- end
101
-
102
- def extract_request_response(context)
103
- if rack_test?(context)
104
- request = ActionDispatch::Request.new(context.last_request.env)
105
- request.body.rewind if request.body.respond_to?(:rewind)
106
- response = ActionDispatch::TestResponse.new(*context.last_response.to_a)
107
- else
108
- request = context.request
109
- response = context.response
110
- end
111
- [request, response]
112
- end
113
-
114
- def rails?
115
- defined?(Rails) && Rails.respond_to?(:application) && Rails.application
116
- end
117
-
118
- def rack_test?(context)
119
- defined?(Rack::Test::Methods) && context.class < Rack::Test::Methods
120
- end
121
-
122
- # @param [ActionDispatch::Request] request
123
- def find_rails_route(request, app: Rails.application, path_prefix: '')
124
- app.routes.router.recognize(request) do |route|
125
- path = route.path.spec.to_s
126
- if route.app.matches?(request)
127
- if route.app.engine?
128
- route, path = find_rails_route(request, app: route.app.app, path_prefix: path)
129
- next if route.nil?
130
- end
131
- return [route, path_prefix + path]
132
- end
133
- end
134
- nil
135
- end
136
-
137
72
  # workaround to get real request parameters
138
73
  # because ActionController::ParamsWrapper overwrites request_parameters
139
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
@@ -64,10 +64,8 @@ class << RSpec::OpenAPI::SchemaBuilder = Object.new
64
64
  end
65
65
 
66
66
  def build_parameters(record)
67
- parameters = []
68
-
69
- record.path_params.each do |key, value|
70
- parameters << {
67
+ path_params = record.path_params.map do |key, value|
68
+ {
71
69
  name: build_parameter_name(key, value),
72
70
  in: 'path',
73
71
  required: true,
@@ -76,8 +74,8 @@ class << RSpec::OpenAPI::SchemaBuilder = Object.new
76
74
  }.compact
77
75
  end
78
76
 
79
- record.query_params.each do |key, value|
80
- parameters << {
77
+ query_params = record.query_params.map do |key, value|
78
+ {
81
79
  name: build_parameter_name(key, value),
82
80
  in: 'query',
83
81
  required: record.required_request_params.include?(key),
@@ -86,8 +84,8 @@ class << RSpec::OpenAPI::SchemaBuilder = Object.new
86
84
  }.compact
87
85
  end
88
86
 
89
- record.request_headers.each do |key, value|
90
- parameters << {
87
+ header_params = record.request_headers.map do |key, value|
88
+ {
91
89
  name: build_parameter_name(key, value),
92
90
  in: 'header',
93
91
  required: true,
@@ -96,6 +94,8 @@ class << RSpec::OpenAPI::SchemaBuilder = Object.new
96
94
  }.compact
97
95
  end
98
96
 
97
+ parameters = path_params + query_params + header_params
98
+
99
99
  return nil if parameters.empty?
100
100
 
101
101
  parameters
@@ -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.1'
5
+ VERSION = '0.18.0'
6
6
  end
7
7
  end
data/lib/rspec/openapi.rb CHANGED
@@ -11,6 +11,29 @@ 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
+ if ENV['OPENAPI']
19
+ DEBUG_ENABLED = ['', '1', 'true'].include?(ENV['DEBUG']&.downcase)
20
+
21
+ begin
22
+ require 'hanami'
23
+ rescue LoadError
24
+ warn 'Hanami not detected' if DEBUG_ENABLED
25
+ else
26
+ require 'rspec/openapi/extractors/hanami'
27
+ end
28
+
29
+ begin
30
+ require 'rails'
31
+ rescue LoadError
32
+ warn 'Rails not detected' if DEBUG_ENABLED
33
+ else
34
+ require 'rspec/openapi/extractors/rails'
35
+ end
36
+ end
14
37
 
15
38
  require 'rspec/openapi/minitest_hooks' if Object.const_defined?('Minitest')
16
39
  require 'rspec/openapi/rspec_hooks' if ENV['OPENAPI'] && Object.const_defined?('RSpec')
@@ -33,6 +56,7 @@ module RSpec::OpenAPI
33
56
  @path_records = Hash.new { |h, k| h[k] = [] }
34
57
  @ignored_path_params = %i[controller action format]
35
58
  @ignored_paths = []
59
+ @post_process_hook = nil
36
60
 
37
61
  # This is the configuraion override file name we look for within each path.
38
62
  @config_filename = 'rspec_openapi.rb'
@@ -54,7 +78,8 @@ module RSpec::OpenAPI
54
78
  :response_headers,
55
79
  :path_records,
56
80
  :ignored_paths,
57
- :ignored_path_params
81
+ :ignored_path_params,
82
+ :post_process_hook
58
83
 
59
84
  attr_reader :config_filename
60
85
  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.1
4
+ version: 0.18.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-04-01 00:00:00.000000000 Z
12
+ date: 2024-04-17 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: actionpack
@@ -62,6 +62,7 @@ extensions: []
62
62
  extra_rdoc_files: []
63
63
  files:
64
64
  - ".github/dependabot.yml"
65
+ - ".github/release.yaml"
65
66
  - ".github/workflows/codeql-analysis.yml"
66
67
  - ".github/workflows/rubocop.yml"
67
68
  - ".github/workflows/test.yml"
@@ -80,6 +81,10 @@ files:
80
81
  - lib/rspec/openapi.rb
81
82
  - lib/rspec/openapi/components_updater.rb
82
83
  - lib/rspec/openapi/default_schema.rb
84
+ - lib/rspec/openapi/extractors.rb
85
+ - lib/rspec/openapi/extractors/hanami.rb
86
+ - lib/rspec/openapi/extractors/rack.rb
87
+ - lib/rspec/openapi/extractors/rails.rb
83
88
  - lib/rspec/openapi/hash_helper.rb
84
89
  - lib/rspec/openapi/key_transformer.rb
85
90
  - lib/rspec/openapi/minitest_hooks.rb
@@ -92,6 +97,7 @@ files:
92
97
  - lib/rspec/openapi/schema_file.rb
93
98
  - lib/rspec/openapi/schema_merger.rb
94
99
  - lib/rspec/openapi/schema_sorter.rb
100
+ - lib/rspec/openapi/shared_hooks.rb
95
101
  - lib/rspec/openapi/version.rb
96
102
  - rspec-openapi.gemspec
97
103
  - scripts/rspec
@@ -103,7 +109,7 @@ licenses:
103
109
  metadata:
104
110
  homepage_uri: https://github.com/exoego/rspec-openapi
105
111
  source_code_uri: https://github.com/exoego/rspec-openapi
106
- changelog_uri: https://github.com/exoego/rspec-openapi/releases/tag/v0.16.1
112
+ changelog_uri: https://github.com/exoego/rspec-openapi/releases/tag/v0.18.0
107
113
  rubygems_mfa_required: 'true'
108
114
  post_install_message:
109
115
  rdoc_options: []