grape-swagger 0.24.0 → 0.25.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|