grape 0.16.2 → 0.17.0

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 (93) hide show
  1. checksums.yaml +4 -4
  2. data/Appraisals +4 -0
  3. data/CHANGELOG.md +54 -27
  4. data/Dangerfile +80 -0
  5. data/Gemfile +23 -0
  6. data/Gemfile.lock +61 -27
  7. data/README.md +135 -7
  8. data/Rakefile +34 -30
  9. data/UPGRADING.md +21 -0
  10. data/gemfiles/rack_1.5.2.gemfile +21 -0
  11. data/gemfiles/rails_3.gemfile +22 -1
  12. data/gemfiles/rails_4.gemfile +21 -0
  13. data/gemfiles/rails_5.gemfile +34 -0
  14. data/grape.gemspec +0 -14
  15. data/lib/grape.rb +2 -0
  16. data/lib/grape/api.rb +9 -2
  17. data/lib/grape/dsl/headers.rb +1 -1
  18. data/lib/grape/dsl/inside_route.rb +15 -17
  19. data/lib/grape/dsl/middleware.rb +15 -1
  20. data/lib/grape/dsl/parameters.rb +16 -14
  21. data/lib/grape/dsl/request_response.rb +24 -20
  22. data/lib/grape/dsl/routing.rb +11 -10
  23. data/lib/grape/dsl/settings.rb +16 -0
  24. data/lib/grape/endpoint.rb +77 -60
  25. data/lib/grape/exceptions/validation.rb +5 -2
  26. data/lib/grape/exceptions/validation_array_errors.rb +11 -0
  27. data/lib/grape/formatter/xml.rb +1 -1
  28. data/lib/grape/middleware/error.rb +34 -25
  29. data/lib/grape/middleware/formatter.rb +9 -9
  30. data/lib/grape/middleware/stack.rb +110 -0
  31. data/lib/grape/middleware/versioner.rb +1 -1
  32. data/lib/grape/middleware/versioner/accept_version_header.rb +1 -1
  33. data/lib/grape/middleware/versioner/header.rb +3 -3
  34. data/lib/grape/path.rb +10 -2
  35. data/lib/grape/request.rb +1 -1
  36. data/lib/grape/router.rb +10 -19
  37. data/lib/grape/router/pattern.rb +2 -2
  38. data/lib/grape/router/route.rb +3 -3
  39. data/lib/grape/util/content_types.rb +1 -1
  40. data/lib/grape/util/inheritable_setting.rb +7 -2
  41. data/lib/grape/util/reverse_stackable_values.rb +45 -0
  42. data/lib/grape/util/stackable_values.rb +10 -11
  43. data/lib/grape/validations/attributes_iterator.rb +32 -7
  44. data/lib/grape/validations/params_scope.rb +33 -21
  45. data/lib/grape/validations/types.rb +4 -4
  46. data/lib/grape/validations/types/build_coercer.rb +9 -1
  47. data/lib/grape/validations/validators/all_or_none.rb +2 -2
  48. data/lib/grape/validations/validators/allow_blank.rb +10 -11
  49. data/lib/grape/validations/validators/at_least_one_of.rb +1 -1
  50. data/lib/grape/validations/validators/base.rb +16 -6
  51. data/lib/grape/validations/validators/coerce.rb +3 -6
  52. data/lib/grape/validations/validators/default.rb +26 -1
  53. data/lib/grape/validations/validators/exactly_one_of.rb +1 -1
  54. data/lib/grape/validations/validators/mutual_exclusion.rb +1 -1
  55. data/lib/grape/validations/validators/presence.rb +1 -1
  56. data/lib/grape/validations/validators/regexp.rb +1 -1
  57. data/lib/grape/validations/validators/values.rb +1 -1
  58. data/lib/grape/version.rb +1 -1
  59. data/spec/grape/api/custom_validations_spec.rb +3 -3
  60. data/spec/grape/api/parameters_modification_spec.rb +41 -0
  61. data/spec/grape/api_spec.rb +335 -108
  62. data/spec/grape/dsl/logger_spec.rb +1 -1
  63. data/spec/grape/dsl/middleware_spec.rb +25 -5
  64. data/spec/grape/dsl/request_response_spec.rb +20 -6
  65. data/spec/grape/dsl/validations_spec.rb +1 -1
  66. data/spec/grape/endpoint_spec.rb +166 -23
  67. data/spec/grape/entity_spec.rb +0 -2
  68. data/spec/grape/exceptions/body_parse_errors_spec.rb +37 -0
  69. data/spec/grape/exceptions/validation_errors_spec.rb +5 -5
  70. data/spec/grape/exceptions/validation_spec.rb +10 -0
  71. data/spec/grape/integration/global_namespace_function_spec.rb +1 -1
  72. data/spec/grape/integration/rack_spec.rb +1 -1
  73. data/spec/grape/middleware/base_spec.rb +1 -1
  74. data/spec/grape/middleware/exception_spec.rb +2 -2
  75. data/spec/grape/middleware/formatter_spec.rb +4 -4
  76. data/spec/grape/middleware/stack_spec.rb +123 -0
  77. data/spec/grape/middleware/versioner/header_spec.rb +6 -6
  78. data/spec/grape/request_spec.rb +22 -22
  79. data/spec/grape/util/inheritable_setting_spec.rb +23 -0
  80. data/spec/grape/util/reverse_stackable_values_spec.rb +131 -0
  81. data/spec/grape/validations/params_scope_spec.rb +88 -1
  82. data/spec/grape/validations/validators/allow_blank_spec.rb +5 -0
  83. data/spec/grape/validations/validators/coerce_spec.rb +5 -5
  84. data/spec/grape/validations/validators/default_spec.rb +44 -0
  85. data/spec/grape/validations/validators/values_spec.rb +1 -1
  86. data/spec/grape/validations_spec.rb +36 -17
  87. data/spec/spec_helper.rb +1 -8
  88. data/spec/support/versioned_helpers.rb +3 -3
  89. metadata +13 -188
  90. data/gemfiles/rails_3.gemfile.lock +0 -225
  91. data/pkg/grape-0.16.1.gem +0 -0
  92. data/pkg/patch.diff +0 -24
  93. data/tmp/Gemfile.lock +0 -63
