grape-swagger 0.9.0 → 0.10.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e9e2d67cf94749b685aff4146aa6f831acd41539
4
- data.tar.gz: 272fa686ecf0e978a0f07c41a42486a06ecfd56c
3
+ metadata.gz: 80476f6e354bf1964123a1f592e8a6ca9fd38d54
4
+ data.tar.gz: b9c15ab8ac8cacbd87e69e187a1afd2a838ff7d7
5
5
  SHA512:
6
- metadata.gz: 612b8bff2f52780da77e09b63a929cb035d3cc6f8bcda1a75b426c7267c67e60f86d30eefa149e05f6ec27571317cef910daf53026b7d3394d149ccafb96ce07
7
- data.tar.gz: 22b1bd381a910aedb712fa1efc53b36ded5965ae743dae44527f49208cc23243d8d97f3ff76694d984f06e2ede024d5d2b01feda3356c436a5890a44b3e06930
6
+ metadata.gz: 4f6d6e3da7140010e92a6a94a716572cf2da3093bc2233c172c6a9d0caa420a4108da068d73a145ae901dd61cc2c754c22974881cab22a5959fb55439fdfaeb1
7
+ data.tar.gz: 04db505e755cf5fa4f72684becaa3ad2a580a1f83baf566fb8c7407f2c1b425f809d813b1151c7b286f0f4ed90e587307be53c15f3ab9ef90b579af4d5c8d803
data/.rubocop_todo.yml CHANGED
@@ -1,42 +1,42 @@
1
1
  # This configuration was generated by `rubocop --auto-gen-config`
2
- # on 2014-11-29 13:48:01 -0500 using RuboCop version 0.27.0.
2
+ # on 2015-02-26 15:04:26 +0100 using RuboCop version 0.27.0.
3
3
  # The point is for the user to remove these configuration records
4
4
  # one by one as the offenses are removed from the code base.
5
5
  # Note that changes in the inspected code, or installation of new
6
6
  # versions of RuboCop, may require this file to be generated again.
7
7
 
8
- # Offense count: 8
8
+ # Offense count: 9
9
9
  Metrics/AbcSize:
10
- Max: 327
10
+ Max: 347
11
11
 
12
12
  # Offense count: 1
13
13
  # Configuration parameters: CountComments.
14
14
  Metrics/ClassLength:
15
- Max: 397
15
+ Max: 485
16
16
 
17
- # Offense count: 5
17
+ # Offense count: 6
18
18
  Metrics/CyclomaticComplexity:
19
- Max: 93
19
+ Max: 99
20
20
 
21
- # Offense count: 195
21
+ # Offense count: 289
22
22
  # Configuration parameters: AllowURI, URISchemes.
23
23
  Metrics/LineLength:
24
24
  Max: 254
25
25
 
26
- # Offense count: 13
26
+ # Offense count: 17
27
27
  # Configuration parameters: CountComments.
28
28
  Metrics/MethodLength:
29
- Max: 359
29
+ Max: 368
30
30
 
31
- # Offense count: 4
31
+ # Offense count: 5
32
32
  Metrics/PerceivedComplexity:
33
- Max: 96
33
+ Max: 101
34
34
 
35
- # Offense count: 7
35
+ # Offense count: 8
36
36
  Style/ClassVars:
37
37
  Enabled: false
38
38
 
39
- # Offense count: 69
39
+ # Offense count: 76
40
40
  Style/Documentation:
41
41
  Enabled: false
42
42
 
data/.travis.yml CHANGED
@@ -13,4 +13,5 @@ env:
13
13
  - GRAPE_VERSION=0.8.0
14
14
  - GRAPE_VERSION=0.9.0
15
15
  - GRAPE_VERSION=0.10.0
16
+ - GRAPE_VERSION=0.10.1
16
17
  - GRAPE_VERSION=HEAD
