grape 1.8.0 → 2.1.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.
Files changed (210) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +65 -1
  3. data/README.md +377 -334
  4. data/UPGRADING.md +231 -6
  5. data/grape.gemspec +6 -10
  6. data/lib/grape/api/instance.rb +13 -10
  7. data/lib/grape/api.rb +17 -8
  8. data/lib/grape/content_types.rb +0 -2
  9. data/lib/grape/cookies.rb +2 -1
  10. data/lib/grape/dry_types.rb +0 -2
  11. data/lib/grape/dsl/desc.rb +23 -21
  12. data/lib/grape/dsl/headers.rb +1 -1
  13. data/lib/grape/dsl/inside_route.rb +47 -22
  14. data/lib/grape/dsl/parameters.rb +4 -3
  15. data/lib/grape/dsl/routing.rb +20 -4
  16. data/lib/grape/dsl/validations.rb +13 -0
  17. data/lib/grape/endpoint.rb +15 -10
  18. data/lib/grape/{util/env.rb → env.rb} +0 -5
  19. data/lib/grape/error_formatter/txt.rb +11 -10
  20. data/lib/grape/exceptions/base.rb +3 -3
  21. data/lib/grape/exceptions/missing_group_type.rb +1 -1
  22. data/lib/grape/exceptions/unsupported_group_type.rb +1 -1
  23. data/lib/grape/exceptions/validation.rb +0 -2
  24. data/lib/grape/exceptions/validation_array_errors.rb +1 -0
  25. data/lib/grape/exceptions/validation_errors.rb +1 -3
  26. data/lib/grape/extensions/hash.rb +5 -1
  27. data/lib/grape/http/headers.rb +18 -24
  28. data/lib/grape/{util/json.rb → json.rb} +1 -3
  29. data/lib/grape/locale/en.yml +3 -0
  30. data/lib/grape/middleware/auth/base.rb +0 -2
  31. data/lib/grape/middleware/auth/dsl.rb +0 -2
  32. data/lib/grape/middleware/auth/strategies.rb +1 -2
  33. data/lib/grape/middleware/base.rb +0 -2
  34. data/lib/grape/middleware/error.rb +55 -50
  35. data/lib/grape/middleware/formatter.rb +21 -18
  36. data/lib/grape/middleware/globals.rb +1 -3
  37. data/lib/grape/middleware/stack.rb +2 -3
  38. data/lib/grape/middleware/versioner/accept_version_header.rb +0 -2
  39. data/lib/grape/middleware/versioner/header.rb +17 -163
  40. data/lib/grape/middleware/versioner/param.rb +2 -4
  41. data/lib/grape/middleware/versioner/path.rb +1 -3
  42. data/lib/grape/namespace.rb +3 -4
  43. data/lib/grape/path.rb +24 -29
  44. data/lib/grape/railtie.rb +9 -0
  45. data/lib/grape/request.rb +3 -5
  46. data/lib/grape/router/base_route.rb +39 -0
  47. data/lib/grape/router/greedy_route.rb +20 -0
  48. data/lib/grape/router/pattern.rb +39 -30
  49. data/lib/grape/router/route.rb +22 -59
  50. data/lib/grape/router.rb +30 -36
  51. data/lib/grape/util/accept/header.rb +19 -0
  52. data/lib/grape/util/accept_header_handler.rb +105 -0
  53. data/lib/grape/util/base_inheritable.rb +4 -4
  54. data/lib/grape/util/cache.rb +0 -3
  55. data/lib/grape/util/endpoint_configuration.rb +1 -1
  56. data/lib/grape/util/header.rb +13 -0
  57. data/lib/grape/util/inheritable_values.rb +0 -2
  58. data/lib/grape/util/lazy/block.rb +29 -0
  59. data/lib/grape/util/lazy/object.rb +45 -0
  60. data/lib/grape/util/lazy/value.rb +38 -0
  61. data/lib/grape/util/lazy/value_array.rb +21 -0
  62. data/lib/grape/util/lazy/value_enumerable.rb +34 -0
  63. data/lib/grape/util/lazy/value_hash.rb +21 -0
  64. data/lib/grape/util/media_type.rb +70 -0
  65. data/lib/grape/util/reverse_stackable_values.rb +1 -6
  66. data/lib/grape/util/stackable_values.rb +1 -6
  67. data/lib/grape/util/strict_hash_configuration.rb +3 -3
  68. data/lib/grape/validations/attributes_doc.rb +38 -36
  69. data/lib/grape/validations/contract_scope.rb +71 -0
  70. data/lib/grape/validations/params_scope.rb +10 -9
  71. data/lib/grape/validations/types/array_coercer.rb +0 -2
  72. data/lib/grape/validations/types/build_coercer.rb +69 -71
  73. data/lib/grape/validations/types/dry_type_coercer.rb +1 -11
  74. data/lib/grape/validations/types/json.rb +0 -2
  75. data/lib/grape/validations/types/primitive_coercer.rb +0 -2
  76. data/lib/grape/validations/types/set_coercer.rb +0 -3
  77. data/lib/grape/validations/types.rb +0 -3
  78. data/lib/grape/validations/validators/base.rb +2 -1
  79. data/lib/grape/validations/validators/default_validator.rb +5 -1
  80. data/lib/grape/validations/validators/length_validator.rb +42 -0
  81. data/lib/grape/validations/validators/values_validator.rb +8 -3
  82. data/lib/grape/validations.rb +3 -7
  83. data/lib/grape/version.rb +1 -1
  84. data/lib/grape/{util/xml.rb → xml.rb} +1 -1
  85. data/lib/grape.rb +38 -269
  86. metadata +33 -274
  87. data/lib/grape/eager_load.rb +0 -20
  88. data/lib/grape/middleware/versioner/parse_media_type_patch.rb +0 -24
  89. data/lib/grape/router/attribute_translator.rb +0 -63
  90. data/lib/grape/util/lazy_block.rb +0 -27
  91. data/lib/grape/util/lazy_object.rb +0 -43
  92. data/lib/grape/util/lazy_value.rb +0 -91
  93. data/spec/grape/api/custom_validations_spec.rb +0 -213
  94. data/spec/grape/api/deeply_included_options_spec.rb +0 -56
  95. data/spec/grape/api/defines_boolean_in_params_spec.rb +0 -38
  96. data/spec/grape/api/documentation_spec.rb +0 -59
  97. data/spec/grape/api/inherited_helpers_spec.rb +0 -114
  98. data/spec/grape/api/instance_spec.rb +0 -103
  99. data/spec/grape/api/invalid_format_spec.rb +0 -45
  100. data/spec/grape/api/namespace_parameters_in_route_spec.rb +0 -38
  101. data/spec/grape/api/nested_helpers_spec.rb +0 -50
  102. data/spec/grape/api/optional_parameters_in_route_spec.rb +0 -43
  103. data/spec/grape/api/parameters_modification_spec.rb +0 -41
  104. data/spec/grape/api/patch_method_helpers_spec.rb +0 -79
  105. data/spec/grape/api/recognize_path_spec.rb +0 -21
  106. data/spec/grape/api/required_parameters_in_route_spec.rb +0 -37
  107. data/spec/grape/api/required_parameters_with_invalid_method_spec.rb +0 -26
  108. data/spec/grape/api/routes_with_requirements_spec.rb +0 -59
  109. data/spec/grape/api/shared_helpers_exactly_one_of_spec.rb +0 -41
  110. data/spec/grape/api/shared_helpers_spec.rb +0 -36
  111. data/spec/grape/api_remount_spec.rb +0 -509
  112. data/spec/grape/api_spec.rb +0 -4356
  113. data/spec/grape/dsl/callbacks_spec.rb +0 -45
  114. data/spec/grape/dsl/desc_spec.rb +0 -98
  115. data/spec/grape/dsl/headers_spec.rb +0 -62
  116. data/spec/grape/dsl/helpers_spec.rb +0 -100
  117. data/spec/grape/dsl/inside_route_spec.rb +0 -531
  118. data/spec/grape/dsl/logger_spec.rb +0 -24
  119. data/spec/grape/dsl/middleware_spec.rb +0 -60
  120. data/spec/grape/dsl/parameters_spec.rb +0 -180
  121. data/spec/grape/dsl/request_response_spec.rb +0 -225
  122. data/spec/grape/dsl/routing_spec.rb +0 -275
  123. data/spec/grape/dsl/settings_spec.rb +0 -261
  124. data/spec/grape/dsl/validations_spec.rb +0 -55
  125. data/spec/grape/endpoint/declared_spec.rb +0 -846
  126. data/spec/grape/endpoint_spec.rb +0 -1085
  127. data/spec/grape/entity_spec.rb +0 -336
  128. data/spec/grape/exceptions/base_spec.rb +0 -81
  129. data/spec/grape/exceptions/body_parse_errors_spec.rb +0 -185
  130. data/spec/grape/exceptions/invalid_accept_header_spec.rb +0 -358
  131. data/spec/grape/exceptions/invalid_formatter_spec.rb +0 -15
  132. data/spec/grape/exceptions/invalid_response_spec.rb +0 -11
  133. data/spec/grape/exceptions/invalid_versioner_option_spec.rb +0 -15
  134. data/spec/grape/exceptions/missing_group_type_spec.rb +0 -17
  135. data/spec/grape/exceptions/missing_mime_type_spec.rb +0 -17
  136. data/spec/grape/exceptions/missing_option_spec.rb +0 -15
  137. data/spec/grape/exceptions/unknown_options_spec.rb +0 -15
  138. data/spec/grape/exceptions/unknown_validator_spec.rb +0 -15
  139. data/spec/grape/exceptions/unsupported_group_type_spec.rb +0 -19
  140. data/spec/grape/exceptions/validation_errors_spec.rb +0 -92
  141. data/spec/grape/exceptions/validation_spec.rb +0 -19
  142. data/spec/grape/extensions/param_builders/hash_spec.rb +0 -83
  143. data/spec/grape/extensions/param_builders/hash_with_indifferent_access_spec.rb +0 -105
  144. data/spec/grape/extensions/param_builders/hashie/mash_spec.rb +0 -79
  145. data/spec/grape/grape_spec.rb +0 -9
  146. data/spec/grape/integration/global_namespace_function_spec.rb +0 -29
  147. data/spec/grape/integration/rack_sendfile_spec.rb +0 -48
  148. data/spec/grape/integration/rack_spec.rb +0 -51
  149. data/spec/grape/loading_spec.rb +0 -44
  150. data/spec/grape/middleware/auth/base_spec.rb +0 -31
  151. data/spec/grape/middleware/auth/dsl_spec.rb +0 -60
  152. data/spec/grape/middleware/auth/strategies_spec.rb +0 -120
  153. data/spec/grape/middleware/base_spec.rb +0 -221
  154. data/spec/grape/middleware/error_spec.rb +0 -85
  155. data/spec/grape/middleware/exception_spec.rb +0 -294
  156. data/spec/grape/middleware/formatter_spec.rb +0 -461
  157. data/spec/grape/middleware/globals_spec.rb +0 -30
  158. data/spec/grape/middleware/stack_spec.rb +0 -155
  159. data/spec/grape/middleware/versioner/accept_version_header_spec.rb +0 -122
  160. data/spec/grape/middleware/versioner/header_spec.rb +0 -345
  161. data/spec/grape/middleware/versioner/param_spec.rb +0 -171
  162. data/spec/grape/middleware/versioner/path_spec.rb +0 -62
  163. data/spec/grape/middleware/versioner_spec.rb +0 -21
  164. data/spec/grape/named_api_spec.rb +0 -19
  165. data/spec/grape/parser_spec.rb +0 -86
  166. data/spec/grape/path_spec.rb +0 -252
  167. data/spec/grape/presenters/presenter_spec.rb +0 -71
  168. data/spec/grape/request_spec.rb +0 -126
  169. data/spec/grape/util/inheritable_setting_spec.rb +0 -242
  170. data/spec/grape/util/inheritable_values_spec.rb +0 -79
  171. data/spec/grape/util/reverse_stackable_values_spec.rb +0 -134
  172. data/spec/grape/util/stackable_values_spec.rb +0 -128
  173. data/spec/grape/util/strict_hash_configuration_spec.rb +0 -38
  174. data/spec/grape/validations/attributes_doc_spec.rb +0 -153
  175. data/spec/grape/validations/instance_behaivour_spec.rb +0 -43
  176. data/spec/grape/validations/multiple_attributes_iterator_spec.rb +0 -38
  177. data/spec/grape/validations/params_scope_spec.rb +0 -1420
  178. data/spec/grape/validations/single_attribute_iterator_spec.rb +0 -56
  179. data/spec/grape/validations/types/array_coercer_spec.rb +0 -33
  180. data/spec/grape/validations/types/primitive_coercer_spec.rb +0 -150
  181. data/spec/grape/validations/types/set_coercer_spec.rb +0 -32
  182. data/spec/grape/validations/types_spec.rb +0 -111
  183. data/spec/grape/validations/validators/all_or_none_spec.rb +0 -162
  184. data/spec/grape/validations/validators/allow_blank_spec.rb +0 -575
  185. data/spec/grape/validations/validators/at_least_one_of_spec.rb +0 -205
  186. data/spec/grape/validations/validators/base_spec.rb +0 -38
  187. data/spec/grape/validations/validators/coerce_spec.rb +0 -1261
  188. data/spec/grape/validations/validators/default_spec.rb +0 -463
  189. data/spec/grape/validations/validators/exactly_one_of_spec.rb +0 -233
  190. data/spec/grape/validations/validators/except_values_spec.rb +0 -192
  191. data/spec/grape/validations/validators/mutual_exclusion_spec.rb +0 -214
  192. data/spec/grape/validations/validators/presence_spec.rb +0 -315
  193. data/spec/grape/validations/validators/regexp_spec.rb +0 -161
  194. data/spec/grape/validations/validators/same_as_spec.rb +0 -57
  195. data/spec/grape/validations/validators/values_spec.rb +0 -733
  196. data/spec/grape/validations/validators/zh-CN.yml +0 -10
  197. data/spec/grape/validations_spec.rb +0 -2030
  198. data/spec/integration/eager_load/eager_load_spec.rb +0 -15
  199. data/spec/integration/multi_json/json_spec.rb +0 -7
  200. data/spec/integration/multi_xml/xml_spec.rb +0 -7
  201. data/spec/shared/deprecated_class_examples.rb +0 -16
  202. data/spec/shared/versioning_examples.rb +0 -215
  203. data/spec/spec_helper.rb +0 -52
  204. data/spec/support/basic_auth_encode_helpers.rb +0 -11
  205. data/spec/support/chunks.rb +0 -14
  206. data/spec/support/content_type_helpers.rb +0 -15
  207. data/spec/support/endpoint_faker.rb +0 -25
  208. data/spec/support/file_streamer.rb +0 -13
  209. data/spec/support/integer_helpers.rb +0 -13
  210. data/spec/support/versioned_helpers.rb +0 -55