@@ -22,20 +22,6 @@ Gem::Specification.new do |s|
22
22
  s.add_runtime_dependency 'virtus', '>= 1.0.0'
23
23
  s.add_runtime_dependency 'builder'
24
24
 
25
- s.add_development_dependency 'grape-entity', '0.5.0'
26
- s.add_development_dependency 'rake', '~> 10'
27
- s.add_development_dependency 'maruku'
28
- s.add_development_dependency 'yard'
29
- s.add_development_dependency 'rack-test'
30
- s.add_development_dependency 'rspec', '~> 3.0'
31
- s.add_development_dependency 'bundler'
32
- s.add_development_dependency 'cookiejar'
33
- s.add_development_dependency 'rack-contrib'
34
- s.add_development_dependency 'mime-types', '< 3.0'
35
- s.add_development_dependency 'appraisal'
36
- s.add_development_dependency 'benchmark-ips'
37
- s.add_development_dependency 'rubocop', '0.35.1'
38
-
39
25
  s.files = Dir['**/*'].keep_if { |file| File.file?(file) }
40
26
  s.test_files = Dir['spec/**/*']
41
27
  s.require_paths = ['lib']
@@ -58,6 +58,7 @@ module Grape
58
58
  extend ActiveSupport::Autoload
59
59
  autoload :Base
60
60
  autoload :Validation
61
+ autoload :ValidationArrayErrors
61
62
  autoload :ValidationErrors
62
63
  autoload :MissingVendorOption
63
64
  autoload :MissingMimeType
@@ -106,6 +107,7 @@ module Grape
106
107
  extend ActiveSupport::Autoload
107
108
  autoload :InheritableValues
108
109
  autoload :StackableValues
110
+ autoload :ReverseStackableValues
109
111
  autoload :InheritableSetting
110
112
  autoload :StrictHashConfiguration
111
113
  autoload :Registrable
@@ -88,6 +88,13 @@ module Grape
88
88
  def inherit_settings(other_settings)
89
89
  top_level_setting.inherit_from other_settings.point_in_time_copy
90
90
 
91
+ # Propagate any inherited params down to our endpoints, and reset any
92
+ # compiled routes.
93
+ endpoints.each do |e|
94
+ e.inherit_settings(top_level_setting.namespace_stackable)
95
+ e.reset_routes!
96
+ end
97
+
91
98
  reset_routes!
92
99
  end
93
100
  end
@@ -123,8 +130,8 @@ module Grape
123
130
  # errors from reaching upstream. This is effectivelly done by unsetting
124
131
  # X-Cascade. Default :cascade is true.
