rswag-specs 2.16.0 → 3.0.0.pre
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/Rakefile +2 -0
- data/lib/generators/rspec/{swagger_generator.rb → openapi_generator.rb} +1 -1
- data/lib/generators/rspec/templates/spec.rb +1 -1
- data/lib/generators/rswag/specs/install/USAGE +2 -2
- data/lib/generators/rswag/specs/install/install_generator.rb +2 -2
- data/lib/generators/rswag/specs/install/templates/{swagger_helper.rb → openapi_helper.rb} +7 -7
- data/lib/rswag/route_parser.rb +2 -2
- data/lib/rswag/specs/configuration.rb +7 -16
- data/lib/rswag/specs/example_group_helpers.rb +28 -41
- data/lib/rswag/specs/example_helpers.rb +7 -16
- data/lib/rswag/specs/extended_schema.rb +3 -1
- data/lib/rswag/specs/openapi_formatter.rb +292 -0
- data/lib/rswag/specs/railtie.rb +1 -7
- data/lib/rswag/specs/request_factory.rb +114 -151
- data/lib/rswag/specs/response_validator.rb +22 -44
- data/lib/rswag/specs.rb +0 -45
- data/lib/tasks/rswag-specs_tasks.rake +6 -7
- metadata +14 -13
- data/lib/rswag/specs/swagger_formatter.rb +0 -229
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c1204ba4bf6963e945006db21b5f303a1ebf305b7a282d68339133c0fff1a50f
|
4
|
+
data.tar.gz: 100e4b03500f6d3095c94cc16c73b43258f6e6a9935a1f518149272a81318c00
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2faf601571a0a755cc6900d40e3e402bc2ff68cdd9c83504562f000c562c9e3d6097fbfdaf5a6b3e7112d1f267324096d974b0cbec28fde6eede8df5a6beb6b3
|
7
|
+
data.tar.gz: daf1e344467c8fd82a07664264befcf28ac4a765be2973344f13e5d438ac5351cbb052b259c6846a24777c491437a13ba8c7d75405badeaeabc8d9e8efa79b48
|
data/Rakefile
CHANGED
@@ -4,7 +4,7 @@ require 'rswag/route_parser'
|
|
4
4
|
require 'rails/generators'
|
5
5
|
|
6
6
|
module Rspec
|
7
|
-
class
|
7
|
+
class OpenapiGenerator < ::Rails::Generators::NamedBase
|
8
8
|
source_root File.expand_path('templates', __dir__)
|
9
9
|
class_option :spec_path, type: :string, default: 'requests'
|
10
10
|
|
@@ -1,8 +1,8 @@
|
|
1
1
|
Description:
|
2
|
-
Adds
|
2
|
+
Adds openapi_helper to enable Swagger DSL in integration specs
|
3
3
|
|
4
4
|
Example:
|
5
5
|
rails generate rswag:specs:install
|
6
6
|
|
7
7
|
This will create:
|
8
|
-
spec/
|
8
|
+
spec/openapi_helper.rb
|
@@ -7,8 +7,8 @@ module Rswag
|
|
7
7
|
class InstallGenerator < Rails::Generators::Base
|
8
8
|
source_root File.expand_path('templates', __dir__)
|
9
9
|
|
10
|
-
def
|
11
|
-
template('
|
10
|
+
def add_openapi_helper
|
11
|
+
template('openapi_helper.rb', 'spec/openapi_helper.rb')
|
12
12
|
end
|
13
13
|
end
|
14
14
|
end
|
@@ -3,19 +3,19 @@
|
|
3
3
|
require 'rails_helper'
|
4
4
|
|
5
5
|
RSpec.configure do |config|
|
6
|
-
# Specify a root folder where
|
6
|
+
# Specify a root folder where OpenAPI JSON files are generated
|
7
7
|
# NOTE: If you're using the rswag-api to serve API descriptions, you'll need
|
8
|
-
# to ensure that it's configured to serve
|
9
|
-
config.openapi_root = Rails.root.join('
|
8
|
+
# to ensure that it's configured to serve OpenAPI from the same folder
|
9
|
+
config.openapi_root = Rails.root.join('openapi').to_s
|
10
10
|
|
11
|
-
# Define one or more
|
11
|
+
# Define one or more OpenAPI documents and provide global metadata for each one
|
12
12
|
# When you run the 'rswag:specs:swaggerize' rake task, the complete Swagger will
|
13
13
|
# be generated at the provided relative path under openapi_root
|
14
14
|
# By default, the operations defined in spec files are added to the first
|
15
15
|
# document below. You can override this behavior by adding a openapi_spec tag to the
|
16
|
-
# the root example_group in your specs, e.g. describe '...', openapi_spec: 'v2/
|
16
|
+
# the root example_group in your specs, e.g. describe '...', openapi_spec: 'v2/openapi.json'
|
17
17
|
config.openapi_specs = {
|
18
|
-
'v1/
|
18
|
+
'v1/openapi.yaml' => {
|
19
19
|
openapi: '3.0.1',
|
20
20
|
info: {
|
21
21
|
title: 'API V1',
|
@@ -35,7 +35,7 @@ RSpec.configure do |config|
|
|
35
35
|
}
|
36
36
|
}
|
37
37
|
|
38
|
-
# Specify the format of the output
|
38
|
+
# Specify the format of the output OpenAPI file when running 'rswag:specs:swaggerize'.
|
39
39
|
# The openapi_specs configuration option has the filename including format in
|
40
40
|
# the key, this may want to be changed to avoid putting yaml in json files.
|
41
41
|
# Defaults to json. Accepts ':json' and ':yaml'.
|
data/lib/rswag/route_parser.rb
CHANGED
@@ -24,8 +24,8 @@ module Rswag
|
|
24
24
|
|
25
25
|
def path_from(route)
|
26
26
|
route.path.spec.to_s
|
27
|
-
|
28
|
-
|
27
|
+
.chomp('(.:format)') # Ignore any format suffix
|
28
|
+
.gsub(%r{:([^/.?]+)}, '{\1}') # Convert :id to {id}
|
29
29
|
end
|
30
30
|
|
31
31
|
def verb_from(route)
|
@@ -9,13 +9,14 @@ module Rswag
|
|
9
9
|
|
10
10
|
def openapi_root
|
11
11
|
@openapi_root ||=
|
12
|
-
@rspec_config.openapi_root || raise(ConfigurationError, 'No openapi_root provided. See
|
12
|
+
@rspec_config.openapi_root || raise(ConfigurationError, 'No openapi_root provided. See openapi_helper.rb')
|
13
13
|
end
|
14
14
|
|
15
15
|
def openapi_specs
|
16
16
|
@openapi_specs ||= begin
|
17
17
|
if @rspec_config.openapi_specs.nil? || @rspec_config.openapi_specs.empty?
|
18
|
-
raise ConfigurationError,
|
18
|
+
raise ConfigurationError,
|
19
|
+
'No openapi_specs defined. See openapi_helper.rb'
|
19
20
|
end
|
20
21
|
|
21
22
|
@rspec_config.openapi_specs
|
@@ -24,9 +25,7 @@ module Rswag
|
|
24
25
|
|
25
26
|
def rswag_dry_run
|
26
27
|
@rswag_dry_run ||= begin
|
27
|
-
|
28
|
-
@rspec_config.rswag_dry_run = ENV['SWAGGER_DRY_RUN'] == '1' || ENV['RSWAG_DRY_RUN'] == '1'
|
29
|
-
end
|
28
|
+
@rspec_config.rswag_dry_run = ENV['RSWAG_DRY_RUN'] == '1' if ENV.key?('RSWAG_DRY_RUN')
|
30
29
|
|
31
30
|
@rspec_config.rswag_dry_run.nil? || @rspec_config.rswag_dry_run
|
32
31
|
end
|
@@ -38,8 +37,9 @@ module Rswag
|
|
38
37
|
@rspec_config.openapi_format = :json
|
39
38
|
end
|
40
39
|
|
41
|
-
unless [
|
42
|
-
raise ConfigurationError,
|
40
|
+
unless %i[json yaml].include?(@rspec_config.openapi_format)
|
41
|
+
raise ConfigurationError,
|
42
|
+
"Unknown openapi_format '#{@rspec_config.openapi_format}'"
|
43
43
|
end
|
44
44
|
|
45
45
|
@rspec_config.openapi_format
|
@@ -53,15 +53,6 @@ module Rswag
|
|
53
53
|
openapi_specs[name]
|
54
54
|
end
|
55
55
|
|
56
|
-
def get_openapi_spec_version(name)
|
57
|
-
doc = get_openapi_spec(name)
|
58
|
-
doc[:openapi] || doc[:swagger]
|
59
|
-
end
|
60
|
-
|
61
|
-
def openapi_strict_schema_validation
|
62
|
-
@rspec_config.openapi_strict_schema_validation || false
|
63
|
-
end
|
64
|
-
|
65
56
|
def openapi_all_properties_required
|
66
57
|
@rspec_config.openapi_all_properties_required || false
|
67
58
|
end
|
@@ -5,19 +5,19 @@ require 'active_support'
|
|
5
5
|
module Rswag
|
6
6
|
module Specs
|
7
7
|
module ExampleGroupHelpers
|
8
|
-
def path(template, metadata
|
8
|
+
def path(template, *tags, **metadata, &block)
|
9
9
|
metadata[:path_item] = { template: template }
|
10
|
-
describe(template, metadata, &block)
|
10
|
+
describe(template, *tags, **metadata, &block)
|
11
11
|
end
|
12
12
|
|
13
|
-
[
|
14
|
-
define_method(verb) do |summary, **metadata, &block|
|
13
|
+
%i[get post patch put delete head options trace].each do |verb|
|
14
|
+
define_method(verb) do |summary, *tags, **metadata, &block|
|
15
15
|
api_metadata = { operation: { verb: verb, summary: summary } }.deep_merge(metadata)
|
16
|
-
describe(verb, **api_metadata, &block)
|
16
|
+
describe(verb, *tags, **api_metadata, &block)
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
20
|
-
[
|
20
|
+
%i[operationId deprecated security].each do |attr_name|
|
21
21
|
define_method(attr_name) do |value|
|
22
22
|
metadata[:operation][attr_name] = value
|
23
23
|
end
|
@@ -33,16 +33,14 @@ module Rswag
|
|
33
33
|
end
|
34
34
|
|
35
35
|
# These are array properties - note the splat operator
|
36
|
-
[
|
36
|
+
%i[tags consumes produces schemes].each do |attr_name|
|
37
37
|
define_method(attr_name) do |*value|
|
38
38
|
metadata[:operation][attr_name] = value
|
39
39
|
end
|
40
40
|
end
|
41
41
|
|
42
42
|
def parameter(attributes)
|
43
|
-
if attributes[:in] && attributes[:in].to_sym == :path
|
44
|
-
attributes[:required] = true
|
45
|
-
end
|
43
|
+
attributes[:required] = true if attributes[:in] && attributes[:in].to_sym == :path
|
46
44
|
|
47
45
|
if metadata.key?(:operation)
|
48
46
|
metadata[:operation][:parameters] ||= []
|
@@ -54,19 +52,20 @@ module Rswag
|
|
54
52
|
end
|
55
53
|
|
56
54
|
def request_body_example(value:, summary: nil, name: nil)
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
55
|
+
return unless metadata.key?(:operation)
|
56
|
+
|
57
|
+
metadata[:operation][:request_examples] ||= []
|
58
|
+
example = { value: value }
|
59
|
+
example[:summary] = summary if summary
|
60
|
+
# We need the examples to have a unique name for a set of examples,
|
61
|
+
# so just make the name the length if one isn't provided.
|
62
|
+
example[:name] = name || metadata[:operation][:request_examples].length
|
63
|
+
metadata[:operation][:request_examples] << example
|
65
64
|
end
|
66
65
|
|
67
|
-
def response(code, description, metadata
|
66
|
+
def response(code, description, *tags, **metadata, &block)
|
68
67
|
metadata[:response] = { code: code, description: description }
|
69
|
-
context(description, metadata, &block)
|
68
|
+
context(description, *tags, **metadata, &block)
|
70
69
|
end
|
71
70
|
|
72
71
|
def schema(value)
|
@@ -84,17 +83,16 @@ module Rswag
|
|
84
83
|
# rspec-core ExampleGroup
|
85
84
|
def examples(examples = nil)
|
86
85
|
return super() if examples.nil?
|
86
|
+
|
87
87
|
# should we add a deprecation warning?
|
88
88
|
examples.each_with_index do |(mime, example_object), index|
|
89
89
|
example(mime, "example_#{index}", example_object)
|
90
90
|
end
|
91
91
|
end
|
92
92
|
|
93
|
-
def example(mime, name, value, summary=nil, description=nil)
|
93
|
+
def example(mime, name, value, summary = nil, description = nil)
|
94
94
|
# Todo - move initialization of metadata somewhere else.
|
95
|
-
if metadata[:response][:content].blank?
|
96
|
-
metadata[:response][:content] = {}
|
97
|
-
end
|
95
|
+
metadata[:response][:content] = {} if metadata[:response][:content].blank?
|
98
96
|
|
99
97
|
if metadata[:response][:content][mime].blank?
|
100
98
|
metadata[:response][:content][mime] = {}
|
@@ -126,24 +124,13 @@ module Rswag
|
|
126
124
|
|
127
125
|
description ||= "returns a #{metadata[:response][:code]} response"
|
128
126
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
end
|
127
|
+
before do |example|
|
128
|
+
submit_request(example.metadata)
|
129
|
+
end
|
133
130
|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
end
|
138
|
-
else
|
139
|
-
before do |example|
|
140
|
-
submit_request(example.metadata)
|
141
|
-
end
|
142
|
-
|
143
|
-
it description, *args, **options do |example|
|
144
|
-
assert_response_matches_metadata(example.metadata, &block)
|
145
|
-
example.instance_exec(response, &block) if block_given?
|
146
|
-
end
|
131
|
+
it description, *args, **options do |example|
|
132
|
+
assert_response_matches_metadata(example.metadata, &block)
|
133
|
+
example.instance_exec(response, &block) if block_given?
|
147
134
|
end
|
148
135
|
end
|
149
136
|
end
|
@@ -7,23 +7,14 @@ module Rswag
|
|
7
7
|
module Specs
|
8
8
|
module ExampleHelpers
|
9
9
|
def submit_request(metadata)
|
10
|
-
request = RequestFactory.new
|
10
|
+
request = RequestFactory.new(metadata, self).build_request
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
)
|
19
|
-
else
|
20
|
-
send(
|
21
|
-
request[:verb],
|
22
|
-
request[:path],
|
23
|
-
params: request[:payload],
|
24
|
-
headers: request[:headers]
|
25
|
-
)
|
26
|
-
end
|
12
|
+
send(
|
13
|
+
request[:verb],
|
14
|
+
request[:path],
|
15
|
+
params: request[:payload],
|
16
|
+
headers: request[:headers]
|
17
|
+
)
|
27
18
|
end
|
28
19
|
|
29
20
|
def assert_response_matches_metadata(metadata)
|
@@ -12,7 +12,9 @@ module Rswag
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def validate(current_schema, data, *)
|
15
|
-
|
15
|
+
if data.nil? && (current_schema.schema['nullable'] == true || current_schema.schema['x-nullable'] == true)
|
16
|
+
return
|
17
|
+
end
|
16
18
|
|
17
19
|
super
|
18
20
|
end
|
@@ -0,0 +1,292 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support/core_ext/hash/deep_merge'
|
4
|
+
require 'rspec/core/formatters/base_text_formatter'
|
5
|
+
require 'openapi_helper'
|
6
|
+
|
7
|
+
module Rswag
|
8
|
+
module Specs
|
9
|
+
class OpenapiFormatter < ::RSpec::Core::Formatters::BaseTextFormatter
|
10
|
+
::RSpec::Core::Formatters.register self, :example_group_finished, :stop
|
11
|
+
|
12
|
+
def initialize(output, config = Rswag::Specs.config)
|
13
|
+
@output = output
|
14
|
+
@config = config
|
15
|
+
|
16
|
+
@output.puts 'Generating OpenAPI spec...'
|
17
|
+
end
|
18
|
+
|
19
|
+
def example_group_finished(notification)
|
20
|
+
metadata = notification.group.metadata
|
21
|
+
# metadata[:document] has to be explicitly false to skip generating docs
|
22
|
+
return if metadata[:document] == false
|
23
|
+
return unless metadata.key?(:response)
|
24
|
+
|
25
|
+
openapi_spec = @config.get_openapi_spec(metadata[:openapi_spec])
|
26
|
+
raise ConfigurationError, 'Unsupported OpenAPI version' unless doc_version(openapi_spec).start_with?('3')
|
27
|
+
|
28
|
+
# This is called multiple times per file!
|
29
|
+
# metadata[:operation] is also re-used between examples within file
|
30
|
+
# therefore be careful NOT to modify its content here.
|
31
|
+
upgrade_request_type!(metadata)
|
32
|
+
upgrade_response_produces!(openapi_spec, metadata)
|
33
|
+
|
34
|
+
openapi_spec.deep_merge!(metadata_to_openapi(metadata))
|
35
|
+
end
|
36
|
+
|
37
|
+
def stop(_notification = nil)
|
38
|
+
@config.openapi_specs.each do |url_path, doc|
|
39
|
+
parse_parameters(doc)
|
40
|
+
|
41
|
+
file_path = File.join(@config.openapi_root, url_path)
|
42
|
+
dirname = File.dirname(file_path)
|
43
|
+
FileUtils.mkdir_p dirname unless File.exist?(dirname)
|
44
|
+
|
45
|
+
File.open(file_path, 'w') do |file|
|
46
|
+
file.write(pretty_generate(doc))
|
47
|
+
end
|
48
|
+
|
49
|
+
@output.puts "OpenAPI doc generated at #{file_path}"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def pretty_generate(doc)
|
56
|
+
if @config.openapi_format == :yaml
|
57
|
+
clean_doc = yaml_prepare(doc)
|
58
|
+
YAML.dump(clean_doc)
|
59
|
+
else # config errors are thrown in 'def openapi_format', no throw needed here
|
60
|
+
JSON.pretty_generate(doc)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def yaml_prepare(doc)
|
65
|
+
json_doc = JSON.pretty_generate(doc)
|
66
|
+
JSON.parse(json_doc)
|
67
|
+
end
|
68
|
+
|
69
|
+
def metadata_to_openapi(metadata)
|
70
|
+
response_code = metadata[:response][:code]
|
71
|
+
response = metadata[:response].reject { |k, _v| k == :code }
|
72
|
+
|
73
|
+
verb = metadata[:operation][:verb]
|
74
|
+
operation = metadata[:operation]
|
75
|
+
.reject { |k, _v| k == :verb }
|
76
|
+
.merge(responses: { response_code => response })
|
77
|
+
|
78
|
+
path_template = metadata[:path_item][:template]
|
79
|
+
path_item = metadata[:path_item]
|
80
|
+
.reject { |k, _v| k == :template }
|
81
|
+
.merge(verb => operation)
|
82
|
+
|
83
|
+
{ paths: { path_template => path_item } }
|
84
|
+
end
|
85
|
+
|
86
|
+
def doc_version(doc)
|
87
|
+
doc[:openapi]
|
88
|
+
end
|
89
|
+
|
90
|
+
def upgrade_response_produces!(openapi_spec, metadata)
|
91
|
+
# Accept header
|
92
|
+
mime_list = Array(metadata[:operation][:produces] || openapi_spec[:produces])
|
93
|
+
target_node = metadata[:response]
|
94
|
+
upgrade_content!(mime_list, target_node)
|
95
|
+
metadata[:response].delete(:schema)
|
96
|
+
end
|
97
|
+
|
98
|
+
def upgrade_content!(mime_list, target_node)
|
99
|
+
schema = target_node[:schema]
|
100
|
+
return if mime_list.empty? || schema.nil?
|
101
|
+
|
102
|
+
target_node[:content] ||= {}
|
103
|
+
mime_list.each do |mime_type|
|
104
|
+
# TODO: upgrade to have content-type specific schema
|
105
|
+
(target_node[:content][mime_type] ||= {}).merge!(schema: schema)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def upgrade_request_type!(metadata)
|
110
|
+
# No deprecation here as it seems valid to allow type as a shorthand
|
111
|
+
operation_nodes = metadata[:operation][:parameters] || []
|
112
|
+
path_nodes = metadata[:path_item][:parameters] || []
|
113
|
+
header_node = metadata[:response][:headers] || {}
|
114
|
+
|
115
|
+
(operation_nodes + path_nodes + header_node.values).each do |node|
|
116
|
+
if node && node[:type] && node[:schema].nil?
|
117
|
+
node[:schema] = { type: node[:type] }
|
118
|
+
node.delete(:type)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def remove_invalid_operation_keys!(value)
|
124
|
+
return unless value.is_a?(Hash)
|
125
|
+
|
126
|
+
value.delete(:consumes) if value[:consumes]
|
127
|
+
value.delete(:produces) if value[:produces]
|
128
|
+
value.delete(:request_examples) if value[:request_examples]
|
129
|
+
end
|
130
|
+
|
131
|
+
def parse_parameters(doc)
|
132
|
+
doc[:paths]&.each_pair do |_k, path|
|
133
|
+
path.each_pair do |_verb, endpoint|
|
134
|
+
is_hash = endpoint.is_a?(Hash)
|
135
|
+
if is_hash && endpoint[:parameters]
|
136
|
+
mime_list = endpoint[:consumes] || doc[:consumes]
|
137
|
+
parse_endpoint(endpoint, mime_list)
|
138
|
+
end
|
139
|
+
remove_invalid_operation_keys!(endpoint)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def parse_endpoint(endpoint, mime_list)
|
145
|
+
parameters = endpoint[:parameters]
|
146
|
+
|
147
|
+
# Parse any parameters
|
148
|
+
parameters.each do |parameter|
|
149
|
+
set_parameter_schema(parameter)
|
150
|
+
convert_file_parameter(parameter)
|
151
|
+
parse_enum(parameter)
|
152
|
+
end
|
153
|
+
|
154
|
+
# Parse parameters that are body parameters:
|
155
|
+
parameters.select { |p| parameter_in_form_data_or_body?(p) }.each do |parameter|
|
156
|
+
parse_form_data_or_body_parameter(endpoint, parameter, mime_list)
|
157
|
+
parameters.delete(parameter) # "consume" parameters that will end up in response body
|
158
|
+
end
|
159
|
+
|
160
|
+
# Remove blank schemas - todo: refactor to not add in the first place
|
161
|
+
parameters.each { |p| p.delete(:schema) if p[:schema].blank? }
|
162
|
+
end
|
163
|
+
|
164
|
+
def set_parameter_schema(parameter)
|
165
|
+
# It might be that the schema has a required attribute as a boolean, but it must be an array, hence remove it
|
166
|
+
# and simply mark the parameter as required, which will be processed later.
|
167
|
+
parameter[:schema] ||= {}
|
168
|
+
if parameter[:schema].key?(:required) && parameter[:schema][:required] == true
|
169
|
+
parameter[:required] =
|
170
|
+
parameter[:schema].delete(:required)
|
171
|
+
end
|
172
|
+
# Also parameters currently can be defined with a datatype (`type:`)
|
173
|
+
# but this should be in `schema:` in the output.
|
174
|
+
parameter[:schema][:type] = parameter.delete(:type) if parameter.key?(:type)
|
175
|
+
end
|
176
|
+
|
177
|
+
def parameter_in_form_data_or_body?(p)
|
178
|
+
p[:in] == :formData || parameter_in_body?(p)
|
179
|
+
end
|
180
|
+
|
181
|
+
def parameter_in_body?(p)
|
182
|
+
p[:in] == :body
|
183
|
+
end
|
184
|
+
|
185
|
+
def parse_form_data_or_body_parameter(endpoint, parameter, mime_list)
|
186
|
+
unless mime_list
|
187
|
+
raise ConfigurationError,
|
188
|
+
'A body or form data parameters are specified without a Media Type for the content'
|
189
|
+
end
|
190
|
+
|
191
|
+
# Only add requestBody if there are any body parameters and not already defined
|
192
|
+
add_request_body(endpoint)
|
193
|
+
|
194
|
+
# If a description is provided for the parameter, it should be moved to the schema description
|
195
|
+
desc = parameter.delete(:description)
|
196
|
+
parameter[:schema][:description] = desc if desc
|
197
|
+
|
198
|
+
mime_list.each do |mime|
|
199
|
+
endpoint[:requestBody][:content][mime] ||= {}
|
200
|
+
mime_config = endpoint[:requestBody][:content][mime]
|
201
|
+
|
202
|
+
# Only parse parameters if there has not already been a reference object set. Ie if a `in: :body` parameter
|
203
|
+
# has been seen already `schema` is defined, or if formData is being used then ensure we have a `properties`
|
204
|
+
# key in schema.
|
205
|
+
next unless mime_config[:schema].nil? || mime_config.dig(:schema, :properties)
|
206
|
+
|
207
|
+
set_mime_config(mime_config, parameter)
|
208
|
+
set_mime_examples(mime_config, endpoint)
|
209
|
+
set_request_body_required(mime_config, endpoint, parameter)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def add_request_body(endpoint)
|
214
|
+
return if endpoint.dig(:requestBody, :content)
|
215
|
+
|
216
|
+
endpoint[:requestBody] = { content: {} }
|
217
|
+
end
|
218
|
+
|
219
|
+
def set_request_body_required(mime_config, endpoint, parameter)
|
220
|
+
return unless parameter[:required]
|
221
|
+
|
222
|
+
# FIXME: If any are `required` then the body is set to `required` but this assumption may not hold in reality as
|
223
|
+
# you could have optional body, but if body is provided then some properties are required.
|
224
|
+
endpoint[:requestBody][:required] = true
|
225
|
+
|
226
|
+
return if parameter_in_body?(parameter)
|
227
|
+
|
228
|
+
if parameter[:name]
|
229
|
+
mime_config[:schema][:required] ||= []
|
230
|
+
mime_config[:schema][:required] << parameter[:name].to_s
|
231
|
+
else
|
232
|
+
mime_config[:schema][:required] = true
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
def convert_file_parameter(parameter)
|
237
|
+
return unless parameter[:schema][:type] == :file
|
238
|
+
|
239
|
+
parameter[:schema][:type] = :string
|
240
|
+
parameter[:schema][:format] = :binary
|
241
|
+
end
|
242
|
+
|
243
|
+
def set_mime_config(mime_config, parameter)
|
244
|
+
schema_with_form_properties = parameter[:name] && !parameter_in_body?(parameter)
|
245
|
+
mime_config[:schema] ||= schema_with_form_properties ? { type: :object, properties: {} } : parameter[:schema]
|
246
|
+
return unless schema_with_form_properties
|
247
|
+
|
248
|
+
mime_config[:schema][:properties][parameter[:name]] = parameter[:schema]
|
249
|
+
set_mime_encoding(mime_config, parameter)
|
250
|
+
end
|
251
|
+
|
252
|
+
def set_mime_encoding(mime_config, parameter)
|
253
|
+
return unless parameter[:encoding]
|
254
|
+
|
255
|
+
encoding = parameter[:encoding].dup
|
256
|
+
encoding[:contentType] = encoding[:contentType].join(',') if encoding[:contentType].is_a?(Array)
|
257
|
+
mime_config[:encoding] ||= {}
|
258
|
+
mime_config[:encoding][parameter[:name]] = encoding
|
259
|
+
end
|
260
|
+
|
261
|
+
def set_mime_examples(mime_config, endpoint)
|
262
|
+
examples = endpoint[:request_examples]
|
263
|
+
return unless examples
|
264
|
+
|
265
|
+
examples.each do |example|
|
266
|
+
mime_config[:examples] ||= {}
|
267
|
+
mime_config[:examples][example[:name]] = {
|
268
|
+
summary: example[:summary] || endpoint[:summary],
|
269
|
+
value: example[:value]
|
270
|
+
}
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
def parse_enum(parameter)
|
275
|
+
return unless parameter.key?(:enum)
|
276
|
+
|
277
|
+
enum = parameter.delete(:enum)
|
278
|
+
parameter[:schema] ||= {}
|
279
|
+
parameter[:schema][:enum] = enum.is_a?(Hash) ? enum.keys.map(&:to_s) : enum
|
280
|
+
parameter[:description] = generate_enum_description(parameter, enum) if enum.is_a?(Hash)
|
281
|
+
end
|
282
|
+
|
283
|
+
def generate_enum_description(param, enum)
|
284
|
+
enum_description = "#{param[:description]}:\n "
|
285
|
+
enum.each do |k, v|
|
286
|
+
enum_description += "* `#{k}` #{v}\n "
|
287
|
+
end
|
288
|
+
enum_description
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
data/lib/rswag/specs/railtie.rb
CHANGED
@@ -8,13 +8,7 @@ module Rswag
|
|
8
8
|
end
|
9
9
|
|
10
10
|
generators do
|
11
|
-
require 'generators/rspec/
|
12
|
-
end
|
13
|
-
|
14
|
-
initializer 'rswag-specs.deprecator' do |app|
|
15
|
-
if app.respond_to?(:deprecators)
|
16
|
-
app.deprecators[:rswag_specs] = Rswag::Specs.deprecator
|
17
|
-
end
|
11
|
+
require 'generators/rspec/openapi_generator'
|
18
12
|
end
|
19
13
|
end
|
20
14
|
end
|