rspec-openapi 0.28.0 → 0.29.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/README.md +37 -1
- data/lib/rspec/openapi/extractors/shared_extractor.rb +5 -7
- data/lib/rspec/openapi/record_builder.rb +1 -0
- data/lib/rspec/openapi/result_recorder.rb +36 -24
- data/lib/rspec/openapi/schema_file.rb +20 -13
- 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: 97857dc0658df5323bf3997e3acc31cf8f8b2f713a2d2ec6e3fdb0be7661aaf5
|
|
4
|
+
data.tar.gz: 9ecdb6c88c4c58042f805d899cda3d50fe2b670286de4d7f64b3799fd1ee5d48
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 14577c70d8caca9754f4893be1720ed67dbb4762f1479a4454e188cc954ba8e9651f887cc0c69a95c91c1c98f994739bf12667f7470a491b60f9f2ce21ac81fc
|
|
7
|
+
data.tar.gz: feb18e9c32bed1f101dca61683e30975857257999fac322ff9ed51519a7ac13d583ad1cf68b3f20c56782919c9bb2d6d83d94396204c3287c468c692b1b121e1
|
data/README.md
CHANGED
|
@@ -116,6 +116,10 @@ RSpec::OpenAPI.path = 'doc/schema.yaml'
|
|
|
116
116
|
# Change the output type to JSON
|
|
117
117
|
RSpec::OpenAPI.path = 'doc/schema.json'
|
|
118
118
|
|
|
119
|
+
# Or emit the same schema in multiple formats from a single run by passing an array.
|
|
120
|
+
# The output format of each file is still chosen by its extension (`.json` -> JSON, otherwise YAML).
|
|
121
|
+
RSpec::OpenAPI.path = ['doc/openapi.yaml', 'doc/openapi.json']
|
|
122
|
+
|
|
119
123
|
# Or generate multiple partial schema files, given an RSpec example
|
|
120
124
|
RSpec::OpenAPI.path = -> (example) {
|
|
121
125
|
case example.file_path
|
|
@@ -125,6 +129,12 @@ RSpec::OpenAPI.path = -> (example) {
|
|
|
125
129
|
end
|
|
126
130
|
}
|
|
127
131
|
|
|
132
|
+
# The two can be combined: a proc may also return an array to fan a partial schema out to multiple formats.
|
|
133
|
+
RSpec::OpenAPI.path = -> (example) {
|
|
134
|
+
base = example.file_path.match?(%r[spec/requests/api/v2/]) ? 'doc/openapi/v2' : 'doc/openapi'
|
|
135
|
+
["#{base}.yaml", "#{base}.json"]
|
|
136
|
+
}
|
|
137
|
+
|
|
128
138
|
# Change the default title of the generated schema
|
|
129
139
|
RSpec::OpenAPI.title = 'OpenAPI Documentation'
|
|
130
140
|
|
|
@@ -190,10 +200,16 @@ RSpec::OpenAPI.description_builder = -> (example) { example.description }
|
|
|
190
200
|
|
|
191
201
|
# Generate a custom summary, given an RSpec example
|
|
192
202
|
# This example uses the summary from the example_group.
|
|
203
|
+
# NOTE: a fixed `dig` only reaches one specific depth. `summary` declared on an
|
|
204
|
+
# ancestor group is already inherited automatically (see "Inheritance of
|
|
205
|
+
# `openapi:` metadata"), so a builder is only needed for fully custom logic.
|
|
193
206
|
RSpec::OpenAPI.summary_builder = ->(example) { example.metadata.dig(:example_group, :openapi, :summary) }
|
|
194
207
|
|
|
195
208
|
# Generate a custom tags, given an RSpec example
|
|
196
|
-
# This example uses the tags from the parent_example_group
|
|
209
|
+
# This example uses the tags from the parent_example_group.
|
|
210
|
+
# NOTE: `dig(:example_group, :parent_example_group, ...)` only matches tags
|
|
211
|
+
# declared exactly two levels up; it won't find tags on deeper ancestors. Rely
|
|
212
|
+
# on the built-in inheritance unless you need custom resolution.
|
|
197
213
|
RSpec::OpenAPI.tags_builder = -> (example) { example.metadata.dig(:example_group, :parent_example_group, :openapi, :tags) }
|
|
198
214
|
|
|
199
215
|
# Configure custom format for specific properties
|
|
@@ -369,6 +385,26 @@ end
|
|
|
369
385
|
|
|
370
386
|
**NOTE**: `description` key will override also the one provided by `RSpec::OpenAPI.description_builder` method.
|
|
371
387
|
|
|
388
|
+
### Inheritance of `openapi:` metadata
|
|
389
|
+
|
|
390
|
+
`openapi:` metadata is inherited from the surrounding `describe`/`context` groups down to each
|
|
391
|
+
example, at any nesting depth. Inner levels override outer ones key by key, so a nested group only
|
|
392
|
+
needs to declare the keys it wants to change:
|
|
393
|
+
|
|
394
|
+
```rb
|
|
395
|
+
describe 'GET /tables', openapi: { summary: 'Get a list of tables', tags: %w[Table] } do
|
|
396
|
+
context 'with pagination', openapi: { example_mode: :multiple } do
|
|
397
|
+
# This example inherits summary: 'Get a list of tables' and tags: ['Table']
|
|
398
|
+
# from the outer describe, and adds example_mode: :multiple.
|
|
399
|
+
it { get '/tables', params: { page: 1 } }
|
|
400
|
+
end
|
|
401
|
+
end
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
The merge happens at the top level of the `openapi:` hash. Scalar keys (`summary`, `operation_id`,
|
|
405
|
+
…) follow last-wins, and a nested level that re-declares a structured key (`tags`, `security`,
|
|
406
|
+
`enum`, …) replaces the inherited value for that key rather than deep-merging it.
|
|
407
|
+
|
|
372
408
|
### Enum Support
|
|
373
409
|
|
|
374
410
|
You can specify enum values for string properties that should have a fixed set of allowed values. Since enums cannot be
|
|
@@ -86,14 +86,12 @@ class SharedExtractor
|
|
|
86
86
|
|
|
87
87
|
def self.collect_openapi_metadata(metadata)
|
|
88
88
|
[].tap do |result|
|
|
89
|
-
|
|
89
|
+
result.unshift(metadata[:openapi]) if metadata[:openapi]
|
|
90
90
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
current = current[:parent_example_group]
|
|
91
|
+
group = metadata.fetch(:example_group) { metadata[:parent_example_group] }
|
|
92
|
+
while group
|
|
93
|
+
result.unshift(group[:openapi]) if group[:openapi]
|
|
94
|
+
group = group[:parent_example_group]
|
|
97
95
|
end
|
|
98
96
|
end
|
|
99
97
|
end
|
|
@@ -7,31 +7,16 @@ class RSpec::OpenAPI::ResultRecorder
|
|
|
7
7
|
end
|
|
8
8
|
|
|
9
9
|
def record_results!
|
|
10
|
-
@path_records.each do |
|
|
11
|
-
#
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
puts "WARNING: Unable to load #{config_file}: #{e}"
|
|
17
|
-
end
|
|
10
|
+
@path_records.each do |paths, records|
|
|
11
|
+
# A single record set may target multiple output files (e.g. both YAML and
|
|
12
|
+
# JSON). The first path is the canonical source we read/merge into; the rest
|
|
13
|
+
# mirror the same built spec, each formatted by its own extension.
|
|
14
|
+
primary, *mirrors = Array(paths)
|
|
15
|
+
next if primary.nil?
|
|
18
16
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
schema[:info].merge!(RSpec::OpenAPI.info)
|
|
23
|
-
RSpec::OpenAPI::SchemaMerger.merge!(spec, schema)
|
|
24
|
-
new_from_zero = {}
|
|
25
|
-
records.each do |record|
|
|
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
|
|
31
|
-
end
|
|
32
|
-
cleanup_schema!(new_from_zero, spec)
|
|
33
|
-
execute_post_process_hook(path, records, spec)
|
|
34
|
-
end
|
|
17
|
+
load_path_config(primary)
|
|
18
|
+
built_spec = build_spec(primary, records)
|
|
19
|
+
mirrors.each { |mirror_path| RSpec::OpenAPI::SchemaFile.new(mirror_path).write(built_spec) }
|
|
35
20
|
end
|
|
36
21
|
end
|
|
37
22
|
|
|
@@ -49,6 +34,33 @@ class RSpec::OpenAPI::ResultRecorder
|
|
|
49
34
|
|
|
50
35
|
private
|
|
51
36
|
|
|
37
|
+
# Look for a path-specific config file and run it.
|
|
38
|
+
def load_path_config(path)
|
|
39
|
+
config_file = File.join(File.dirname(path), RSpec::OpenAPI.config_filename)
|
|
40
|
+
require config_file if File.exist?(config_file)
|
|
41
|
+
rescue StandardError => e
|
|
42
|
+
puts "WARNING: Unable to load #{config_file}: #{e}"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def build_spec(primary, records)
|
|
46
|
+
title = records.first.title
|
|
47
|
+
RSpec::OpenAPI::SchemaFile.new(primary).edit do |spec|
|
|
48
|
+
schema = RSpec::OpenAPI::DefaultSchema.build(title)
|
|
49
|
+
schema[:info].merge!(RSpec::OpenAPI.info)
|
|
50
|
+
RSpec::OpenAPI::SchemaMerger.merge!(spec, schema)
|
|
51
|
+
new_from_zero = {}
|
|
52
|
+
records.each do |record|
|
|
53
|
+
record_schema = RSpec::OpenAPI::SchemaBuilder.build(record)
|
|
54
|
+
RSpec::OpenAPI::SchemaMerger.merge!(spec, record_schema)
|
|
55
|
+
RSpec::OpenAPI::SchemaMerger.merge!(new_from_zero, record_schema)
|
|
56
|
+
rescue StandardError, NotImplementedError => e # e.g. SchemaBuilder raises a NotImplementedError
|
|
57
|
+
@error_records[e] = record # Avoid failing the build
|
|
58
|
+
end
|
|
59
|
+
cleanup_schema!(new_from_zero, spec)
|
|
60
|
+
execute_post_process_hook(primary, records, spec)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
52
64
|
def execute_post_process_hook(path, records, spec)
|
|
53
65
|
RSpec::OpenAPI.post_process_hook.call(path, records, spec) if RSpec::OpenAPI.post_process_hook.is_a?(Proc)
|
|
54
66
|
end
|
|
@@ -14,11 +14,30 @@ class RSpec::OpenAPI::SchemaFile
|
|
|
14
14
|
@path = path
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
+
# Reads the existing spec, lets the block mutate it, writes the result back
|
|
18
|
+
# and returns the (symbolized) spec so it can be mirrored to other files.
|
|
19
|
+
# @return [Hash]
|
|
17
20
|
def edit(&block)
|
|
18
21
|
spec = read
|
|
19
22
|
block.call(spec)
|
|
23
|
+
spec
|
|
20
24
|
ensure
|
|
21
|
-
write(
|
|
25
|
+
write(spec)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Writes an already-built spec to this file, choosing the format from the
|
|
29
|
+
# file extension.
|
|
30
|
+
# @param [Hash] spec
|
|
31
|
+
def write(spec)
|
|
32
|
+
stringified = RSpec::OpenAPI::KeyTransformer.stringify(spec)
|
|
33
|
+
FileUtils.mkdir_p(File.dirname(@path))
|
|
34
|
+
output =
|
|
35
|
+
if json?
|
|
36
|
+
JSON.pretty_generate(stringified)
|
|
37
|
+
else
|
|
38
|
+
prepend_comment(YAML.dump(stringified))
|
|
39
|
+
end
|
|
40
|
+
File.write(@path, output)
|
|
22
41
|
end
|
|
23
42
|
|
|
24
43
|
private
|
|
@@ -33,18 +52,6 @@ class RSpec::OpenAPI::SchemaFile
|
|
|
33
52
|
RSpec::OpenAPI::KeyTransformer.symbolize(content)
|
|
34
53
|
end
|
|
35
54
|
|
|
36
|
-
# @param [Hash] spec
|
|
37
|
-
def write(spec)
|
|
38
|
-
FileUtils.mkdir_p(File.dirname(@path))
|
|
39
|
-
output =
|
|
40
|
-
if json?
|
|
41
|
-
JSON.pretty_generate(spec)
|
|
42
|
-
else
|
|
43
|
-
prepend_comment(YAML.dump(spec))
|
|
44
|
-
end
|
|
45
|
-
File.write(@path, output)
|
|
46
|
-
end
|
|
47
|
-
|
|
48
55
|
def prepend_comment(content)
|
|
49
56
|
return content if RSpec::OpenAPI.comment.nil?
|
|
50
57
|
|
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.29.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Takashi Kokubun
|
|
@@ -93,7 +93,7 @@ licenses:
|
|
|
93
93
|
metadata:
|
|
94
94
|
homepage_uri: https://github.com/exoego/rspec-openapi
|
|
95
95
|
source_code_uri: https://github.com/exoego/rspec-openapi
|
|
96
|
-
changelog_uri: https://github.com/exoego/rspec-openapi/releases/tag/v0.
|
|
96
|
+
changelog_uri: https://github.com/exoego/rspec-openapi/releases/tag/v0.29.0
|
|
97
97
|
rubygems_mfa_required: 'true'
|
|
98
98
|
rdoc_options: []
|
|
99
99
|
require_paths:
|