data/CHANGELOG.md CHANGED
@@ -1,3 +1,22 @@
1
+ ### 0.10.0 (March 10, 2015)
2
+
3
+ #### Features
4
+
5
+ * [#217](https://github.com/tim-vandecasteele/grape-swagger/pull/217): Support Array of entities for proper rendering of grape-entity input dependencies - [@swistaczek](https://github.com/swistaczek).
6
+ * [#214](https://github.com/tim-vandecasteele/grape-swagger/pull/214): Allow anything that responds to `call` to be used in `:hidden` - [@zbelzer](https://github.com/zbelzer).
7
+ * [#196](https://github.com/tim-vandecasteele/grape-swagger/pull/196): If `:type` is omitted, see if it's available in `:using` - [@jhollinger](https://github.com/jhollinger).
8
+ * [#200](https://github.com/tim-vandecasteele/grape-swagger/pull/200): Treat `type: Symbol` as string form parameter - [@ypresto](https://github.com/ypresto).
9
+ * [#207](https://github.com/tim-vandecasteele/grape-swagger/pull/207): Support grape `mutually_exclusive` - [@mintuhouse](https://github.com/mintuhouse).
10
+ * [#220](https://github.com/tim-vandecasteele/grape-swagger/pull/220): Support standalone appearance of namespace routes with a custom name instead of forced nesting - [@croeck](https://github.com/croeck).
11
+
12
+ #### Fixes
13
+
14
+ * [#221](https://github.com/tim-vandecasteele/grape-swagger/pull/221): Fixed group parameters' name with type Array - [@u2](https://github.com/u2).
15
+ * [#211](https://github.com/tim-vandecasteele/grape-swagger/pull/211): Fixed the dependency, just `require 'grape'` - [@u2](https://github.com/u2).
16
+ * [#210](https://github.com/tim-vandecasteele/grape-swagger/pull/210): Fixed the range `:values` option, now exposed as `enum` parameters - [@u2](https://github.com/u2).
17
+ * [#208](https://github.com/tim-vandecasteele/grape-swagger/pull/208): Fixed `Float` parameters, exposed as Swagger `float` types - [@u2](https://github.com/u2).
18
+ * [#216](https://github.com/tim-vandecasteele/grape-swagger/pull/216), [#192](https://github.com/tim-vandecasteele/grape-swagger/issues/192), [#189](https://github.com/tim-vandecasteele/grape-swagger/issues/189): Fixed API route paths matching for root endpoints with `grape ~> 0.10.0`, specific `format` and `:path` versioning - [@dm1try](https://github.com/dm1try), [@minch](https://github.com/minch).
19
+
1
20
  ### 0.9.0 (December 19, 2014)
2
21
 
3
22
  * [#91](https://github.com/tim-vandecasteele/grape-swagger/issues/91): Fixed empty field for group parameters' name with type hash or Array - [@dukedave](https://github.com/dukedave).
data/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # grape-swagger
2
2
 
3
- [![Build Status](https://travis-ci.org/tim-vandecasteele/grape-swagger.svg?branch=master)](https://travis-ci.org/tim-vandecasteele/grape-swagger)
3
+ [![Gem Version](http://img.shields.io/gem/v/grape-swagger.svg)](http://badge.fury.io/rb/grape-swagger)
4
+ [![Build Status](http://img.shields.io/travis/tim-vandecasteele/grape-swagger.svg)](https://travis-ci.org/tim-vandecasteele/grape-swagger)
5
+ [![Code Climate](https://codeclimate.com/github/tim-vandecasteele/grape-swagger.svg)](https://codeclimate.com/github/tim-vandecasteele/grape-swagger)
4
6
 
5
7
  ## What is grape-swagger?
6
8
 
@@ -174,6 +176,13 @@ You can hide an endpoint by adding ```hidden: true``` in the description of the
174
176
  desc 'Hide this endpoint', hidden: true
175
177
  ```
176
178
 
179
+ Endpoints can be conditionally hidden by providing a callable object such as a lambda which evaluates to the desired
180
+ state:
181
+
182
+ ``` ruby
183
+ desc 'Conditionally hide this endpoint', hidden: lambda { ENV['EXPERIMENTAL'] != 'true' }
184
+ ```
185
+
177
186
  ## Overriding Auto-Generated Nicknames
178
187
 
179
188
  You can specify a swagger nickname to use instead of the auto generated name by adding `:nickname 'string'``` in the description of the endpoint.
@@ -182,6 +191,44 @@ You can specify a swagger nickname to use instead of the auto generated name by
182
191
  desc 'Get a full list of pets', nickname: 'getAllPets'
183
192
  ```
184
193
 
194
+ ## Expose nested namespace as standalone route
195
+ Use the `nested: false` property in the `swagger` option to make nested namespaces appear as standalone resources.
196
+ This option can help to structure and keep the swagger schema simple.
197
+
198
+ namespace 'store/order', desc: 'Order operations within a store', swagger: { nested: false } do
199
+ get :order_id do
200
+ ...
201
+ end
202
+ end
203
+
204
+ All routes that belong to this namespace (here: the `GET /order_id`) will then be assigned to the `store_order` route instead of the `store` resource route.
205
+
206
+ It is also possible to expose a namspace within another already exposed namespace:
207
+
208
+ namespace 'store/order', desc: 'Order operations within a store', swagger: { nested: false } do
209
+ get :order_id do
210
+ ...
211
+ end
212
+ namespace 'actions', desc: 'Order actions' do, nested: false
213
+ get 'evaluate' do
214
+ ...
215
+ end
216
+ end
217
+ end
218
+
219
+ Here, the `GET /order_id` appears as operation of the `store_order` resource and the `GET /evaluate` as operation of the `store_orders_actions` route.
220
+
221
+ ### With a custom name
222
+ Auto generated names for the standalone version of complex nested resource do not have a nice look.
223
+ You can set a custom name with the `name` property inside the `swagger` option, but only if the namespace gets exposed as standalone route.
224
+ The name should not contain whitespaces or any other special characters due to further issues within swagger-ui.
225
+
226
+ namespace 'store/order', desc: 'Order operations within a store', swagger: { nested: false, name: 'Store-orders' } do
227
+ get :order_id do
228
+ ...
229
+ end
230
+ end
231
+
185
232
  ## Grape Entities
186
233
 
187
234
  Add the [grape-entity](https://github.com/agileanimal/grape-entity) gem to our Gemfile.
@@ -227,7 +274,7 @@ end
227
274
 
228
275
  ### Relationships
229
276
 
230
- Put the full name of the relationship's class in `type`, leaving out any modules named `Entities` or `Entity`. So for the entity class `API::Entities::Address`, you would put `type: 'API::Address'`.
277
+ You may safely omit `type` from relationships, as it can be inferred. However, if you need to specify or override it, use the full name of the class leaving out any modules named `Entities` or `Entity`.
231
278
 
232
279
  #### 1xN
233
280
 
data/UPGRADING.md CHANGED
@@ -1,6 +1,26 @@
1
1
  Upgrading Grape-swagger
2
2
  =======================
3
3
 
4
+ ### Upgrading to >= 0.9.0
5
+
6
+ #### Changes in Configuration
7
+
8
+ If you're using [grape-swagger-rails](https://github.com/BrandyMint/grape-swagger-rails), remove the `.json` extension from `GrapeSwaggerRails.options.url`.
9
+
10
+ For example, change
11
+
12
+ ```ruby
13
+ GrapeSwaggerRails.options.url = '/api/v1/swagger_doc.json'
14
+ ```
15
+
16
+ to
17
+
18
+ ```ruby
19
+ GrapeSwaggerRails.options.url = '/api/v1/swagger_doc'
20
+ ```
21
+
22
+ See [#187](https://github.com/tim-vandecasteele/grape-swagger/issues/187) for more information.
23
+
4
24
  ### Upgrading to >= 0.8.0
5
25
 
6
26
  #### Changes in Configuration
data/lib/grape-swagger.rb CHANGED
@@ -1,3 +1,4 @@
1
+ require 'grape'
1
2
  require 'grape-swagger/version'
2
3
  require 'grape-swagger/errors'
3
4
  require 'grape-swagger/markdown'
@@ -7,7 +8,7 @@ require 'grape-swagger/markdown/redcarpet_adapter'
7
8
  module Grape
8
9
  class API
9
10
  class << self
10
- attr_reader :combined_routes, :combined_namespaces
11
+ attr_reader :combined_routes, :combined_namespaces, :combined_namespace_routes, :combined_namespace_identifiers
11
12
 
12
13
  def add_swagger_documentation(options = {})
13
14
  documentation_class = create_documentation_class
@@ -20,7 +21,7 @@ module Grape
20
21
  route_path = route.route_path
21
22
  route_match = route_path.split(/^.*?#{route.route_prefix.to_s}/).last
22
23
  next unless route_match
23
- route_match = route_match.match('\/([\w|-]*?)[\.\/\(]') || route_match.match('\/([\w|-]*)')
24
+ route_match = route_match.match('\/([\w|-]*?)[\.\/\(]') || route_match.match('\/([\w|-]*)$')
24
25
  next unless route_match
25
26
  resource = route_match.captures.first
26
27
  next if resource.empty?
@@ -32,6 +33,13 @@ module Grape
32
33
 
33
34
  @combined_namespaces = {}
34
35
  combine_namespaces(self)
36
+
37
+ @combined_namespace_routes = {}
38
+ @combined_namespace_identifiers = {}
39
+ combine_namespace_routes(@combined_namespaces)
40
+
41
+ exclusive_route_keys = @combined_routes.keys - @combined_namespaces.keys
42
+ exclusive_route_keys.each { |key| @combined_namespace_routes[key] = @combined_routes[key] }
35
43
  documentation_class
36
44
  end
37
45
 
@@ -44,12 +52,112 @@ module Grape
44
52
  else
45
53
  endpoint.settings.stack.last[:namespace]
46
54
  end
47
- @combined_namespaces[ns.space] = ns if ns
55
+ # use the full namespace here (not the latest level only)
56
+ # and strip leading slash
57
+ @combined_namespaces[endpoint.namespace.sub(/^\//, '')] = ns if ns
48
58
 
49
59
  combine_namespaces(endpoint.options[:app]) if endpoint.options[:app]
50
60
  end
51
61
  end
52
62
 
63
+ def combine_namespace_routes(namespaces)
64
+ # iterate over each single namespace
65
+ namespaces.each do |name, namespace|
66
+ # get the parent route for the namespace
67
+ parent_route_name = name.match(%r{^/?([^/]*).*$})[1]
68
+ parent_route = @combined_routes[parent_route_name]
69
+ # fetch all routes that are within the current namespace
70
+ namespace_routes = parent_route.collect do |route|
71
+ route if (route.route_path.start_with?("/#{name}") || route.route_path.start_with?("/:version/#{name}")) &&
72
+ (route.instance_variable_get(:@options)[:namespace] == "/#{name}" || route.instance_variable_get(:@options)[:namespace] == "/:version/#{name}")
73
+ end.compact
74
+
75
+ if namespace.options.key?(:swagger) && namespace.options[:swagger][:nested] == false
76
+ # Namespace shall appear as standalone resource, use specified name or use normalized path as name
77
+ if namespace.options[:swagger].key?(:name)
78
+ identifier = namespace.options[:swagger][:name].gsub(/ /, '-')
79
+ else
80
+ identifier = name.gsub(/_/, '-').gsub(/\//, '_')
81
+ end
82
+ @combined_namespace_identifiers[identifier] = name
83
+ @combined_namespace_routes[identifier] = namespace_routes
84
+
85
+ # get all nested namespaces below the current namespace
86
+ sub_namespaces = standalone_sub_namespaces(name, namespaces)
87
+ # convert namespace to route names
88
+ sub_ns_paths = sub_namespaces.collect { |ns_name, _| "/#{ns_name}" }
89
+ sub_ns_paths_versioned = sub_namespaces.collect { |ns_name, _| "/:version/#{ns_name}" }
90
+ # get the actual route definitions for the namespace path names
91
+ sub_routes = parent_route.collect do |route|
92
+ route if sub_ns_paths.include?(route.instance_variable_get(:@options)[:namespace]) || sub_ns_paths_versioned.include?(route.instance_variable_get(:@options)[:namespace])
93
+ end.compact
94
+ # add all determined routes of the sub namespaces to standalone resource
95
+ @combined_namespace_routes[identifier].push(*sub_routes)
96
+ else
97
+ # default case when not explicitly specified or nested == true
98
+ standalone_namespaces = namespaces.reject { |_, ns| !ns.options.key?(:swagger) || !ns.options[:swagger].key?(:nested) || ns.options[:swagger][:nested] != false }
99
+ parent_standalone_namespaces = standalone_namespaces.reject { |ns_name, _| !name.start_with?(ns_name) }
100
+ # add only to the main route if the namespace is not within any other namespace appearing as standalone resource
101
+ if parent_standalone_namespaces.empty?
102
+ # default option, append namespace methods to parent route
103
+ @combined_namespace_routes[parent_route_name] = [] unless @combined_namespace_routes.key?(parent_route_name)
104
+ @combined_namespace_routes[parent_route_name].push(*namespace_routes)
105
+ end
106
+ end
107
+ end
108
+ end
109
+
110
+ def standalone_sub_namespaces(name, namespaces)
111
+ # assign all nested namespace routes to this resource, too
112
+ # (unless they are assigned to another standalone namespace themselves)
113
+ sub_namespaces = {}
114
+ # fetch all namespaces that are children of the current namespace
115
+ namespaces.each { |ns_name, ns| sub_namespaces[ns_name] = ns if ns_name.start_with?(name) && ns_name != name }
116
+ # remove the sub namespaces if they are assigned to another standalone namespace themselves
117
+ sub_namespaces.each do |sub_name, sub_ns|
118
+ # skip if sub_ns is standalone, too
119
+ next unless sub_ns.options.key?(:swagger) && sub_ns.options[:swagger][:nested] == false
120
+ # remove all namespaces that are nested below this standalone sub_ns
121
+ sub_namespaces.each { |sub_sub_name, _| sub_namespaces.delete(sub_sub_name) if sub_sub_name.start_with?(sub_name) }
122
+ end
123
+ sub_namespaces
124
+ end
125
+
126
+ def get_non_nested_params(params)
127
+ # Duplicate the params as we are going to modify them
128
+ dup_params = params.each_with_object(Hash.new) do |(param, value), dparams|
129
+ dparams[param] = value.dup
130
+ end
131
+
132
+ dup_params.reject do |param, value|
133
+ is_nested_param = /^#{ Regexp.quote param }\[.+\]$/
134
+ 0 < dup_params.count do |p, _|
135
+ match = p.match(is_nested_param)
136
+ dup_params[p][:required] = false if match && !value[:required]
137
+ match
138
+ end
139
+ end
140
+ end
141
+
142
+ def parse_array_params(params)
143
+ modified_params = {}
144
+ array_param = nil
145
+ params.each_key do |k|
146
+ if params[k].is_a?(Hash) && params[k][:type] == 'Array'
147
+ array_param = k
148
+ else
149
+ new_key = k
150
+ unless array_param.nil?
151
+ if k.to_s.start_with?(array_param.to_s + '[')
152
+ new_key = array_param.to_s + '[]' + k.to_s.split(array_param)[1]
153
+ end
154
+ end
155
+ modified_params[new_key] = params[k]
156
+ end
157
+ end
158
+ modified_params
159
+ end
160
+
53
161
  def create_documentation_class
54
162
  Class.new(Grape::API) do
55
163
  class << self
@@ -64,10 +172,9 @@ module Grape
64
172
  def parse_params(params, path, method)
65
173
  params ||= []
66
174
 
67
- non_nested_parent_params = params.reject do |param, _|
68
- is_nested_param = /^#{ Regexp.quote param }\[.+\]$/
69
- params.keys.any? { |p| p.match is_nested_param }
70
- end
175
+ parsed_array_params = parse_array_params(params)
176
+
177
+ non_nested_parent_params = get_non_nested_params(parsed_array_params)
71
178
 
72
179
  non_nested_parent_params.map do |param, value|
73
180
  items = {}
@@ -80,7 +187,7 @@ module Grape
80
187
  'File'
81
188
  when 'Virtus::Attribute::Boolean'
82
189
  'boolean'
83
- when 'Boolean', 'Date', 'Integer', 'String'
190
+ when 'Boolean', 'Date', 'Integer', 'String', 'Float'
84
191
  raw_data_type.downcase
85
192
  when 'BigDecimal'
86
193
  'long'
@@ -88,6 +195,8 @@ module Grape
88
195
  'dateTime'
89
196
  when 'Numeric'
90
197
  'double'
198
+ when 'Symbol'
199
+ 'string'
91
200
  else
92
201
  @@documentation_class.parse_entity_name(raw_data_type)
93
202
  end
@@ -96,6 +205,7 @@ module Grape
96
205
  default_value = value.is_a?(Hash) ? value[:default] : nil
97
206
  is_array = value.is_a?(Hash) ? (value[:is_array] || false) : false
98
207
  enum_values = value.is_a?(Hash) ? value[:values] : nil
208
+ enum_values = enum_values.to_a if enum_values && enum_values.is_a?(Range)
99
209
  enum_values = enum_values.call if enum_values && enum_values.is_a?(Proc)
100
210
 
101
211
  if value.is_a?(Hash) && value.key?(:param_type)
@@ -220,11 +330,17 @@ module Grape
220
330
 
221
331
  required << property_name.to_s if p.delete(:required)
222
332
 
333
+ type = if p[:type]
334
+ p.delete(:type)
335
+ elsif (entity = model.exposures[property_name][:using])
336
+ parse_entity_name(entity)
337
+ end
338
+
223
339
  if p.delete(:is_array)
224
- p[:items] = generate_typeref(p[:type])
340
+ p[:items] = generate_typeref(type)
225
341
  p[:type] = 'array'
226
342
  else
227
- p.merge! generate_typeref(p.delete(:type))
343
+ p.merge! generate_typeref(type)
228
344
  end
229
345
 
230
346
  # rename Grape Entity's "desc" to "description"
@@ -239,7 +355,6 @@ module Grape
239
355
  end
240
356
 
241
357
  properties[property_name] = p
242
-
243
358
  end
244
359
 
245
360
  result[name] = {
@@ -372,20 +487,21 @@ module Grape
372
487
  header['Access-Control-Allow-Origin'] = '*'
373
488
  header['Access-Control-Request-Method'] = '*'
374
489
 
375
- routes = target_class.combined_routes
376
490
  namespaces = target_class.combined_namespaces
491
+ namespace_routes = target_class.combined_namespace_routes
377
492
 
378
493
  if @@hide_documentation_path
379
- routes.reject! { |route, _value| "/#{route}/".index(@@documentation_class.parse_path(@@mount_path, nil) << '/') == 0 }
494
+ namespace_routes.reject! { |route, _value| "/#{route}/".index(@@documentation_class.parse_path(@@mount_path, nil) << '/') == 0 }
380
495
  end
381
496
 
382
- routes_array = routes.keys.map do |local_route|
383
- next if routes[local_route].all?(&:route_hidden)
497
+ namespace_routes_array = namespace_routes.keys.map do |local_route|
498
+ next if namespace_routes[local_route].map(&:route_hidden).all? { |value| value.respond_to?(:call) ? value.call : value }
384
499
 
385
500
  url_format = '.{format}' unless @@hide_format
386
501
 
387
- description = namespaces[local_route] && namespaces[local_route].options[:desc]
388
- description ||= "Operations about #{local_route.pluralize}"
502
+ original_namespace_name = target_class.combined_namespace_identifiers.key?(local_route) ? target_class.combined_namespace_identifiers[local_route] : local_route
503
+ description = namespaces[original_namespace_name] && namespaces[original_namespace_name].options[:desc]
504
+ description ||= "Operations about #{original_namespace_name.pluralize}"
389
505
 
390
506
  {
391
507
  path: "/#{local_route}#{url_format}",
@@ -397,7 +513,7 @@ module Grape
397
513
  apiVersion: api_version,
398
514
  swaggerVersion: '1.2',
399
515
  produces: @@documentation_class.content_types_for(target_class),
400
- apis: routes_array,
516
+ apis: namespace_routes_array,
401
517
  info: @@documentation_class.parse_info(extra_info)
402
518
  }
403
519
 
@@ -419,10 +535,14 @@ module Grape
419
535
  header['Access-Control-Request-Method'] = '*'
420
536
 
421
537
  models = []
422
- routes = target_class.combined_routes[params[:name]]
538
+ routes = target_class.combined_namespace_routes[params[:name]]
423
539
  error!('Not Found', 404) unless routes
424
540
 
425
- ops = routes.reject(&:route_hidden).group_by do |route|
541
+ visible_ops = routes.reject do |route|
542
+ route.route_hidden.respond_to?(:call) ? route.route_hidden.call : route.route_hidden
543
+ end
544
+
545
+ ops = visible_ops.group_by do |route|
426
546
  @@documentation_class.parse_path(route.route_path, api_version)
427
547
  end
428
548
 
@@ -438,7 +558,7 @@ module Grape
438
558
 
439
559
  models |= @@models if @@models.present?
440
560
 
441
- models |= [route.route_entity] if route.route_entity.present?
561
+ models |= Array(route.route_entity) if route.route_entity.present?
442
562
 
443
563
  models = @@documentation_class.models_with_included_presenters(models.flatten.compact)
444
564
 
@@ -457,7 +577,7 @@ module Grape
457
577
  operation.merge!(responseMessages: http_codes) unless http_codes.empty?
458
578
 
459
579
  if route.route_entity
460
- type = @@documentation_class.parse_entity_name(route.route_entity)
580
+ type = @@documentation_class.parse_entity_name(Array(route.route_entity).first)
461
581
  operation.merge!('type' => type)
462
582
  end
463
583
 
@@ -470,10 +590,16 @@ module Grape
470
590
  }
471
591
  end
472
592
 
593
+ # use custom resource naming if available
594
+ if target_class.combined_namespace_identifiers.key? params[:name]
595
+ resource_path = target_class.combined_namespace_identifiers[params[:name]]
596
+ else
597
+ resource_path = params[:name]
598
+ end
473
599
  api_description = {
474
600
  apiVersion: api_version,
475
601
  swaggerVersion: '1.2',
476
- resourcePath: "/#{params[:name]}",
602
+ resourcePath: "/#{resource_path}",
477
603
  produces: @@documentation_class.content_types_for(target_class),
478
604
  apis: apis
479
605
  }