rspec-openapi 0.19.0 → 0.21.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/workflows/codeql-analysis.yml +4 -4
- data/.github/workflows/rubocop.yml +2 -2
- data/.github/workflows/test.yml +1 -1
- data/.gitignore +2 -0
- data/README.md +6 -0
- data/lib/rspec/openapi/extractors/hanami.rb +21 -1
- data/lib/rspec/openapi/extractors/rack.rb +21 -1
- data/lib/rspec/openapi/extractors/rails.rb +21 -1
- data/lib/rspec/openapi/schema_builder.rb +69 -1
- data/lib/rspec/openapi/schema_file.rb +9 -1
- data/lib/rspec/openapi/version.rb +1 -1
- metadata +4 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6b40fcb7598d29cf3c8bf2f95f6aafb5a011ddbb2beff06d6366052d11effa02
|
|
4
|
+
data.tar.gz: 85bd479d5b7e4da9e19dcc8bbbb963a594eed99437cb911a965695d04768a4bd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 89c7e3788cfccd022711d1ce76be3ee1b1568af4bade87908afb3384e64e88714a0d4b30499f0eeebb45909fe5eab5267c96afc231ea1db60a6b2fe8fc0155d6
|
|
7
|
+
data.tar.gz: 71e418493c5f3db1c8a5388bd2826fe36e15375445d3ddc7c1b2364cdfb7d160cdf3c9b02554e6134abf82e0f994355b725223c773857fb034e3b7eb034c2db7
|
|
@@ -25,15 +25,15 @@ jobs:
|
|
|
25
25
|
|
|
26
26
|
steps:
|
|
27
27
|
- name: Checkout repository
|
|
28
|
-
uses: actions/checkout@
|
|
28
|
+
uses: actions/checkout@v5
|
|
29
29
|
|
|
30
30
|
- name: Initialize CodeQL
|
|
31
|
-
uses: github/codeql-action/init@
|
|
31
|
+
uses: github/codeql-action/init@v4
|
|
32
32
|
with:
|
|
33
33
|
languages: ${{ matrix.language }}
|
|
34
34
|
|
|
35
35
|
- name: Autobuild
|
|
36
|
-
uses: github/codeql-action/autobuild@
|
|
36
|
+
uses: github/codeql-action/autobuild@v4
|
|
37
37
|
|
|
38
38
|
- name: Perform CodeQL Analysis
|
|
39
|
-
uses: github/codeql-action/analyze@
|
|
39
|
+
uses: github/codeql-action/analyze@v4
|
|
@@ -14,7 +14,7 @@ jobs:
|
|
|
14
14
|
|
|
15
15
|
steps:
|
|
16
16
|
- name: Checkout repository
|
|
17
|
-
uses: actions/checkout@
|
|
17
|
+
uses: actions/checkout@v5
|
|
18
18
|
|
|
19
19
|
- name: Set up Ruby
|
|
20
20
|
uses: ruby/setup-ruby@v1
|
|
@@ -30,6 +30,6 @@ jobs:
|
|
|
30
30
|
"
|
|
31
31
|
|
|
32
32
|
- name: Upload Sarif output
|
|
33
|
-
uses: github/codeql-action/upload-sarif@
|
|
33
|
+
uses: github/codeql-action/upload-sarif@v4
|
|
34
34
|
with:
|
|
35
35
|
sarif_file: rubocop.sarif
|
data/.github/workflows/test.yml
CHANGED
|
@@ -32,7 +32,7 @@ jobs:
|
|
|
32
32
|
RAILS_VERSION: ${{ matrix.rails == '' && '6.1.6' || matrix.rails }}
|
|
33
33
|
COVERAGE: ${{ matrix.coverage || '' }}
|
|
34
34
|
steps:
|
|
35
|
-
- uses: actions/checkout@
|
|
35
|
+
- uses: actions/checkout@v5
|
|
36
36
|
- name: bundle install
|
|
37
37
|
run: bundle install -j$(nproc) --retry 3
|
|
38
38
|
- run: bundle exec rspec
|
data/.gitignore
CHANGED
data/README.md
CHANGED
|
@@ -408,6 +408,12 @@ Existing RSpec plugins which have OpenAPI integration:
|
|
|
408
408
|
* Orignally created by [k0kubun](https://github.com/k0kubun) and the ownership was transferred to [exoego](https://github.com/exoego) in 2022-11-29.
|
|
409
409
|
|
|
410
410
|
|
|
411
|
+
## Releasing
|
|
412
|
+
|
|
413
|
+
1. Bump version in `lib/rspec/openapi/version.rb`
|
|
414
|
+
2. Run `bundle exec rake release`
|
|
415
|
+
3. Push tag
|
|
416
|
+
|
|
411
417
|
## License
|
|
412
418
|
|
|
413
419
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
@@ -56,7 +56,7 @@ class << RSpec::OpenAPI::Extractors::Hanami = Object.new
|
|
|
56
56
|
|
|
57
57
|
return RSpec::OpenAPI::Extractors::Rack.request_attributes(request, example) unless route.routable?
|
|
58
58
|
|
|
59
|
-
metadata = example.metadata
|
|
59
|
+
metadata = merge_openapi_metadata(example.metadata)
|
|
60
60
|
summary = metadata[:summary] || RSpec::OpenAPI.summary_builder.call(example)
|
|
61
61
|
tags = metadata[:tags] || RSpec::OpenAPI.tags_builder.call(example)
|
|
62
62
|
formats = metadata[:formats] || RSpec::OpenAPI.formats_builder.curry.call(example)
|
|
@@ -100,6 +100,26 @@ class << RSpec::OpenAPI::Extractors::Hanami = Object.new
|
|
|
100
100
|
[request, response]
|
|
101
101
|
end
|
|
102
102
|
|
|
103
|
+
private
|
|
104
|
+
|
|
105
|
+
def merge_openapi_metadata(metadata)
|
|
106
|
+
collect_openapi_metadata(metadata).reduce({}, &:merge)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def collect_openapi_metadata(metadata)
|
|
110
|
+
[].tap do |result|
|
|
111
|
+
current = metadata
|
|
112
|
+
|
|
113
|
+
while current
|
|
114
|
+
[current[:example_group], current].each do |meta|
|
|
115
|
+
result.unshift(meta[:openapi]) if meta&.dig(:openapi)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
current = current[:parent_example_group]
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
103
123
|
def add_id(path, route)
|
|
104
124
|
return path if route.params.empty?
|
|
105
125
|
|
|
@@ -6,7 +6,7 @@ class << RSpec::OpenAPI::Extractors::Rack = Object.new
|
|
|
6
6
|
# @param [RSpec::Core::Example] example
|
|
7
7
|
# @return Array
|
|
8
8
|
def request_attributes(request, example)
|
|
9
|
-
metadata = example.metadata
|
|
9
|
+
metadata = merge_openapi_metadata(example.metadata)
|
|
10
10
|
summary = metadata[:summary] || RSpec::OpenAPI.summary_builder.call(example)
|
|
11
11
|
tags = metadata[:tags] || RSpec::OpenAPI.tags_builder.call(example)
|
|
12
12
|
formats = metadata[:formats] || RSpec::OpenAPI.formats_builder.curry.call(example)
|
|
@@ -40,4 +40,24 @@ class << RSpec::OpenAPI::Extractors::Rack = Object.new
|
|
|
40
40
|
|
|
41
41
|
[request, response]
|
|
42
42
|
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def merge_openapi_metadata(metadata)
|
|
47
|
+
collect_openapi_metadata(metadata).reduce({}, &:merge)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def collect_openapi_metadata(metadata)
|
|
51
|
+
[].tap do |result|
|
|
52
|
+
current = metadata
|
|
53
|
+
|
|
54
|
+
while current
|
|
55
|
+
[current[:example_group], current].each do |meta|
|
|
56
|
+
result.unshift(meta[:openapi]) if meta&.dig(:openapi)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
current = current[:parent_example_group]
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
43
63
|
end
|
|
@@ -16,7 +16,7 @@ class << RSpec::OpenAPI::Extractors::Rails = Object.new
|
|
|
16
16
|
|
|
17
17
|
raise "No route matched for #{fixed_request.request_method} #{fixed_request.path_info}" if route.nil?
|
|
18
18
|
|
|
19
|
-
metadata = example.metadata
|
|
19
|
+
metadata = merge_openapi_metadata(example.metadata)
|
|
20
20
|
summary = metadata[:summary] || RSpec::OpenAPI.summary_builder.call(example)
|
|
21
21
|
tags = metadata[:tags] || RSpec::OpenAPI.tags_builder.call(example)
|
|
22
22
|
formats = metadata[:formats] || RSpec::OpenAPI.formats_builder.curry.call(example)
|
|
@@ -55,6 +55,26 @@ class << RSpec::OpenAPI::Extractors::Rails = Object.new
|
|
|
55
55
|
[context.request, context.response]
|
|
56
56
|
end
|
|
57
57
|
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
def merge_openapi_metadata(metadata)
|
|
61
|
+
collect_openapi_metadata(metadata).reduce({}, &:merge)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def collect_openapi_metadata(metadata)
|
|
65
|
+
[].tap do |result|
|
|
66
|
+
current = metadata
|
|
67
|
+
|
|
68
|
+
while current
|
|
69
|
+
[current[:example_group], current].each do |meta|
|
|
70
|
+
result.unshift(meta[:openapi]) if meta&.dig(:openapi)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
current = current[:parent_example_group]
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
58
78
|
# @param [ActionDispatch::Request] request
|
|
59
79
|
def find_rails_route(request, app: Rails.application, path_prefix: '')
|
|
60
80
|
app.routes.router.recognize(request) do |route, _parameters|
|
|
@@ -151,7 +151,7 @@ class << RSpec::OpenAPI::SchemaBuilder = Object.new
|
|
|
151
151
|
property[:items] = if value.empty?
|
|
152
152
|
{} # unknown
|
|
153
153
|
else
|
|
154
|
-
|
|
154
|
+
build_array_items_schema(value, record: record)
|
|
155
155
|
end
|
|
156
156
|
when Hash
|
|
157
157
|
property[:properties] = {}.tap do |properties|
|
|
@@ -243,4 +243,72 @@ class << RSpec::OpenAPI::SchemaBuilder = Object.new
|
|
|
243
243
|
def normalize_content_disposition(content_disposition)
|
|
244
244
|
content_disposition&.sub(/;.+\z/, '')
|
|
245
245
|
end
|
|
246
|
+
|
|
247
|
+
def build_array_items_schema(array, record: nil)
|
|
248
|
+
return {} if array.empty?
|
|
249
|
+
|
|
250
|
+
merged_schema = build_property(array.first, record: record)
|
|
251
|
+
|
|
252
|
+
# Future improvement - cover other types than just hashes
|
|
253
|
+
if array.size > 1 && array.all? { |item| item.is_a?(Hash) }
|
|
254
|
+
array[1..].each do |item|
|
|
255
|
+
item_schema = build_property(item, record: record)
|
|
256
|
+
merged_schema = merge_object_schemas(merged_schema, item_schema)
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
merged_schema
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def merge_object_schemas(schema1, schema2)
|
|
264
|
+
return schema1 unless schema2.is_a?(Hash) && schema1.is_a?(Hash)
|
|
265
|
+
return schema1 unless schema1[:type] == 'object' && schema2[:type] == 'object'
|
|
266
|
+
|
|
267
|
+
merged = schema1.dup
|
|
268
|
+
|
|
269
|
+
if schema1[:properties] && schema2[:properties]
|
|
270
|
+
merged[:properties] = schema1[:properties].dup
|
|
271
|
+
|
|
272
|
+
schema2[:properties].each do |key, prop2|
|
|
273
|
+
if merged[:properties][key]
|
|
274
|
+
prop1 = merged[:properties][key]
|
|
275
|
+
merged[:properties][key] = merge_property_schemas(prop1, prop2)
|
|
276
|
+
else
|
|
277
|
+
merged[:properties][key] = make_property_nullable(prop2)
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
schema1[:properties].each do |key, prop1|
|
|
282
|
+
merged[:properties][key] = make_property_nullable(prop1) unless schema2[:properties][key]
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
required1 = Set.new(schema1[:required] || [])
|
|
286
|
+
required2 = Set.new(schema2[:required] || [])
|
|
287
|
+
merged[:required] = (required1 & required2).to_a
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
merged
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
def merge_property_schemas(prop1, prop2)
|
|
294
|
+
return prop1 unless prop2.is_a?(Hash) && prop1.is_a?(Hash)
|
|
295
|
+
|
|
296
|
+
merged = prop1.dup
|
|
297
|
+
|
|
298
|
+
# If either property is nullable, the merged property should be nullable
|
|
299
|
+
merged[:nullable] = true if prop2[:nullable] && !prop1[:nullable]
|
|
300
|
+
|
|
301
|
+
# If both are objects, recursively merge their properties
|
|
302
|
+
merged = merge_object_schemas(prop1, prop2) if prop1[:type] == 'object' && prop2[:type] == 'object'
|
|
303
|
+
|
|
304
|
+
merged
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
def make_property_nullable(property)
|
|
308
|
+
return property unless property.is_a?(Hash)
|
|
309
|
+
|
|
310
|
+
nullable_prop = property.dup
|
|
311
|
+
nullable_prop[:nullable] = true unless nullable_prop[:nullable]
|
|
312
|
+
nullable_prop
|
|
313
|
+
end
|
|
246
314
|
end
|
|
@@ -4,6 +4,9 @@ require 'fileutils'
|
|
|
4
4
|
require 'yaml'
|
|
5
5
|
require 'json'
|
|
6
6
|
|
|
7
|
+
# For Ruby 2.7
|
|
8
|
+
require 'date'
|
|
9
|
+
|
|
7
10
|
# TODO: Support JSON
|
|
8
11
|
class RSpec::OpenAPI::SchemaFile
|
|
9
12
|
# @param [String] path
|
|
@@ -24,7 +27,12 @@ class RSpec::OpenAPI::SchemaFile
|
|
|
24
27
|
def read
|
|
25
28
|
return {} unless File.exist?(@path)
|
|
26
29
|
|
|
27
|
-
RSpec::OpenAPI::KeyTransformer.symbolize(
|
|
30
|
+
RSpec::OpenAPI::KeyTransformer.symbolize(
|
|
31
|
+
YAML.safe_load(
|
|
32
|
+
File.read(@path),
|
|
33
|
+
permitted_classes: [Date, Time],
|
|
34
|
+
),
|
|
35
|
+
) # this can also parse JSON
|
|
28
36
|
end
|
|
29
37
|
|
|
30
38
|
# @param [Hash] spec
|
metadata
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rspec-openapi
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.21.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Takashi Kokubun
|
|
8
8
|
- TATSUNO Yasuhiro
|
|
9
|
-
autorequire:
|
|
10
9
|
bindir: exe
|
|
11
10
|
cert_chain: []
|
|
12
|
-
date:
|
|
11
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
13
12
|
dependencies:
|
|
14
13
|
- !ruby/object:Gem::Dependency
|
|
15
14
|
name: actionpack
|
|
@@ -109,9 +108,8 @@ licenses:
|
|
|
109
108
|
metadata:
|
|
110
109
|
homepage_uri: https://github.com/exoego/rspec-openapi
|
|
111
110
|
source_code_uri: https://github.com/exoego/rspec-openapi
|
|
112
|
-
changelog_uri: https://github.com/exoego/rspec-openapi/releases/tag/v0.
|
|
111
|
+
changelog_uri: https://github.com/exoego/rspec-openapi/releases/tag/v0.21.0
|
|
113
112
|
rubygems_mfa_required: 'true'
|
|
114
|
-
post_install_message:
|
|
115
113
|
rdoc_options: []
|
|
116
114
|
require_paths:
|
|
117
115
|
- lib
|
|
@@ -126,8 +124,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
126
124
|
- !ruby/object:Gem::Version
|
|
127
125
|
version: '0'
|
|
128
126
|
requirements: []
|
|
129
|
-
rubygems_version: 3.
|
|
130
|
-
signing_key:
|
|
127
|
+
rubygems_version: 3.6.9
|
|
131
128
|
specification_version: 4
|
|
132
129
|
summary: Generate OpenAPI schema from RSpec request specs
|
|
133
130
|
test_files: []
|