rspec-openapi 0.11.0 → 0.12.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0a48ebba5d2201f35bb94f47d5bd649444bb9c58ae1f0ef91f57e315553f9ef2
4
- data.tar.gz: 71d4dec68449c2e14b4d3ce7058622b7c56629bee2814e04b1f16e5e817a7bf2
3
+ metadata.gz: b221d36e6ff92306e1bd720b4bb07e114839c8e047ad530f6ac4cb2a11eb4ae5
4
+ data.tar.gz: 5f5daf56dddc8a7b9477f961611d3bd4550eb498aa38d166035e5b18fe7027bd
5
5
  SHA512:
6
- metadata.gz: 4c984f5dbd1a4587a9d34686c50e06a832051215f5e22abc8a96781ecebe57e0bdddabeef093db06f5bea23f58e70322a7574168f621e8765721ab2c2cf19c48
7
- data.tar.gz: 2877daab4753106d04275e3189b8a8e648b7268a6c6d7e422e94d5bfb230064a2e875e73531abe6930bfa6c8b7cdefe7d06ee512cdcb4e22adb618915349514b
6
+ metadata.gz: 18410768ff613a19b39f910e20e58ecda87337095b354e718bdb5f6b6e2713429acd26a289fd4ffa89a493348aa226561d89340d9685d37caa30b44c54d17be8
7
+ data.tar.gz: 5542bf85a5f1d46eacbdc0936b57ae197279bf618fa1628082e7d33c5600a2583f450426999dbc6eea4d4a401ffb307ea5d207b551aa27d586eb4682e572c9fb
@@ -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@v3
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.11.0
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
- ## What's Changed
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.slice(2, paths.size - 3)
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') || obj.dig(*paths, 'schema')
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).select do |paths|
60
- dig_schema(base, paths)&.dig('$ref')&.start_with?('#/components/schemas/')
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RSpec
4
4
  module OpenAPI
5
- VERSION = '0.11.0'
5
+ VERSION = '0.12.0'
6
6
  end
7
7
  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.11.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-01-13 00:00:00.000000000 Z
12
+ date: 2024-02-17 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: actionpack