grape-swagger 0.24.0 → 0.25.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/.gitignore +3 -0
- data/.rubocop.yml +4 -0
- data/.rubocop_todo.yml +29 -32
- data/.travis.yml +2 -0
- data/CHANGELOG.md +15 -0
- data/Gemfile +1 -1
- data/LICENSE.txt +1 -1
- data/README.md +253 -223
- data/UPGRADING.md +4 -0
- data/grape-swagger.gemspec +2 -2
- data/lib/grape-swagger.rb +10 -4
- data/lib/grape-swagger/doc_methods.rb +10 -5
- data/lib/grape-swagger/doc_methods/build_model_definition.rb +38 -0
- data/lib/grape-swagger/doc_methods/data_type.rb +6 -5
- data/lib/grape-swagger/doc_methods/move_params.rb +9 -1
- data/lib/grape-swagger/doc_methods/optional_object.rb +1 -1
- data/lib/grape-swagger/doc_methods/parse_params.rb +20 -19
- data/lib/grape-swagger/doc_methods/tag_name_description.rb +9 -17
- data/lib/grape-swagger/endpoint.rb +26 -29
- data/lib/grape-swagger/model_parsers.rb +7 -0
- data/lib/grape-swagger/version.rb +1 -1
- data/spec/issues/427_entity_as_string_spec.rb +39 -0
- data/spec/issues/430_entity_definitions_spec.rb +48 -4
- data/spec/support/model_parsers/entity_parser.rb +10 -1
- data/spec/support/model_parsers/mock_parser.rb +4 -1
- data/spec/support/model_parsers/representable_parser.rb +13 -2
- data/spec/swagger_v2/api_swagger_v2_detail_spec.rb +0 -2
- data/spec/swagger_v2/api_swagger_v2_param_type_body_spec.rb +56 -0
- data/spec/swagger_v2/api_swagger_v2_spec.rb +87 -61
- data/spec/swagger_v2/endpoint_versioned_path_spec.rb +24 -3
- data/spec/swagger_v2/guarded_endpoint_spec.rb +8 -3
- data/spec/swagger_v2/namespace_tags_prefix_spec.rb +2 -8
- data/spec/swagger_v2/namespace_tags_spec.rb +2 -8
- data/spec/swagger_v2/params_array_spec.rb +36 -0
- data/spec/swagger_v2/simple_mounted_api_spec.rb +2 -14
- metadata +12 -9
data/UPGRADING.md
CHANGED
@@ -1,6 +1,10 @@
|
|
1
1
|
Upgrading Grape-swagger
|
2
2
|
=======================
|
3
3
|
|
4
|
+
### Upgrading to >= 0.25.0
|
5
|
+
|
6
|
+
The global tag set now only includes tags for documented routes. This behaviour has impact in particular for calling the documtation of a specific route.
|
7
|
+
|
4
8
|
### Upgrading to >= 0.21.0
|
5
9
|
|
6
10
|
With grape >= 0.21.0, `grape-entity` support moved to separate gem `grape-swagger-entity`, if you use grape entity, update your Gemfile:
|
data/grape-swagger.gemspec
CHANGED
@@ -8,7 +8,7 @@ Gem::Specification.new do |s|
|
|
8
8
|
s.authors = ['Tim Vandecasteele']
|
9
9
|
s.email = ['tim.vandecasteele@gmail.com']
|
10
10
|
s.homepage = 'https://github.com/ruby-grape/grape-swagger'
|
11
|
-
s.summary = '
|
11
|
+
s.summary = 'Add auto generated documentation to your Grape API that can be displayed with Swagger.'
|
12
12
|
s.license = 'MIT'
|
13
13
|
|
14
14
|
s.add_runtime_dependency 'grape', '>= 0.12.0'
|
@@ -20,7 +20,7 @@ Gem::Specification.new do |s|
|
|
20
20
|
s.add_development_dependency 'bundler'
|
21
21
|
s.add_development_dependency 'rack-test'
|
22
22
|
s.add_development_dependency 'rack-cors'
|
23
|
-
s.add_development_dependency 'rubocop', '0.40
|
23
|
+
s.add_development_dependency 'rubocop', '~> 0.40'
|
24
24
|
s.add_development_dependency 'kramdown'
|
25
25
|
s.add_development_dependency 'redcarpet' unless RUBY_PLATFORM.eql?('java') || RUBY_ENGINE.eql?('rbx')
|
26
26
|
s.add_development_dependency 'rouge' unless RUBY_PLATFORM.eql?('java') || RUBY_ENGINE.eql?('rbx')
|
data/lib/grape-swagger.rb
CHANGED
@@ -52,7 +52,9 @@ module Grape
|
|
52
52
|
combine_namespace_routes(@target_class.combined_namespaces)
|
53
53
|
|
54
54
|
exclusive_route_keys = @target_class.combined_routes.keys - @target_class.combined_namespaces.keys
|
55
|
-
exclusive_route_keys.each
|
55
|
+
exclusive_route_keys.each do |key|
|
56
|
+
@target_class.combined_namespace_routes[key] = @target_class.combined_routes[key]
|
57
|
+
end
|
56
58
|
documentation_class
|
57
59
|
end
|
58
60
|
|
@@ -128,10 +130,12 @@ module Grape
|
|
128
130
|
end
|
129
131
|
|
130
132
|
parent_standalone_namespaces = standalone_namespaces.reject { |ns_name, _| !name.start_with?(ns_name) }
|
131
|
-
# add only to the main route
|
133
|
+
# add only to the main route
|
134
|
+
# if the namespace is not within any other namespace appearing as standalone resource
|
132
135
|
if parent_standalone_namespaces.empty?
|
133
136
|
# default option, append namespace methods to parent route
|
134
|
-
|
137
|
+
parent_route = @target_class.combined_namespace_routes.key?(parent_route_name)
|
138
|
+
@target_class.combined_namespace_routes[parent_route_name] = [] unless parent_route
|
135
139
|
@target_class.combined_namespace_routes[parent_route_name].push(*namespace_routes)
|
136
140
|
end
|
137
141
|
end
|
@@ -175,7 +179,9 @@ module Grape
|
|
175
179
|
# skip if sub_ns is standalone, too
|
176
180
|
next unless sub_ns.options.key?(:swagger) && sub_ns.options[:swagger][:nested] == false
|
177
181
|
# remove all namespaces that are nested below this standalone sub_ns
|
178
|
-
sub_namespaces.each
|
182
|
+
sub_namespaces.each do |sub_sub_name, _|
|
183
|
+
sub_namespaces.delete(sub_sub_name) if sub_sub_name.start_with?(sub_name)
|
184
|
+
end
|
179
185
|
end
|
180
186
|
sub_namespaces
|
181
187
|
end
|
@@ -10,6 +10,7 @@ require 'grape-swagger/doc_methods/tag_name_description'
|
|
10
10
|
require 'grape-swagger/doc_methods/parse_params'
|
11
11
|
require 'grape-swagger/doc_methods/move_params'
|
12
12
|
require 'grape-swagger/doc_methods/headers'
|
13
|
+
require 'grape-swagger/doc_methods/build_model_definition'
|
13
14
|
|
14
15
|
module GrapeSwagger
|
15
16
|
module DocMethods
|
@@ -36,9 +37,11 @@ module GrapeSwagger
|
|
36
37
|
|
37
38
|
class_variables_from(options)
|
38
39
|
|
39
|
-
|
40
|
-
|
41
|
-
|
40
|
+
if formatter
|
41
|
+
[:format, :default_format, :default_error_formatter].each do |method|
|
42
|
+
send(method, formatter)
|
43
|
+
end
|
44
|
+
end
|
42
45
|
|
43
46
|
send(guard.split.first.to_sym, *guard.split(/[\s,]+/).drop(1)) unless guard.nil?
|
44
47
|
|
@@ -49,7 +52,9 @@ module GrapeSwagger
|
|
49
52
|
options
|
50
53
|
)
|
51
54
|
|
52
|
-
paths, definitions
|
55
|
+
paths, definitions = endpoint.path_and_definition_objects(combi_routes, options)
|
56
|
+
tags = GrapeSwagger::DocMethods::TagNameDescription.build(paths)
|
57
|
+
output[:tags] = tags unless tags.empty? || paths.blank?
|
53
58
|
output[:paths] = paths unless paths.blank?
|
54
59
|
output[:definitions] = definitions unless definitions.blank?
|
55
60
|
|
@@ -97,7 +102,7 @@ module GrapeSwagger
|
|
97
102
|
specific_api_documentation: { desc: 'Swagger compatible API description for specific API' },
|
98
103
|
endpoint_auth_wrapper: nil,
|
99
104
|
swagger_endpoint_guard: nil,
|
100
|
-
|
105
|
+
token_owner: nil
|
101
106
|
}
|
102
107
|
end
|
103
108
|
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module GrapeSwagger
|
2
|
+
module DocMethods
|
3
|
+
class BuildModelDefinition
|
4
|
+
class << self
|
5
|
+
def build(model, properties)
|
6
|
+
definition = { type: 'object', properties: properties }
|
7
|
+
|
8
|
+
required = required_attributes(model)
|
9
|
+
definition[:required] = required unless required.blank?
|
10
|
+
|
11
|
+
definition
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def required_attributes(model)
|
17
|
+
parse_entity(model) || parse_representable(model)
|
18
|
+
end
|
19
|
+
|
20
|
+
def parse_entity(model)
|
21
|
+
return unless model.respond_to?(:documentation)
|
22
|
+
|
23
|
+
model.documentation
|
24
|
+
.select { |_name, options| options[:required] }
|
25
|
+
.map { |name, options| options[:as] || name }
|
26
|
+
end
|
27
|
+
|
28
|
+
def parse_representable(model)
|
29
|
+
return unless model.respond_to?(:map)
|
30
|
+
|
31
|
+
model.map
|
32
|
+
.select { |p| p[:documentation] && p[:documentation][:required] }
|
33
|
+
.map(&:name)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -41,13 +41,14 @@ module GrapeSwagger
|
|
41
41
|
end
|
42
42
|
|
43
43
|
def parse_entity_name(model)
|
44
|
-
if model.
|
44
|
+
if model.methods(false).include?(:entity_name)
|
45
45
|
model.entity_name
|
46
|
+
elsif model.to_s.end_with?('::Entity', '::Entities')
|
47
|
+
model.to_s.split('::')[-2]
|
48
|
+
elsif model.respond_to?(:name)
|
49
|
+
model.name.demodulize.camelize
|
46
50
|
else
|
47
|
-
|
48
|
-
entity_parts = name.split('::')
|
49
|
-
entity_parts.reject! { |p| p == 'Entity' || p == 'Entities' }
|
50
|
-
entity_parts.join('::')
|
51
|
+
model.to_s.split('::').last
|
51
52
|
end
|
52
53
|
end
|
53
54
|
|
@@ -74,7 +74,15 @@ module GrapeSwagger
|
|
74
74
|
end
|
75
75
|
|
76
76
|
def document_as_property(param)
|
77
|
-
property_keys.each_with_object({})
|
77
|
+
property_keys.each_with_object({}) do |x, memo|
|
78
|
+
value = param[x]
|
79
|
+
next if value.blank?
|
80
|
+
if x == :type && @definitions[value].present?
|
81
|
+
memo['$ref'] = "#/definitions/#{value}"
|
82
|
+
else
|
83
|
+
memo[x] = value
|
84
|
+
end
|
85
|
+
end
|
78
86
|
end
|
79
87
|
|
80
88
|
def build_nested_properties(params, properties = {})
|
@@ -2,7 +2,7 @@ module GrapeSwagger
|
|
2
2
|
module DocMethods
|
3
3
|
class ParseParams
|
4
4
|
class << self
|
5
|
-
def call(param, settings, route)
|
5
|
+
def call(param, settings, route, definitions)
|
6
6
|
path = route.path
|
7
7
|
method = route.request_method
|
8
8
|
|
@@ -21,7 +21,7 @@ module GrapeSwagger
|
|
21
21
|
# optional properties
|
22
22
|
document_description(settings)
|
23
23
|
document_type_and_format(data_type)
|
24
|
-
document_array_param(value_type)
|
24
|
+
document_array_param(value_type, definitions) if value_type[:is_array]
|
25
25
|
document_default_value(settings)
|
26
26
|
document_range_values(settings)
|
27
27
|
document_required(settings)
|
@@ -60,25 +60,26 @@ module GrapeSwagger
|
|
60
60
|
end
|
61
61
|
end
|
62
62
|
|
63
|
-
def document_array_param(value_type)
|
64
|
-
if value_type[:
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
end
|
71
|
-
|
72
|
-
array_items = {
|
73
|
-
type: type || @parsed_param[:type],
|
74
|
-
format: @parsed_param.delete(:format)
|
75
|
-
}.delete_if { |_, value| value.blank? }
|
63
|
+
def document_array_param(value_type, definitions)
|
64
|
+
if value_type[:documentation].present?
|
65
|
+
param_type = value_type[:documentation][:param_type]
|
66
|
+
doc_type = value_type[:documentation][:type]
|
67
|
+
type = GrapeSwagger::DocMethods::DataType.mapping(doc_type) if doc_type && !DataType.request_primitive?(doc_type)
|
68
|
+
collection_format = value_type[:documentation][:collectionFormat]
|
69
|
+
end
|
76
70
|
|
77
|
-
|
78
|
-
|
79
|
-
@parsed_param[:type]
|
80
|
-
|
71
|
+
array_items = {}
|
72
|
+
if definitions[value_type[:data_type]]
|
73
|
+
array_items['$ref'] = "#/definitions/#{@parsed_param[:type]}"
|
74
|
+
else
|
75
|
+
array_items[:type] = type || @parsed_param[:type]
|
81
76
|
end
|
77
|
+
array_items[:format] = @parsed_param.delete(:format) if @parsed_param[:format]
|
78
|
+
|
79
|
+
@parsed_param[:in] = param_type || 'formData'
|
80
|
+
@parsed_param[:items] = array_items
|
81
|
+
@parsed_param[:type] = 'array'
|
82
|
+
@parsed_param[:collectionFormat] = collection_format if %w(csv ssv tsv pipes multi).include?(collection_format)
|
82
83
|
end
|
83
84
|
|
84
85
|
def param_type(value_type)
|
@@ -2,23 +2,15 @@ module GrapeSwagger
|
|
2
2
|
module DocMethods
|
3
3
|
class TagNameDescription
|
4
4
|
class << self
|
5
|
-
def build(
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
description = namespaces[original_namespace_name] && namespaces[original_namespace_name].options[:desc]
|
15
|
-
description ||= "Operations about #{original_namespace_name.pluralize}"
|
16
|
-
|
17
|
-
{
|
18
|
-
name: local_route,
|
19
|
-
description: description
|
20
|
-
}
|
21
|
-
end.compact
|
5
|
+
def build(paths)
|
6
|
+
paths.values.each_with_object([]) do |path, memo|
|
7
|
+
path.values.first[:tags].each do |tag|
|
8
|
+
memo << {
|
9
|
+
name: tag,
|
10
|
+
description: "Operations about #{tag.pluralize}"
|
11
|
+
}
|
12
|
+
end
|
13
|
+
end.uniq
|
22
14
|
end
|
23
15
|
end
|
24
16
|
end
|
@@ -1,5 +1,3 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
1
|
require 'active_support'
|
4
2
|
require 'active_support/core_ext/string/inflections.rb'
|
5
3
|
|
@@ -29,7 +27,6 @@ module Grape
|
|
29
27
|
securityDefinitions: options[:security_definitions],
|
30
28
|
host: GrapeSwagger::DocMethods::OptionalObject.build(:host, options, request),
|
31
29
|
basePath: GrapeSwagger::DocMethods::OptionalObject.build(:base_path, options, request),
|
32
|
-
tags: GrapeSwagger::DocMethods::TagNameDescription.build(options),
|
33
30
|
schemes: options[:schemes].is_a?(String) ? [options[:schemes]] : options[:schemes]
|
34
31
|
}.delete_if { |_, value| value.blank? }
|
35
32
|
end
|
@@ -112,7 +109,7 @@ module Grape
|
|
112
109
|
method[:parameters] = params_object(route)
|
113
110
|
method[:security] = security_object(route)
|
114
111
|
method[:responses] = response_object(route, options[:markdown])
|
115
|
-
method[:tags] = tag_object(route)
|
112
|
+
method[:tags] = route.options.fetch(:tags, tag_object(route))
|
116
113
|
method[:operationId] = GrapeSwagger::DocMethods::OperationId.build(route, path)
|
117
114
|
method.delete_if { |_, value| value.blank? }
|
118
115
|
|
@@ -164,7 +161,12 @@ module Grape
|
|
164
161
|
parameters = partition_params(route).map do |param, value|
|
165
162
|
value = { required: false }.merge(value) if value.is_a?(Hash)
|
166
163
|
_, value = default_type([[param, value]]).first if value == ''
|
167
|
-
|
164
|
+
if value[:type]
|
165
|
+
expose_params(value[:type])
|
166
|
+
elsif value[:documentation]
|
167
|
+
expose_params(value[:documentation][:type])
|
168
|
+
end
|
169
|
+
GrapeSwagger::DocMethods::ParseParams.call(param, value, route, @definitions)
|
168
170
|
end
|
169
171
|
|
170
172
|
if GrapeSwagger::DocMethods::MoveParams.can_be_moved?(parameters, route.request_method)
|
@@ -262,49 +264,44 @@ module Grape
|
|
262
264
|
param_types.size == 1
|
263
265
|
end
|
264
266
|
|
267
|
+
def expose_params(value)
|
268
|
+
if value.is_a?(Class) && GrapeSwagger.model_parsers.find(value)
|
269
|
+
expose_params_from_model(value)
|
270
|
+
elsif value.is_a?(String)
|
271
|
+
begin
|
272
|
+
expose_params(Object.const_get(value.gsub(/\[|\]/, ''))) # try to load class from its name
|
273
|
+
rescue NameError
|
274
|
+
nil
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
265
279
|
def expose_params_from_model(model)
|
280
|
+
model = model.is_a?(String) ? model.constantize : model
|
266
281
|
model_name = model_name(model)
|
267
282
|
|
268
283
|
return model_name if @definitions.key?(model_name)
|
269
284
|
@definitions[model_name] = nil
|
270
285
|
|
271
|
-
|
272
|
-
parser = nil
|
273
|
-
|
274
|
-
GrapeSwagger.model_parsers.each do |klass, ancestor|
|
275
|
-
next unless model.ancestors.map(&:to_s).include?(ancestor)
|
276
|
-
parser = klass.new(model, self)
|
277
|
-
break
|
278
|
-
end
|
279
|
-
|
280
|
-
properties = parser.call unless parser.nil?
|
281
|
-
|
286
|
+
parser = GrapeSwagger.model_parsers.find(model)
|
282
287
|
raise GrapeSwagger::Errors::UnregisteredParser, "No parser registered for #{model_name}." unless parser
|
288
|
+
|
289
|
+
properties = parser.new(model, self).call
|
283
290
|
raise GrapeSwagger::Errors::SwaggerSpec, "Empty model #{model_name}, swagger 2.0 doesn't support empty definitions." unless properties && properties.any?
|
284
291
|
|
285
|
-
@definitions[model_name] =
|
292
|
+
@definitions[model_name] = GrapeSwagger::DocMethods::BuildModelDefinition.build(model, properties)
|
286
293
|
|
287
294
|
model_name
|
288
295
|
end
|
289
296
|
|
290
297
|
def model_name(name)
|
291
|
-
|
292
|
-
name.entity_name
|
293
|
-
elsif name.to_s.end_with?('Entity', 'Entities')
|
294
|
-
length = 0
|
295
|
-
name.to_s.split('::')[0..-2].reverse.take_while do |x|
|
296
|
-
length += x.length
|
297
|
-
length < 42
|
298
|
-
end.reverse.join
|
299
|
-
else
|
300
|
-
name.name.demodulize.camelize
|
301
|
-
end
|
298
|
+
GrapeSwagger::DocMethods::DataType.parse_entity_name(name)
|
302
299
|
end
|
303
300
|
|
304
301
|
def hidden?(route, options)
|
305
302
|
route_hidden = route.options[:hidden]
|
306
303
|
return route_hidden unless route_hidden.is_a?(Proc)
|
307
|
-
options[:
|
304
|
+
options[:token_owner] ? route_hidden.call(send(options[:token_owner].to_sym)) : route_hidden.call
|
308
305
|
end
|
309
306
|
|
310
307
|
def public_parameter?(param)
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'grape-entity'
|
3
|
+
require 'grape-swagger-entity'
|
4
|
+
|
5
|
+
describe '#427 nested entity given as string' do
|
6
|
+
let(:app) do
|
7
|
+
Class.new(Grape::API) do
|
8
|
+
namespace :issue_427 do
|
9
|
+
module Permission
|
10
|
+
class WithoutRole < Grape::Entity
|
11
|
+
expose :id
|
12
|
+
expose :description
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class RoleEntity < Grape::Entity
|
17
|
+
expose :id
|
18
|
+
expose :description
|
19
|
+
expose :role
|
20
|
+
expose :permissions, using: 'Permission::WithoutRole'
|
21
|
+
end
|
22
|
+
desc 'Get a list of roles',
|
23
|
+
success: RoleEntity
|
24
|
+
get '/' do
|
25
|
+
present [], with: RoleEntity
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
add_swagger_documentation format: :json
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
subject do
|
34
|
+
get '/swagger_doc'
|
35
|
+
JSON.parse(last_response.body)['definitions']
|
36
|
+
end
|
37
|
+
|
38
|
+
specify { expect(subject.keys).to include 'RoleEntity', 'WithoutRole' }
|
39
|
+
end
|