125
132
  def cascade?
126
- return !!self.class.namespace_inheritable(:cascade) if self.class.inheritable_setting.namespace_inheritable.keys.include?(:cascade)
127
- return !!self.class.namespace_inheritable(:version_options)[:cascade] if self.class.namespace_inheritable(:version_options) && self.class.namespace_inheritable(:version_options).key?(:cascade)
133
+ return self.class.namespace_inheritable(:cascade) if self.class.inheritable_setting.namespace_inheritable.keys.include?(:cascade)
134
+ return self.class.namespace_inheritable(:version_options)[:cascade] if self.class.namespace_inheritable(:version_options) && self.class.namespace_inheritable(:version_options).key?(:cascade)
128
135
  true
129
136
  end
130
137
 
@@ -10,7 +10,7 @@ module Grape
10
10
  @header ||= {}
11
11
  end
12
12
  end
13
- alias_method :headers, :header
13
+ alias headers header
14
14
  end
15
15
  end
16
16
  end
@@ -29,9 +29,12 @@ module Grape
29
29
  def declared(params, options = {}, declared_params = nil)
30
30
  options = options.reverse_merge(include_missing: true, include_parent_namespaces: true)
31
31
 
32
- declared_params ||= (!options[:include_parent_namespaces] ? route_setting(:declared_params) : (route_setting(:saved_declared_params) || [])).flatten(1) || []
32
+ all_declared_params = route_setting(:declared_params)
33
+ current_namespace_declared_params = route_setting(:saved_declared_params).last
33
34
 
34
- fail ArgumentError, 'Tried to filter for declared parameters but none exist.' unless declared_params
35
+ declared_params ||= options[:include_parent_namespaces] ? all_declared_params : current_namespace_declared_params
36
+
37
+ raise ArgumentError, 'Tried to filter for declared parameters but none exist.' unless declared_params
35
38
 
36
39
  if params.is_a? Array
37
40
  params.map do |param|
@@ -70,7 +73,7 @@ module Grape
70
73
  # options. `:include_parent_namespaces` defaults to true, hence must be set to false if
71
74
  # you want only to return params declared against the current/target endpoint.
72
75
  def declared(*)
73
- fail MethodNotYetAvailable, '#declared is not available prior to parameter validation.'
76
+ raise MethodNotYetAvailable, '#declared is not available prior to parameter validation.'
74
77
  end
75
78
 
76
79
  # The API version as specified in the URL.
@@ -100,14 +103,12 @@ module Grape
100
103
  if permanent
101
104
  status 301
102
105
  body_message ||= "This resource has been moved permanently to #{url}."
106
+ elsif env[Grape::Http::Headers::HTTP_VERSION] == 'HTTP/1.1' && request.request_method.to_s.upcase != Grape::Http::Headers::GET
107
+ status 303
108
+ body_message ||= "An alternate resource is located at #{url}."
103
109
  else
104
- if env[Grape::Http::Headers::HTTP_VERSION] == 'HTTP/1.1' && request.request_method.to_s.upcase != Grape::Http::Headers::GET
105
- status 303
106
- body_message ||= "An alternate resource is located at #{url}."
107
- else
108
- status 302
109
- body_message ||= "This resource has been moved temporarily to #{url}."
110
- end
110
+ status 302
111
+ body_message ||= "This resource has been moved temporarily to #{url}."
111
112
  end
112
113
  header 'Location', url
113
114
  content_type 'text/plain'
@@ -120,11 +121,8 @@ module Grape
120
121
  def status(status = nil)
121
122
  case status
122
123
  when Symbol
123
- if Rack::Utils::SYMBOL_TO_STATUS_CODE.keys.include?(status)
124
- @status = Rack::Utils.status_code(status)
125
- else
126
- fail ArgumentError, "Status code :#{status} is invalid."
127
- end
124
+ raise ArgumentError, "Status code :#{status} is invalid." unless Rack::Utils::SYMBOL_TO_STATUS_CODE.keys.include?(status)
125
+ @status = Rack::Utils.status_code(status)
128
126
  when Fixnum
129
127
  @status = status
130
128
  when nil
@@ -136,7 +134,7 @@ module Grape
136
134
  200
137
135
  end
138
136
  else
139
- fail ArgumentError, 'Status code must be Fixnum or Symbol.'
137
+ raise ArgumentError, 'Status code must be Fixnum or Symbol.'
140
138
  end
141
139
  end
