rspec-openapi 0.25.1 → 0.27.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/create_release.yml +1 -1
- data/.github/workflows/publish.yml +1 -1
- data/.github/workflows/validate-openapi.yml +21 -0
- data/.gitignore +2 -0
- data/.rubocop.yml +4 -0
- data/.rubocop_todo.yml +13 -13
- data/README.md +337 -36
- data/lib/rspec/openapi/extractors/hanami.rb +10 -29
- data/lib/rspec/openapi/extractors/rack.rb +7 -24
- data/lib/rspec/openapi/extractors/rails.rb +14 -27
- data/lib/rspec/openapi/extractors/shared_extractor.rb +102 -18
- data/lib/rspec/openapi/record.rb +6 -1
- data/lib/rspec/openapi/record_builder.rb +8 -22
- data/lib/rspec/openapi/schema_builder/build_context.rb +20 -0
- data/lib/rspec/openapi/schema_builder.rb +288 -286
- data/lib/rspec/openapi/schema_cleaner.rb +4 -1
- data/lib/rspec/openapi/schema_file.rb +4 -6
- data/lib/rspec/openapi/schema_merger.rb +81 -40
- data/lib/rspec/openapi/version.rb +1 -1
- data/lib/rspec/openapi.rb +2 -2
- data/redocly.yaml +31 -0
- metadata +6 -3
|
@@ -27,7 +27,7 @@ class << RSpec::OpenAPI::SchemaCleaner = Object.new
|
|
|
27
27
|
cleanup_hash!(base, spec, 'paths.*.*')
|
|
28
28
|
|
|
29
29
|
# cleanup parameters
|
|
30
|
-
cleanup_array!(base, spec, 'paths.*.*.parameters',
|
|
30
|
+
cleanup_array!(base, spec, 'paths.*.*.parameters', [:name, :in])
|
|
31
31
|
|
|
32
32
|
# cleanup requestBody
|
|
33
33
|
cleanup_hash!(base, spec, 'paths.*.*.requestBody.content.application/json.schema.properties.*')
|
|
@@ -86,6 +86,9 @@ class << RSpec::OpenAPI::SchemaCleaner = Object.new
|
|
|
86
86
|
hash.delete(:_example_key)
|
|
87
87
|
hash.delete(:_example_summary)
|
|
88
88
|
hash.delete(:_example_name)
|
|
89
|
+
if (fallback = hash.delete(:_fallback_description))
|
|
90
|
+
hash[:description] ||= fallback
|
|
91
|
+
end
|
|
89
92
|
|
|
90
93
|
hash.each_value do |value|
|
|
91
94
|
case value
|
|
@@ -27,12 +27,10 @@ class RSpec::OpenAPI::SchemaFile
|
|
|
27
27
|
def read
|
|
28
28
|
return {} unless File.exist?(@path)
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
),
|
|
35
|
-
) # this can also parse JSON
|
|
30
|
+
content = YAML.safe_load(File.read(@path), permitted_classes: [Date, Time]) # this can also parse JSON
|
|
31
|
+
return {} if content.nil?
|
|
32
|
+
|
|
33
|
+
RSpec::OpenAPI::KeyTransformer.symbolize(content)
|
|
36
34
|
end
|
|
37
35
|
|
|
38
36
|
# @param [Hash] spec
|
|
@@ -25,28 +25,53 @@ class << RSpec::OpenAPI::SchemaMerger = Object.new
|
|
|
25
25
|
return base
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
normalize_example_fields!(base[key], value)
|
|
32
|
-
|
|
33
|
-
# If the new value has oneOf, replace the entire value instead of merging
|
|
34
|
-
if value.key?(:oneOf)
|
|
35
|
-
base[key] = value
|
|
36
|
-
else
|
|
37
|
-
merge_schema!(base[key], value) unless base[key].key?(:$ref)
|
|
38
|
-
end
|
|
39
|
-
elsif base[key].is_a?(Array) && value.is_a?(Array)
|
|
40
|
-
# parameters need to be merged as if `name` and `in` were the Hash keys.
|
|
41
|
-
merge_arrays(base, key, value)
|
|
42
|
-
else
|
|
43
|
-
# do not ADD `properties` or `required` fields if `additionalProperties` field is present
|
|
44
|
-
base[key] = value unless base.key?(:additionalProperties) && %i[properties required].include?(key)
|
|
45
|
-
end
|
|
46
|
-
end
|
|
28
|
+
prune_stale_object_fields!(base, spec)
|
|
29
|
+
|
|
30
|
+
spec.each { |key, value| merge_entry!(base, key, value) }
|
|
47
31
|
base
|
|
48
32
|
end
|
|
49
33
|
|
|
34
|
+
# When the new spec converts an object to a dictionary (introduces
|
|
35
|
+
# `additionalProperties` on a node that previously had `properties` /
|
|
36
|
+
# `required`), drop the stale fields so the merged result reflects the
|
|
37
|
+
# new intent. We only prune when base does not already declare
|
|
38
|
+
# `additionalProperties`, to preserve manual edits that intentionally
|
|
39
|
+
# combine fixed and dynamic keys.
|
|
40
|
+
def prune_stale_object_fields!(base, spec)
|
|
41
|
+
return unless spec.is_a?(Hash) && spec.key?(:additionalProperties) && !base.key?(:additionalProperties)
|
|
42
|
+
|
|
43
|
+
base.delete(:properties)
|
|
44
|
+
base.delete(:required)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def merge_entry!(base, key, value)
|
|
48
|
+
if base[key].is_a?(Hash) && value.is_a?(Hash)
|
|
49
|
+
merge_hash_entry!(base, key, value)
|
|
50
|
+
elsif base[key].is_a?(Array) && value.is_a?(Array)
|
|
51
|
+
# parameters need to be merged as if `name` and `in` were the Hash keys.
|
|
52
|
+
merge_arrays(base, key, value)
|
|
53
|
+
elsif !skip_due_to_additional_properties?(base, key)
|
|
54
|
+
base[key] = value
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def merge_hash_entry!(base, key, value)
|
|
59
|
+
# Handle example/examples conflict - convert to examples when mixed
|
|
60
|
+
normalize_example_fields!(base[key], value)
|
|
61
|
+
|
|
62
|
+
# If the new value has oneOf, replace the entire value instead of merging
|
|
63
|
+
if value.key?(:oneOf)
|
|
64
|
+
base[key] = value
|
|
65
|
+
elsif !base[key].key?(:$ref)
|
|
66
|
+
merge_schema!(base[key], value)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# do not ADD `properties` or `required` fields if `additionalProperties` field is present
|
|
71
|
+
def skip_due_to_additional_properties?(base, key)
|
|
72
|
+
base.key?(:additionalProperties) && [:properties, :required].include?(key)
|
|
73
|
+
end
|
|
74
|
+
|
|
50
75
|
def merge_arrays(base, key, value)
|
|
51
76
|
base[key] = case key
|
|
52
77
|
when :parameters
|
|
@@ -61,21 +86,33 @@ class << RSpec::OpenAPI::SchemaMerger = Object.new
|
|
|
61
86
|
end
|
|
62
87
|
|
|
63
88
|
def merge_parameters(base, key, value)
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
89
|
+
base_params = index_parameters_by_identity(base[key])
|
|
90
|
+
new_params = index_parameters_by_identity(value)
|
|
91
|
+
|
|
92
|
+
base[key] = (base_params.keys | new_params.keys).map do |param_key|
|
|
93
|
+
base_param = base_params[param_key]
|
|
94
|
+
new_param = new_params[param_key]
|
|
95
|
+
|
|
96
|
+
if base_param && new_param
|
|
97
|
+
merge_parameter_with_schema(base_param, new_param)
|
|
98
|
+
elsif new_param
|
|
99
|
+
# Parameter only in the new spec. Treat as optional unless its `required: true`
|
|
100
|
+
# came from explicit `required_request_params` metadata — distinguishable only
|
|
101
|
+
# for `query`, where the schema_builder default is `required: false`. `header`
|
|
102
|
+
# defaults to `required: true`, so the value alone can't signal user intent.
|
|
103
|
+
new_param[:in] == 'query' && new_param[:required] ? new_param : mark_optional_unless_path(new_param)
|
|
72
104
|
else
|
|
73
|
-
|
|
105
|
+
mark_optional_unless_path(base_param)
|
|
74
106
|
end
|
|
75
107
|
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# OpenAPI requires `in: path` parameters to be `required: true`, so this leaves
|
|
111
|
+
# them untouched.
|
|
112
|
+
def mark_optional_unless_path(parameter)
|
|
113
|
+
return parameter if parameter[:in] == 'path'
|
|
76
114
|
|
|
77
|
-
|
|
78
|
-
base[key] = all_parameters
|
|
115
|
+
parameter.merge(required: false)
|
|
79
116
|
end
|
|
80
117
|
|
|
81
118
|
def merge_parameter_with_schema(base_param, new_param)
|
|
@@ -83,12 +120,18 @@ class << RSpec::OpenAPI::SchemaMerger = Object.new
|
|
|
83
120
|
new_schema = new_param[:schema]
|
|
84
121
|
|
|
85
122
|
# If schemas have different types, create a oneOf
|
|
86
|
-
if base_schema && new_schema && schemas_have_different_types?(base_schema, new_schema)
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
123
|
+
merged = if base_schema && new_schema && schemas_have_different_types?(base_schema, new_schema)
|
|
124
|
+
merged_schema = merge_schemas_into_one_of(base_schema, new_schema)
|
|
125
|
+
base_param.merge(new_param).merge(schema: merged_schema)
|
|
126
|
+
else
|
|
127
|
+
base_param.merge(new_param)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Once a parameter has been seen missing in any earlier test case, keep it optional
|
|
131
|
+
# even if later test cases mark it required again.
|
|
132
|
+
merged = mark_optional_unless_path(merged) if base_param[:required] == false || new_param[:required] == false
|
|
133
|
+
|
|
134
|
+
merged
|
|
92
135
|
end
|
|
93
136
|
|
|
94
137
|
def schemas_have_different_types?(schema1, schema2)
|
|
@@ -122,10 +165,8 @@ class << RSpec::OpenAPI::SchemaMerger = Object.new
|
|
|
122
165
|
end
|
|
123
166
|
end
|
|
124
167
|
|
|
125
|
-
def
|
|
126
|
-
|
|
127
|
-
[[parameter[:name], parameter[:in]], parameter]
|
|
128
|
-
end
|
|
168
|
+
def index_parameters_by_identity(parameters)
|
|
169
|
+
parameters.to_h { |p| [[p[:name], p[:in]], p] }
|
|
129
170
|
end
|
|
130
171
|
|
|
131
172
|
# Normalize example/examples fields when there's a conflict
|
data/lib/rspec/openapi.rb
CHANGED
|
@@ -43,10 +43,10 @@ module RSpec::OpenAPI
|
|
|
43
43
|
@request_headers = []
|
|
44
44
|
@servers = []
|
|
45
45
|
@security_schemes = []
|
|
46
|
-
@example_types =
|
|
46
|
+
@example_types = [:request]
|
|
47
47
|
@response_headers = []
|
|
48
48
|
@path_records = Hash.new { |h, k| h[k] = [] }
|
|
49
|
-
@ignored_path_params =
|
|
49
|
+
@ignored_path_params = [:controller, :action, :format]
|
|
50
50
|
@ignored_paths = []
|
|
51
51
|
@post_process_hook = nil
|
|
52
52
|
|
data/redocly.yaml
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Config for `redocly lint` used by .github/workflows/validate-openapi.yml
|
|
2
|
+
# to validate test-fixture OpenAPI documents shipped under spec/apps/.
|
|
3
|
+
#
|
|
4
|
+
# spec/apps/rails/doc/smart/openapi.yaml is intentionally excluded:
|
|
5
|
+
# it is the input fixture for the smart-merge feature test (it contains
|
|
6
|
+
# unresolved $refs by design), not a complete API description.
|
|
7
|
+
extends:
|
|
8
|
+
- minimal
|
|
9
|
+
apis:
|
|
10
|
+
rails-rspec-yaml:
|
|
11
|
+
root: spec/apps/rails/doc/rspec_openapi.yaml
|
|
12
|
+
rails-rspec-json:
|
|
13
|
+
root: spec/apps/rails/doc/rspec_openapi.json
|
|
14
|
+
rails-minitest-yaml:
|
|
15
|
+
root: spec/apps/rails/doc/minitest_openapi.yaml
|
|
16
|
+
rails-minitest-json:
|
|
17
|
+
root: spec/apps/rails/doc/minitest_openapi.json
|
|
18
|
+
rails-smart-expected:
|
|
19
|
+
root: spec/apps/rails/doc/smart/expected.yaml
|
|
20
|
+
roda-rspec-yaml:
|
|
21
|
+
root: spec/apps/roda/doc/rspec_openapi.yaml
|
|
22
|
+
roda-rspec-json:
|
|
23
|
+
root: spec/apps/roda/doc/rspec_openapi.json
|
|
24
|
+
roda-minitest-yaml:
|
|
25
|
+
root: spec/apps/roda/doc/minitest_openapi.yaml
|
|
26
|
+
roda-minitest-json:
|
|
27
|
+
root: spec/apps/roda/doc/minitest_openapi.json
|
|
28
|
+
hanami-yaml:
|
|
29
|
+
root: spec/apps/hanami/doc/openapi.yaml
|
|
30
|
+
hanami-json:
|
|
31
|
+
root: spec/apps/hanami/doc/openapi.json
|
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.27.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Takashi Kokubun
|
|
@@ -67,6 +67,7 @@ files:
|
|
|
67
67
|
- ".github/workflows/publish.yml"
|
|
68
68
|
- ".github/workflows/rubocop.yml"
|
|
69
69
|
- ".github/workflows/test.yml"
|
|
70
|
+
- ".github/workflows/validate-openapi.yml"
|
|
70
71
|
- ".gitignore"
|
|
71
72
|
- ".rspec"
|
|
72
73
|
- ".rubocop.yml"
|
|
@@ -96,12 +97,14 @@ files:
|
|
|
96
97
|
- lib/rspec/openapi/result_recorder.rb
|
|
97
98
|
- lib/rspec/openapi/rspec_hooks.rb
|
|
98
99
|
- lib/rspec/openapi/schema_builder.rb
|
|
100
|
+
- lib/rspec/openapi/schema_builder/build_context.rb
|
|
99
101
|
- lib/rspec/openapi/schema_cleaner.rb
|
|
100
102
|
- lib/rspec/openapi/schema_file.rb
|
|
101
103
|
- lib/rspec/openapi/schema_merger.rb
|
|
102
104
|
- lib/rspec/openapi/schema_sorter.rb
|
|
103
105
|
- lib/rspec/openapi/shared_hooks.rb
|
|
104
106
|
- lib/rspec/openapi/version.rb
|
|
107
|
+
- redocly.yaml
|
|
105
108
|
- rspec-openapi.gemspec
|
|
106
109
|
- scripts/rspec
|
|
107
110
|
- scripts/rspec_with_simplecov
|
|
@@ -112,7 +115,7 @@ licenses:
|
|
|
112
115
|
metadata:
|
|
113
116
|
homepage_uri: https://github.com/exoego/rspec-openapi
|
|
114
117
|
source_code_uri: https://github.com/exoego/rspec-openapi
|
|
115
|
-
changelog_uri: https://github.com/exoego/rspec-openapi/releases/tag/v0.
|
|
118
|
+
changelog_uri: https://github.com/exoego/rspec-openapi/releases/tag/v0.27.0
|
|
116
119
|
rubygems_mfa_required: 'true'
|
|
117
120
|
rdoc_options: []
|
|
118
121
|
require_paths:
|
|
@@ -128,7 +131,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
128
131
|
- !ruby/object:Gem::Version
|
|
129
132
|
version: '0'
|
|
130
133
|
requirements: []
|
|
131
|
-
rubygems_version: 4.0.
|
|
134
|
+
rubygems_version: 4.0.10
|
|
132
135
|
specification_version: 4
|
|
133
136
|
summary: Generate OpenAPI schema from RSpec request specs
|
|
134
137
|
test_files: []
|