@@ -130,7 +130,7 @@ module Grape
130
130
 
131
131
  opts = attrs.extract_options!.clone
132
132
  opts[:presence] = { value: true, message: opts[:message] }
133
- opts = @group.merge(opts) if instance_variable_defined?(:@group) && @group
133
+ opts = @group.deep_merge(opts) if instance_variable_defined?(:@group) && @group
134
134
 
135
135
  if opts[:using]
136
136
  require_required_and_optional_fields(attrs.first, opts)
@@ -149,7 +149,7 @@ module Grape
149
149
 
150
150
  opts = attrs.extract_options!.clone
151
151
  type = opts[:type]
152
- opts = @group.merge(opts) if instance_variable_defined?(:@group) && @group
152
+ opts = @group.deep_merge(opts) if instance_variable_defined?(:@group) && @group
153
153
 
154
154
  # check type for optional parameter group
155
155
  if attrs && block
@@ -170,7 +170,8 @@ module Grape
170
170
  # @param (see #requires)
171
171
  # @option (see #requires)
172
172
  def with(*attrs, &block)
173
- new_group_scope(attrs.clone, &block)
173
+ new_group_attrs = [@group, attrs.clone.first].compact.reduce(&:deep_merge)
174
+ new_group_scope([new_group_attrs], &block)
174
175
  end