142
140
 
@@ -260,7 +258,7 @@ module Grape
260
258
  if key
261
259
  representation = (@body || {}).merge(key => representation)
262
260
  elsif entity_class.present? && @body
263
- fail ArgumentError, "Representation of type #{representation.class} cannot be merged." unless representation.respond_to?(:merge)
261
+ raise ArgumentError, "Representation of type #{representation.class} cannot be merged." unless representation.respond_to?(:merge)
264
262
  representation = @body.merge(representation)
265
263
  end
266
264
 
@@ -15,7 +15,21 @@ module Grape
15
15
  # @param middleware_class [Class] The class of the middleware you'd like
16
16
  # to inject.
17
17
  def use(middleware_class, *args, &block)
18
- arr = [middleware_class, *args]
18
+ arr = [:use, middleware_class, *args]
19
+ arr << block if block_given?
20
+
21
+ namespace_stackable(:middleware, arr)
22
+ end
23
+
24
+ def insert_before(*args, &block)
25
+ arr = [:insert_before, *args]
26
+ arr << block if block_given?
27
+
28
+ namespace_stackable(:middleware, arr)
29
+ end
30
+
31
+ def insert_after(*args, &block)
32
+ arr = [:insert_after, *args]
19
33
  arr << block if block_given?
20
34
 
21
35
  namespace_stackable(:middleware, arr)
@@ -34,13 +34,13 @@ module Grape
34
34
  options = names.extract_options!
35
35
  names.each do |name|
36
36
  params_block = named_params.fetch(name) do
37
- fail "Params :#{name} not found!"
37
+ raise "Params :#{name} not found!"
38
38
  end
39
39
  instance_exec(options, &params_block)
40
40
  end
41
41
  end
42
- alias_method :use_scope, :use
43
- alias_method :includes, :use
42
+ alias use_scope use
43
+ alias includes use
44
44
 
45
45
  # Require one or more parameters for the current endpoint.
46
46
  #
@@ -121,8 +121,8 @@ module Grape
121
121
 
122
122
  # check type for optional parameter group
123
123
  if attrs && block_given?
124
- fail Grape::Exceptions::MissingGroupTypeError.new if type.nil?
125
- fail Grape::Exceptions::UnsupportedGroupTypeError.new unless Grape::Validations::Types.group?(type)
124
+ raise Grape::Exceptions::MissingGroupTypeError.new if type.nil?
125
+ raise Grape::Exceptions::UnsupportedGroupTypeError.new unless Grape::Validations::Types.group?(type)
126
126
  end
127
127
 
128
128
  if opts[:using]
@@ -167,7 +167,8 @@ module Grape
167
167
  # @yield a parameter definition DSL
168
168
  def given(*attrs, &block)
169
169
  attrs.each do |attr|
170
- fail Grape::Exceptions::UnknownParameter.new(attr) unless declared_param?(attr)
170
+ attr_ = attr.is_a?(Hash) ? attr.keys[0] : attr
171
+ raise Grape::Exceptions::UnknownParameter.new(attr_) unless declared_param?(attr_)
171
172
  end
172
173
  new_lateral_scope(dependent_on: attrs, &block)
173
174
  end
@@ -181,7 +182,7 @@ module Grape
181
182
  @declared_params.flatten.include?(param)
182
183
  end
183
184
 
184
- alias_method :group, :requires
185
+ alias group requires
185
186
 
186
187
  # @param params [Hash] initial hash of parameters
187
188
  # @return hash of parameters relevant for the current scope
@@ -189,13 +190,14 @@ module Grape
189
190
  def params(params)
190
191
  params = @parent.params(params) if @parent
191
192
  if @element
192
- if params.is_a?(Array)
193
- params = params.flat_map { |el| el[@element] || {} }
194
- elsif params.is_a?(Hash)
195
- params = params[@element] || {}
196
- else
197
- params = {}
198
- end
193
+ params = if params.is_a?(Array)
194
+ # used for calculating parent array indices for error messages
195
+ params.map { |el| el[@element] || {} }
196
+ elsif params.is_a?(Hash)
197
+ params[@element] || {}
198
+ else
199
+ {}
200
+ end
199
201
  end
200
202
  params
201
203
  end
@@ -23,7 +23,7 @@ module Grape
23
23
  namespace_inheritable(:default_error_formatter, Grape::ErrorFormatter.formatter_for(new_format, {}))
24
24
  # define a single mime type
