grape 1.0.1 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of grape might be problematic. Click here for more details.

Files changed (87) hide show
  1. checksums.yaml +5 -5
  2. data/Appraisals +1 -1
  3. data/CHANGELOG.md +18 -0
  4. data/Dangerfile +1 -0
  5. data/Gemfile +9 -10
  6. data/Gemfile.lock +42 -40
  7. data/LICENSE +1 -1
  8. data/README.md +89 -40
  9. data/Rakefile +1 -46
  10. data/gemfiles/multi_json.gemfile +9 -10
  11. data/gemfiles/multi_xml.gemfile +9 -10
  12. data/gemfiles/rack_1.5.2.gemfile +9 -10
  13. data/gemfiles/rack_edge.gemfile +9 -10
  14. data/gemfiles/rails_3.gemfile +9 -10
  15. data/gemfiles/rails_4.gemfile +9 -10
  16. data/gemfiles/rails_5.gemfile +9 -10
  17. data/gemfiles/rails_edge.gemfile +9 -10
  18. data/grape.gemspec +3 -3
  19. data/lib/grape/api.rb +2 -2
  20. data/lib/grape/dsl/inside_route.rb +30 -10
  21. data/lib/grape/dsl/routing.rb +1 -1
  22. data/lib/grape/dsl/settings.rb +6 -6
  23. data/lib/grape/endpoint.rb +1 -1
  24. data/lib/grape/exceptions/incompatible_option_values.rb +0 -1
  25. data/lib/grape/exceptions/invalid_accept_header.rb +0 -1
  26. data/lib/grape/exceptions/invalid_formatter.rb +0 -1
  27. data/lib/grape/exceptions/invalid_message_body.rb +0 -1
  28. data/lib/grape/exceptions/invalid_version_header.rb +0 -1
  29. data/lib/grape/exceptions/invalid_versioner_option.rb +0 -1
  30. data/lib/grape/exceptions/invalid_with_option_for_represent.rb +0 -1
  31. data/lib/grape/exceptions/method_not_allowed.rb +0 -1
  32. data/lib/grape/exceptions/missing_group_type.rb +0 -1
  33. data/lib/grape/exceptions/missing_mime_type.rb +0 -1
  34. data/lib/grape/exceptions/missing_option.rb +0 -1
  35. data/lib/grape/exceptions/missing_vendor_option.rb +0 -1
  36. data/lib/grape/exceptions/unknown_options.rb +0 -1
  37. data/lib/grape/exceptions/unknown_parameter.rb +0 -1
  38. data/lib/grape/exceptions/unknown_validator.rb +0 -1
  39. data/lib/grape/exceptions/unsupported_group_type.rb +0 -1
  40. data/lib/grape/namespace.rb +1 -1
  41. data/lib/grape/router.rb +2 -0
  42. data/lib/grape/router/pattern.rb +1 -1
  43. data/lib/grape/router/route.rb +14 -14
  44. data/lib/grape/validations/params_scope.rb +3 -4
  45. data/lib/grape/validations/types.rb +14 -1
  46. data/lib/grape/validations/types/build_coercer.rb +8 -0
  47. data/lib/grape/validations/types/custom_type_coercer.rb +1 -1
  48. data/lib/grape/validations/types/custom_type_collection_coercer.rb +71 -0
  49. data/lib/grape/validations/validators/allow_blank.rb +1 -1
  50. data/lib/grape/validations/validators/base.rb +1 -0
  51. data/lib/grape/validations/validators/coerce.rb +6 -0
  52. data/lib/grape/version.rb +1 -1
  53. data/pkg/grape-1.0.1.gem +0 -0
  54. data/spec/grape/api_spec.rb +22 -12
  55. data/spec/grape/dsl/inside_route_spec.rb +1 -1
  56. data/spec/grape/dsl/parameters_spec.rb +5 -5
  57. data/spec/grape/dsl/settings_spec.rb +2 -2
  58. data/spec/grape/endpoint_spec.rb +25 -12
  59. data/spec/grape/entity_spec.rb +1 -1
  60. data/spec/grape/exceptions/invalid_formatter_spec.rb +0 -1
  61. data/spec/grape/exceptions/invalid_versioner_option_spec.rb +0 -1
  62. data/spec/grape/exceptions/missing_option_spec.rb +0 -1
  63. data/spec/grape/exceptions/unknown_options_spec.rb +1 -2
  64. data/spec/grape/exceptions/unknown_validator_spec.rb +0 -1
  65. data/spec/grape/exceptions/validation_errors_spec.rb +1 -1
  66. data/spec/grape/middleware/exception_spec.rb +36 -12
  67. data/spec/grape/middleware/formatter_spec.rb +1 -1
  68. data/spec/grape/middleware/versioner/header_spec.rb +1 -1
  69. data/spec/grape/middleware/versioner/param_spec.rb +1 -1
  70. data/spec/grape/middleware/versioner/path_spec.rb +1 -1
  71. data/spec/grape/path_spec.rb +3 -3
  72. data/spec/grape/util/inheritable_setting_spec.rb +2 -2
  73. data/spec/grape/util/reverse_stackable_values_spec.rb +13 -13
  74. data/spec/grape/util/stackable_values_spec.rb +13 -13
  75. data/spec/grape/util/strict_hash_configuration_spec.rb +1 -1
  76. data/spec/grape/validations/params_scope_spec.rb +19 -7
  77. data/spec/grape/validations/validators/all_or_none_spec.rb +1 -1
  78. data/spec/grape/validations/validators/at_least_one_of_spec.rb +1 -1
  79. data/spec/grape/validations/validators/coerce_spec.rb +94 -13
  80. data/spec/grape/validations/validators/default_spec.rb +40 -0
  81. data/spec/grape/validations/validators/exactly_one_of_spec.rb +1 -1
  82. data/spec/grape/validations/validators/mutual_exclusion_spec.rb +1 -1
  83. data/spec/grape/validations/validators/values_spec.rb +3 -3
  84. data/spec/grape/validations_spec.rb +9 -9
  85. data/spec/shared/versioning_examples.rb +58 -0
  86. data/spec/support/content_type_helpers.rb +1 -1
  87. metadata +26 -24
