rspec-openapi 0.16.1 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/release.yaml +24 -0
- data/.github/workflows/test.yml +3 -4
- data/.rubocop_todo.yml +11 -10
- data/Gemfile +1 -1
- data/README.md +9 -2
- data/lib/rspec/openapi/extractors/hanami.rb +120 -0
- data/lib/rspec/openapi/extractors/rack.rb +31 -0
- data/lib/rspec/openapi/extractors/rails.rb +81 -0
- data/lib/rspec/openapi/extractors.rb +5 -0
- data/lib/rspec/openapi/minitest_hooks.rb +1 -1
- data/lib/rspec/openapi/record_builder.rb +3 -68
- data/lib/rspec/openapi/result_recorder.rb +16 -5
- data/lib/rspec/openapi/rspec_hooks.rb +1 -1
- data/lib/rspec/openapi/schema_builder.rb +8 -8
- data/lib/rspec/openapi/shared_hooks.rb +15 -0
- data/lib/rspec/openapi/version.rb +1 -1
- data/lib/rspec/openapi.rb +26 -1
- metadata +9 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c6e2096ff66c70fb288c3a18a0a9e14c47908ce55963f1cd22a26fa676f86858
|
4
|
+
data.tar.gz: 1e2baa5f03c45f80b26647ef2c623bf9af4ae281fd4305c682112786d509ef44
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
+
- "*"
|
data/.github/workflows/test.yml
CHANGED
@@ -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.
|
23
|
+
rails: 6.1.7
|
25
24
|
- ruby: ruby:3.1
|
26
|
-
rails: 7.0.
|
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-
|
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:
|
9
|
+
# Offense count: 13
|
10
10
|
# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes.
|
11
11
|
Metrics/AbcSize:
|
12
|
-
Max:
|
12
|
+
Max: 47
|
13
13
|
|
14
|
-
# Offense count:
|
14
|
+
# Offense count: 1
|
15
15
|
# Configuration parameters: CountComments, CountAsOne.
|
16
16
|
Metrics/ClassLength:
|
17
17
|
Max: 195
|
18
18
|
|
19
|
-
# Offense count:
|
19
|
+
# Offense count: 9
|
20
20
|
# Configuration parameters: AllowedMethods, AllowedPatterns.
|
21
21
|
Metrics/CyclomaticComplexity:
|
22
|
-
Max:
|
22
|
+
Max: 12
|
23
23
|
|
24
|
-
# Offense count:
|
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:
|
29
|
+
# Offense count: 5
|
30
30
|
# Configuration parameters: AllowedMethods, AllowedPatterns.
|
31
31
|
Metrics/PerceivedComplexity:
|
32
|
-
Max:
|
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:
|
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.
|
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 [](https://
|
1
|
+
# rspec-openapi [](https://rubygems.org/gems/rspec-openapi) [](https://github.com/exoego/rspec-openapi/actions/workflows/test.yml) [](https://codecov.io/gh/exoego/rspec-openapi) [](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
|
-

|
100
|
+

|
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
|
@@ -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 =
|
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
|
-
|
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
|
-
|
33
|
-
|
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
|
-
|
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.
|
80
|
-
|
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.
|
90
|
-
|
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
|
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.
|
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-
|
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.
|
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: []
|