25
25
  mime_type = content_types[new_format.to_sym]
26
- fail Grape::Exceptions::MissingMimeType.new(new_format) unless mime_type
26
+ raise Grape::Exceptions::MissingMimeType.new(new_format) unless mime_type
27
27
  namespace_stackable(:content_types, new_format.to_sym => mime_type)
28
28
  else
29
29
  namespace_inheritable(:format)
@@ -51,11 +51,11 @@ module Grape
51
51
  end
52
52
 
53
53
  def error_formatter(format, options)
54
- if options.is_a?(Hash) && options.key?(:with)
55
- formatter = options[:with]
56
- else
57
- formatter = options
58
- end
54
+ formatter = if options.is_a?(Hash) && options.key?(:with)
55
+ options[:with]
56
+ else
57
+ options
58
+ end
59
59
 
60
60
  namespace_stackable(:error_formatters, format.to_sym => formatter)
61
61
  end
@@ -106,23 +106,27 @@ module Grape
106
106
 
107
107
  options = args.extract_options!
108
108
  if block_given? && options.key?(:with)
109
- fail ArgumentError, 'both :with option and block cannot be passed'
109
+ raise ArgumentError, 'both :with option and block cannot be passed'
110
110
  end
111
111
  handler ||= extract_with(options)
112
112
 
113
- if args.include?(:all)
113
+ case
114
+ when args.include?(:all)
114
115
  namespace_inheritable(:rescue_all, true)
115
116
  namespace_inheritable :all_rescue_handler, handler
117
+ when args.include?(:grape_exceptions)
118
+ namespace_inheritable(:rescue_all, true)
119
+ namespace_inheritable(:rescue_grape_exceptions, true)
116
120
  else
117
121
  handler_type =
118
- case options[:rescue_subclasses]
119
- when nil, true
120
- :rescue_handlers
121
- else
122
- :base_only_rescue_handlers
123
- end
124
-
125
- namespace_stackable handler_type, Hash[args.map { |arg| [arg, handler] }]
122
+ case options[:rescue_subclasses]
123
+ when nil, true
124
+ :rescue_handlers
125
+ else
126
+ :base_only_rescue_handlers
127
+ end
128
+
129
+ namespace_reverse_stackable handler_type, Hash[args.map { |arg| [arg, handler] }]
126
130
  end
127
131
 
128
132
  namespace_stackable(:rescue_options, options)
@@ -149,7 +153,7 @@ module Grape
149
153
  # @param model_class [Class] The model class that will be represented.
150
154
  # @option options [Class] :with The entity class that will represent the model.
151
155
  def represent(model_class, options)
152
- fail Grape::Exceptions::InvalidWithOptionForRepresent.new unless options[:with] && options[:with].is_a?(Class)
156
+ raise Grape::Exceptions::InvalidWithOptionForRepresent.new unless options[:with] && options[:with].is_a?(Class)
153
157
  namespace_stackable(:representations, model_class => options[:with])
154
158
  end
155
159
 
@@ -157,10 +161,10 @@ module Grape
157
161
 
158
162
  def extract_with(options)
159
163
  return unless options.key?(:with)
160
- with_option = options[:with]
164
+ with_option = options.delete(:with)
161
165
  return with_option if with_option.instance_of?(Proc)
162
- return if with_option.instance_of?(Symbol) || with_option.instance_of?(String)
163
- fail ArgumentError, "with: #{with_option.class}, expected Symbol, String or Proc"
166
+ return with_option.to_sym if with_option.instance_of?(Symbol) || with_option.instance_of?(String)
167
+ raise ArgumentError, "with: #{with_option.class}, expected Symbol, String or Proc"
164
168
  end
165
169
  end
166
170
  end
@@ -30,20 +30,21 @@ module Grape
30
30
  if args.any?
31
31
  options = args.extract_options!
32
32
  options = options.reverse_merge(using: :path)
33
+ requested_versions = args.flatten
33
34
 
34
- fail Grape::Exceptions::MissingVendorOption.new if options[:using] == :header && !options.key?(:vendor)
35
+ raise Grape::Exceptions::MissingVendorOption.new if options[:using] == :header && !options.key?(:vendor)
35
36
 
36
- @versions = versions | args
37
+ @versions = versions | requested_versions
37
38
 
38
39
  if block_given?
39
40
  within_namespace do
40
- namespace_inheritable(:version, args)
41
+ namespace_inheritable(:version, requested_versions)
41
42
  namespace_inheritable(:version_options, options)