175
176
 
176
177
  # Disallow the given parameters to be present in the same request.
@@ -30,7 +30,7 @@ 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
+ requested_versions = args.flatten.map(&:to_s)
34
34
 
35
35
  raise Grape::Exceptions::MissingVendorOption.new if options[:using] == :header && !options.key?(:vendor)
36
36
 
@@ -54,7 +54,7 @@ module Grape
54
54
 
55
55
  # Define a root URL prefix for your entire API.
56
56
  def prefix(prefix = nil)
57
- namespace_inheritable(:root_prefix, prefix)
57
+ namespace_inheritable(:root_prefix, prefix&.to_s)
58
58
  end
59
59
 
60
60
  # Create a scope without affecting the URL.
@@ -85,8 +85,8 @@ module Grape
85
85
  mounts = { mounts => '/' } unless mounts.respond_to?(:each_pair)
86
86
  mounts.each_pair do |app, path|
87
87
  if app.respond_to?(:mount_instance)
88
- opts_with = opts.any? ? opts.shift[:with] : {}
89
- mount({ app.mount_instance(configuration: opts_with) => path })
88
+ opts_with = opts.any? ? opts.first[:with] : {}
89
+ mount({ app.mount_instance(configuration: opts_with) => path }, *opts)
90
90
  next
