rspec-openapi 0.10.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/codeql-analysis.yml +3 -3
- data/.github/workflows/rubocop.yml +1 -1
- data/.github/workflows/test.yml +6 -1
- data/.rubocop_todo.yml +18 -8
- data/CHANGELOG.md +15 -0
- data/lib/rspec/openapi/components_updater.rb +25 -5
- data/lib/rspec/openapi/hash_helper.rb +4 -0
- data/lib/rspec/openapi/result_recorder.rb +16 -9
- data/lib/rspec/openapi/schema_cleaner.rb +30 -7
- data/lib/rspec/openapi/schema_merger.rb +41 -0
- data/lib/rspec/openapi/schema_sorter.rb +35 -0
- data/lib/rspec/openapi/version.rb +1 -1
- data/lib/rspec/openapi.rb +6 -0
- metadata +3 -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
|
@@ -28,12 +28,12 @@ jobs:
|
|
28
28
|
uses: actions/checkout@v4
|
29
29
|
|
30
30
|
- name: Initialize CodeQL
|
31
|
-
uses: github/codeql-action/init@
|
31
|
+
uses: github/codeql-action/init@v3
|
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@v3
|
37
37
|
|
38
38
|
- name: Perform CodeQL Analysis
|
39
|
-
uses: github/codeql-action/analyze@
|
39
|
+
uses: github/codeql-action/analyze@v3
|
data/.github/workflows/test.yml
CHANGED
@@ -26,6 +26,8 @@ jobs:
|
|
26
26
|
rails: 6.1.6
|
27
27
|
- ruby: ruby:3.1
|
28
28
|
rails: 7.0.3
|
29
|
+
- ruby: ruby:3.3
|
30
|
+
rails: 7.1.2
|
29
31
|
coverage: coverage
|
30
32
|
env:
|
31
33
|
RAILS_VERSION: ${{ matrix.rails == '' && '6.1.6' || matrix.rails }}
|
@@ -41,9 +43,12 @@ jobs:
|
|
41
43
|
if: matrix.coverage == 'coverage'
|
42
44
|
- run: bundle exec rspec
|
43
45
|
timeout-minutes: 1
|
46
|
+
- run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
47
|
+
name: codecov-action@v4 workaround
|
44
48
|
- name: Upload coverage reports
|
45
|
-
uses: codecov/codecov-action@
|
49
|
+
uses: codecov/codecov-action@v4
|
46
50
|
if: matrix.coverage == 'coverage'
|
47
51
|
with:
|
48
52
|
fail_ci_if_error: true
|
49
53
|
files: ./coverage/coverage.xml
|
54
|
+
token: ${{ secrets.CODECOV_TOKEN }}
|
data/.rubocop_todo.yml
CHANGED
@@ -1,25 +1,35 @@
|
|
1
1
|
# This configuration was generated by
|
2
2
|
# `rubocop --auto-gen-config`
|
3
|
-
# on
|
3
|
+
# on 2024-01-13 11:12:43 UTC using RuboCop version 1.50.2.
|
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: 9
|
10
10
|
# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes.
|
11
11
|
Metrics/AbcSize:
|
12
|
-
Max:
|
12
|
+
Max: 48
|
13
|
+
|
14
|
+
# Offense count: 2
|
15
|
+
# Configuration parameters: CountComments, CountAsOne.
|
16
|
+
Metrics/ClassLength:
|
17
|
+
Max: 192
|
13
18
|
|
14
|
-
# Offense count:
|
19
|
+
# Offense count: 5
|
15
20
|
# Configuration parameters: AllowedMethods, AllowedPatterns.
|
16
21
|
Metrics/CyclomaticComplexity:
|
17
|
-
Max:
|
22
|
+
Max: 13
|
18
23
|
|
19
|
-
# Offense count:
|
24
|
+
# Offense count: 16
|
20
25
|
# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
|
21
26
|
Metrics/MethodLength:
|
22
|
-
Max:
|
27
|
+
Max: 31
|
28
|
+
|
29
|
+
# Offense count: 1
|
30
|
+
# Configuration parameters: AllowedMethods, AllowedPatterns.
|
31
|
+
Metrics/PerceivedComplexity:
|
32
|
+
Max: 13
|
23
33
|
|
24
34
|
# Offense count: 1
|
25
35
|
# Configuration parameters: EnforcedStyle, CheckMethodNames, CheckSymbols, AllowedIdentifiers, AllowedPatterns.
|
@@ -29,7 +39,7 @@ Naming/VariableNumber:
|
|
29
39
|
Exclude:
|
30
40
|
- 'spec/integration_tests/roda_test.rb'
|
31
41
|
|
32
|
-
# Offense count:
|
42
|
+
# Offense count: 6
|
33
43
|
# Configuration parameters: AllowedConstants.
|
34
44
|
Style/Documentation:
|
35
45
|
Exclude:
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,18 @@
|
|
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)
|
7
|
+
|
8
|
+
## v0.11.0
|
9
|
+
- feat: Allow path-based config overrides
|
10
|
+
[#162](https://github.com/exoego/rspec-openapi/pull/162)
|
11
|
+
- enhancement: Sort HTTP methods, response status codes, and contents lexicographically
|
12
|
+
[#163](https://github.com/exoego/rspec-openapi/pull/163)
|
13
|
+
- enhancement: Remove parameters that conflict with security schemas
|
14
|
+
[#166](https://github.com/exoego/rspec-openapi/pull/166)
|
15
|
+
|
1
16
|
## v0.10.0
|
2
17
|
- bugfix: Merge parameter data to preserve description in manually edited Openapi spec
|
3
18
|
[#149](https://github.com/exoego/rspec-openapi/pull/149)
|
@@ -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
|
@@ -7,26 +7,33 @@ class RSpec::OpenAPI::ResultRecorder
|
|
7
7
|
end
|
8
8
|
|
9
9
|
def record_results!
|
10
|
-
title = RSpec::OpenAPI.title
|
11
10
|
@path_records.each do |path, records|
|
11
|
+
# Look for a path-specific config file and run it.
|
12
|
+
config_file = File.join(File.dirname(path), RSpec::OpenAPI.config_filename)
|
13
|
+
begin
|
14
|
+
require config_file if File.exist?(config_file)
|
15
|
+
rescue StandardError => e
|
16
|
+
puts "WARNING: Unable to load #{config_file}: #{e}"
|
17
|
+
end
|
18
|
+
|
19
|
+
title = RSpec::OpenAPI.title
|
12
20
|
RSpec::OpenAPI::SchemaFile.new(path).edit do |spec|
|
13
21
|
schema = RSpec::OpenAPI::DefaultSchema.build(title)
|
14
22
|
schema[:info].merge!(RSpec::OpenAPI.info)
|
15
23
|
RSpec::OpenAPI::SchemaMerger.merge!(spec, schema)
|
16
24
|
new_from_zero = {}
|
17
25
|
records.each do |record|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
@error_records[e] = record # Avoid failing the build
|
24
|
-
end
|
26
|
+
record_schema = RSpec::OpenAPI::SchemaBuilder.build(record)
|
27
|
+
RSpec::OpenAPI::SchemaMerger.merge!(spec, record_schema)
|
28
|
+
RSpec::OpenAPI::SchemaMerger.merge!(new_from_zero, record_schema)
|
29
|
+
rescue StandardError, NotImplementedError => e # e.g. SchemaBuilder raises a NotImplementedError
|
30
|
+
@error_records[e] = record # Avoid failing the build
|
25
31
|
end
|
32
|
+
RSpec::OpenAPI::SchemaCleaner.cleanup_conflicting_security_parameters!(spec)
|
26
33
|
RSpec::OpenAPI::SchemaCleaner.cleanup!(spec, new_from_zero)
|
27
34
|
RSpec::OpenAPI::ComponentsUpdater.update!(spec, new_from_zero)
|
28
35
|
RSpec::OpenAPI::SchemaCleaner.cleanup_empty_required_array!(spec)
|
29
|
-
RSpec::OpenAPI::
|
36
|
+
RSpec::OpenAPI::SchemaSorter.deep_sort!(spec)
|
30
37
|
end
|
31
38
|
end
|
32
39
|
end
|
@@ -39,6 +39,24 @@ class << RSpec::OpenAPI::SchemaCleaner = Object.new
|
|
39
39
|
base
|
40
40
|
end
|
41
41
|
|
42
|
+
def cleanup_conflicting_security_parameters!(base)
|
43
|
+
security_schemes = base.dig('components', 'securitySchemes') || {}
|
44
|
+
|
45
|
+
return if security_schemes.empty?
|
46
|
+
|
47
|
+
paths_to_security_definitions = RSpec::OpenAPI::HashHelper.matched_paths_deeply_nested(base, 'paths', 'security')
|
48
|
+
|
49
|
+
paths_to_security_definitions.each do |path|
|
50
|
+
parent_path_definition = base.dig(*path.take(path.length - 1))
|
51
|
+
|
52
|
+
security_schemes.each do |security_scheme_name, security_scheme|
|
53
|
+
remove_parameters_conflicting_with_security_sceheme!(
|
54
|
+
parent_path_definition, security_scheme, security_scheme_name,
|
55
|
+
)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
42
60
|
def cleanup_empty_required_array!(base)
|
43
61
|
paths_to_objects = [
|
44
62
|
*RSpec::OpenAPI::HashHelper.matched_paths_deeply_nested(base, 'components.schemas', 'properties'),
|
@@ -51,15 +69,20 @@ class << RSpec::OpenAPI::SchemaCleaner = Object.new
|
|
51
69
|
end
|
52
70
|
end
|
53
71
|
|
54
|
-
# Sort "paths" lexicographically to make the order more predictable
|
55
|
-
#
|
56
|
-
# @param [Hash] #
|
57
|
-
def sort_paths!(spec)
|
58
|
-
spec['paths'] = spec['paths']&.entries&.sort_by! { |path, _| path }.to_h
|
59
|
-
end
|
60
|
-
|
61
72
|
private
|
62
73
|
|
74
|
+
def remove_parameters_conflicting_with_security_sceheme!(path_definition, security_scheme, security_scheme_name)
|
75
|
+
return unless path_definition['security']
|
76
|
+
return unless path_definition['parameters']
|
77
|
+
return unless path_definition.dig('security', 0).keys.include?(security_scheme_name)
|
78
|
+
|
79
|
+
path_definition['parameters'].reject! do |parameter|
|
80
|
+
parameter['in'] == security_scheme['in'] && # same location (ie. header)
|
81
|
+
parameter['name'] == security_scheme['name'] # same name (ie. AUTHORIZATION)
|
82
|
+
end
|
83
|
+
path_definition.delete('parameters') if path_definition['parameters'].empty?
|
84
|
+
end
|
85
|
+
|
63
86
|
def cleanup_array!(base, spec, selector, fields_for_identity = [])
|
64
87
|
marshal = lambda do |obj|
|
65
88
|
Marshal.dump(slice(obj, fields_for_identity))
|
@@ -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
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class << RSpec::OpenAPI::SchemaSorter = Object.new
|
4
|
+
# Sort some unpredictably ordered properties in a lexicographical manner to make the order more predictable.
|
5
|
+
#
|
6
|
+
# @param [Hash|Array]
|
7
|
+
def deep_sort!(spec)
|
8
|
+
# paths
|
9
|
+
deep_sort_by_selector!(spec, 'paths')
|
10
|
+
|
11
|
+
# methods
|
12
|
+
deep_sort_by_selector!(spec, 'paths.*')
|
13
|
+
|
14
|
+
# response status code
|
15
|
+
deep_sort_by_selector!(spec, 'paths.*.*.responses')
|
16
|
+
|
17
|
+
# content-type
|
18
|
+
deep_sort_by_selector!(spec, 'paths.*.*.responses.*.content')
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
# @param [Hash] base
|
24
|
+
# @param [String] selector
|
25
|
+
def deep_sort_by_selector!(base, selector)
|
26
|
+
RSpec::OpenAPI::HashHelper.matched_paths(base, selector).each do |paths|
|
27
|
+
deep_sort_hash!(base.dig(*paths))
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def deep_sort_hash!(hash)
|
32
|
+
sorted = hash.entries.sort_by { |k, _| k }.to_h
|
33
|
+
hash.replace(sorted)
|
34
|
+
end
|
35
|
+
end
|
data/lib/rspec/openapi.rb
CHANGED
@@ -9,6 +9,7 @@ require 'rspec/openapi/schema_builder'
|
|
9
9
|
require 'rspec/openapi/schema_file'
|
10
10
|
require 'rspec/openapi/schema_merger'
|
11
11
|
require 'rspec/openapi/schema_cleaner'
|
12
|
+
require 'rspec/openapi/schema_sorter'
|
12
13
|
|
13
14
|
require 'rspec/openapi/minitest_hooks' if Object.const_defined?('Minitest')
|
14
15
|
require 'rspec/openapi/rspec_hooks' if ENV['OPENAPI'] && Object.const_defined?('RSpec')
|
@@ -31,6 +32,9 @@ module RSpec::OpenAPI
|
|
31
32
|
@path_records = Hash.new { |h, k| h[k] = [] }
|
32
33
|
@ignored_path_params = %i[controller action format]
|
33
34
|
|
35
|
+
# This is the configuraion override file name we look for within each path.
|
36
|
+
@config_filename = 'rspec_openapi.rb'
|
37
|
+
|
34
38
|
class << self
|
35
39
|
attr_accessor :path,
|
36
40
|
:title,
|
@@ -48,5 +52,7 @@ module RSpec::OpenAPI
|
|
48
52
|
:response_headers,
|
49
53
|
:path_records,
|
50
54
|
:ignored_path_params
|
55
|
+
|
56
|
+
attr_reader :config_filename
|
51
57
|
end
|
52
58
|
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:
|
12
|
+
date: 2024-02-17 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: actionpack
|
@@ -76,6 +76,7 @@ files:
|
|
76
76
|
- lib/rspec/openapi/schema_cleaner.rb
|
77
77
|
- lib/rspec/openapi/schema_file.rb
|
78
78
|
- lib/rspec/openapi/schema_merger.rb
|
79
|
+
- lib/rspec/openapi/schema_sorter.rb
|
79
80
|
- lib/rspec/openapi/version.rb
|
80
81
|
- rspec-openapi.gemspec
|
81
82
|
- scripts/rspec
|