42
43
 
43
44
  instance_eval(&block)
44
45
  end
45
46
  else
46
- namespace_inheritable(:version, args)
47
+ namespace_inheritable(:version, requested_versions)
47
48
  namespace_inheritable(:version_options, options)
48
49
  end
49
50
  end
@@ -121,9 +122,9 @@ module Grape
121
122
  method: methods,
122
123
  path: paths,
123
124
  for: self,
124
- route_options: ({
125
+ route_options: {
125
126
  params: namespace_stackable_with_hash(:params) || {}
126
- }).deep_merge(route_setting(:description) || {}).deep_merge(route_options || {})
127
+ }.deep_merge(route_setting(:description) || {}).deep_merge(route_options || {})
127
128
  }
128
129
 
129
130
  new_endpoint = Grape::Endpoint.new(inheritable_setting, endpoint_options, &block)
@@ -170,10 +171,10 @@ module Grape
170
171
  end
171
172
  end
172
173
 
173
- alias_method :group, :namespace
174
- alias_method :resource, :namespace
175
- alias_method :resources, :namespace
176
- alias_method :segment, :namespace
174
+ alias group namespace
175
+ alias resource namespace
176
+ alias resources namespace
177
+ alias segment namespace
177
178
 
178
179
  # An array of API routes.
179
180
  def routes
@@ -94,12 +94,28 @@ module Grape
94
94
  get_or_set :namespace_stackable, key, value
95
95
  end
96
96
 
97
+ def namespace_reverse_stackable(key, value = nil)
98
+ get_or_set :namespace_reverse_stackable, key, value
99
+ end
100
+
97
101
  def namespace_stackable_with_hash(key)
98
102
  settings = get_or_set :namespace_stackable, key, nil
99
103
  return if settings.blank?
100
104
  settings.each_with_object({}) { |value, result| result.deep_merge!(value) }
101
105
  end
102
106
 
107
+ def namespace_reverse_stackable_with_hash(key)
108
+ settings = get_or_set :namespace_reverse_stackable, key, nil
109
+ return if settings.blank?
110
+ result = {}
111
+ settings.each do |setting|
112
+ setting.each do |field, value|
113
+ result[field] ||= value
114
+ end
115
+ end
116
+ result
117
+ end
118
+
103
119
  # (see #unset_global_setting)
104
120
  def unset_namespace_stackable(key)
105
121
  unset :namespace_stackable, key
@@ -1,3 +1,5 @@
1
+ require 'grape/middleware/stack'
2
+
1
3
  module Grape
2
4
  # An Endpoint is the proxy scope in which all routing
3
5
  # blocks are executed. In other words, any methods
@@ -12,21 +14,14 @@ module Grape
12
14
 
13
15
  class << self
14
16
  def new(*args, &block)
15
- if self == Endpoint
16
- Class.new(Endpoint).new(*args, &block)
17
- else
18
- super
19
- end
17
+ self == Endpoint ? Class.new(Endpoint).new(*args, &block) : super
20
18
  end
21
19
 
22
20
  def before_each(new_setup = false, &block)
23
21
  @before_each ||= []
24
22
  if new_setup == false
25
- if block_given?
26
- @before_each << block
27
- else
28
- return @before_each
29
- end
23
+ return @before_each unless block_given?
24
+ @before_each << block
30
25
  else
31
26
  @before_each = [new_setup]
32
27
  end
@@ -34,9 +29,7 @@ module Grape
34
29
 
35
30
  def run_before_each(endpoint)
36
31
  superclass.run_before_each(endpoint) unless self == Endpoint
37
- before_each.each do |blk|
38
- blk.call(endpoint) if blk.respond_to? :call
39
- end
32
+ before_each.each { |blk| blk.call(endpoint) if blk.respond_to?(:call) }
40
33
  end
41
34
 
42
35
  # @api private
@@ -54,7 +47,7 @@ module Grape
54
47
  # @raise [NameError] an instance method with the same name already exists
55
48
  def generate_api_method(method_name, &block)
56
49
  if instance_methods.include?(method_name.to_sym) || instance_methods.include?(method_name.to_s)
57
- fail NameError.new("method #{method_name.inspect} already exists and cannot be used as an unbound method name")
50
+ raise NameError.new("method #{method_name.inspect} already exists and cannot be used as an unbound method name")
58
51
  end
59
52
 