91
91
  end
92
92
  in_setting = inheritable_setting
@@ -103,6 +103,15 @@ module Grape
103
103
  change!
104
104
  end
105
105
 
106
+ # When trying to mount multiple times the same endpoint, remove the previous ones
107
+ # from the list of endpoints if refresh_already_mounted parameter is true
108
+ refresh_already_mounted = opts.any? ? opts.first[:refresh_already_mounted] : false
109
+ if refresh_already_mounted && !endpoints.empty?
110
+ endpoints.delete_if do |endpoint|
111
+ endpoint.options[:app].to_s == app.to_s
112
+ end
113
+ end
114
+
106
115
  endpoints << Grape::Endpoint.new(
107
116
  in_setting,
108
117
  method: :any,
@@ -225,6 +234,13 @@ module Grape
225
234
  def versions
226
235
  @versions ||= []
227
236
  end
237
+
238
+ private
239
+
240
+ def refresh_mounted_api(mounts, *opts)
241
+ opts << { refresh_already_mounted: true }
242
+ mount(mounts, *opts)
243
+ end
228
244
  end
229
245
  end
230
246
  end
@@ -38,6 +38,19 @@ module Grape
38
38
  def params(&block)
39
39
  Grape::Validations::ParamsScope.new(api: self, type: Hash, &block)
40
40
  end
41
+
42
+ # Declare the contract to be used for the endpoint's parameters.
43
+ # @param contract [Class<Dry::Validation::Contract> | Dry::Schema::Processor]
44
+ # The contract or schema to be used for validation. Optional.
45
+ # @yield a block yielding a new instance of Dry::Schema::Params
46
+ # subclass, allowing to define the schema inline. When the
47
+ # +contract+ parameter is a schema, it will be used as a parent. Optional.
48
+ def contract(contract = nil, &block)
49
+ raise ArgumentError, 'Either contract or block must be provided' unless contract || block
50
+ raise ArgumentError, 'Cannot inherit from contract, only schema' if block && contract.respond_to?(:schema)
51
+
52
+ Grape::Validations::ContractScope.new(self, contract, &block)
53
+ end
41
54
  end
42
55
  end
43
56
  end
@@ -13,8 +13,8 @@ module Grape
13
13
  attr_reader :env, :request, :headers, :params
14
14
 
15
15
  class << self
16
- def new(*args, &block)
17
- self == Endpoint ? Class.new(Endpoint).new(*args, &block) : super
16
+ def new(...)
17
+ self == Endpoint ? Class.new(Endpoint).new(...) : super
18
18
  end
19
19
 
20
20
  def before_each(new_setup = false, &block)
@@ -55,7 +55,7 @@ module Grape
55
55
 
56
56
  proc do |endpoint_instance|
57
57
  ActiveSupport::Notifications.instrument('endpoint_render.grape', endpoint: endpoint_instance) do
58
- method.bind(endpoint_instance).call
58
+ method.bind_call(endpoint_instance)
59
59
  end
60
60
  end
61
61
  end
@@ -151,7 +151,7 @@ module Grape
151
151
  reset_routes!
152
152
  routes.each do |route|
153
153
  methods = [route.request_method]
154
- methods << Grape::Http::Headers::HEAD if !namespace_inheritable(:do_not_route_head) && route.request_method == Grape::Http::Headers::GET
154
+ methods << Rack::HEAD if !namespace_inheritable(:do_not_route_head) && route.request_method == Rack::GET
155
155
  methods.each do |method|
156
156
  route = Grape::Router::Route.new(method, route.origin, **route.attributes.to_h) unless route.request_method == method
157
157
  router.append(route.apply(self))
@@ -190,10 +190,11 @@ module Grape
190
190
  end
191
191
 
192
192
  def prepare_version
193
- version = namespace_inheritable(:version) || []
193
+ version = namespace_inheritable(:version)
194
+ return unless version
194
195
  return if version.empty?
195
196
 
196
- version.length == 1 ? version.first.to_s : version
197
+ version.length == 1 ? version.first : version
197
198
  end
198
199
 
199
200
  def merge_route_options(**default)
@@ -206,7 +207,7 @@ module Grape
206
207
 
207
208
  def prepare_path(path)
208
209
  path_settings = inheritable_setting.to_hash[:namespace_stackable].merge(inheritable_setting.to_hash[:namespace_inheritable])
209
- Path.prepare(path, namespace, path_settings)
210
+ Path.new(path, namespace, path_settings)
210
211
  end
211
212
 
212
213
  def namespace
@@ -238,7 +239,7 @@ module Grape
238
239
 
239
240
  def run
240
241
  ActiveSupport::Notifications.instrument('endpoint_run.grape', endpoint: self, env: env) do
241
- @header = {}
242
+ @header = Grape::Util::Header.new
242
243
  @request = Grape::Request.new(env, build_params_with: namespace_inheritable(:build_params_with))
243
244
  @params = @request.params
244
245
  @headers = @request.headers
@@ -250,7 +251,7 @@ module Grape
250
251
  if (allowed_methods = env[Grape::Env::GRAPE_ALLOWED_METHODS])
251
252
  raise Grape::Exceptions::MethodNotAllowed.new(header.merge('Allow' => allowed_methods)) unless options?
252
253
 
253
- header 'Allow', allowed_methods
254
+ header Grape::Http::Headers::ALLOW, allowed_methods
254
255
  response_object = ''
255
256
  status 204
256
257
  else
@@ -401,7 +402,11 @@ module Grape
401
402
 
402
403
  def options?
403
404
  options[:options_route_enabled] &&
404
- env[Grape::Http::Headers::REQUEST_METHOD] == Grape::Http::Headers::OPTIONS
405
+ env[Rack::REQUEST_METHOD] == Rack::OPTIONS
406
+ end
407
+
408
+ def inspect
409
+ "#{self.class} in `#{route.origin}' endpoint"
405
410
  end
406
411
  end
407
412
  end
@@ -11,11 +11,6 @@ module Grape
11
11
  API_VENDOR = 'api.vendor'
12
12
  API_FORMAT = 'api.format'
13
13
 
14
- RACK_INPUT = 'rack.input'
15
- RACK_REQUEST_QUERY_HASH = 'rack.request.query_hash'
16
- RACK_REQUEST_FORM_HASH = 'rack.request.form_hash'
17
- RACK_REQUEST_FORM_INPUT = 'rack.request.form_input'
18
-
19
14
  GRAPE_REQUEST = 'grape.request'
20
15
  GRAPE_REQUEST_HEADERS = 'grape.request.headers'
21
16
  GRAPE_REQUEST_PARAMS = 'grape.request.params'
@@ -10,16 +10,17 @@ module Grape
10
10
  message = present(message, env)
11
11
 
12
12
  result = message.is_a?(Hash) ? ::Grape::Json.dump(message) : message
13
- rescue_options = options[:rescue_options] || {}
14
- if rescue_options[:backtrace] && backtrace && !backtrace.empty?
15
- result += "\r\n backtrace:"
16
- result += backtrace.join("\r\n ")
17
- end
18
- if rescue_options[:original_exception] && original_exception
19
- result += "\r\n original exception:"
20
- result += "\r\n #{original_exception.inspect}"
21
- end
22
- result
13
+ Array.wrap(result).tap do |final_result|
14
+ rescue_options = options[:rescue_options] || {}
15
+ if rescue_options[:backtrace] && backtrace.present?
16
+ final_result << 'backtrace:'
17
+ final_result.concat(backtrace)
18
+ end
19
+ if rescue_options[:original_exception] && original_exception
20
+ final_result << 'original exception:'
21
+ final_result << original_exception.inspect
22
+ end
23
+ end.join("\r\n ")
23
24
  end
24
25
  end
25
26
  end
@@ -41,15 +41,15 @@ module Grape
41
41
  end
42
42
 
43
43
  def problem(key, **attributes)
44
- translate_message("#{key}.problem".to_sym, **attributes)
44
+ translate_message(:"#{key}.problem", **attributes)
45
45
  end
46
46
 
47
47
  def summary(key, **attributes)
48
- translate_message("#{key}.summary".to_sym, **attributes)
48
+ translate_message(:"#{key}.summary", **attributes)
49
49
  end
50
50
 
51
51
  def resolution(key, **attributes)
52
- translate_message("#{key}.resolution".to_sym, **attributes)
52
+ translate_message(:"#{key}.resolution", **attributes)
53
53
  end
54
54
 
55
55
  def translate_attributes(keys, **options)
@@ -10,4 +10,4 @@ module Grape
10
10
  end
11
11
  end
12
12
 
13
- Grape::Exceptions::MissingGroupTypeError = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Grape::Exceptions::MissingGroupTypeError', 'Grape::Exceptions::MissingGroupType')
13
+ Grape::Exceptions::MissingGroupTypeError = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Grape::Exceptions::MissingGroupTypeError', 'Grape::Exceptions::MissingGroupType', Grape.deprecator)
@@ -10,4 +10,4 @@ module Grape
10
10
  end
11
11
  end
12
12
 
13
- Grape::Exceptions::UnsupportedGroupTypeError = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Grape::Exceptions::UnsupportedGroupTypeError', 'Grape::Exceptions::UnsupportedGroupType')
13
+ Grape::Exceptions::UnsupportedGroupTypeError = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Grape::Exceptions::UnsupportedGroupTypeError', 'Grape::Exceptions::UnsupportedGroupType', Grape.deprecator)
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'grape/exceptions/base'
4
-
5
3
  module Grape
6
4
  module Exceptions
7
5
  class Validation < Grape::Exceptions::Base
@@ -6,6 +6,7 @@ module Grape
6
6
  attr_reader :errors
7
7
 
8
8
  def initialize(errors)
9
+ super()
9
10
  @errors = errors
10
11
  end
11
12
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'grape/exceptions/base'
4
-
5
3
  module Grape
6
4
  module Exceptions
7
5
  class ValidationErrors < Grape::Exceptions::Base
@@ -14,7 +12,7 @@ module Grape
14
12
 
15
13
  def initialize(errors: [], headers: {}, **_options)
16
14
  @errors = errors.group_by(&:params)
17
- super message: full_messages.join(', '), status: 400, headers: headers
15
+ super(message: full_messages.join(', '), status: 400, headers: headers)
18
16
  end
19
17
 
20
18
  def each
@@ -12,8 +12,12 @@ module Grape
12
12
 
13
13
  def build_params
14
14
  rack_params.deep_dup.tap do |params|
15
- params.deep_merge!(grape_routing_args) if env.key?(Grape::Env::GRAPE_ROUTING_ARGS)
16
15
  params.deep_symbolize_keys!
16
+
17
+ if env.key?(Grape::Env::GRAPE_ROUTING_ARGS)
18
+ grape_routing_args.deep_symbolize_keys!
19
+ params.deep_merge!(grape_routing_args)
20
+ end
17
21
  end
18
22
  end
19
23
  end
@@ -1,36 +1,30 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'grape/util/lazy_object'
4
-
5
3
  module Grape
6
4
  module Http
7
5
  module Headers
8
- # https://github.com/rack/rack/blob/master/lib/rack.rb
9
- HTTP_VERSION = 'HTTP_VERSION'
10
- PATH_INFO = 'PATH_INFO'
11
- REQUEST_METHOD = 'REQUEST_METHOD'
12
- QUERY_STRING = 'QUERY_STRING'
13
- CONTENT_TYPE = 'Content-Type'
14
-
15
- GET = 'GET'
16
- POST = 'POST'
17
- PUT = 'PUT'
18
- PATCH = 'PATCH'
19
- DELETE = 'DELETE'
20
- HEAD = 'HEAD'
21
- OPTIONS = 'OPTIONS'
6
+ HTTP_ACCEPT_VERSION = 'HTTP_ACCEPT_VERSION'
7
+ HTTP_ACCEPT = 'HTTP_ACCEPT'
8
+ HTTP_TRANSFER_ENCODING = 'HTTP_TRANSFER_ENCODING'
22
9
 
23
- SUPPORTED_METHODS = [GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS].freeze
24
- SUPPORTED_METHODS_WITHOUT_OPTIONS = Grape::Util::LazyObject.new { [GET, POST, PUT, PATCH, DELETE, HEAD].freeze }
10
+ ALLOW = 'Allow'
11
+ LOCATION = 'Location'
12
+ X_CASCADE = 'X-Cascade'
13
+ TRANSFER_ENCODING = 'Transfer-Encoding'
25
14
 
26
- HTTP_ACCEPT_VERSION = 'HTTP_ACCEPT_VERSION'
27
- X_CASCADE = 'X-Cascade'
28
- HTTP_TRANSFER_ENCODING = 'HTTP_TRANSFER_ENCODING'
29
- HTTP_ACCEPT = 'HTTP_ACCEPT'
15
+ SUPPORTED_METHODS = [
16
+ Rack::GET,
17
+ Rack::POST,
18
+ Rack::PUT,
19
+ Rack::PATCH,
20
+ Rack::DELETE,
21
+ Rack::HEAD,
22
+ Rack::OPTIONS
23
+ ].freeze
30
24
 
31
- FORMAT = 'format'
25
+ SUPPORTED_METHODS_WITHOUT_OPTIONS = (SUPPORTED_METHODS - [Rack::OPTIONS]).freeze
32
26
 
33
- HTTP_HEADERS = Grape::Util::LazyObject.new do
27
+ HTTP_HEADERS = Grape::Util::Lazy::Object.new do
34
28
  common_http_headers = %w[
35
29
  Version
36
30
  Host
@@ -1,9 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'json'
4
-
5
3
  module Grape
6
- if Object.const_defined? :MultiJson
4
+ if defined?(::MultiJson)
7
5
  Json = ::MultiJson
8
6
  else
9
7
  Json = ::JSON
@@ -10,6 +10,9 @@ en:
10
10
  values: 'does not have a valid value'
11
11
  except_values: 'has a value not allowed'
12
12
  same_as: 'is not the same as %{parameter}'
13
+ length: 'is expected to have length within %{min} and %{max}'
14
+ length_min: 'is expected to have length greater than or equal to %{min}'
15
+ length_max: 'is expected to have length less than or equal to %{max}'
13
16
  missing_vendor_option:
14
17
  problem: 'missing :vendor option'
15
18
  summary: 'when version using header, you must specify :vendor option'
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rack/auth/basic'
4
-
5
3
  module Grape
6
4
  module Middleware
7
5
  module Auth
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rack/auth/basic'
4
-
5
3
  module Grape
6
4
  module Middleware
7
5
  module Auth
@@ -12,8 +12,7 @@ module Grape
12
12
 
13
13
  def auth_strategies
14
14
  @auth_strategies ||= {
15
- http_basic: StrategyInfo.new(Rack::Auth::Basic, ->(settings) { [settings[:realm]] }),
16
- http_digest: StrategyInfo.new(Rack::Auth::Digest::MD5, ->(settings) { [settings[:realm], settings[:opaque]] })
15
+ http_basic: StrategyInfo.new(Rack::Auth::Basic, ->(settings) { [settings[:realm]] })
17
16
  }
18
17
  end
19
18
 
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'grape/dsl/headers'
4
-
5
3
  module Grape
6
4
  module Middleware
7
5
  class Base
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'grape/middleware/base'
4
- require 'active_support/core_ext/string/output_safety'
5
-
6
3
  module Grape
7
4
  module Middleware
8
5
  class Error < Base
@@ -34,66 +31,59 @@ module Grape
34
31
 
35
32
  def call!(env)
36
33
  @env = env
37
- begin
38
- error_response(catch(:error) do
39
- return @app.call(@env)
40
- end)
41
- rescue Exception => e # rubocop:disable Lint/RescueException
42
- handler =
43
- rescue_handler_for_base_only_class(e.class) ||
44
- rescue_handler_for_class_or_its_ancestor(e.class) ||
45
- rescue_handler_for_grape_exception(e.class) ||
46
- rescue_handler_for_any_class(e.class) ||
47
- raise
48
-
49
- run_rescue_handler(handler, e)
50
- end
51
- end
52
-
53
- def error!(message, status = options[:default_status], headers = {}, backtrace = [], original_exception = nil)
54
- headers = headers.reverse_merge(Grape::Http::Headers::CONTENT_TYPE => content_type)
55
- rack_response(format_message(message, backtrace, original_exception), status, headers)
34
+ error_response(catch(:error) { return @app.call(@env) })
35
+ rescue Exception => e # rubocop:disable Lint/RescueException
36
+ run_rescue_handler(find_handler(e.class), e, @env[Grape::Env::API_ENDPOINT])
56
37
  end
57
38
 
58
- def default_rescue_handler(e)
59
- error_response(message: e.message, backtrace: e.backtrace, original_exception: e)
60
- end
61
-
62
- # TODO: This method is deprecated. Refactor out.
63
- def error_response(error = {})
64
- status = error[:status] || options[:default_status]
65
- message = error[:message] || options[:default_message]
66
- headers = { Grape::Http::Headers::CONTENT_TYPE => content_type }
67
- headers.merge!(error[:headers]) if error[:headers].is_a?(Hash)
68
- backtrace = error[:backtrace] || error[:original_exception]&.backtrace || []
69
- original_exception = error.is_a?(Exception) ? error : error[:original_exception] || nil
70
- rack_response(format_message(message, backtrace, original_exception), status, headers)
71
- end
39
+ private
72
40
 
73
- def rack_response(message, status = options[:default_status], headers = { Grape::Http::Headers::CONTENT_TYPE => content_type })
74
- message = ERB::Util.html_escape(message) if headers[Grape::Http::Headers::CONTENT_TYPE] == TEXT_HTML
75
- Rack::Response.new([message], Rack::Utils.status_code(status), headers)
41
+ def rack_response(status, headers, message)
42
+ message = Rack::Utils.escape_html(message) if headers[Rack::CONTENT_TYPE] == TEXT_HTML
43
+ Rack::Response.new(Array.wrap(message), Rack::Utils.status_code(status), Grape::Util::Header.new.merge(headers))
76
44
  end
77
45
 
78
46
  def format_message(message, backtrace, original_exception = nil)
79
47
  format = env[Grape::Env::API_FORMAT] || options[:format]
80
48
  formatter = Grape::ErrorFormatter.formatter_for(format, **options)
49
+ return formatter.call(message, backtrace, options, env, original_exception) if formatter
50
+
81
51
  throw :error,
82
52
  status: 406,
83
53
  message: "The requested format '#{format}' is not supported.",
84
54
  backtrace: backtrace,
85
- original_exception: original_exception unless formatter
86
- formatter.call(message, backtrace, options, env, original_exception)
55
+ original_exception: original_exception
87
56
  end
88
57
 
89
- private
58
+ def find_handler(klass)
59
+ rescue_handler_for_base_only_class(klass) ||
60
+ rescue_handler_for_class_or_its_ancestor(klass) ||
61
+ rescue_handler_for_grape_exception(klass) ||
62
+ rescue_handler_for_any_class(klass) ||
63
+ raise
64
+ end
65
+
66
+ def error_response(error = {})
67
+ status = error[:status] || options[:default_status]
68
+ message = error[:message] || options[:default_message]
69
+ headers = { Rack::CONTENT_TYPE => content_type }.tap do |h|
70
+ h.merge!(error[:headers]) if error[:headers].is_a?(Hash)
71
+ end
72
+ backtrace = error[:backtrace] || error[:original_exception]&.backtrace || []
73
+ original_exception = error.is_a?(Exception) ? error : error[:original_exception] || nil
74
+ rack_response(status, headers, format_message(message, backtrace, original_exception))
75
+ end
76
+
77
+ def default_rescue_handler(e)
78
+ error_response(message: e.message, backtrace: e.backtrace, original_exception: e)
79
+ end
90
80
 
91
81
  def rescue_handler_for_base_only_class(klass)
92
82
  error, handler = options[:base_only_rescue_handlers].find { |err, _handler| klass == err }
93
83
 
94
84
  return unless error
95
85
 
96
- handler || :default_rescue_handler
86
+ handler || method(:default_rescue_handler)
97
87
  end
98
88
 
99
89
  def rescue_handler_for_class_or_its_ancestor(klass)
@@ -101,39 +91,54 @@ module Grape
101
91
 
102
92
  return unless error
103
93
 
104
- handler || :default_rescue_handler
94
+ handler || method(:default_rescue_handler)
105
95
  end
106
96
 
107
97
  def rescue_handler_for_grape_exception(klass)
108
98
  return unless klass <= Grape::Exceptions::Base
109
- return :error_response if klass == Grape::Exceptions::InvalidVersionHeader
99
+ return method(:error_response) if klass == Grape::Exceptions::InvalidVersionHeader
110
100
  return unless options[:rescue_grape_exceptions] || !options[:rescue_all]
111
101
 
112
- options[:grape_exceptions_rescue_handler] || :error_response
102
+ options[:grape_exceptions_rescue_handler] || method(:error_response)
113
103
  end
114
104
 
115
105
  def rescue_handler_for_any_class(klass)
116
106
  return unless klass <= StandardError
117
107
  return unless options[:rescue_all] || options[:rescue_grape_exceptions]
118
108
 
119
- options[:all_rescue_handler] || :default_rescue_handler
109
+ options[:all_rescue_handler] || method(:default_rescue_handler)
120
110
  end
121
111
 
122
- def run_rescue_handler(handler, error)
112
+ def run_rescue_handler(handler, error, endpoint)
123
113
  if handler.instance_of?(Symbol)
124
114
  raise NoMethodError, "undefined method '#{handler}'" unless respond_to?(handler)
125
115
 
126
116
  handler = public_method(handler)
127
117
  end
128
118
 
129
- response = handler.arity.zero? ? instance_exec(&handler) : instance_exec(error, &handler)
119
+ response = catch(:error) do
120
+ handler.arity.zero? ? endpoint.instance_exec(&handler) : endpoint.instance_exec(error, &handler)
121
+ end
122
+
123
+ response = error!(response[:message], response[:status], response[:headers]) if error?(response)
130
124
 
131
125
  if response.is_a?(Rack::Response)
132
126
  response
133
127
  else
134
- run_rescue_handler(:default_rescue_handler, Grape::Exceptions::InvalidResponse.new)
128
+ run_rescue_handler(:default_rescue_handler, Grape::Exceptions::InvalidResponse.new, endpoint)
135
129
  end
136
130
  end
131
+
132
+ def error!(message, status = options[:default_status], headers = {}, backtrace = [], original_exception = nil)
133
+ rack_response(
134
+ status, headers.reverse_merge(Rack::CONTENT_TYPE => content_type),
135
+ format_message(message, backtrace, original_exception)
136
+ )
137
+ end
138
+
139
+ def error?(response)
140
+ response.is_a?(Hash) && response[:message] && response[:status] && response[:headers]
141
+ end
137
142
  end
138
143
  end
139
144
  end