@@ -136,7 +136,7 @@ module Grape
136
136
  reset_validations!
137
137
  end
138
138
 
139
- %w(get post put head delete options patch).each do |meth|
139
+ %w[get post put head delete options patch].each do |meth|
140
140
  define_method meth do |*args, &block|
141
141
  options = args.extract_options!
142
142
  paths = args.first || ['/']
@@ -165,13 +165,13 @@ module Grape
165
165
 
166
166
  private
167
167
 
168
- # Builds the current class :inheritable_setting. If available, it returns the superclass's :inheritable_setting.
169
- # Otherwise, a clean :inheritable_setting is returned.
168
+ # Builds the current class :inheritable_setting. If available, it inherits from
169
+ # the superclass's :inheritable_setting.
170
170
  def build_top_level_setting
171
- if defined?(superclass) && superclass.respond_to?(:inheritable_setting) && superclass != Grape::API
172
- superclass.inheritable_setting
173
- else
174
- Grape::Util::InheritableSetting.new
171
+ Grape::Util::InheritableSetting.new.tap do |setting|
172
+ if defined?(superclass) && superclass.respond_to?(:inheritable_setting) && superclass != Grape::API
173
+ setting.inherit_from superclass.inheritable_setting
174
+ end
175
175
  end
176
176
  end
177
177
  end
@@ -44,7 +44,7 @@ module Grape
44
44
  # @return [Proc]
45
45
  # @raise [NameError] an instance method with the same name already exists
46
46
  def generate_api_method(method_name, &block)
47
- if instance_methods.include?(method_name.to_sym) || instance_methods.include?(method_name.to_s)
47
+ if method_defined?(method_name)
48
48
  raise NameError.new("method #{method_name.inspect} already exists and cannot be used as an unbound method name")
49
49
  end
50
50
 
@@ -1,4 +1,3 @@
1
- # encoding: utf-8
2
1
  module Grape
3
2
  module Exceptions
4
3
  class IncompatibleOptionValues < Base
@@ -1,4 +1,3 @@
1
- # encoding: utf-8
2
1
  module Grape
3
2
  module Exceptions
4
3
  class InvalidAcceptHeader < Base
@@ -1,4 +1,3 @@
1
- # encoding: utf-8
2
1
  module Grape
3
2
  module Exceptions
4
3
  class InvalidFormatter < Base
@@ -1,4 +1,3 @@
1
- # encoding: utf-8
2
1
  module Grape
3
2
  module Exceptions
4
3
  class InvalidMessageBody < Base
@@ -1,4 +1,3 @@
1
- # encoding: utf-8
2
1
  module Grape
3
2
  module Exceptions
4
3
  class InvalidVersionHeader < Base
@@ -1,4 +1,3 @@
1
- # encoding: utf-8
2
1
  module Grape
