grape-swagger 0.34.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +55 -0
- data/.travis.yml +14 -10
- data/CHANGELOG.md +37 -4
- data/Gemfile +9 -4
- data/README.md +123 -10
- data/UPGRADING.md +30 -0
- data/grape-swagger.gemspec +1 -1
- data/lib/grape-swagger.rb +1 -0
- data/lib/grape-swagger/doc_methods/build_model_definition.rb +53 -2
- data/lib/grape-swagger/doc_methods/data_type.rb +6 -6
- data/lib/grape-swagger/doc_methods/operation_id.rb +2 -2
- data/lib/grape-swagger/doc_methods/parse_params.rb +19 -4
- data/lib/grape-swagger/endpoint.rb +11 -11
- data/lib/grape-swagger/endpoint/params_parser.rb +12 -5
- data/lib/grape-swagger/rake/oapi_tasks.rb +10 -2
- data/lib/grape-swagger/version.rb +1 -1
- data/spec/issues/427_entity_as_string_spec.rb +1 -1
- data/spec/issues/430_entity_definitions_spec.rb +7 -5
- data/spec/issues/784_extensions_on_params_spec.rb +38 -0
- data/spec/lib/data_type_spec.rb +14 -2
- data/spec/lib/endpoint/params_parser_spec.rb +2 -1
- data/spec/lib/endpoint_spec.rb +1 -1
- data/spec/lib/oapi_tasks_spec.rb +15 -5
- data/spec/support/model_parsers/entity_parser.rb +1 -1
- data/spec/support/model_parsers/representable_parser.rb +1 -1
- data/spec/swagger_v2/api_swagger_v2_hide_param_spec.rb +14 -3
- data/spec/swagger_v2/api_swagger_v2_param_type_body_spec.rb +2 -2
- data/spec/swagger_v2/boolean_params_spec.rb +1 -1
- data/spec/swagger_v2/inheritance_and_discriminator_spec.rb +56 -0
- data/spec/swagger_v2/reference_entity_spec.rb +74 -29
- metadata +14 -16
data/grape-swagger.gemspec
CHANGED
@@ -14,7 +14,7 @@ Gem::Specification.new do |s|
|
|
14
14
|
s.license = 'MIT'
|
15
15
|
|
16
16
|
s.required_ruby_version = '>= 2.4'
|
17
|
-
s.add_runtime_dependency 'grape', '
|
17
|
+
s.add_runtime_dependency 'grape', '~> 1.3'
|
18
18
|
|
19
19
|
s.files = `git ls-files`.split("\n")
|
20
20
|
s.test_files = `git ls-files -- {test,spec}/*`.split("\n")
|
data/lib/grape-swagger.rb
CHANGED
@@ -109,6 +109,7 @@ end
|
|
109
109
|
module SwaggerDocumentationAdder
|
110
110
|
attr_accessor :combined_namespaces, :combined_namespace_identifiers
|
111
111
|
attr_accessor :combined_routes, :combined_namespace_routes
|
112
|
+
|
112
113
|
include SwaggerRouting
|
113
114
|
|
114
115
|
def add_swagger_documentation(options = {})
|
@@ -4,8 +4,8 @@ module GrapeSwagger
|
|
4
4
|
module DocMethods
|
5
5
|
class BuildModelDefinition
|
6
6
|
class << self
|
7
|
-
def build(model, properties, required)
|
8
|
-
definition = { type: 'object', properties: properties }
|
7
|
+
def build(model, properties, required, other_def_properties = {})
|
8
|
+
definition = { type: 'object', properties: properties }.merge(other_def_properties)
|
9
9
|
|
10
10
|
if required.nil?
|
11
11
|
required_attrs = required_attributes(model)
|
@@ -17,6 +17,57 @@ module GrapeSwagger
|
|
17
17
|
definition
|
18
18
|
end
|
19
19
|
|
20
|
+
def parse_params_from_model(parsed_response, model, model_name)
|
21
|
+
if parsed_response.is_a?(Hash) && parsed_response.keys.first == :allOf
|
22
|
+
refs_or_models = parsed_response[:allOf]
|
23
|
+
parsed = parse_refs_and_models(refs_or_models, model)
|
24
|
+
|
25
|
+
{
|
26
|
+
allOf: parsed
|
27
|
+
}
|
28
|
+
else
|
29
|
+
properties, required = parsed_response
|
30
|
+
unless properties&.any?
|
31
|
+
raise GrapeSwagger::Errors::SwaggerSpec,
|
32
|
+
"Empty model #{model_name}, swagger 2.0 doesn't support empty definitions."
|
33
|
+
end
|
34
|
+
properties, other_def_properties = parse_properties(properties)
|
35
|
+
|
36
|
+
build(
|
37
|
+
model, properties, required, other_def_properties
|
38
|
+
)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def parse_properties(properties)
|
43
|
+
other_properties = {}
|
44
|
+
|
45
|
+
discriminator_key, discriminator_value =
|
46
|
+
properties.find do |_key, value|
|
47
|
+
value[:documentation].try(:[], :is_discriminator)
|
48
|
+
end
|
49
|
+
|
50
|
+
if discriminator_key
|
51
|
+
discriminator_value.delete(:documentation)
|
52
|
+
properties[discriminator_key] = discriminator_value
|
53
|
+
|
54
|
+
other_properties[:discriminator] = discriminator_key
|
55
|
+
end
|
56
|
+
|
57
|
+
[properties, other_properties]
|
58
|
+
end
|
59
|
+
|
60
|
+
def parse_refs_and_models(refs_or_models, model)
|
61
|
+
refs_or_models.map do |ref_or_models|
|
62
|
+
if ref_or_models.is_a?(Hash) && ref_or_models.keys.first == '$ref'
|
63
|
+
ref_or_models
|
64
|
+
else
|
65
|
+
properties, required = ref_or_models
|
66
|
+
GrapeSwagger::DocMethods::BuildModelDefinition.build(model, properties, required)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
20
71
|
private
|
21
72
|
|
22
73
|
def required_attributes(model)
|
@@ -16,7 +16,7 @@ module GrapeSwagger
|
|
16
16
|
'object'
|
17
17
|
when 'Rack::Multipart::UploadedFile', 'File'
|
18
18
|
'file'
|
19
|
-
when '
|
19
|
+
when 'Grape::API::Boolean'
|
20
20
|
'boolean'
|
21
21
|
when 'BigDecimal'
|
22
22
|
'double'
|
@@ -48,14 +48,14 @@ module GrapeSwagger
|
|
48
48
|
end
|
49
49
|
|
50
50
|
def parse_entity_name(model)
|
51
|
-
if model.
|
51
|
+
if model.respond_to?(:entity_name)
|
52
52
|
model.entity_name
|
53
53
|
elsif model.to_s.end_with?('::Entity', '::Entities')
|
54
|
-
model.to_s.split('::')[
|
55
|
-
elsif model.
|
56
|
-
model.
|
54
|
+
model.to_s.split('::')[0..-2].join('_')
|
55
|
+
elsif model.to_s.start_with?('Entity::', 'Entities::', 'Representable::')
|
56
|
+
model.to_s.split('::')[1..-1].join('_')
|
57
57
|
else
|
58
|
-
model.to_s.split('::').
|
58
|
+
model.to_s.split('::').join('_')
|
59
59
|
end
|
60
60
|
end
|
61
61
|
|
@@ -16,8 +16,8 @@ module GrapeSwagger
|
|
16
16
|
|
17
17
|
def manipulate(path)
|
18
18
|
operation = path.split('/').map(&:capitalize).join
|
19
|
-
operation.gsub!(
|
20
|
-
operation.gsub!(
|
19
|
+
operation.gsub!(/-(\w)/, &:upcase).delete!('-') if operation[/-(\w)/]
|
20
|
+
operation.gsub!(/_(\w)/, &:upcase).delete!('_') if operation.include?('_')
|
21
21
|
operation.gsub!(/\.(\w)/, &:upcase).delete!('.') if operation[/\.(\w)/]
|
22
22
|
if path.include?('{')
|
23
23
|
operation.gsub!(/\{(\w)/, &:upcase)
|
@@ -26,6 +26,7 @@ module GrapeSwagger
|
|
26
26
|
document_range_values(settings) unless value_type[:is_array]
|
27
27
|
document_required(settings)
|
28
28
|
document_additional_properties(settings)
|
29
|
+
document_add_extensions(settings)
|
29
30
|
|
30
31
|
@parsed_param
|
31
32
|
end
|
@@ -62,6 +63,10 @@ module GrapeSwagger
|
|
62
63
|
@parsed_param[:format] = settings[:format] if settings[:format].present?
|
63
64
|
end
|
64
65
|
|
66
|
+
def document_add_extensions(settings)
|
67
|
+
GrapeSwagger::DocMethods::Extensions.add_extensions_to_root(settings, @parsed_param)
|
68
|
+
end
|
69
|
+
|
65
70
|
def document_array_param(value_type, definitions)
|
66
71
|
if value_type[:documentation].present?
|
67
72
|
param_type = value_type[:documentation][:param_type]
|
@@ -72,6 +77,19 @@ module GrapeSwagger
|
|
72
77
|
|
73
78
|
param_type ||= value_type[:param_type]
|
74
79
|
|
80
|
+
array_items = parse_array_item(
|
81
|
+
definitions,
|
82
|
+
type,
|
83
|
+
value_type
|
84
|
+
)
|
85
|
+
|
86
|
+
@parsed_param[:in] = param_type || 'formData'
|
87
|
+
@parsed_param[:items] = array_items
|
88
|
+
@parsed_param[:type] = 'array'
|
89
|
+
@parsed_param[:collectionFormat] = collection_format if DataType.collections.include?(collection_format)
|
90
|
+
end
|
91
|
+
|
92
|
+
def parse_array_item(definitions, type, value_type)
|
75
93
|
array_items = {}
|
76
94
|
if definitions[value_type[:data_type]]
|
77
95
|
array_items['$ref'] = "#/definitions/#{@parsed_param[:type]}"
|
@@ -86,10 +104,7 @@ module GrapeSwagger
|
|
86
104
|
|
87
105
|
array_items[:default] = value_type[:default] if value_type[:default].present?
|
88
106
|
|
89
|
-
|
90
|
-
@parsed_param[:items] = array_items
|
91
|
-
@parsed_param[:type] = 'array'
|
92
|
-
@parsed_param[:collectionFormat] = collection_format if DataType.collections.include?(collection_format)
|
107
|
+
array_items
|
93
108
|
end
|
94
109
|
|
95
110
|
def document_additional_properties(settings)
|
@@ -196,10 +196,7 @@ module Grape
|
|
196
196
|
end
|
197
197
|
|
198
198
|
def response_object(route, options)
|
199
|
-
codes
|
200
|
-
codes.map! { |x| x.is_a?(Array) ? { code: x[0], message: x[1], model: x[2], examples: x[3], headers: x[4] } : x }
|
201
|
-
|
202
|
-
codes.each_with_object({}) do |value, memo|
|
199
|
+
codes(route).each_with_object({}) do |value, memo|
|
203
200
|
value[:message] ||= ''
|
204
201
|
memo[value[:code]] = { description: value[:message] }
|
205
202
|
|
@@ -225,6 +222,12 @@ module Grape
|
|
225
222
|
end
|
226
223
|
end
|
227
224
|
|
225
|
+
def codes(route)
|
226
|
+
http_codes_from_route(route).map do |x|
|
227
|
+
x.is_a?(Array) ? { code: x[0], message: x[1], model: x[2], examples: x[3], headers: x[4] } : x
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
228
231
|
def success_code?(code)
|
229
232
|
status = code.is_a?(Array) ? code.first : code[:code]
|
230
233
|
status.between?(200, 299)
|
@@ -301,7 +304,7 @@ module Grape
|
|
301
304
|
default_type(required)
|
302
305
|
|
303
306
|
request_params = unless declared_params.nil? && route.headers.nil?
|
304
|
-
GrapeSwagger::Endpoint::ParamsParser.parse_request_params(required, settings)
|
307
|
+
GrapeSwagger::Endpoint::ParamsParser.parse_request_params(required, settings, self)
|
305
308
|
end || {}
|
306
309
|
|
307
310
|
request_params.empty? ? required : request_params
|
@@ -340,13 +343,10 @@ module Grape
|
|
340
343
|
parser = GrapeSwagger.model_parsers.find(model)
|
341
344
|
raise GrapeSwagger::Errors::UnregisteredParser, "No parser registered for #{model_name}." unless parser
|
342
345
|
|
343
|
-
|
344
|
-
unless properties&.any?
|
345
|
-
raise GrapeSwagger::Errors::SwaggerSpec,
|
346
|
-
"Empty model #{model_name}, swagger 2.0 doesn't support empty definitions."
|
347
|
-
end
|
346
|
+
parsed_response = parser.new(model, self).call
|
348
347
|
|
349
|
-
@definitions[model_name] =
|
348
|
+
@definitions[model_name] =
|
349
|
+
GrapeSwagger::DocMethods::BuildModelDefinition.parse_params_from_model(parsed_response, model, model_name)
|
350
350
|
|
351
351
|
model_name
|
352
352
|
end
|
@@ -3,15 +3,16 @@
|
|
3
3
|
module GrapeSwagger
|
4
4
|
module Endpoint
|
5
5
|
class ParamsParser
|
6
|
-
attr_reader :params, :settings
|
6
|
+
attr_reader :params, :settings, :endpoint
|
7
7
|
|
8
|
-
def self.parse_request_params(params, settings)
|
9
|
-
new(params, settings).parse_request_params
|
8
|
+
def self.parse_request_params(params, settings, endpoint)
|
9
|
+
new(params, settings, endpoint).parse_request_params
|
10
10
|
end
|
11
11
|
|
12
|
-
def initialize(params, settings)
|
12
|
+
def initialize(params, settings, endpoint)
|
13
13
|
@params = params
|
14
14
|
@settings = settings
|
15
|
+
@endpoint = endpoint
|
15
16
|
end
|
16
17
|
|
17
18
|
def parse_request_params
|
@@ -55,7 +56,13 @@ module GrapeSwagger
|
|
55
56
|
return true unless param_options.key?(:documentation) && !param_options[:required]
|
56
57
|
|
57
58
|
param_hidden = param_options[:documentation].fetch(:hidden, false)
|
58
|
-
|
59
|
+
if param_hidden.is_a?(Proc)
|
60
|
+
param_hidden = if settings[:token_owner]
|
61
|
+
param_hidden.call(endpoint.send(settings[:token_owner].to_sym))
|
62
|
+
else
|
63
|
+
param_hidden.call
|
64
|
+
end
|
65
|
+
end
|
59
66
|
!param_hidden
|
60
67
|
end
|
61
68
|
|
@@ -10,17 +10,25 @@ module GrapeSwagger
|
|
10
10
|
include Rack::Test::Methods
|
11
11
|
|
12
12
|
attr_reader :oapi
|
13
|
-
attr_reader :api_class
|
14
13
|
|
15
14
|
def initialize(api_class)
|
16
15
|
super()
|
17
16
|
|
18
|
-
|
17
|
+
if api_class.is_a? String
|
18
|
+
@api_class_name = api_class
|
19
|
+
else
|
20
|
+
@api_class = api_class
|
21
|
+
end
|
22
|
+
|
19
23
|
define_tasks
|
20
24
|
end
|
21
25
|
|
22
26
|
private
|
23
27
|
|
28
|
+
def api_class
|
29
|
+
@api_class ||= @api_class_name.constantize
|
30
|
+
end
|
31
|
+
|
24
32
|
def define_tasks
|
25
33
|
namespace :oapi do
|
26
34
|
fetch
|
@@ -35,5 +35,5 @@ describe '#427 nested entity given as string' do
|
|
35
35
|
JSON.parse(last_response.body)['definitions']
|
36
36
|
end
|
37
37
|
|
38
|
-
specify { expect(subject.keys).to include 'RoleEntity', '
|
38
|
+
specify { expect(subject.keys).to include 'RoleEntity', 'Permission_WithoutRole' }
|
39
39
|
end
|
@@ -55,6 +55,8 @@ describe 'definition names' do
|
|
55
55
|
class Class7
|
56
56
|
class SeventhEntity < Class6::SixthEntity
|
57
57
|
expose :seventh_thing
|
58
|
+
|
59
|
+
private_class_method :entity_name
|
58
60
|
end
|
59
61
|
end
|
60
62
|
end
|
@@ -82,11 +84,11 @@ describe 'definition names' do
|
|
82
84
|
JSON.parse(last_response.body)['definitions']
|
83
85
|
end
|
84
86
|
|
85
|
-
specify { expect(subject).to include '
|
86
|
-
specify { expect(subject).to include '
|
87
|
+
specify { expect(subject).to include 'TestDefinition_DummyEntities_WithVeryLongName_AnotherGroupingModule_Class1' }
|
88
|
+
specify { expect(subject).to include 'TestDefinition_DummyEntities_WithVeryLongName_AnotherGroupingModule_Class2' }
|
87
89
|
specify { expect(subject).to include 'FooKlass' }
|
88
|
-
specify { expect(subject).to include '
|
89
|
-
specify { expect(subject).to include '
|
90
|
+
specify { expect(subject).to include 'TestDefinition_DummyEntities_WithVeryLongName_AnotherGroupingModule_Class4_FourthEntity' }
|
91
|
+
specify { expect(subject).to include 'TestDefinition_DummyEntities_WithVeryLongName_AnotherGroupingModule_Class5_FithEntity' }
|
90
92
|
specify { expect(subject).to include 'BarKlass' }
|
91
|
-
specify { expect(subject).to include '
|
93
|
+
specify { expect(subject).to include 'TestDefinition_DummyEntities_WithVeryLongName_AnotherGroupingModule_Class7_SeventhEntity' }
|
92
94
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe '#532 allow custom format' do
|
6
|
+
let(:app) do
|
7
|
+
Class.new(Grape::API) do
|
8
|
+
namespace :issue_784 do
|
9
|
+
params do
|
10
|
+
requires :logs, type: String, documentation: { format: 'log', x: { name: 'Log' } }
|
11
|
+
optional :phone_number, type: Integer, documentation: { format: 'phone_number', x: { name: 'PhoneNumber' } }
|
12
|
+
end
|
13
|
+
|
14
|
+
post do
|
15
|
+
present params
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
add_swagger_documentation format: :json
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
subject do
|
24
|
+
get '/swagger_doc'
|
25
|
+
JSON.parse(last_response.body)
|
26
|
+
end
|
27
|
+
|
28
|
+
let(:parameters) { subject['paths']['/issue_784']['post']['parameters'] }
|
29
|
+
|
30
|
+
specify do
|
31
|
+
expect(parameters).to eql(
|
32
|
+
[
|
33
|
+
{ 'in' => 'formData', 'name' => 'logs', 'type' => 'string', 'format' => 'log', 'required' => true, 'x-name' => 'Log' },
|
34
|
+
{ 'in' => 'formData', 'name' => 'phone_number', 'type' => 'integer', 'format' => 'phone_number', 'required' => false, 'x-name' => 'PhoneNumber' }
|
35
|
+
]
|
36
|
+
)
|
37
|
+
end
|
38
|
+
end
|
data/spec/lib/data_type_spec.rb
CHANGED
@@ -49,14 +49,26 @@ describe GrapeSwagger::DocMethods::DataType do
|
|
49
49
|
it { is_expected.to eq 'MyInteger' }
|
50
50
|
end
|
51
51
|
|
52
|
+
describe 'Types in array with inherited entity_name' do
|
53
|
+
before do
|
54
|
+
stub_const 'EntityBase', Class.new
|
55
|
+
allow(EntityBase).to receive(:entity_name).and_return 'MyInteger'
|
56
|
+
stub_const 'MyEntity', Class.new(EntityBase)
|
57
|
+
end
|
58
|
+
|
59
|
+
let(:value) { { type: '[MyEntity]' } }
|
60
|
+
|
61
|
+
it { is_expected.to eq 'MyInteger' }
|
62
|
+
end
|
63
|
+
|
52
64
|
describe 'Rack::Multipart::UploadedFile' do
|
53
65
|
let(:value) { { type: Rack::Multipart::UploadedFile } }
|
54
66
|
|
55
67
|
it { is_expected.to eq 'file' }
|
56
68
|
end
|
57
69
|
|
58
|
-
describe '
|
59
|
-
let(:value) { { type:
|
70
|
+
describe 'Grape::API::Boolean' do
|
71
|
+
let(:value) { { type: Grape::API::Boolean } }
|
60
72
|
|
61
73
|
it { is_expected.to eq 'boolean' }
|
62
74
|
end
|
@@ -5,8 +5,9 @@ require 'spec_helper'
|
|
5
5
|
describe GrapeSwagger::Endpoint::ParamsParser do
|
6
6
|
let(:settings) { {} }
|
7
7
|
let(:params) { [] }
|
8
|
+
let(:endpoint) { nil }
|
8
9
|
|
9
|
-
let(:parser) { described_class.new(params, settings) }
|
10
|
+
let(:parser) { described_class.new(params, settings, endpoint) }
|
10
11
|
|
11
12
|
describe '#parse_request_params' do
|
12
13
|
subject(:parse_request_params) { parser.parse_request_params }
|
data/spec/lib/endpoint_spec.rb
CHANGED
@@ -49,7 +49,7 @@ describe Grape::Endpoint do
|
|
49
49
|
describe 'parse_request_params' do
|
50
50
|
let(:subject) { GrapeSwagger::Endpoint::ParamsParser }
|
51
51
|
before do
|
52
|
-
subject.send(:parse_request_params, params, {})
|
52
|
+
subject.send(:parse_request_params, params, {}, nil)
|
53
53
|
end
|
54
54
|
|
55
55
|
context 'when params do not contain an array' do
|
data/spec/lib/oapi_tasks_spec.rb
CHANGED
@@ -3,8 +3,8 @@
|
|
3
3
|
require 'spec_helper'
|
4
4
|
|
5
5
|
RSpec.describe GrapeSwagger::Rake::OapiTasks do
|
6
|
-
|
7
|
-
|
6
|
+
module Api
|
7
|
+
class Item < Grape::API
|
8
8
|
version 'v1', using: :path
|
9
9
|
|
10
10
|
namespace :item do
|
@@ -16,14 +16,24 @@ RSpec.describe GrapeSwagger::Rake::OapiTasks do
|
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
|
-
|
19
|
+
class Base < Grape::API
|
20
20
|
prefix :api
|
21
|
-
mount
|
21
|
+
mount Api::Item
|
22
22
|
add_swagger_documentation add_version: true
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
26
|
-
subject { described_class.new(
|
26
|
+
subject { described_class.new(Api::Base) }
|
27
|
+
|
28
|
+
describe '.new' do
|
29
|
+
it 'accepts class name as a constant' do
|
30
|
+
expect(described_class.new(::Api::Base).send(:api_class)).to eq(Api::Base)
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'accepts class name as a string' do
|
34
|
+
expect(described_class.new('::Api::Base').send(:api_class)).to eq(Api::Base)
|
35
|
+
end
|
36
|
+
end
|
27
37
|
|
28
38
|
describe '#make_request' do
|
29
39
|
describe 'complete documentation' do
|