rspec-openapi 0.11.0 → 0.12.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/test.yml +4 -1
- data/CHANGELOG.md +7 -2
- data/lib/rspec/openapi/components_updater.rb +25 -5
- data/lib/rspec/openapi/hash_helper.rb +4 -0
- data/lib/rspec/openapi/schema_merger.rb +41 -0
- data/lib/rspec/openapi/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b221d36e6ff92306e1bd720b4bb07e114839c8e047ad530f6ac4cb2a11eb4ae5
|
4
|
+
data.tar.gz: 5f5daf56dddc8a7b9477f961611d3bd4550eb498aa38d166035e5b18fe7027bd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 18410768ff613a19b39f910e20e58ecda87337095b354e718bdb5f6b6e2713429acd26a289fd4ffa89a493348aa226561d89340d9685d37caa30b44c54d17be8
|
7
|
+
data.tar.gz: 5542bf85a5f1d46eacbdc0936b57ae197279bf618fa1628082e7d33c5600a2583f450426999dbc6eea4d4a401ffb307ea5d207b551aa27d586eb4682e572c9fb
|
data/.github/workflows/test.yml
CHANGED
@@ -43,9 +43,12 @@ jobs:
|
|
43
43
|
if: matrix.coverage == 'coverage'
|
44
44
|
- run: bundle exec rspec
|
45
45
|
timeout-minutes: 1
|
46
|
+
- run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
47
|
+
name: codecov-action@v4 workaround
|
46
48
|
- name: Upload coverage reports
|
47
|
-
uses: codecov/codecov-action@
|
49
|
+
uses: codecov/codecov-action@v4
|
48
50
|
if: matrix.coverage == 'coverage'
|
49
51
|
with:
|
50
52
|
fail_ci_if_error: true
|
51
53
|
files: ./coverage/coverage.xml
|
54
|
+
token: ${{ secrets.CODECOV_TOKEN }}
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,11 @@
|
|
1
|
-
## v0.
|
1
|
+
## v0.12.0
|
2
|
+
|
3
|
+
- feat: Initial support of complex schema with manually-added `oneOf`
|
4
|
+
[#174](https://github.com/exoego/rspec-openapi/pull/174)
|
5
|
+
- chore: Test with Ruby 3.3 and Rails 7.1.x
|
6
|
+
[#169](https://github.com/exoego/rspec-openapi/pull/169)
|
2
7
|
|
3
|
-
##
|
8
|
+
## v0.11.0
|
4
9
|
- feat: Allow path-based config overrides
|
5
10
|
[#162](https://github.com/exoego/rspec-openapi/pull/162)
|
6
11
|
- enhancement: Sort HTTP methods, response status codes, and contents lexicographically
|
@@ -23,7 +23,8 @@ class << RSpec::OpenAPI::ComponentsUpdater = Object.new
|
|
23
23
|
# 0 1 2 ^...............................^
|
24
24
|
# ["components", "schema", "Table", "properties", "owner", "properties", "company", "$ref"]
|
25
25
|
# 0 1 2 ^...........................................^
|
26
|
-
needle = paths.
|
26
|
+
needle = paths.reject { |path| path.is_a?(Integer) || path == 'oneOf' }
|
27
|
+
needle = needle.slice(2, needle.size - 3)
|
27
28
|
nested_schema = fresh_schemas.dig(*needle)
|
28
29
|
|
29
30
|
# Skip if the property using $ref is not found in the parent schema. The property may be removed.
|
@@ -44,20 +45,28 @@ class << RSpec::OpenAPI::ComponentsUpdater = Object.new
|
|
44
45
|
references.inject({}) do |acc, paths|
|
45
46
|
ref_link = dig_schema(base, paths)['$ref']
|
46
47
|
schema_name = ref_link.gsub('#/components/schemas/', '')
|
47
|
-
schema_body = dig_schema(fresh, paths)
|
48
|
+
schema_body = dig_schema(fresh, paths.reject { |path| path.is_a?(Integer) })
|
49
|
+
|
48
50
|
RSpec::OpenAPI::SchemaMerger.merge!(acc, { schema_name => schema_body })
|
49
51
|
end
|
50
52
|
end
|
51
53
|
|
52
54
|
def dig_schema(obj, paths)
|
53
|
-
obj.dig(*paths, 'schema', 'items')
|
55
|
+
item_schema = obj.dig(*paths, 'schema', 'items')
|
56
|
+
object_schema = obj.dig(*paths, 'schema')
|
57
|
+
one_of_schema = obj.dig(*paths.take(paths.size - 1), 'schema', 'oneOf', paths.last)
|
58
|
+
|
59
|
+
item_schema || object_schema || one_of_schema
|
54
60
|
end
|
55
61
|
|
56
62
|
def paths_to_top_level_refs(base)
|
57
63
|
request_bodies = RSpec::OpenAPI::HashHelper.matched_paths(base, 'paths.*.*.requestBody.content.application/json')
|
58
64
|
responses = RSpec::OpenAPI::HashHelper.matched_paths(base, 'paths.*.*.responses.*.content.application/json')
|
59
|
-
(request_bodies + responses).
|
60
|
-
|
65
|
+
(request_bodies + responses).flat_map do |paths|
|
66
|
+
object_paths = find_object_refs(base, paths)
|
67
|
+
one_of_paths = find_one_of_refs(base, paths)
|
68
|
+
|
69
|
+
object_paths || one_of_paths || []
|
61
70
|
end
|
62
71
|
end
|
63
72
|
|
@@ -65,6 +74,7 @@ class << RSpec::OpenAPI::ComponentsUpdater = Object.new
|
|
65
74
|
nested_refs = [
|
66
75
|
*RSpec::OpenAPI::HashHelper.matched_paths_deeply_nested(base, 'components.schemas', 'properties.*.$ref'),
|
67
76
|
*RSpec::OpenAPI::HashHelper.matched_paths_deeply_nested(base, 'components.schemas', 'properties.*.items.$ref'),
|
77
|
+
*RSpec::OpenAPI::HashHelper.matched_paths_deeply_nested(base, 'components.schemas', 'oneOf.*.$ref'),
|
68
78
|
]
|
69
79
|
# Reject already-generated schemas to reduce unnecessary loop
|
70
80
|
nested_refs.reject do |paths|
|
@@ -73,4 +83,14 @@ class << RSpec::OpenAPI::ComponentsUpdater = Object.new
|
|
73
83
|
generated_names.include?(schema_name)
|
74
84
|
end
|
75
85
|
end
|
86
|
+
|
87
|
+
def find_one_of_refs(base, paths)
|
88
|
+
dig_schema(base, paths)&.dig('oneOf')&.map&.with_index do |schema, index|
|
89
|
+
paths + [index] if schema&.dig('$ref')&.start_with?('#/components/schemas/')
|
90
|
+
end&.compact
|
91
|
+
end
|
92
|
+
|
93
|
+
def find_object_refs(base, paths)
|
94
|
+
[paths] if dig_schema(base, paths)&.dig('$ref')&.start_with?('#/components/schemas/')
|
95
|
+
end
|
76
96
|
end
|
@@ -8,6 +8,10 @@ class << RSpec::OpenAPI::HashHelper = Object.new
|
|
8
8
|
k = k.to_s
|
9
9
|
[[k]] + paths_to_all_fields(v).map { |x| [k, *x] }
|
10
10
|
end
|
11
|
+
when Array
|
12
|
+
obj.flat_map.with_index do |value, i|
|
13
|
+
[[i]] + paths_to_all_fields(value).map { |x| [i, *x] }
|
14
|
+
end
|
11
15
|
else
|
12
16
|
[]
|
13
17
|
end
|
@@ -29,6 +29,12 @@ class << RSpec::OpenAPI::SchemaMerger = Object.new
|
|
29
29
|
#
|
30
30
|
# TODO: Should we probably force-merge `summary` regardless of manual modifications?
|
31
31
|
def merge_schema!(base, spec)
|
32
|
+
if (options = base['oneOf'])
|
33
|
+
merge_closest_match!(options, spec)
|
34
|
+
|
35
|
+
return base
|
36
|
+
end
|
37
|
+
|
32
38
|
spec.each do |key, value|
|
33
39
|
if base[key].is_a?(Hash) && value.is_a?(Hash)
|
34
40
|
merge_schema!(base[key], value) unless base[key].key?('$ref')
|
@@ -67,4 +73,39 @@ class << RSpec::OpenAPI::SchemaMerger = Object.new
|
|
67
73
|
all_parameters.uniq! { |param| param.slice('name', 'in') }
|
68
74
|
base[key] = all_parameters
|
69
75
|
end
|
76
|
+
|
77
|
+
SIMILARITY_THRESHOLD = 0.5
|
78
|
+
|
79
|
+
def merge_closest_match!(options, spec)
|
80
|
+
score, option = options.map { |option| [similarity(option, spec), option] }.max_by(&:first)
|
81
|
+
|
82
|
+
return if option&.key?('$ref')
|
83
|
+
|
84
|
+
if score.to_f > SIMILARITY_THRESHOLD
|
85
|
+
merge_schema!(option, spec)
|
86
|
+
else
|
87
|
+
options.push(spec)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def similarity(first, second)
|
92
|
+
return 1 if first == second
|
93
|
+
|
94
|
+
score =
|
95
|
+
case [first.class, second.class]
|
96
|
+
when [Array, Array]
|
97
|
+
(first & second).size / [first.size, second.size].max.to_f
|
98
|
+
when [Hash, Hash]
|
99
|
+
return 1 if first.merge(second).key?('$ref')
|
100
|
+
|
101
|
+
intersection = first.keys & second.keys
|
102
|
+
total_size = [first.size, second.size].max.to_f
|
103
|
+
|
104
|
+
intersection.sum { |key| similarity(first[key], second[key]) } / total_size
|
105
|
+
else
|
106
|
+
0
|
107
|
+
end
|
108
|
+
|
109
|
+
score.finite? ? score : 0
|
110
|
+
end
|
70
111
|
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.12.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-
|
12
|
+
date: 2024-02-17 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: actionpack
|