60
53
  define_method(method_name, &block)
@@ -69,6 +62,18 @@ module Grape
69
62
  end
70
63
  end
71
64
 
65
+ # Create a new endpoint.
66
+ # @param new_settings [InheritableSetting] settings to determine the params,
67
+ # validations, and other properties from.
68
+ # @param options [Hash] attributes of this endpoint
69
+ # @option options path [String or Array] the path to this endpoint, within
70
+ # the current scope.
71
+ # @option options method [String or Array] which HTTP method(s) can be used
72
+ # to reach this endpoint.
73
+ # @option options route_options [Hash]
74
+ # @note This happens at the time of API definition, so in this context the
75
+ # endpoint does not know if it will be mounted under a different endpoint.
76
+ # @yield a block defining what your API should do when this endpoint is hit
72
77
  def initialize(new_settings, options = {}, &block)
73
78
  require_option(options, :path)
74
79
  require_option(options, :method)
@@ -97,8 +102,22 @@ module Grape
97
102
  @block = self.class.generate_api_method(method_name, &block)
98
103
  end
99
104
 
105
+ # Update our settings from a given set of stackable parameters. Used when
106
+ # the endpoint's API is mounted under another one.
107
+ def inherit_settings(namespace_stackable)
108
+ inheritable_setting.route[:saved_validations] += namespace_stackable[:validations]
109
+ parent_declared_params = namespace_stackable[:declared_params]
110
+
111
+ if parent_declared_params
112
+ inheritable_setting.route[:declared_params] ||= []
113
+ inheritable_setting.route[:declared_params].concat(parent_declared_params.flatten)
114
+ end
115
+
116
+ endpoints && endpoints.each { |e| e.inherit_settings(namespace_stackable) }
117
+ end
118
+
100
119
  def require_option(options, key)
101
- fail Grape::Exceptions::MissingOption.new(key) unless options.key?(key)
120
+ raise Grape::Exceptions::MissingOption.new(key) unless options.key?(key)
102
121
  end
103
122
 
104
123
  def method_name
@@ -171,7 +190,7 @@ module Grape
171
190
 
172
191
  def prepare_version
173
192
  version = namespace_inheritable(:version) || []
174
- return if version.length == 0
193
+ return if version.empty?
175
194
  version.length == 1 ? version.first.to_s : version
176
195
  end
177
196
 
@@ -229,9 +248,12 @@ module Grape
229
248
 
230
249
  run_filters befores, :before
231
250
 
251
+ allowed_methods = env[Grape::Env::GRAPE_METHOD_NOT_ALLOWED]
252
+ raise Grape::Exceptions::MethodNotAllowed, header.merge('Allow' => allowed_methods) if allowed_methods
253
+
232
254
  run_filters before_validations, :before_validation
233
255
 
234
- run_validators validations, request unless env[Grape::Env::GRAPE_METHOD_NOT_ALLOWED]
256
+ run_validators validations, request
235
257
 
236
258
  run_filters after_validations, :after_validation
237
259
 
@@ -245,53 +267,48 @@ module Grape
245
267
  end
246
268
  end
247
269
 
248
- def build_stack
249
- b = Rack::Builder.new
250
-
251
- b.use Rack::Head
252
- b.use Grape::Middleware::Error,
253
- format: namespace_inheritable(:format),
254
- content_types: namespace_stackable_with_hash(:content_types),
255
- default_status: namespace_inheritable(:default_error_status),
256
- rescue_all: namespace_inheritable(:rescue_all),
257
- default_error_formatter: namespace_inheritable(:default_error_formatter),
258
- error_formatters: namespace_stackable_with_hash(:error_formatters),
259
- rescue_options: namespace_stackable_with_hash(:rescue_options) || {},
260
- rescue_handlers: namespace_stackable_with_hash(:rescue_handlers) || {},
261
- base_only_rescue_handlers: namespace_stackable_with_hash(:base_only_rescue_handlers) || {},
262
- all_rescue_handler: namespace_inheritable(:all_rescue_handler)
263
-
264
- (namespace_stackable(:middleware) || []).each do |m|
265
- m = m.dup
266
- block = m.pop if m.last.is_a?(Proc)
267
- block ? b.use(*m, &block) : b.use(*m)
268
- end
270
+ def build_stack(helpers)
271
+ stack = Grape::Middleware::Stack.new
272
+
273
+ stack.use Rack::Head
274
+ stack.use Class.new(Grape::Middleware::Error),
275
+ helpers: helpers,
276
+ format: namespace_inheritable(:format),
277
+ content_types: namespace_stackable_with_hash(:content_types),
278
+ default_status: namespace_inheritable(:default_error_status),
279
+ rescue_all: namespace_inheritable(:rescue_all),
280
+ rescue_grape_exceptions: namespace_inheritable(:rescue_grape_exceptions),
281
+ default_error_formatter: namespace_inheritable(:default_error_formatter),
282
+ error_formatters: namespace_stackable_with_hash(:error_formatters),
283
+ rescue_options: namespace_stackable_with_hash(:rescue_options) || {},
284
+ rescue_handlers: namespace_reverse_stackable_with_hash(:rescue_handlers) || {},
285
+ base_only_rescue_handlers: namespace_stackable_with_hash(:base_only_rescue_handlers) || {},
286
+ all_rescue_handler: namespace_inheritable(:all_rescue_handler)
287
+
288
+ stack.concat namespace_stackable(:middleware)
269
289
 