3
2
  module Exceptions
4
3
  class InvalidVersionerOption < Base
@@ -1,4 +1,3 @@
1
- # encoding: utf-8
2
1
  module Grape
3
2
  module Exceptions
4
3
  class InvalidWithOptionForRepresent < Base
@@ -1,4 +1,3 @@
1
- # encoding: utf-8
2
1
  module Grape
3
2
  module Exceptions
4
3
  class MethodNotAllowed < Base
@@ -1,4 +1,3 @@
1
- # encoding: utf-8
2
1
  module Grape
3
2
  module Exceptions
4
3
  class MissingGroupTypeError < Base
@@ -1,4 +1,3 @@
1
- # encoding: utf-8
2
1
  module Grape
3
2
  module Exceptions
4
3
  class MissingMimeType < Base
@@ -1,4 +1,3 @@
1
- # encoding: utf-8
2
1
  module Grape
3
2
  module Exceptions
4
3
  class MissingOption < Base
@@ -1,4 +1,3 @@
1
- # encoding: utf-8
2
1
  module Grape
3
2
  module Exceptions
4
3
  class MissingVendorOption < Base
@@ -1,4 +1,3 @@
1
- # encoding: utf-8
2
1
  module Grape
3
2
  module Exceptions
4
3
  class UnknownOptions < Base
@@ -1,4 +1,3 @@
1
- # encoding: utf-8
2
1
  module Grape
3
2
  module Exceptions
4
3
  class UnknownParameter < Base
@@ -1,4 +1,3 @@
1
- # encoding: utf-8
2
1
  module Grape
3
2
  module Exceptions
4
3
  class UnknownValidator < Base
@@ -1,4 +1,3 @@
1
- # encoding: utf-8
2
1
  module Grape
3
2
  module Exceptions
4
3
  class UnsupportedGroupTypeError < Base
@@ -1,6 +1,6 @@
1
1
  module Grape
2
2
  # A container for endpoints or other namespaces, which allows for both
3
- # logical grouping of endpoints as well as sharing commonconfiguration.
3
+ # logical grouping of endpoints as well as sharing common configuration.
4
4
  # May also be referred to as group, segment, or resource.
5
5
  class Namespace
6
6
  attr_reader :space, :options
@@ -104,6 +104,8 @@ module Grape
104
104
  ) if neighbor && method == 'OPTIONS' && !cascade
105
105
 
106
106
  route = match?(input, '*')
107
+ return neighbor.endpoint.call(env) if neighbor && cascade && route
108
+
107
109
  if route
108
110
  response = process_route(route, env)
109
111
  return response if response && !(cascade = cascade?(response))
@@ -5,7 +5,7 @@ module Grape
5
5
  class Router
6
6
  class Pattern
7
7
  DEFAULT_PATTERN_OPTIONS = { uri_decode: true, type: :grape }.freeze
8
- DEFAULT_SUPPORTED_CAPTURE = [:format, :version].freeze
8
+ DEFAULT_SUPPORTED_CAPTURE = %i[format version].freeze
9
9
 
10
10
  attr_reader :origin, :path, :capture, :pattern
11
11
 
@@ -8,7 +8,7 @@ module Grape
8
8
  class Route
9
9
  ROUTE_ATTRIBUTE_REGEXP = /route_([_a-zA-Z]\w*)/