270
290
  if namespace_inheritable(:version)
271
- b.use Grape::Middleware::Versioner.using(namespace_inheritable(:version_options)[:using]),
272
- versions: namespace_inheritable(:version) ? namespace_inheritable(:version).flatten : nil,
273
- version_options: namespace_inheritable(:version_options),
274
- prefix: namespace_inheritable(:root_prefix)
275
-
291
+ stack.use Grape::Middleware::Versioner.using(namespace_inheritable(:version_options)[:using]),
292
+ versions: namespace_inheritable(:version) ? namespace_inheritable(:version).flatten : nil,
293
+ version_options: namespace_inheritable(:version_options),
294
+ prefix: namespace_inheritable(:root_prefix)
276
295
  end
277
296
 
278
- b.use Grape::Middleware::Formatter,
279
- format: namespace_inheritable(:format),
280
- default_format: namespace_inheritable(:default_format) || :txt,
281
- content_types: namespace_stackable_with_hash(:content_types),
282
- formatters: namespace_stackable_with_hash(:formatters),
283
- parsers: namespace_stackable_with_hash(:parsers)
284
-
285
- b.run ->(env) { env[Grape::Env::API_ENDPOINT].run }
297
+ stack.use Grape::Middleware::Formatter,
298
+ format: namespace_inheritable(:format),
299
+ default_format: namespace_inheritable(:default_format) || :txt,
300
+ content_types: namespace_stackable_with_hash(:content_types),
301
+ formatters: namespace_stackable_with_hash(:formatters),
302
+ parsers: namespace_stackable_with_hash(:parsers)
286
303
 
287
- b.to_app
304
+ builder = stack.build
305
+ builder.run ->(env) { env[Grape::Env::API_ENDPOINT].run }
306
+ builder.to_app
288
307
  end
289
308
 
290
309
  def build_helpers
291
310
  helpers = namespace_stackable(:helpers) || []
292
- Module.new do
293
- helpers.each { |mod_to_include| include mod_to_include }
294
- end
311
+ Module.new { helpers.each { |mod_to_include| include mod_to_include } }
295
312
  end
296
313
 
297
314
  private :build_stack, :build_helpers
@@ -306,10 +323,8 @@ module Grape
306
323
  @lazy_initialize_lock.synchronize do
307
324
  return true if @lazy_initialized
308
325
 
309
- @app = options[:app] || build_stack
310
- @helpers = build_helpers.tap do |mod|
311
- self.class.send(:include, mod)
312
- end
326
+ @helpers = build_helpers.tap { |mod| self.class.send(:include, mod) }
327
+ @app = options[:app] || build_stack(@helpers)
313
328
 
314
329
  @lazy_initialized = true
315
330
  end
@@ -323,10 +338,12 @@ module Grape
323
338
  validator.validate(request)
324
339
  rescue Grape::Exceptions::Validation => e
325
340
  validation_errors << e
341
+ rescue Grape::Exceptions::ValidationArrayErrors => e
342
+ validation_errors += e.errors
326
343
  end
327
344
  end
328
345
 
329
- validation_errors.any? && fail(Grape::Exceptions::ValidationErrors, errors: validation_errors, headers: header)
346
+ validation_errors.any? && raise(Grape::Exceptions::ValidationErrors, errors: validation_errors, headers: header)
330
347
  end
331
348
 
332
349
  def run_filters(filters, type = :other)