10
10
  SOURCE_LOCATION_REGEXP = /^(.*?):(\d+?)(?::in `.+?')?$/
11
- FIXED_NAMED_CAPTURES = %w(format version).freeze
11
+ FIXED_NAMED_CAPTURES = %w[format version].freeze
12
12
 
13
13
  attr_accessor :pattern, :translator, :app, :index, :regexp, :options
14
14
 
@@ -32,19 +32,19 @@ module Grape
32
32
  ROUTE_ATTRIBUTE_REGEXP.match(method_id.to_s)
33
33
  end
34
34
 
35
- [
36
- :prefix,
37
- :version,
38
- :settings,
39
- :format,
40
- :description,
41
- :http_codes,
42
- :headers,
43
- :entity,
44
- :details,
45
- :requirements,
46
- :request_method,
47
- :namespace
35
+ %i[
36
+ prefix
37
+ version
38
+ settings
39
+ format
40
+ description
41
+ http_codes
42
+ headers
43
+ entity
44
+ details
45
+ requirements
46
+ request_method
47
+ namespace
48
48
  ].each do |method_name|
49
49
  define_method method_name do
50
50
  attributes.public_send method_name
@@ -39,8 +39,7 @@ module Grape
39
39
  # @return [Boolean] whether or not this entire scope needs to be
40
40
  # validated
41
41
  def should_validate?(parameters)
42
- return false if @optional && (params(parameters).blank? ||
43
- any_element_blank?(parameters))
42
+ return false if @optional && (params(parameters).blank? || all_element_blank?(parameters))
44
43
 
45
44
  return true if parent.nil?
46
45
  parent.should_validate?(parameters)
@@ -441,8 +440,8 @@ module Grape
441
440
  validations[type].respond_to?(:key?) && validations[type].key?(key) && !validations[type][key].nil?
442
441
  end
443
442
 
444
- def any_element_blank?(parameters)
445
- params(parameters).respond_to?(:any?) && params(parameters).any?(&:blank?)
443
+ def all_element_blank?(parameters)
444
+ params(parameters).respond_to?(:all?) && params(parameters).all?(&:blank?)
446
445
  end
447
446
  end
448
447
  end
@@ -1,5 +1,6 @@
1
1
  require_relative 'types/build_coercer'
2
2
  require_relative 'types/custom_type_coercer'
3
+ require_relative 'types/custom_type_collection_coercer'
3
4
  require_relative 'types/multiple_type_coercer'
4
5
  require_relative 'types/variant_collection_coercer'
5
6
  require_relative 'types/json'
@@ -143,7 +144,8 @@ module Grape
143
144
  end
144
145
 
145
146
  # A valid custom type must implement a class-level `parse` method, taking
146
- # one String argument and returning the parsed value in its correct type.
147
+ # one String argument and returning the parsed value in its correct type.
148
+ #
147
149
  # @param type [Class] type to check
148
150
  # @return [Boolean] whether or not the type can be used as a custom type
149
151
  def self.custom?(type)
@@ -155,6 +157,17 @@ module Grape
155
157
  type.respond_to?(:parse) &&
156
158
  type.method(:parse).arity == 1
157
159
  end
160
+
161
+ # Is the declared type an +Array+ or +Set+ of a {#custom?} type?
162
+ #
163
+ # @param type [Array<Class>,Class] type to check
164
+ # @return [Boolean] true if +type+ is a collection of a type that implements
165
+ # its own +#parse+ method.
166
+ def self.collection_of_custom?(type)
167
+ (type.is_a?(Array) || type.is_a?(Set)) &&
168
+ type.length == 1 &&
169
+ custom?(type.first)
170
+ end
158
171
  end
159
172
  end
160
173
  end
@@ -51,6 +51,14 @@ module Grape
51
51
  elsif method || Types.custom?(type)
52
52
  converter_options[:coercer] = Types::CustomTypeCoercer.new(type, method)
53
53
 
54
+ # Special coercer for collections of types that implement a parse method.
55
+ # CustomTypeCoercer (above) already handles such types when an explicit coercion
56
+ # method is supplied.
57
+ elsif Types.collection_of_custom?(type)
58
+ converter_options[:coercer] = Types::CustomTypeCollectionCoercer.new(
59
+ type.first, type.is_a?(Set)
60
+ )
61
+
54
62
  # Grape swaps in its own Virtus::Attribute implementations
55
63
  # for certain special types that merit first-class support
56
64
  # (but not if a custom coercion method has been supplied).
@@ -137,7 +137,7 @@ module Grape
137
137
  # passed, or if the type also implements a parse() method.
138
138
  type
139
139
  elsif type.is_a?(Enumerable)
140
- ->(value) { value.all? { |item| item.is_a? type[0] } }
140
+ ->(value) { value.respond_to?(:all?) && value.all? { |item| item.is_a? type[0] } }
141
141
  else
142
142
  # By default, do a simple type check
143
143
  ->(value) { value.is_a? type }
@@ -0,0 +1,71 @@
1
+ module Grape
2
+ module Validations
3
+ module Types
4
+ # Instances of this class may be passed to
5
+ # +Virtus::Attribute.build+ as the +:coercer+
6
+ # option, to handle collections of types that
7
+ # provide their own parsing (and optionally,
8
+ # type-checking) functionality.
9
+ #
10
+ # See {CustomTypeCoercer} for details on types
11
+ # that will be supported by this by this coercer.
12
+ # This coercer works in the same way as +CustomTypeCoercer+
13
+ # except that it expects to receive an array of strings to
14
+ # coerce and will return an array (or optionally, a set)
15
+ # of coerced values.
16
+ #
17
+ # +CustomTypeCoercer+ is already capable of providing type
18
+ # checking for arrays where an independent coercion method
19
+ # is supplied. As such, +CustomTypeCollectionCoercer+ does
20
+ # not allow for such a method to be supplied independently
21
+ # of the type.
22
+ class CustomTypeCollectionCoercer < CustomTypeCoercer
23
+ # A new coercer for collections of the given type.
24
+ #
25
+ # @param type [Class,#parse]
26
+ # type to which items in the array should be coerced.
27
+ # Must implement a +parse+ method which accepts a string,
28
+ # and for the purposes of type-checking it may either be
29
+ # a class, or it may implement a +coerced?+, +parsed?+ or
30
+ # +call+ method (in that order of precedence) which
31
+ # accepts a single argument and returns true if the given
32
+ # array item has been coerced correctly.
33
+ # @param set [Boolean]
34
+ # when true, a +Set+ will be returned by {#call} instead
35
+ # of an +Array+ and duplicate items will be discarded.
36
+ def initialize(type, set = false)
37
+ super(type)
38
+ @set = set
39
+ end
40
+
41
+ # This method is called from somewhere within
42
+ # +Virtus::Attribute::coerce+ in order to coerce
43
+ # the given value.
44
+ #
45
+ # @param value [Array<String>] an array of values to be coerced
46
+ # @return [Array,Set] the coerced result. May be an +Array+ or a
47
+ # +Set+ depending on the setting given to the constructor
48
+ def call(value)
49
+ coerced = value.map { |item| super(item) }
50
+
51
+ @set ? Set.new(coerced) : coerced
52
+ end
53
+
54
+ # This method is called from somewhere within
55
+ # +Virtus::Attribute::value_coerced?+ in order to assert
56
+ # that the all of the values in the array have been coerced
57
+ # successfully.
58
+ #
59
+ # @param primitive [Axiom::Types::Type] primitive type for
60
+ # the coercion as deteced by axiom-types' inference system.
61
+ # @param value [Enumerable] a coerced result returned from {#call}
62
+ # @return [true,false] whether or not all of the coerced values in
63
+ # the collection satisfy type requirements.
64
+ def success?(primitive, value)
65
+ value.is_a?(@set ? Set : Array) &&
66
+ value.all? { |item| super(primitive, item) }
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -7,7 +7,7 @@ module Grape
7
7
  value = params[attr_name]
8
8
  value = value.strip if value.respond_to?(:strip)
9
9
 
10
- return if false == value || value.present?
10
+ return if value == false || value.present?
11
11
 
12
12
  raise Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: message(:blank)
13
13
  end
@@ -38,6 +38,7 @@ module Grape
38
38
  attributes = AttributesIterator.new(self, @scope, params)
39
39
  array_errors = []
40
40
  attributes.each do |resource_params, attr_name|
41
+ next if !@scope.required? && resource_params.empty?
41
42
  next unless @required || (resource_params.respond_to?(:key?) && resource_params.key?(attr_name))
42
43
  next unless @scope.meets_dependency?(resource_params, params)
43
44
 
@@ -16,6 +16,7 @@ module Grape
16
16
 
17
17
  def validate_param!(attr_name, params)
18
18
  raise Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: message(:coerce) unless params.is_a? Hash
19
+ return unless requires_coercion?(params[attr_name])
19
20
  new_value = coerce_value(params[attr_name])
20
21
  raise Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: message(:coerce) unless valid_type?(new_value)
21
22
  params[attr_name] = new_value
@@ -63,6 +64,11 @@ module Grape
63
64
  def type
64
65
  @option[:type].is_a?(Hash) ? @option[:type][:value] : @option[:type]
65
66
  end
67
+
68
+ def requires_coercion?(value)
69
+ # JSON types do not require coercion if value is valid
70
+ !valid_type?(value) || converter.coercer.respond_to?(:method) && !converter.is_a?(Grape::Validations::Types::Json)
71
+ end
66
72
  end
67
73
  end
68
74
  end
@@ -1,4 +1,4 @@
1
1
  module Grape
2
2
  # The current version of Grape.
3
- VERSION = '1.0.1'.freeze
3
+ VERSION = '1.0.2'.freeze
4
4
  end
Binary file
@@ -209,7 +209,7 @@ describe Grape::API do
209
209
  expect(last_response.body).to eq('outer')
210
210
  end
211
211
 
212
- %w(group resource resources segment).each do |als|
212
+ %w[group resource resources segment].each do |als|
213
213
  it "`.#{als}` is an alias" do
214
214
  inner_namespace = nil
215
215
  subject.send(als, :awesome) do
@@ -318,7 +318,7 @@ describe Grape::API do
318
318
  end
319
319
 
320
320
  context 'when array of versions provided' do
321
- let(:version) { %w(v1 v2) }
321
+ let(:version) { %w[v1 v2] }
322
322
 
323
323
  it { versioned_get '/', 'v1', using: :path }
324
324
  it { versioned_get '/', 'v2', using: :path }
@@ -333,7 +333,7 @@ describe Grape::API do
333
333
  end
334
334
 
335
335
  it 'header versioned APIs with multiple headers' do
336
- subject.version %w(v1 v2), using: :header, vendor: 'test'
336
+ subject.version %w[v1 v2], using: :header, vendor: 'test'
337
337
  subject.enable_root_route!
338
338
 
339
339
  versioned_get '/', 'v1', using: :header, vendor: 'test'
@@ -421,7 +421,7 @@ describe Grape::API do
421
421
  end
422
422
 
423
423
  it 'allows for multiple verbs' do
424
- subject.route([:get, :post], '/abc') do
424
+ subject.route(%i[get post], '/abc') do
425
425
  'hiya'
426
426
  end
427
427
 
@@ -435,7 +435,7 @@ describe Grape::API do
435
435
  expect(last_response.body).to eql 'hiya'
436
436
  end
437
437
 
438
- [:put, :post].each do |verb|
438
+ %i[put post].each do |verb|
439
439
  context verb do
440
440
  ['string', :symbol, 1, -1.1, {}, [], true, false, nil].each do |object|
441
441
  it "allows a(n) #{object.class} json object in params" do
@@ -473,14 +473,14 @@ describe Grape::API do
473
473
  end
474
474
 
475
475
  it 'allows for multipart paths' do
476
- subject.route([:get, :post], '/:id/first') do
476
+ subject.route(%i[get post], '/:id/first') do
477
477
  'first'
478
478
  end
479
479
 
480
- subject.route([:get, :post], '/:id') do
480
+ subject.route(%i[get post], '/:id') do
481
481
  'ola'
482
482
  end
483
- subject.route([:get, :post], '/:id/first/second') do
483
+ subject.route(%i[get post], '/:id/first/second') do
484
484
  'second'
485
485
  end
486
486
 
@@ -501,7 +501,7 @@ describe Grape::API do
501
501
  'lol'
502
502
  end
503
503
 
504
- %w(get post put delete options patch).each do |m|
504
+ %w[get post put delete options patch].each do |m|
505
505
  send(m, '/abc')
506
506
  expect(last_response.body).to eql 'lol'
507
507
  end
@@ -538,7 +538,7 @@ describe Grape::API do
538
538
  expect(last_response.body).to eql 'catch-all'
539
539
  end
540
540
 
541
- verbs = %w(post get head delete put options patch)
541
+ verbs = %w[post get head delete put options patch]
542
542
  verbs.each do |verb|
543
543
  it "allows and properly constrain a #{verb.upcase} method" do
544
544
  subject.send(verb, '/example') do
@@ -2393,7 +2393,7 @@ XML
2393
2393
  expect(subject.version).to eq('v2')
2394
2394
  end
2395
2395
  it 'returns versions' do
2396
- expect(subject.versions).to eq(%w(v1 v2))
2396
+ expect(subject.versions).to eq(%w[v1 v2])
2397
2397
  end
2398
2398
  it 'sets route paths' do
2399
2399
  expect(subject.routes.size).to be >= 2
@@ -3129,6 +3129,16 @@ XML
3129
3129
  get '/two/v1/world'
3130
3130
  expect(last_response.status).to eq 200
3131
3131
  end
3132
+
3133
+ context 'when mounting class extends a subclass of Grape::API' do
3134
+ it 'mounts APIs with the same superclass' do
3135
+ base_api = Class.new(Grape::API)
3136
+ a = Class.new(base_api)
3137
+ b = Class.new(base_api)
3138
+
3139
+ expect { a.mount b }.to_not raise_error
3140
+ end
3141
+ end
3132
3142
  end
3133
3143
  end
3134
3144
 
@@ -3368,7 +3378,7 @@ XML
3368
3378
  end
3369
3379
  it 'array' do
3370
3380
  subject.get '/example' do
3371
- %w(example1 example2)
3381
+ %w[example1 example2]
3372
3382
  end
3373
3383
  get '/example'
3374
3384
  expect(last_response.status).to eq(200)